refactor(scheduler-sim-decision-trace): split trace sources under the 800-line cap

Task 2.4: outcome capture moves to sched_trace_outcome.c and the
position/host/summary/rollback half of the model to sched_trace_scan.c;
shared helpers go behind sched_trace_internal.h (sti_ prefix). Code is
moved unchanged; every target that compiles the trace sources gets all
four, and the core isolation scan covers the new files.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-10-08 10:16:46 +00:00
parent eacb1fa599
commit 144a01982e
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
40 changed files with 1355 additions and 1094 deletions

View file

@ -17,9 +17,10 @@
* Only the simulator REPL ever publishes a control block (`trace_ctl`), so
* the live scheduler never builds a record (Requirement 11.2).
*
* The model (src/sched_trace.c) and the capture helpers
* (src/sched_trace_capture.c) do no I/O and no logging; test_core_isolation.c
* scans both files for stdio and file I/O.
* The model (src/sched_trace.c, src/sched_trace_scan.c) and the capture
* helpers (src/sched_trace_capture.c, src/sched_trace_outcome.c) do no I/O
* and no logging; test_core_isolation.c scans these files and their shared
* src/sched_trace_internal.h for stdio and file I/O.
*/
#include <stdbool.h>
@ -697,7 +698,8 @@ extern void sched_trace_finalize_failed(xce_sched_trace_rec_t* rec, int err);
extern void sched_trace_search_end(xce_sched_trace_rec_t* rec, int rc);
/*
* TraceCapture (src/sched_trace_capture.c, design.md "TraceCapture"). Each
* TraceCapture (src/sched_trace_capture.c and, for the outcomes,
* src/sched_trace_outcome.c; design.md "TraceCapture"). Each
* reads scheduler state on the thread that owns it, read-only, and copies
* what it reports into `rec` (never NULL: the SCHED_TRACE gate).
*/

View file

@ -36,7 +36,9 @@ scheduler_srcs = [
'src/host_iter.c',
'src/alloc_runner.c',
'src/sched_trace.c',
'src/sched_trace_scan.c',
'src/sched_trace_capture.c',
'src/sched_trace_outcome.c',
'src/plugins/fcfs.c',
'src/plugins/builtin_fcfs.c',
'src/plugins/seqjob_allocator.c',

View file

@ -25,6 +25,10 @@
* level host verdicts are not stored: they are tallied per position, and
* the position's end becomes one position-summary entry (5.9).
*
* This file holds the record, the entry sequence and the requirement, plan
* and check entries; positions, hosts, tallies, rollback and the search
* end are in sched_trace_scan.c (sched_trace_internal.h, task 2.4).
*
* No I/O and no logging: test_core_isolation.c scans this file for stdio
* and file I/O.
*/
@ -33,6 +37,7 @@
#include "sched/alloc_iter.h"
#include "sched/trace.h"
#include "sched_trace_internal.h"
#include "utils.h"
/* Smallest chunk the arena allocates; a larger request gets a chunk of its
@ -57,17 +62,8 @@ static unsigned char* chunk_data(sched_trace_chunk_t* c)
return ((unsigned char*)c) + chunk_hdr_size();
}
/* Summary level: one limit hit by hosts of the current position. Builder
* memory, owned by the record until the position ends. */
struct sched_trace_tally_build_s {
char* limit;
char* scope;
int count; /* hosts with a limit-exceeded verdict */
unsigned last_host; /* host_seq of the last host that hit it */
};
/* Drops the current position's limit tallies (keeps the array). */
static void pos_limits_clear(xce_sched_trace_rec_t* rec)
void sti_pos_limits_clear(xce_sched_trace_rec_t* rec)
{
int k;
@ -79,7 +75,7 @@ static void pos_limits_clear(xce_sched_trace_rec_t* rec)
}
/* Drops the current host's held rows (keeps the array). */
static void host_rows_clear(xce_sched_trace_rec_t* rec)
void sti_host_rows_clear(xce_sched_trace_rec_t* rec)
{
int k;
@ -92,13 +88,6 @@ static void host_rows_clear(xce_sched_trace_rec_t* rec)
rec->host_nrows = 0;
}
/* Starts the current position's tallies over. */
static void pos_tally_reset(xce_sched_trace_rec_t* rec)
{
memset(rec->pos_counts, 0, sizeof(rec->pos_counts));
pos_limits_clear(rec);
}
bool sched_trace_ctl_wants(const xce_sched_trace_ctl_t* ctl, uint64_t jobid)
{
int i;
@ -246,9 +235,9 @@ void sched_trace_rec_free(xce_sched_trace_rec_t* rec)
return;
}
pos_limits_clear(rec);
sti_pos_limits_clear(rec);
free(rec->pos_limits);
host_rows_clear(rec);
sti_host_rows_clear(rec);
free(rec->host_rows);
c = rec->arena;
@ -301,8 +290,8 @@ size_t sched_trace_rec_bytes(const xce_sched_trace_rec_t* rec)
* budget (counted as one omitted line), or after an arena failure (the
* record is already marked incomplete and appending has stopped).
*/
static sched_trace_entry_t* entry_add(xce_sched_trace_rec_t* rec,
sched_trace_entry_type_t type)
sched_trace_entry_t* sti_entry_add(xce_sched_trace_rec_t* rec,
sched_trace_entry_type_t type)
{
sched_trace_entry_t* e;
@ -338,30 +327,30 @@ static sched_trace_entry_t* entry_add(xce_sched_trace_rec_t* rec,
}
/* A call that stores no entry still ends a run of released hosts. */
static void rollback_run_end(xce_sched_trace_rec_t* rec)
void sti_rollback_run_end(xce_sched_trace_rec_t* rec)
{
rec->rollback_open = false;
rec->rollback_e = NULL;
}
/* A scope begin is stored at the current level and opens the next one. */
static sched_trace_entry_t* scope_begin(xce_sched_trace_rec_t* rec,
sched_trace_entry_type_t type)
sched_trace_entry_t* sti_scope_begin(xce_sched_trace_rec_t* rec,
sched_trace_entry_type_t type)
{
sched_trace_entry_t* e = entry_add(rec, type);
sched_trace_entry_t* e = sti_entry_add(rec, type);
rec->depth++;
return e;
}
/* A scope end closes the level and is stored at its begin's level. */
static sched_trace_entry_t* scope_end(xce_sched_trace_rec_t* rec,
sched_trace_entry_type_t type)
sched_trace_entry_t* sti_scope_end(xce_sched_trace_rec_t* rec,
sched_trace_entry_type_t type)
{
if (rec->depth > 0) {
rec->depth--;
}
return entry_add(rec, type);
return sti_entry_add(rec, type);
}
/* Arena copy of `n` ints; NULL for none. */
@ -398,7 +387,7 @@ void sched_trace_path_push(xce_sched_trace_rec_t* rec,
{
sched_trace_path_t* p = &rec->path;
rollback_run_end(rec);
sti_rollback_run_end(rec);
if (p->depth >= 2) {
/* deeper than container > member: not kept, but balanced */
rec->path_overflow++;
@ -412,7 +401,7 @@ void sched_trace_path_push(xce_sched_trace_rec_t* rec,
void sched_trace_path_pop(xce_sched_trace_rec_t* rec)
{
rollback_run_end(rec);
sti_rollback_run_end(rec);
if (rec->path_overflow > 0) {
rec->path_overflow--;
return;
@ -438,7 +427,7 @@ void sched_trace_resreq_begin(xce_sched_trace_rec_t* rec,
rec->scope_not_tried = 0;
rec->scope_plan_err = false;
e = scope_begin(rec, SCHED_TRACE_E_RESREQ_BEGIN);
e = sti_scope_begin(rec, SCHED_TRACE_E_RESREQ_BEGIN);
if (e == NULL) {
return;
}
@ -486,7 +475,7 @@ void sched_trace_resreq_end(xce_sched_trace_rec_t* rec, int rc,
rec->resreq_open--;
}
e = scope_end(rec, SCHED_TRACE_E_RESREQ_END);
e = sti_scope_end(rec, SCHED_TRACE_E_RESREQ_END);
if (e == NULL) {
return;
}
@ -497,7 +486,7 @@ void sched_trace_resreq_end(xce_sched_trace_rec_t* rec, int rc,
void sched_trace_member_failed(xce_sched_trace_rec_t* rec, int j, int m,
int err)
{
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_MEMBER_FAILED);
sched_trace_entry_t* e = sti_entry_add(rec, SCHED_TRACE_E_MEMBER_FAILED);
if (e == NULL) {
return;
@ -509,7 +498,7 @@ void sched_trace_member_failed(xce_sched_trace_rec_t* rec, int j, int m,
void sched_trace_alt_result(xce_sched_trace_rec_t* rec, int chosen_i, int n)
{
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_ALT_RESULT);
sched_trace_entry_t* e = sti_entry_add(rec, SCHED_TRACE_E_ALT_RESULT);
if (e == NULL) {
return;
@ -520,7 +509,7 @@ void sched_trace_alt_result(xce_sched_trace_rec_t* rec, int chosen_i, int n)
void sched_trace_alt_skipped(xce_sched_trace_rec_t* rec, int i, int n)
{
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_ALT_SKIPPED);
sched_trace_entry_t* e = sti_entry_add(rec, SCHED_TRACE_E_ALT_SKIPPED);
if (e == NULL) {
return;
@ -534,7 +523,7 @@ void sched_trace_attempt_reasons(xce_sched_trace_rec_t* rec,
int ncodes, const char* const* hosts,
int nhosts, const char* const* res, int nres)
{
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_ATTEMPT_REASONS);
sched_trace_entry_t* e = sti_entry_add(rec, SCHED_TRACE_E_ATTEMPT_REASONS);
if (e == NULL) {
return;
@ -560,7 +549,7 @@ void sched_trace_attempt_reasons(xce_sched_trace_rec_t* rec,
void sched_trace_check_begin(xce_sched_trace_rec_t* rec,
const xce_sched_obj_t* obj)
{
rollback_run_end(rec);
sti_rollback_run_end(rec);
rec->scope_obj = obj;
rec->scope_w = NULL;
rec->check_open = true;
@ -574,7 +563,7 @@ void sched_trace_check_end(xce_sched_trace_rec_t* rec, int rc,
rec->scope_obj = NULL;
rec->check_open = false;
e = entry_add(rec, SCHED_TRACE_E_CHECK_END);
e = sti_entry_add(rec, SCHED_TRACE_E_CHECK_END);
if (e == NULL) {
return;
}
@ -585,7 +574,7 @@ void sched_trace_check_end(xce_sched_trace_rec_t* rec, int rc,
void sched_trace_no_plan(xce_sched_trace_rec_t* rec,
xce_sched_trace_noplan_t why)
{
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_NO_PLAN);
sched_trace_entry_t* e = sti_entry_add(rec, SCHED_TRACE_E_NO_PLAN);
if (e == NULL) {
return;
@ -614,7 +603,7 @@ void sched_trace_plan_begin(xce_sched_trace_rec_t* rec,
rec->cur_plan_no = ++rec->plan_no;
rec->scope_tried++;
rec->counters.plans_tried++;
e = scope_begin(rec, SCHED_TRACE_E_PLAN_BEGIN);
e = sti_scope_begin(rec, SCHED_TRACE_E_PLAN_BEGIN);
if (e != NULL) {
plan_fill(rec, e, plan, rec->cur_plan_no, 0);
}
@ -628,7 +617,7 @@ void sched_trace_plan_discarded(xce_sched_trace_rec_t* rec,
rec->plan_no++;
rec->scope_discarded++;
rec->counters.plans_discarded++;
e = entry_add(rec, SCHED_TRACE_E_PLAN_DISCARDED);
e = sti_entry_add(rec, SCHED_TRACE_E_PLAN_DISCARDED);
if (e != NULL) {
plan_fill(rec, e, plan, rec->plan_no, 0);
}
@ -641,7 +630,7 @@ void sched_trace_plan_not_tried(xce_sched_trace_rec_t* rec,
rec->plan_no++;
rec->scope_not_tried++;
e = entry_add(rec, SCHED_TRACE_E_PLAN_NOT_TRIED);
e = sti_entry_add(rec, SCHED_TRACE_E_PLAN_NOT_TRIED);
if (e != NULL) {
plan_fill(rec, e, plan, rec->plan_no, err);
}
@ -655,7 +644,7 @@ void sched_trace_plan_end(xce_sched_trace_rec_t* rec, int rc, int failed_pos)
rec->scope_plan_err = true;
}
e = scope_end(rec, SCHED_TRACE_E_PLAN_END);
e = sti_scope_end(rec, SCHED_TRACE_E_PLAN_END);
if (e == NULL) {
return;
}
@ -663,468 +652,3 @@ void sched_trace_plan_end(xce_sched_trace_rec_t* rec, int rc, int failed_pos)
e->u.plan_end.rc = rc;
e->u.plan_end.failed_pos = failed_pos;
}
void sched_trace_pos_begin(xce_sched_trace_rec_t* rec, int pos, int slots)
{
sched_trace_entry_t* e;
rec->cur_pos = pos;
rec->scan_err = 0;
rec->scan_err_set = false;
pos_tally_reset(rec);
e = scope_begin(rec, SCHED_TRACE_E_POS_BEGIN);
if (e == NULL) {
return;
}
e->u.pos_begin.pos = pos;
e->u.pos_begin.slots = slots;
}
void sched_trace_pos_scan_error(xce_sched_trace_rec_t* rec, int err)
{
rollback_run_end(rec);
rec->scan_err = err;
rec->scan_err_set = true;
}
/* Fills a POS_SUMMARY entry from the current position's tallies; only
* limits some host was rejected for are kept. */
static void pos_summary_fill(xce_sched_trace_rec_t* rec, sched_trace_entry_t* e,
xce_sched_trace_pos_end_t how, const char* host,
int err)
{
int n = 0;
int k;
e->u.pos_summary.pos = rec->cur_pos;
e->u.pos_summary.how = how;
e->u.pos_summary.err = err;
e->u.pos_summary.host = sched_trace_arena_strdup(rec, host);
memcpy(e->u.pos_summary.counts, rec->pos_counts,
sizeof(e->u.pos_summary.counts));
for (k = 0; k < rec->pos_nlimits; k++) {
if (rec->pos_limits[k].count > 0) {
n++;
}
}
if (n == 0) {
return;
}
e->u.pos_summary.limits = (sched_trace_limit_tally_t*)
sched_trace_arena_alloc(rec, sizeof(sched_trace_limit_tally_t)
* (size_t)n);
if (e->u.pos_summary.limits == NULL) {
return;
}
n = 0;
for (k = 0; k < rec->pos_nlimits; k++) {
const struct sched_trace_tally_build_s* b = &rec->pos_limits[k];
sched_trace_limit_tally_t* t;
if (b->count <= 0) {
continue;
}
t = &e->u.pos_summary.limits[n++];
t->limit = sched_trace_arena_strdup(rec, b->limit);
t->scope = sched_trace_arena_strdup(rec, b->scope);
t->count = b->count;
}
e->u.pos_summary.nlimits = n;
}
void sched_trace_pos_end(xce_sched_trace_rec_t* rec,
xce_sched_trace_pos_end_t how, const char* host)
{
sched_trace_entry_t* e;
int err = 0;
if (rec->scan_err_set) {
/* the host scan stopped: the runner's UNFILLED is a scan error */
err = rec->scan_err;
if (how == XCE_TRACE_POS_UNFILLED) {
how = XCE_TRACE_POS_SCAN_ERROR;
}
}
rec->scan_err = 0;
rec->scan_err_set = false;
if (rec->level == XCE_TRACE_LEVEL_SUMMARY) {
/* the position's end carries its host tallies (5.9) */
e = scope_end(rec, SCHED_TRACE_E_POS_SUMMARY);
if (e != NULL) {
pos_summary_fill(rec, e, how, host, err);
}
pos_tally_reset(rec);
return;
}
e = scope_end(rec, SCHED_TRACE_E_POS_END);
if (e == NULL) {
return;
}
e->u.pos_end.pos = rec->cur_pos;
e->u.pos_end.how = how;
e->u.pos_end.err = err;
e->u.pos_end.host = sched_trace_arena_strdup(rec, host);
}
void sched_trace_host_begin(xce_sched_trace_rec_t* rec,
const xce_sched_obj_t* obj,
const xce_sched_worker_t* w)
{
rollback_run_end(rec);
rec->host_seq++;
rec->scope_obj = obj;
rec->scope_w = w;
rec->host_row_verdicts = 0;
host_rows_clear(rec); /* a scope that ended without its verdict */
}
/* Counts one host evaluation: decision-wide per verdict kind, and at
* summary level per position, with each limit the host hit counted once
* when its verdict is limit exceeded (5.9, 9.3). */
static void host_count(xce_sched_trace_rec_t* rec, unsigned verdicts)
{
int k;
for (k = 0; k < XCE_TRACE_V_NKINDS; k++) {
if ((verdicts & (1u << k)) != 0) {
rec->counters.host_evals[k]++;
rec->pos_counts[k]++;
}
}
if ((verdicts & XCE_TRACE_V_LIMIT) == 0) {
return;
}
for (k = 0; k < rec->pos_nlimits; k++) {
if (rec->pos_limits[k].last_host == rec->host_seq) {
rec->pos_limits[k].count++;
}
}
}
/* Copies a row's strings into the arena (NULL stays NULL). */
static void row_to_arena(xce_sched_trace_rec_t* rec, sched_trace_row_t* dst,
const sched_trace_row_t* src)
{
*dst = *src;
dst->res = sched_trace_arena_strdup(rec, src->res);
dst->limit = sched_trace_arena_strdup(rec, src->limit);
dst->scope = sched_trace_arena_strdup(rec, src->scope);
dst->limited_res = sched_trace_arena_strdup(rec, src->limited_res);
}
/* Stores the current host's held rows after its verdict line, one level
* below it, then drops them. Each counts against the budget. */
static void host_rows_flush(xce_sched_trace_rec_t* rec)
{
int k;
for (k = 0; k < rec->host_nrows; k++) {
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_HOST_ROW);
if (e == NULL) {
continue;
}
e->depth++;
row_to_arena(rec, &e->u.row, &rec->host_rows[k]);
}
host_rows_clear(rec);
}
/* NULL-preserving heap copy for held rows; sets *failed on failure. */
static const char* row_strdup(const char* s, bool* failed)
{
char* d;
if (s == NULL) {
return NULL;
}
d = strdup(s);
if (d == NULL) {
*failed = true;
}
return d;
}
void sched_trace_row(xce_sched_trace_rec_t* rec, const sched_trace_row_t* row)
{
sched_trace_entry_t* e;
sched_trace_row_t* h;
bool failed = false;
rollback_run_end(rec);
if (row == NULL) {
return;
}
if (rec->scope_w == NULL) {
/* the cluster-wide check: stored now, before its check-end line */
e = entry_add(rec, SCHED_TRACE_E_CHECK_ROW);
if (e != NULL) {
row_to_arena(rec, &e->u.row, row);
}
return;
}
/* a host scope: no host lines at summary level; at full level the row
* waits for the host's verdict line, which it belongs under */
if ((rec->level == XCE_TRACE_LEVEL_SUMMARY) || rec->incomplete) {
return;
}
if (rec->host_nrows == rec->host_rows_cap) {
int cap = (rec->host_rows_cap > 0) ? rec->host_rows_cap * 2 : 4;
void* p = realloc(rec->host_rows, sizeof(*h) * (size_t)cap);
if (p == NULL) {
rec->incomplete = true; /* design "Error Strategy" */
return;
}
rec->host_rows = (sched_trace_row_t*)p;
rec->host_rows_cap = cap;
}
h = &rec->host_rows[rec->host_nrows];
*h = *row;
h->res = row_strdup(row->res, &failed);
h->limit = row_strdup(row->limit, &failed);
h->scope = row_strdup(row->scope, &failed);
h->limited_res = row_strdup(row->limited_res, &failed);
if (failed) {
free((void*)h->res);
free((void*)h->limit);
free((void*)h->scope);
free((void*)h->limited_res);
rec->incomplete = true;
return;
}
rec->host_nrows++;
}
void sched_trace_host_verdict(xce_sched_trace_rec_t* rec, unsigned verdicts,
int err)
{
sched_trace_entry_t* e;
sched_trace_phase_t phase = SCHED_TRACE_PHASE_NONE;
const char* name
= (rec->scope_w != NULL) ? rec->scope_w->name : NULL;
if (verdicts == 0) {
/* from the rows the per-host check recorded; none means the
* check's own selection branch rejected the host */
verdicts = rec->host_row_verdicts & (XCE_TRACE_V_SHORTFALL
| XCE_TRACE_V_LIMIT);
if (verdicts == 0) {
verdicts = XCE_TRACE_V_SELECT;
}
phase = SCHED_TRACE_PHASE_PERHOST;
} else if ((verdicts & XCE_TRACE_V_SELECT) != 0) {
phase = SCHED_TRACE_PHASE_PREFILTER;
} else if ((verdicts & (XCE_TRACE_V_SHORTFALL | XCE_TRACE_V_LIMIT)) != 0) {
phase = SCHED_TRACE_PHASE_PERHOST;
}
/* one host evaluation, counted once under each kind it matched (9.3) */
host_count(rec, verdicts);
/* the host scope ends here */
rec->scope_obj = NULL;
rec->scope_w = NULL;
rec->host_row_verdicts = 0;
rec->host_seq++; /* later hits belong to no host until the next one */
rollback_run_end(rec);
if (rec->level == XCE_TRACE_LEVEL_SUMMARY) {
host_rows_clear(rec);
return; /* tallied for the position summary, no host line */
}
e = entry_add(rec, SCHED_TRACE_E_HOST);
if (e != NULL) {
e->u.host.host = sched_trace_arena_strdup(rec, name);
e->u.host.verdicts = verdicts;
e->u.host.phase = phase;
e->u.host.err = err;
}
/* the host's unmet rows, under its verdict line (5.4, 5.5) */
host_rows_flush(rec);
}
void sched_trace_commit(xce_sched_trace_rec_t* rec)
{
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_COMMIT);
if (e == NULL) {
return;
}
e->u.commit.plan_no = rec->cur_plan_no;
}
void sched_trace_finalize_failed(xce_sched_trace_rec_t* rec, int err)
{
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_FINALIZE_FAILED);
if (e == NULL) {
return;
}
e->u.commit.plan_no = rec->cur_plan_no;
e->u.commit.err = err;
}
static bool tally_str_eq(const char* a, const char* b)
{
if ((a == NULL) || (b == NULL)) {
return a == b;
}
return strcmp(a, b) == 0;
}
/* NULL-preserving heap copy for the builder's tallies; sets *failed when a
* non-NULL string could not be copied. */
static char* tally_strdup(const char* s, bool* failed)
{
char* d;
if (s == NULL) {
return NULL;
}
d = strdup(s);
if (d == NULL) {
*failed = true;
}
return d;
}
void sched_trace_host_limit_hit(xce_sched_trace_rec_t* rec, const char* limit,
const char* scope)
{
struct sched_trace_tally_build_s* b;
bool failed = false;
int k;
rollback_run_end(rec);
if (rec->scope_w == NULL) {
return; /* not a host scope: the cluster-wide check's rows */
}
rec->host_row_verdicts |= XCE_TRACE_V_LIMIT;
if ((rec->level != XCE_TRACE_LEVEL_SUMMARY) || rec->incomplete) {
return; /* full level shows the rows themselves (2.2) */
}
for (k = 0; k < rec->pos_nlimits; k++) {
b = &rec->pos_limits[k];
if (tally_str_eq(b->limit, limit) && tally_str_eq(b->scope, scope)) {
b->last_host = rec->host_seq; /* once per host, at its verdict */
return;
}
}
if (rec->pos_nlimits == rec->pos_limits_cap) {
int cap = (rec->pos_limits_cap > 0) ? rec->pos_limits_cap * 2 : 4;
void* p = realloc(rec->pos_limits, sizeof(*b) * (size_t)cap);
if (p == NULL) {
rec->incomplete = true;
return;
}
rec->pos_limits = (struct sched_trace_tally_build_s*)p;
rec->pos_limits_cap = cap;
}
b = &rec->pos_limits[rec->pos_nlimits];
b->limit = tally_strdup(limit, &failed);
b->scope = tally_strdup(scope, &failed);
b->count = 0;
b->last_host = rec->host_seq;
if (failed) {
free(b->limit);
free(b->scope);
rec->incomplete = true;
return;
}
rec->pos_nlimits++;
}
/* Appends one host to a ROLLBACK entry, growing its array in the arena. */
static void rollback_append(xce_sched_trace_rec_t* rec, sched_trace_entry_t* e,
const char* host)
{
if (e->u.rollback.nhosts == e->u.rollback.cap) {
int cap = (e->u.rollback.cap > 0) ? e->u.rollback.cap * 2 : 4;
const char** h = (const char**)sched_trace_arena_alloc(
rec, sizeof(char*) * (size_t)cap);
if (h == NULL) {
return;
}
if (e->u.rollback.nhosts > 0) {
memcpy(h, e->u.rollback.hosts,
sizeof(char*) * (size_t)e->u.rollback.nhosts);
}
e->u.rollback.hosts = h;
e->u.rollback.cap = cap;
}
e->u.rollback.hosts[e->u.rollback.nhosts++]
= sched_trace_arena_strdup(rec, host);
}
void sched_trace_rollback_host(xce_sched_trace_rec_t* rec, const char* host)
{
sched_trace_entry_t* e;
if (rec->rollback_open) {
/* the same run of released hosts: the same line */
if ((rec->rollback_e != NULL) && !rec->incomplete) {
rollback_append(rec, rec->rollback_e, host);
}
return;
}
e = entry_add(rec, SCHED_TRACE_E_ROLLBACK);
rec->rollback_open = true; /* also when omitted: one line either way */
rec->rollback_e = e;
if (e != NULL) {
rollback_append(rec, e, host);
}
}
void sched_trace_search_end(xce_sched_trace_rec_t* rec, int rc)
{
sched_trace_entry_t* e;
sched_trace_search_stmt_t stmt = SCHED_TRACE_SEARCH_NO_STMT;
int t = rec->scope_tried;
int d = rec->scope_discarded;
int x = rec->scope_not_tried;
int g = t + d + x;
if (rc == 0) {
if (g == 0) {
stmt = SCHED_TRACE_SEARCH_NONE_GENERATED;
rec->counters.reqs_none_generated++;
} else if (d == g) {
stmt = SCHED_TRACE_SEARCH_ALL_DISCARDED;
rec->counters.reqs_all_discarded++;
} else if ((t > 0) && !rec->scope_plan_err) {
stmt = SCHED_TRACE_SEARCH_NONE_PLACED;
rec->counters.reqs_none_placed++;
}
}
/* the search is over: a later search in this requirement counts anew */
rec->scope_tried = 0;
rec->scope_discarded = 0;
rec->scope_not_tried = 0;
rec->scope_plan_err = false;
e = entry_add(rec, SCHED_TRACE_E_SEARCH_END);
if (e == NULL) {
return;
}
e->u.search_end.generated = g;
e->u.search_end.tried = t;
e->u.search_end.discarded = d;
e->u.search_end.not_tried = x;
e->u.search_end.rc = rc;
e->u.search_end.stmt = stmt;
}

View file

@ -22,18 +22,20 @@
* librtti's vget()/key_find(), which every scheduler target links), so
* every white-box test that #includes this file links without extra
* sources.
*
* The outcome capture (task 2.3) is in sched_trace_outcome.c; the helpers
* it shares with this file are declared in sched_trace_internal.h (task
* 2.4).
*/
#include <stdlib.h>
#include <string.h>
#include "core.h"
#include "sched/trace.h"
#include "sched_trace_internal.h"
/* ---- limit name and scope instance ------------------------------------ */
#define VRES_PREFIX "limit="
#define VRES_PREFIX_LEN (sizeof(VRES_PREFIX) - 1)
/*
* Splits a limit vres name, "limit=<name>[/u=..][/q=..][/p=..]" (lib
* xcelimit.c build_vres_name()), into the limit name and the scope part.
@ -74,8 +76,8 @@ static bool split_vres_name(const char* vname, const char* known,
/* Arena copy of a vres scope part, "u=a/q=b" -> "u=a,q=b", with
* ",h=<host>" ("h=<host>" when the scope is empty) appended for a non-NULL
* host: host-limit vres names do not encode the host. */
static char* copy_scope(xce_sched_trace_rec_t* rec, const char* scope,
const char* host)
char* sti_copy_scope(xce_sched_trace_rec_t* rec, const char* scope,
const char* host)
{
size_t n = strlen(scope);
size_t hn = (host != NULL) ? strlen(host) : 0;
@ -103,7 +105,7 @@ static char* copy_scope(xce_sched_trace_rec_t* rec, const char* scope,
}
/* Arena copy of the first `n` bytes of `s`, NUL-terminated. */
static char* copy_prefix(xce_sched_trace_rec_t* rec, const char* s, size_t n)
char* sti_copy_prefix(xce_sched_trace_rec_t* rec, const char* s, size_t n)
{
char* out = (char*)sched_trace_arena_alloc(rec, n + 1);
@ -148,9 +150,9 @@ static bool limit_ref(xce_sched_trace_rec_t* rec, const xce_vres_t* vres,
if (known != NULL) {
out->limit = sched_trace_arena_strdup(rec, known);
} else {
out->limit = copy_prefix(rec, name, name_len);
out->limit = sti_copy_prefix(rec, name, name_len);
}
out->scope = copy_scope(rec, scope, NULL);
out->scope = sti_copy_scope(rec, scope, NULL);
return (out->limit != NULL) && (out->scope != NULL);
}
@ -234,7 +236,7 @@ static float row_uncomit_at(const xvec_t* uncomitted, int idx)
/* Worker `idx`'s matched host limits, obj->hlimits[idx] (sparse: NULL
* when the worker has none). Read directly: zarray_get() takes a
* non-const array. */
static const xlist_t* worker_limits(const xce_sched_obj_t* obj, int idx)
const xlist_t* sti_worker_limits(const xce_sched_obj_t* obj, int idx)
{
const zarray_t* hl = &obj->hlimits;
@ -254,13 +256,13 @@ static const xlist_t* scope_limits(const xce_sched_trace_rec_t* rec)
if (rec->scope_w == NULL) {
return &rec->scope_obj->glimits;
}
return worker_limits(rec->scope_obj, rec->scope_w->idx);
return sti_worker_limits(rec->scope_obj, rec->scope_w->idx);
}
/* The matched limit in `list` whose vres resource is `r` or is named
* `name`, or NULL. Read directly: xlist_get() takes a non-const list. */
static const xce_vres_t* list_vres(const xlist_t* list,
const xce_resource_t* r, const char* name)
const xce_vres_t* sti_list_vres(const xlist_t* list, const xce_resource_t* r,
const char* name)
{
int i;
@ -282,21 +284,12 @@ static const xce_vres_t* list_vres(const xlist_t* list,
return NULL;
}
/* A limit resource named `resname`, resolved: the limit name (not
* NUL-terminated), the vres-form scope part and the limited resource. */
typedef struct {
const char* name;
size_t name_len;
const char* scope;
const char* limited_res; /* NULL: no matched limit names it */
} charge_ref_t;
/* Resolves `resname` through its matched limit `v` (NULL: none, so the
* limit name and scope are parsed from the name, and the limited resource
* is unknown). A name without the "limit=" prefix names the limit
* itself. */
static void charge_resolve(const xce_vres_t* v, const char* resname,
charge_ref_t* c)
void sti_charge_resolve(const xce_vres_t* v, const char* resname,
charge_ref_t* c)
{
const char* known = ((v != NULL) && (v->limit != NULL)) ? v->limit->name
: NULL;
@ -328,7 +321,8 @@ static bool limit_row(const xce_sched_trace_rec_t* rec, const xce_resource_t* r,
char* p;
size_t k;
charge_resolve(list_vres(scope_limits(rec), r, r->name), r->name, &c);
sti_charge_resolve(sti_list_vres(scope_limits(rec), r, r->name), r->name,
&c);
if (rec->scope_w != NULL) {
/* host-limit vres names do not encode the host */
host = (rec->scope_w->name != NULL) ? rec->scope_w->name : "";
@ -430,8 +424,7 @@ void sched_trace_check_rows(xce_sched_trace_rec_t* rec, xce_sched_core_t* core,
/* ---- attempt failure reasons (3.5) ------------------------------------ */
static void count_code(sched_trace_reason_count_t* codes, int* ncodes,
int code)
void sti_count_code(sched_trace_reason_count_t* codes, int* ncodes, int code)
{
int i;
@ -510,7 +503,7 @@ void sched_trace_capture_attempt_reasons(xce_sched_trace_rec_t* rec,
}
if (r->main_reason != 0) {
count_code(codes, &ncodes, r->main_reason);
sti_count_code(codes, &ncodes, r->main_reason);
}
for (i = 0; i < nh; i++) {
const xce_host_reason_t* h = r->host_based_reasons[i];
@ -518,7 +511,7 @@ void sched_trace_capture_attempt_reasons(xce_sched_trace_rec_t* rec,
if (h == NULL) {
continue;
}
count_code(codes, &ncodes, h->reason);
sti_count_code(codes, &ncodes, h->reason);
add_distinct(hosts, &nhosts, h->host);
}
for (i = 0; i < nr; i++) {
@ -527,7 +520,7 @@ void sched_trace_capture_attempt_reasons(xce_sched_trace_rec_t* rec,
if (x == NULL) {
continue;
}
count_code(codes, &ncodes, x->reason);
sti_count_code(codes, &ncodes, x->reason);
add_distinct(hosts, &nhosts, x->host);
add_distinct(res, &nres, x->resource);
}
@ -538,528 +531,3 @@ void sched_trace_capture_attempt_reasons(xce_sched_trace_rec_t* rec,
free((void*)hosts);
free((void*)res);
}
/* ---- dispatch outcome (6.1-6.3, 9.4) ---------------------------------- */
/* True iff the allocated resource `resname` is a limit charge: its
* registered resource has XCE_RESF_LIMIT, or, when the name is not
* registered in this replay, it has the "limit=" prefix. */
static bool is_limit_charge(xce_sched_core_t* core, const char* resname)
{
xce_resource_t* r = NULL;
if ((core != NULL) && (core->resmgr != NULL)
&& (key_find(core->resmgr, (void*)resname, (void**)&r) >= 0)
&& (r != NULL)) {
return (r->flags & XCE_RESF_LIMIT) != 0;
}
return strncmp(resname, VRES_PREFIX, VRES_PREFIX_LEN) == 0;
}
/* The host limits matched to the job on the worker named `host`, or NULL
* (no schedobj, unknown worker, none matched). */
static const xlist_t* host_limits(xce_sched_core_t* core,
const xce_sched_obj_t* obj, const char* host)
{
xce_sched_worker_t* w = NULL;
if ((obj == NULL) || (core == NULL) || (core->workers == NULL)
|| (host == NULL)
|| (key_find(core->workers, (void*)host, (void**)&w) < 0)
|| (w == NULL)) {
return NULL;
}
return worker_limits(obj, w->idx);
}
/* Stores allocated amount `a` as a limit charge, resolved through `limits`;
* a host charge (`host` non-NULL) gets the host appended to its scope. */
static bool charge_row(xce_sched_trace_rec_t* rec, const xlist_t* limits,
const xce_alloc_t* a, const char* host,
sched_trace_alloc_row_t* out)
{
charge_ref_t c;
charge_resolve(list_vres(limits, NULL, a->resname), a->resname, &c);
out->res = sched_trace_arena_strdup(rec, a->resname);
out->value = a->value;
out->limit = copy_prefix(rec, c.name, c.name_len);
out->scope = copy_scope(rec, c.scope, host);
out->limited_res = sched_trace_arena_strdup(rec, c.limited_res);
return (out->res != NULL) && (out->limit != NULL) && (out->scope != NULL)
&& ((c.limited_res == NULL) || (out->limited_res != NULL));
}
/* One allocation list (a host's, or the shared one) as resource rows and
* limit charges, each in allocation order. False on an arena failure. */
static bool alloc_rows(xce_sched_trace_rec_t* rec, xce_sched_core_t* core,
const xlist_t* limits, int n, xce_alloc_t** allocs,
const char* host, int* nres,
sched_trace_alloc_row_t** res, int* ncharges,
sched_trace_alloc_row_t** charges)
{
size_t sz = sizeof(**res) * (size_t)n;
int i;
if ((n <= 0) || (allocs == NULL)) {
return true;
}
*res = (sched_trace_alloc_row_t*)sched_trace_arena_alloc(rec, sz);
*charges = (sched_trace_alloc_row_t*)sched_trace_arena_alloc(rec, sz);
if ((*res == NULL) || (*charges == NULL)) {
return false;
}
for (i = 0; i < n; i++) {
const xce_alloc_t* a = allocs[i];
sched_trace_alloc_row_t* row;
if ((a == NULL) || (a->resname == NULL)) {
continue;
}
if (is_limit_charge(core, a->resname)) {
if (!charge_row(rec, limits, a, host, &(*charges)[(*ncharges)++])) {
return false;
}
continue;
}
row = &(*res)[(*nres)++];
row->res = sched_trace_arena_strdup(rec, a->resname);
row->value = a->value;
if (row->res == NULL) {
return false;
}
}
return true;
}
/* A total over the omitted hosts, keyed by resource name or, for a charge,
* by limit name and scope. Scratch memory; the strings are the sources'. */
typedef struct {
const char* res; /* resource total; NULL: a limit total */
charge_ref_t c; /* limit total */
double value;
} agg_t;
static void agg_add(agg_t* agg, int* n, const char* res,
const charge_ref_t* c, double value)
{
int i;
for (i = 0; i < *n; i++) {
if ((res != NULL)
? (strcmp(agg[i].res, res) == 0)
: ((agg[i].c.name_len == c->name_len)
&& (strncmp(agg[i].c.name, c->name, c->name_len) == 0)
&& (strcmp(agg[i].c.scope, c->scope) == 0))) {
break;
}
}
if (i == *n) {
memset(&agg[i], 0, sizeof(agg[i]));
agg[i].res = res;
if (c != NULL) {
agg[i].c = *c;
}
(*n)++;
}
agg[i].value += value;
if ((c != NULL) && (agg[i].c.limited_res == NULL)) {
agg[i].c.limited_res = c->limited_res;
}
}
/* Copies `n` totals into the arena as rows (a charge's scope has no
* host part: it totals hosts). */
static bool agg_store(xce_sched_trace_rec_t* rec, const agg_t* agg, int n,
int* nout, sched_trace_alloc_row_t** out)
{
int i;
if (n == 0) {
return true;
}
*out = (sched_trace_alloc_row_t*)sched_trace_arena_alloc(
rec, sizeof(**out) * (size_t)n);
if (*out == NULL) {
return false;
}
for (i = 0; i < n; i++) {
sched_trace_alloc_row_t* row = &(*out)[i];
row->value = agg[i].value;
if (agg[i].res != NULL) {
row->res = sched_trace_arena_strdup(rec, agg[i].res);
if (row->res == NULL) {
return false;
}
continue;
}
row->limit = copy_prefix(rec, agg[i].c.name, agg[i].c.name_len);
row->scope = copy_scope(rec, agg[i].c.scope, NULL);
row->limited_res = sched_trace_arena_strdup(rec, agg[i].c.limited_res);
if ((row->limit == NULL) || (row->scope == NULL)) {
return false;
}
}
*nout = n;
return true;
}
/* Totals of the hosts from index `from` on (9.4): per resource without the
* limit charges, and per limit. False on an allocation failure. */
static bool aggregate_hosts(xce_sched_trace_rec_t* rec, xce_sched_core_t* core,
const xce_sched_obj_t* obj,
const xce_job_alloc_t* alloc, int from)
{
sched_trace_dispatch_out_t* d = &rec->dispatch;
size_t total = 0;
agg_t* res;
agg_t* chg;
int nres = 0;
int nchg = 0;
bool ok;
int i;
int k;
for (i = from; i < alloc->nhosts; i++) {
if ((alloc->allocs[i] != NULL) && (alloc->allocs[i]->allocs != NULL)
&& (alloc->allocs[i]->nallocs > 0)) {
total += (size_t)alloc->allocs[i]->nallocs;
}
}
res = (agg_t*)malloc(sizeof(*res) * (total + 1));
chg = (agg_t*)malloc(sizeof(*chg) * (total + 1));
ok = (res != NULL) && (chg != NULL);
for (i = from; ok && (i < alloc->nhosts); i++) {
const xce_host_alloc_t* h = alloc->allocs[i];
const xlist_t* limits;
if ((h == NULL) || (h->allocs == NULL)) {
continue;
}
limits = host_limits(core, obj, h->host);
for (k = 0; k < h->nallocs; k++) {
const xce_alloc_t* a = h->allocs[k];
charge_ref_t c;
if ((a == NULL) || (a->resname == NULL)) {
continue;
}
if (!is_limit_charge(core, a->resname)) {
agg_add(res, &nres, a->resname, NULL, a->value);
continue;
}
charge_resolve(list_vres(limits, NULL, a->resname), a->resname,
&c);
agg_add(chg, &nchg, NULL, &c, a->value);
}
}
ok = ok && agg_store(rec, res, nres, &d->nagg_res, &d->agg_res)
&& agg_store(rec, chg, nchg, &d->nagg_charges, &d->agg_charges);
free(res);
free(chg);
return ok;
}
void sched_trace_capture_dispatch(xce_sched_trace_rec_t* rec,
xce_sched_core_t* core,
const xce_sched_obj_t* obj,
const xce_job_alloc_t* alloc)
{
sched_trace_dispatch_out_t* d = &rec->dispatch;
int from = -1;
int nh;
int i;
memset(d, 0, sizeof(*d));
rec->outcome = SCHED_TRACE_OUTCOME_DISPATCH;
if (alloc == NULL) {
return;
}
if (!alloc_rows(rec, core, (obj != NULL) ? &obj->glimits : NULL,
alloc->nres_shared, alloc->allocs_shared, NULL,
&d->nshared, &d->shared, &d->nshared_charges,
&d->shared_charges)) {
goto fail;
}
nh = (alloc->allocs != NULL) ? alloc->nhosts : 0;
if (nh > 0) {
int keep = (nh < XCE_TRACE_OUTCOME_HOSTS) ? nh : XCE_TRACE_OUTCOME_HOSTS;
d->hosts = (sched_trace_host_out_t*)sched_trace_arena_alloc(
rec, sizeof(*d->hosts) * (size_t)keep);
if (d->hosts == NULL) {
goto fail;
}
}
for (i = 0; i < nh; i++) {
const xce_host_alloc_t* h = alloc->allocs[i];
sched_trace_host_out_t* ho;
if (h == NULL) {
continue;
}
if (d->nhosts == XCE_TRACE_OUTCOME_HOSTS) {
if (from < 0) {
from = i;
}
d->omitted_hosts++;
continue;
}
ho = &d->hosts[d->nhosts++];
ho->host = sched_trace_arena_strdup(rec, h->host);
if (((h->host != NULL) && (ho->host == NULL))
|| !alloc_rows(rec, core, host_limits(core, obj, h->host),
h->nallocs, h->allocs,
(h->host != NULL) ? h->host : "", &ho->nres,
&ho->res, &ho->ncharges, &ho->charges)) {
goto fail;
}
}
if ((from >= 0) && !aggregate_hosts(rec, core, obj, alloc, from)) {
goto fail;
}
return;
fail:
rec->incomplete = true; /* design "Error Strategy" */
}
xce_sched_trace_rec_t*
sched_trace_rec_reconstructed(const xce_sched_trace_ctl_t* ctl,
xce_sched_core_t* core, uint64_t jobid,
const xce_sched_obj_t* obj,
const xce_job_alloc_t* allocs)
{
xce_sched_trace_rec_t* rec;
rec = sched_trace_rec_new(ctl, jobid, XCE_TRACE_REC_RECONSTRUCTED);
if (rec != NULL) {
sched_trace_capture_dispatch(rec, core, obj, allocs);
}
return rec;
}
/* ---- pend outcome (6.4, 9.4) ------------------------------------------ */
/* A set of host names, open addressing over a power-of-two table. Scratch
* memory; the names are the source's. */
typedef struct {
const char** slot;
size_t mask;
int n;
} host_set_t;
static bool host_set_init(host_set_t* s, int nmax)
{
size_t cap = 16;
while (cap < (size_t)nmax * 2 + 2) {
cap <<= 1;
}
s->slot = (const char**)calloc(cap, sizeof(*s->slot));
s->mask = cap - 1;
s->n = 0;
return s->slot != NULL;
}
/* True iff `h` is in the set; when absent and `insert`, adds it. */
static bool host_set_has(host_set_t* s, const char* h, bool insert)
{
size_t i = 2166136261u; /* FNV-1a */
const unsigned char* p;
for (p = (const unsigned char*)h; *p != '\0'; p++) {
i = (i ^ *p) * 16777619u;
}
for (i &= s->mask; s->slot[i] != NULL; i = (i + 1) & s->mask) {
if (strcmp(s->slot[i], h) == 0) {
return true;
}
}
if (insert) {
s->slot[i] = h;
s->n++;
}
return false;
}
typedef struct {
host_set_t kept; /* the first 50 distinct hosts */
host_set_t omitted; /* the hosts after them */
sched_trace_reason_count_t* codes; /* omitted entries per code */
int ncodes;
} pend_build_t;
/* True iff an entry naming `host` is kept: it names no host, or one of the
* first 50 distinct hosts. Otherwise it is counted under its code. */
static bool pend_keep(pend_build_t* b, const char* host, int code)
{
if ((host == NULL) || (host[0] == '\0')
|| host_set_has(&b->kept, host, false)) {
return true;
}
if (b->kept.n < XCE_TRACE_OUTCOME_HOSTS) {
(void)host_set_has(&b->kept, host, true);
return true;
}
(void)host_set_has(&b->omitted, host, true);
count_code(b->codes, &b->ncodes, code);
return false;
}
/* Arena copies of the kept host entries (NULL entries stay NULL). */
static bool copy_host_reasons(xce_sched_trace_rec_t* rec,
const xce_job_preason_t* r, const bool* keep,
int nh, xce_job_preason_t* c)
{
int i;
for (i = 0; i < nh; i++) {
c->nhost_reasons += keep[i] ? 1 : 0;
}
if (c->nhost_reasons == 0) {
return true;
}
c->host_based_reasons = (xce_host_reason_t**)sched_trace_arena_alloc(
rec, sizeof(*c->host_based_reasons) * (size_t)c->nhost_reasons);
if (c->host_based_reasons == NULL) {
return false;
}
c->nhost_reasons = 0;
for (i = 0; i < nh; i++) {
const xce_host_reason_t* src = r->host_based_reasons[i];
xce_host_reason_t* dst = NULL;
if (!keep[i]) {
continue;
}
if (src != NULL) {
dst = (xce_host_reason_t*)sched_trace_arena_alloc(rec, sizeof(*dst));
if (dst == NULL) {
return false;
}
dst->reason = src->reason;
dst->host = sched_trace_arena_strdup(rec, src->host);
if ((src->host != NULL) && (dst->host == NULL)) {
return false;
}
}
c->host_based_reasons[c->nhost_reasons++] = dst;
}
return true;
}
/* Arena copies of the kept resource entries (NULL entries stay NULL). */
static bool copy_res_reasons(xce_sched_trace_rec_t* rec,
const xce_job_preason_t* r, const bool* keep,
int nr, xce_job_preason_t* c)
{
int i;
for (i = 0; i < nr; i++) {
c->nres_reasons += keep[i] ? 1 : 0;
}
if (c->nres_reasons == 0) {
return true;
}
c->res_based_reasons = (xce_res_reason_t**)sched_trace_arena_alloc(
rec, sizeof(*c->res_based_reasons) * (size_t)c->nres_reasons);
if (c->res_based_reasons == NULL) {
return false;
}
c->nres_reasons = 0;
for (i = 0; i < nr; i++) {
const xce_res_reason_t* src = r->res_based_reasons[i];
xce_res_reason_t* dst = NULL;
if (!keep[i]) {
continue;
}
if (src != NULL) {
dst = (xce_res_reason_t*)sched_trace_arena_alloc(rec, sizeof(*dst));
if (dst == NULL) {
return false;
}
dst->reason = src->reason;
dst->host = sched_trace_arena_strdup(rec, src->host);
dst->resource = sched_trace_arena_strdup(rec, src->resource);
if (((src->host != NULL) && (dst->host == NULL))
|| ((src->resource != NULL) && (dst->resource == NULL))) {
return false;
}
}
c->res_based_reasons[c->nres_reasons++] = dst;
}
return true;
}
void sched_trace_capture_pend(xce_sched_trace_rec_t* rec,
const xce_pendreason_t* reason)
{
sched_trace_pend_out_t* p = &rec->pend;
const xce_job_preason_t* r = (reason != NULL) ? reason->reasons : NULL;
pend_build_t b;
bool* keep;
int nh;
int nr;
int i;
bool ok;
memset(p, 0, sizeof(*p));
rec->outcome = SCHED_TRACE_OUTCOME_PEND;
if (r == NULL) {
return;
}
nh = ((r->host_based_reasons != NULL) && (r->nhost_reasons > 0))
? r->nhost_reasons : 0;
nr = ((r->res_based_reasons != NULL) && (r->nres_reasons > 0))
? r->nres_reasons : 0;
/* which entries are kept, in one pass over the source order */
memset(&b, 0, sizeof(b));
keep = (bool*)malloc(sizeof(*keep) * (size_t)(nh + nr + 1));
b.codes = (sched_trace_reason_count_t*)malloc(sizeof(*b.codes)
* (size_t)(nh + nr + 1));
ok = (keep != NULL) && (b.codes != NULL)
&& host_set_init(&b.kept, XCE_TRACE_OUTCOME_HOSTS)
&& host_set_init(&b.omitted, nh + nr);
for (i = 0; ok && (i < nh); i++) {
const xce_host_reason_t* e = r->host_based_reasons[i];
keep[i] = (e == NULL) || pend_keep(&b, e->host, e->reason);
}
for (i = 0; ok && (i < nr); i++) {
const xce_res_reason_t* e = r->res_based_reasons[i];
keep[nh + i] = (e == NULL) || pend_keep(&b, e->host, e->reason);
}
if (ok) {
p->reasons = (xce_job_preason_t*)sched_trace_arena_alloc(
rec, sizeof(*p->reasons));
ok = (p->reasons != NULL);
}
if (ok) {
p->reasons->main_reason = r->main_reason;
ok = copy_host_reasons(rec, r, keep, nh, p->reasons)
&& copy_res_reasons(rec, r, keep + nh, nr, p->reasons);
}
if (ok && (b.ncodes > 0)) {
p->omitted_codes = (sched_trace_reason_count_t*)sched_trace_arena_memdup(
rec, b.codes, sizeof(*b.codes) * (size_t)b.ncodes);
ok = (p->omitted_codes != NULL);
p->ncodes = ok ? b.ncodes : 0;
}
p->omitted_hosts = b.omitted.n;
if (!ok) {
rec->incomplete = true; /* design "Error Strategy" */
}
free(keep);
free(b.codes);
free((void*)b.kept.slot);
free((void*)b.omitted.slot);
}

View file

@ -0,0 +1,83 @@
#if !defined(_SCHED_TRACE_INTERNAL_H_)
#define _SCHED_TRACE_INTERNAL_H_ 1
/*
* sched_trace_internal.h - declarations shared by the decision trace
* sources (scheduler-sim-decision-trace spec, task 2.4). NOT installed and
* NOT included outside src/scheduler/src/sched_trace*.c; the public
* interface stays include/sched/trace.h.
*
* The sources are split to keep each under the scheduler's 800-line cap:
*
* - sched_trace.c: the record (arena, header, lifecycle), the entry
* sequence and line budget, and the requirement, plan and check
* entries (TraceModel).
* - sched_trace_scan.c: positions, host verdicts and rows, the
* summary-level tallies, rollback, commit and the search-end counters
* (TraceModel).
* - sched_trace_capture.c: limit names and scopes, selection,
* cluster-wide limits, check rows and attempt reasons (TraceCapture).
* - sched_trace_outcome.c: the dispatch and pend outcomes and the
* reconstructed-dispatch record (TraceCapture).
*
* The helpers below were file statics before the split. They have
* external linkage now, so they carry the sti_ prefix; nothing outside
* the trace sources calls them. The same no-I/O rule applies to this
* header (test_core_isolation.c).
*/
#include <stdbool.h>
#include <stddef.h>
#include "core.h"
#include "sched/trace.h"
/* ---- model (sched_trace.c), used by sched_trace_scan.c ---------------- */
/* Summary level: one limit hit by hosts of the current position. Builder
* memory, owned by the record until the position ends. */
struct sched_trace_tally_build_s {
char* limit;
char* scope;
int count; /* hosts with a limit-exceeded verdict */
unsigned last_host; /* host_seq of the last host that hit it */
};
extern void sti_pos_limits_clear(xce_sched_trace_rec_t* rec);
extern void sti_host_rows_clear(xce_sched_trace_rec_t* rec);
extern sched_trace_entry_t* sti_entry_add(xce_sched_trace_rec_t* rec,
sched_trace_entry_type_t type);
extern void sti_rollback_run_end(xce_sched_trace_rec_t* rec);
extern sched_trace_entry_t* sti_scope_begin(xce_sched_trace_rec_t* rec,
sched_trace_entry_type_t type);
extern sched_trace_entry_t* sti_scope_end(xce_sched_trace_rec_t* rec,
sched_trace_entry_type_t type);
/* ---- capture (sched_trace_capture.c), used by sched_trace_outcome.c --- */
#define VRES_PREFIX "limit="
#define VRES_PREFIX_LEN (sizeof(VRES_PREFIX) - 1)
/* A limit resource named `resname`, resolved: the limit name (not
* NUL-terminated), the vres-form scope part and the limited resource. */
typedef struct {
const char* name;
size_t name_len;
const char* scope;
const char* limited_res; /* NULL: no matched limit names it */
} charge_ref_t;
extern char* sti_copy_scope(xce_sched_trace_rec_t* rec, const char* scope,
const char* host);
extern char* sti_copy_prefix(xce_sched_trace_rec_t* rec, const char* s,
size_t n);
extern const xlist_t* sti_worker_limits(const xce_sched_obj_t* obj, int idx);
extern const xce_vres_t* sti_list_vres(const xlist_t* list,
const xce_resource_t* r,
const char* name);
extern void sti_charge_resolve(const xce_vres_t* v, const char* resname,
charge_ref_t* c);
extern void sti_count_code(sched_trace_reason_count_t* codes, int* ncodes,
int code);
#endif /* _SCHED_TRACE_INTERNAL_H_ */

View file

@ -0,0 +1,548 @@
/*
* sched_trace_outcome.c - allocator-side trace capture, part 2
* (scheduler-sim-decision-trace spec, design.md "TraceCapture").
*
* The decision's outcome: the dispatch (6.1-6.3) with its per-host
* resource rows and limit charges, totals over the hosts past the first
* XCE_TRACE_OUTCOME_HOSTS (9.4), the pend reasons (6.4, 9.4), and the
* reconstructed-dispatch record (2.5). Split from sched_trace_capture.c
* (task 2.4), whose limit-resolution helpers it shares through
* sched_trace_internal.h.
*
* Same rules as sched_trace_capture.c: inputs are read-only, struct fields
* and librtti's key_find() only, no scheduler API that mutates state
* (Requirement 10), and no I/O and no logging (test_core_isolation.c scans
* this file).
*/
#include <stdlib.h>
#include <string.h>
#include "core.h"
#include "sched/trace.h"
#include "sched_trace_internal.h"
/* ---- dispatch outcome (6.1-6.3, 9.4) ---------------------------------- */
/* True iff the allocated resource `resname` is a limit charge: its
* registered resource has XCE_RESF_LIMIT, or, when the name is not
* registered in this replay, it has the "limit=" prefix. */
static bool is_limit_charge(xce_sched_core_t* core, const char* resname)
{
xce_resource_t* r = NULL;
if ((core != NULL) && (core->resmgr != NULL)
&& (key_find(core->resmgr, (void*)resname, (void**)&r) >= 0)
&& (r != NULL)) {
return (r->flags & XCE_RESF_LIMIT) != 0;
}
return strncmp(resname, VRES_PREFIX, VRES_PREFIX_LEN) == 0;
}
/* The host limits matched to the job on the worker named `host`, or NULL
* (no schedobj, unknown worker, none matched). */
static const xlist_t* host_limits(xce_sched_core_t* core,
const xce_sched_obj_t* obj, const char* host)
{
xce_sched_worker_t* w = NULL;
if ((obj == NULL) || (core == NULL) || (core->workers == NULL)
|| (host == NULL)
|| (key_find(core->workers, (void*)host, (void**)&w) < 0)
|| (w == NULL)) {
return NULL;
}
return sti_worker_limits(obj, w->idx);
}
/* Stores allocated amount `a` as a limit charge, resolved through `limits`;
* a host charge (`host` non-NULL) gets the host appended to its scope. */
static bool charge_row(xce_sched_trace_rec_t* rec, const xlist_t* limits,
const xce_alloc_t* a, const char* host,
sched_trace_alloc_row_t* out)
{
charge_ref_t c;
sti_charge_resolve(sti_list_vres(limits, NULL, a->resname), a->resname, &c);
out->res = sched_trace_arena_strdup(rec, a->resname);
out->value = a->value;
out->limit = sti_copy_prefix(rec, c.name, c.name_len);
out->scope = sti_copy_scope(rec, c.scope, host);
out->limited_res = sched_trace_arena_strdup(rec, c.limited_res);
return (out->res != NULL) && (out->limit != NULL) && (out->scope != NULL)
&& ((c.limited_res == NULL) || (out->limited_res != NULL));
}
/* One allocation list (a host's, or the shared one) as resource rows and
* limit charges, each in allocation order. False on an arena failure. */
static bool alloc_rows(xce_sched_trace_rec_t* rec, xce_sched_core_t* core,
const xlist_t* limits, int n, xce_alloc_t** allocs,
const char* host, int* nres,
sched_trace_alloc_row_t** res, int* ncharges,
sched_trace_alloc_row_t** charges)
{
size_t sz = sizeof(**res) * (size_t)n;
int i;
if ((n <= 0) || (allocs == NULL)) {
return true;
}
*res = (sched_trace_alloc_row_t*)sched_trace_arena_alloc(rec, sz);
*charges = (sched_trace_alloc_row_t*)sched_trace_arena_alloc(rec, sz);
if ((*res == NULL) || (*charges == NULL)) {
return false;
}
for (i = 0; i < n; i++) {
const xce_alloc_t* a = allocs[i];
sched_trace_alloc_row_t* row;
if ((a == NULL) || (a->resname == NULL)) {
continue;
}
if (is_limit_charge(core, a->resname)) {
if (!charge_row(rec, limits, a, host, &(*charges)[(*ncharges)++])) {
return false;
}
continue;
}
row = &(*res)[(*nres)++];
row->res = sched_trace_arena_strdup(rec, a->resname);
row->value = a->value;
if (row->res == NULL) {
return false;
}
}
return true;
}
/* A total over the omitted hosts, keyed by resource name or, for a charge,
* by limit name and scope. Scratch memory; the strings are the sources'. */
typedef struct {
const char* res; /* resource total; NULL: a limit total */
charge_ref_t c; /* limit total */
double value;
} agg_t;
static void agg_add(agg_t* agg, int* n, const char* res,
const charge_ref_t* c, double value)
{
int i;
for (i = 0; i < *n; i++) {
if ((res != NULL)
? (strcmp(agg[i].res, res) == 0)
: ((agg[i].c.name_len == c->name_len)
&& (strncmp(agg[i].c.name, c->name, c->name_len) == 0)
&& (strcmp(agg[i].c.scope, c->scope) == 0))) {
break;
}
}
if (i == *n) {
memset(&agg[i], 0, sizeof(agg[i]));
agg[i].res = res;
if (c != NULL) {
agg[i].c = *c;
}
(*n)++;
}
agg[i].value += value;
if ((c != NULL) && (agg[i].c.limited_res == NULL)) {
agg[i].c.limited_res = c->limited_res;
}
}
/* Copies `n` totals into the arena as rows (a charge's scope has no
* host part: it totals hosts). */
static bool agg_store(xce_sched_trace_rec_t* rec, const agg_t* agg, int n,
int* nout, sched_trace_alloc_row_t** out)
{
int i;
if (n == 0) {
return true;
}
*out = (sched_trace_alloc_row_t*)sched_trace_arena_alloc(
rec, sizeof(**out) * (size_t)n);
if (*out == NULL) {
return false;
}
for (i = 0; i < n; i++) {
sched_trace_alloc_row_t* row = &(*out)[i];
row->value = agg[i].value;
if (agg[i].res != NULL) {
row->res = sched_trace_arena_strdup(rec, agg[i].res);
if (row->res == NULL) {
return false;
}
continue;
}
row->limit
= sti_copy_prefix(rec, agg[i].c.name, agg[i].c.name_len);
row->scope = sti_copy_scope(rec, agg[i].c.scope, NULL);
row->limited_res = sched_trace_arena_strdup(rec, agg[i].c.limited_res);
if ((row->limit == NULL) || (row->scope == NULL)) {
return false;
}
}
*nout = n;
return true;
}
/* Totals of the hosts from index `from` on (9.4): per resource without the
* limit charges, and per limit. False on an allocation failure. */
static bool aggregate_hosts(xce_sched_trace_rec_t* rec, xce_sched_core_t* core,
const xce_sched_obj_t* obj,
const xce_job_alloc_t* alloc, int from)
{
sched_trace_dispatch_out_t* d = &rec->dispatch;
size_t total = 0;
agg_t* res;
agg_t* chg;
int nres = 0;
int nchg = 0;
bool ok;
int i;
int k;
for (i = from; i < alloc->nhosts; i++) {
if ((alloc->allocs[i] != NULL) && (alloc->allocs[i]->allocs != NULL)
&& (alloc->allocs[i]->nallocs > 0)) {
total += (size_t)alloc->allocs[i]->nallocs;
}
}
res = (agg_t*)malloc(sizeof(*res) * (total + 1));
chg = (agg_t*)malloc(sizeof(*chg) * (total + 1));
ok = (res != NULL) && (chg != NULL);
for (i = from; ok && (i < alloc->nhosts); i++) {
const xce_host_alloc_t* h = alloc->allocs[i];
const xlist_t* limits;
if ((h == NULL) || (h->allocs == NULL)) {
continue;
}
limits = host_limits(core, obj, h->host);
for (k = 0; k < h->nallocs; k++) {
const xce_alloc_t* a = h->allocs[k];
charge_ref_t c;
if ((a == NULL) || (a->resname == NULL)) {
continue;
}
if (!is_limit_charge(core, a->resname)) {
agg_add(res, &nres, a->resname, NULL, a->value);
continue;
}
sti_charge_resolve(sti_list_vres(limits, NULL, a->resname),
a->resname, &c);
agg_add(chg, &nchg, NULL, &c, a->value);
}
}
ok = ok && agg_store(rec, res, nres, &d->nagg_res, &d->agg_res)
&& agg_store(rec, chg, nchg, &d->nagg_charges, &d->agg_charges);
free(res);
free(chg);
return ok;
}
void sched_trace_capture_dispatch(xce_sched_trace_rec_t* rec,
xce_sched_core_t* core,
const xce_sched_obj_t* obj,
const xce_job_alloc_t* alloc)
{
sched_trace_dispatch_out_t* d = &rec->dispatch;
int from = -1;
int nh;
int i;
memset(d, 0, sizeof(*d));
rec->outcome = SCHED_TRACE_OUTCOME_DISPATCH;
if (alloc == NULL) {
return;
}
if (!alloc_rows(rec, core, (obj != NULL) ? &obj->glimits : NULL,
alloc->nres_shared, alloc->allocs_shared, NULL,
&d->nshared, &d->shared, &d->nshared_charges,
&d->shared_charges)) {
goto fail;
}
nh = (alloc->allocs != NULL) ? alloc->nhosts : 0;
if (nh > 0) {
int keep = (nh < XCE_TRACE_OUTCOME_HOSTS) ? nh : XCE_TRACE_OUTCOME_HOSTS;
d->hosts = (sched_trace_host_out_t*)sched_trace_arena_alloc(
rec, sizeof(*d->hosts) * (size_t)keep);
if (d->hosts == NULL) {
goto fail;
}
}
for (i = 0; i < nh; i++) {
const xce_host_alloc_t* h = alloc->allocs[i];
sched_trace_host_out_t* ho;
if (h == NULL) {
continue;
}
if (d->nhosts == XCE_TRACE_OUTCOME_HOSTS) {
if (from < 0) {
from = i;
}
d->omitted_hosts++;
continue;
}
ho = &d->hosts[d->nhosts++];
ho->host = sched_trace_arena_strdup(rec, h->host);
if (((h->host != NULL) && (ho->host == NULL))
|| !alloc_rows(rec, core, host_limits(core, obj, h->host),
h->nallocs, h->allocs,
(h->host != NULL) ? h->host : "", &ho->nres,
&ho->res, &ho->ncharges, &ho->charges)) {
goto fail;
}
}
if ((from >= 0) && !aggregate_hosts(rec, core, obj, alloc, from)) {
goto fail;
}
return;
fail:
rec->incomplete = true; /* design "Error Strategy" */
}
xce_sched_trace_rec_t*
sched_trace_rec_reconstructed(const xce_sched_trace_ctl_t* ctl,
xce_sched_core_t* core, uint64_t jobid,
const xce_sched_obj_t* obj,
const xce_job_alloc_t* allocs)
{
xce_sched_trace_rec_t* rec;
rec = sched_trace_rec_new(ctl, jobid, XCE_TRACE_REC_RECONSTRUCTED);
if (rec != NULL) {
sched_trace_capture_dispatch(rec, core, obj, allocs);
}
return rec;
}
/* ---- pend outcome (6.4, 9.4) ------------------------------------------ */
/* A set of host names, open addressing over a power-of-two table. Scratch
* memory; the names are the source's. */
typedef struct {
const char** slot;
size_t mask;
int n;
} host_set_t;
static bool host_set_init(host_set_t* s, int nmax)
{
size_t cap = 16;
while (cap < (size_t)nmax * 2 + 2) {
cap <<= 1;
}
s->slot = (const char**)calloc(cap, sizeof(*s->slot));
s->mask = cap - 1;
s->n = 0;
return s->slot != NULL;
}
/* True iff `h` is in the set; when absent and `insert`, adds it. */
static bool host_set_has(host_set_t* s, const char* h, bool insert)
{
size_t i = 2166136261u; /* FNV-1a */
const unsigned char* p;
for (p = (const unsigned char*)h; *p != '\0'; p++) {
i = (i ^ *p) * 16777619u;
}
for (i &= s->mask; s->slot[i] != NULL; i = (i + 1) & s->mask) {
if (strcmp(s->slot[i], h) == 0) {
return true;
}
}
if (insert) {
s->slot[i] = h;
s->n++;
}
return false;
}
typedef struct {
host_set_t kept; /* the first 50 distinct hosts */
host_set_t omitted; /* the hosts after them */
sched_trace_reason_count_t* codes; /* omitted entries per code */
int ncodes;
} pend_build_t;
/* True iff an entry naming `host` is kept: it names no host, or one of the
* first 50 distinct hosts. Otherwise it is counted under its code. */
static bool pend_keep(pend_build_t* b, const char* host, int code)
{
if ((host == NULL) || (host[0] == '\0')
|| host_set_has(&b->kept, host, false)) {
return true;
}
if (b->kept.n < XCE_TRACE_OUTCOME_HOSTS) {
(void)host_set_has(&b->kept, host, true);
return true;
}
(void)host_set_has(&b->omitted, host, true);
sti_count_code(b->codes, &b->ncodes, code);
return false;
}
/* Arena copies of the kept host entries (NULL entries stay NULL). */
static bool copy_host_reasons(xce_sched_trace_rec_t* rec,
const xce_job_preason_t* r, const bool* keep,
int nh, xce_job_preason_t* c)
{
int i;
for (i = 0; i < nh; i++) {
c->nhost_reasons += keep[i] ? 1 : 0;
}
if (c->nhost_reasons == 0) {
return true;
}
c->host_based_reasons = (xce_host_reason_t**)sched_trace_arena_alloc(
rec, sizeof(*c->host_based_reasons) * (size_t)c->nhost_reasons);
if (c->host_based_reasons == NULL) {
return false;
}
c->nhost_reasons = 0;
for (i = 0; i < nh; i++) {
const xce_host_reason_t* src = r->host_based_reasons[i];
xce_host_reason_t* dst = NULL;
if (!keep[i]) {
continue;
}
if (src != NULL) {
dst = (xce_host_reason_t*)sched_trace_arena_alloc(rec, sizeof(*dst));
if (dst == NULL) {
return false;
}
dst->reason = src->reason;
dst->host = sched_trace_arena_strdup(rec, src->host);
if ((src->host != NULL) && (dst->host == NULL)) {
return false;
}
}
c->host_based_reasons[c->nhost_reasons++] = dst;
}
return true;
}
/* Arena copies of the kept resource entries (NULL entries stay NULL). */
static bool copy_res_reasons(xce_sched_trace_rec_t* rec,
const xce_job_preason_t* r, const bool* keep,
int nr, xce_job_preason_t* c)
{
int i;
for (i = 0; i < nr; i++) {
c->nres_reasons += keep[i] ? 1 : 0;
}
if (c->nres_reasons == 0) {
return true;
}
c->res_based_reasons = (xce_res_reason_t**)sched_trace_arena_alloc(
rec, sizeof(*c->res_based_reasons) * (size_t)c->nres_reasons);
if (c->res_based_reasons == NULL) {
return false;
}
c->nres_reasons = 0;
for (i = 0; i < nr; i++) {
const xce_res_reason_t* src = r->res_based_reasons[i];
xce_res_reason_t* dst = NULL;
if (!keep[i]) {
continue;
}
if (src != NULL) {
dst = (xce_res_reason_t*)sched_trace_arena_alloc(rec, sizeof(*dst));
if (dst == NULL) {
return false;
}
dst->reason = src->reason;
dst->host = sched_trace_arena_strdup(rec, src->host);
dst->resource = sched_trace_arena_strdup(rec, src->resource);
if (((src->host != NULL) && (dst->host == NULL))
|| ((src->resource != NULL) && (dst->resource == NULL))) {
return false;
}
}
c->res_based_reasons[c->nres_reasons++] = dst;
}
return true;
}
void sched_trace_capture_pend(xce_sched_trace_rec_t* rec,
const xce_pendreason_t* reason)
{
sched_trace_pend_out_t* p = &rec->pend;
const xce_job_preason_t* r = (reason != NULL) ? reason->reasons : NULL;
pend_build_t b;
bool* keep;
int nh;
int nr;
int i;
bool ok;
memset(p, 0, sizeof(*p));
rec->outcome = SCHED_TRACE_OUTCOME_PEND;
if (r == NULL) {
return;
}
nh = ((r->host_based_reasons != NULL) && (r->nhost_reasons > 0))
? r->nhost_reasons : 0;
nr = ((r->res_based_reasons != NULL) && (r->nres_reasons > 0))
? r->nres_reasons : 0;
/* which entries are kept, in one pass over the source order */
memset(&b, 0, sizeof(b));
keep = (bool*)malloc(sizeof(*keep) * (size_t)(nh + nr + 1));
b.codes = (sched_trace_reason_count_t*)malloc(sizeof(*b.codes)
* (size_t)(nh + nr + 1));
ok = (keep != NULL) && (b.codes != NULL)
&& host_set_init(&b.kept, XCE_TRACE_OUTCOME_HOSTS)
&& host_set_init(&b.omitted, nh + nr);
for (i = 0; ok && (i < nh); i++) {
const xce_host_reason_t* e = r->host_based_reasons[i];
keep[i] = (e == NULL) || pend_keep(&b, e->host, e->reason);
}
for (i = 0; ok && (i < nr); i++) {
const xce_res_reason_t* e = r->res_based_reasons[i];
keep[nh + i] = (e == NULL) || pend_keep(&b, e->host, e->reason);
}
if (ok) {
p->reasons = (xce_job_preason_t*)sched_trace_arena_alloc(
rec, sizeof(*p->reasons));
ok = (p->reasons != NULL);
}
if (ok) {
p->reasons->main_reason = r->main_reason;
ok = copy_host_reasons(rec, r, keep, nh, p->reasons)
&& copy_res_reasons(rec, r, keep + nh, nr, p->reasons);
}
if (ok && (b.ncodes > 0)) {
p->omitted_codes = (sched_trace_reason_count_t*)sched_trace_arena_memdup(
rec, b.codes, sizeof(*b.codes) * (size_t)b.ncodes);
ok = (p->omitted_codes != NULL);
p->ncodes = ok ? b.ncodes : 0;
}
p->omitted_hosts = b.omitted.n;
if (!ok) {
rec->incomplete = true; /* design "Error Strategy" */
}
free(keep);
free(b.codes);
free((void*)b.kept.slot);
free((void*)b.omitted.slot);
}

View file

@ -0,0 +1,491 @@
/*
* sched_trace_scan.c - decision trace record model, part 2
* (scheduler-sim-decision-trace spec, design.md "TraceModel").
*
* The host scan's entries and counters: positions, host verdicts and the
* rows held under them, the summary-level per-position tallies (5.9),
* rollback runs, commit and finalize failures, and the per-requirement
* search-end statement with its decision-wide counters (9.3). Split from
* sched_trace.c (task 2.4), which keeps the record, its arena and the entry
* sequence these functions append to (sched_trace_internal.h).
*
* Same rules as sched_trace.c: one building thread, no locking, and no
* I/O and no logging (test_core_isolation.c scans this file).
*/
#include <stdlib.h>
#include <string.h>
#include "sched/trace.h"
#include "sched_trace_internal.h"
/* Starts the current position's tallies over. */
static void pos_tally_reset(xce_sched_trace_rec_t* rec)
{
memset(rec->pos_counts, 0, sizeof(rec->pos_counts));
sti_pos_limits_clear(rec);
}
void sched_trace_pos_begin(xce_sched_trace_rec_t* rec, int pos, int slots)
{
sched_trace_entry_t* e;
rec->cur_pos = pos;
rec->scan_err = 0;
rec->scan_err_set = false;
pos_tally_reset(rec);
e = sti_scope_begin(rec, SCHED_TRACE_E_POS_BEGIN);
if (e == NULL) {
return;
}
e->u.pos_begin.pos = pos;
e->u.pos_begin.slots = slots;
}
void sched_trace_pos_scan_error(xce_sched_trace_rec_t* rec, int err)
{
sti_rollback_run_end(rec);
rec->scan_err = err;
rec->scan_err_set = true;
}
/* Fills a POS_SUMMARY entry from the current position's tallies; only
* limits some host was rejected for are kept. */
static void pos_summary_fill(xce_sched_trace_rec_t* rec, sched_trace_entry_t* e,
xce_sched_trace_pos_end_t how, const char* host,
int err)
{
int n = 0;
int k;
e->u.pos_summary.pos = rec->cur_pos;
e->u.pos_summary.how = how;
e->u.pos_summary.err = err;
e->u.pos_summary.host = sched_trace_arena_strdup(rec, host);
memcpy(e->u.pos_summary.counts, rec->pos_counts,
sizeof(e->u.pos_summary.counts));
for (k = 0; k < rec->pos_nlimits; k++) {
if (rec->pos_limits[k].count > 0) {
n++;
}
}
if (n == 0) {
return;
}
e->u.pos_summary.limits = (sched_trace_limit_tally_t*)
sched_trace_arena_alloc(rec, sizeof(sched_trace_limit_tally_t)
* (size_t)n);
if (e->u.pos_summary.limits == NULL) {
return;
}
n = 0;
for (k = 0; k < rec->pos_nlimits; k++) {
const struct sched_trace_tally_build_s* b = &rec->pos_limits[k];
sched_trace_limit_tally_t* t;
if (b->count <= 0) {
continue;
}
t = &e->u.pos_summary.limits[n++];
t->limit = sched_trace_arena_strdup(rec, b->limit);
t->scope = sched_trace_arena_strdup(rec, b->scope);
t->count = b->count;
}
e->u.pos_summary.nlimits = n;
}
void sched_trace_pos_end(xce_sched_trace_rec_t* rec,
xce_sched_trace_pos_end_t how, const char* host)
{
sched_trace_entry_t* e;
int err = 0;
if (rec->scan_err_set) {
/* the host scan stopped: the runner's UNFILLED is a scan error */
err = rec->scan_err;
if (how == XCE_TRACE_POS_UNFILLED) {
how = XCE_TRACE_POS_SCAN_ERROR;
}
}
rec->scan_err = 0;
rec->scan_err_set = false;
if (rec->level == XCE_TRACE_LEVEL_SUMMARY) {
/* the position's end carries its host tallies (5.9) */
e = sti_scope_end(rec, SCHED_TRACE_E_POS_SUMMARY);
if (e != NULL) {
pos_summary_fill(rec, e, how, host, err);
}
pos_tally_reset(rec);
return;
}
e = sti_scope_end(rec, SCHED_TRACE_E_POS_END);
if (e == NULL) {
return;
}
e->u.pos_end.pos = rec->cur_pos;
e->u.pos_end.how = how;
e->u.pos_end.err = err;
e->u.pos_end.host = sched_trace_arena_strdup(rec, host);
}
void sched_trace_host_begin(xce_sched_trace_rec_t* rec,
const xce_sched_obj_t* obj,
const xce_sched_worker_t* w)
{
sti_rollback_run_end(rec);
rec->host_seq++;
rec->scope_obj = obj;
rec->scope_w = w;
rec->host_row_verdicts = 0;
sti_host_rows_clear(rec); /* a scope that ended without its verdict */
}
/* Counts one host evaluation: decision-wide per verdict kind, and at
* summary level per position, with each limit the host hit counted once
* when its verdict is limit exceeded (5.9, 9.3). */
static void host_count(xce_sched_trace_rec_t* rec, unsigned verdicts)
{
int k;
for (k = 0; k < XCE_TRACE_V_NKINDS; k++) {
if ((verdicts & (1u << k)) != 0) {
rec->counters.host_evals[k]++;
rec->pos_counts[k]++;
}
}
if ((verdicts & XCE_TRACE_V_LIMIT) == 0) {
return;
}
for (k = 0; k < rec->pos_nlimits; k++) {
if (rec->pos_limits[k].last_host == rec->host_seq) {
rec->pos_limits[k].count++;
}
}
}
/* Copies a row's strings into the arena (NULL stays NULL). */
static void row_to_arena(xce_sched_trace_rec_t* rec, sched_trace_row_t* dst,
const sched_trace_row_t* src)
{
*dst = *src;
dst->res = sched_trace_arena_strdup(rec, src->res);
dst->limit = sched_trace_arena_strdup(rec, src->limit);
dst->scope = sched_trace_arena_strdup(rec, src->scope);
dst->limited_res = sched_trace_arena_strdup(rec, src->limited_res);
}
/* Stores the current host's held rows after its verdict line, one level
* below it, then drops them. Each counts against the budget. */
static void host_rows_flush(xce_sched_trace_rec_t* rec)
{
int k;
for (k = 0; k < rec->host_nrows; k++) {
sched_trace_entry_t* e = sti_entry_add(rec, SCHED_TRACE_E_HOST_ROW);
if (e == NULL) {
continue;
}
e->depth++;
row_to_arena(rec, &e->u.row, &rec->host_rows[k]);
}
sti_host_rows_clear(rec);
}
/* NULL-preserving heap copy for held rows; sets *failed on failure. */
static const char* row_strdup(const char* s, bool* failed)
{
char* d;
if (s == NULL) {
return NULL;
}
d = strdup(s);
if (d == NULL) {
*failed = true;
}
return d;
}
void sched_trace_row(xce_sched_trace_rec_t* rec, const sched_trace_row_t* row)
{
sched_trace_entry_t* e;
sched_trace_row_t* h;
bool failed = false;
sti_rollback_run_end(rec);
if (row == NULL) {
return;
}
if (rec->scope_w == NULL) {
/* the cluster-wide check: stored now, before its check-end line */
e = sti_entry_add(rec, SCHED_TRACE_E_CHECK_ROW);
if (e != NULL) {
row_to_arena(rec, &e->u.row, row);
}
return;
}
/* a host scope: no host lines at summary level; at full level the row
* waits for the host's verdict line, which it belongs under */
if ((rec->level == XCE_TRACE_LEVEL_SUMMARY) || rec->incomplete) {
return;
}
if (rec->host_nrows == rec->host_rows_cap) {
int cap = (rec->host_rows_cap > 0) ? rec->host_rows_cap * 2 : 4;
void* p = realloc(rec->host_rows, sizeof(*h) * (size_t)cap);
if (p == NULL) {
rec->incomplete = true; /* design "Error Strategy" */
return;
}
rec->host_rows = (sched_trace_row_t*)p;
rec->host_rows_cap = cap;
}
h = &rec->host_rows[rec->host_nrows];
*h = *row;
h->res = row_strdup(row->res, &failed);
h->limit = row_strdup(row->limit, &failed);
h->scope = row_strdup(row->scope, &failed);
h->limited_res = row_strdup(row->limited_res, &failed);
if (failed) {
free((void*)h->res);
free((void*)h->limit);
free((void*)h->scope);
free((void*)h->limited_res);
rec->incomplete = true;
return;
}
rec->host_nrows++;
}
void sched_trace_host_verdict(xce_sched_trace_rec_t* rec, unsigned verdicts,
int err)
{
sched_trace_entry_t* e;
sched_trace_phase_t phase = SCHED_TRACE_PHASE_NONE;
const char* name
= (rec->scope_w != NULL) ? rec->scope_w->name : NULL;
if (verdicts == 0) {
/* from the rows the per-host check recorded; none means the
* check's own selection branch rejected the host */
verdicts = rec->host_row_verdicts & (XCE_TRACE_V_SHORTFALL
| XCE_TRACE_V_LIMIT);
if (verdicts == 0) {
verdicts = XCE_TRACE_V_SELECT;
}
phase = SCHED_TRACE_PHASE_PERHOST;
} else if ((verdicts & XCE_TRACE_V_SELECT) != 0) {
phase = SCHED_TRACE_PHASE_PREFILTER;
} else if ((verdicts & (XCE_TRACE_V_SHORTFALL | XCE_TRACE_V_LIMIT)) != 0) {
phase = SCHED_TRACE_PHASE_PERHOST;
}
/* one host evaluation, counted once under each kind it matched (9.3) */
host_count(rec, verdicts);
/* the host scope ends here */
rec->scope_obj = NULL;
rec->scope_w = NULL;
rec->host_row_verdicts = 0;
rec->host_seq++; /* later hits belong to no host until the next one */
sti_rollback_run_end(rec);
if (rec->level == XCE_TRACE_LEVEL_SUMMARY) {
sti_host_rows_clear(rec);
return; /* tallied for the position summary, no host line */
}
e = sti_entry_add(rec, SCHED_TRACE_E_HOST);
if (e != NULL) {
e->u.host.host = sched_trace_arena_strdup(rec, name);
e->u.host.verdicts = verdicts;
e->u.host.phase = phase;
e->u.host.err = err;
}
/* the host's unmet rows, under its verdict line (5.4, 5.5) */
host_rows_flush(rec);
}
void sched_trace_commit(xce_sched_trace_rec_t* rec)
{
sched_trace_entry_t* e = sti_entry_add(rec, SCHED_TRACE_E_COMMIT);
if (e == NULL) {
return;
}
e->u.commit.plan_no = rec->cur_plan_no;
}
void sched_trace_finalize_failed(xce_sched_trace_rec_t* rec, int err)
{
sched_trace_entry_t* e = sti_entry_add(rec, SCHED_TRACE_E_FINALIZE_FAILED);
if (e == NULL) {
return;
}
e->u.commit.plan_no = rec->cur_plan_no;
e->u.commit.err = err;
}
static bool tally_str_eq(const char* a, const char* b)
{
if ((a == NULL) || (b == NULL)) {
return a == b;
}
return strcmp(a, b) == 0;
}
/* NULL-preserving heap copy for the builder's tallies; sets *failed when a
* non-NULL string could not be copied. */
static char* tally_strdup(const char* s, bool* failed)
{
char* d;
if (s == NULL) {
return NULL;
}
d = strdup(s);
if (d == NULL) {
*failed = true;
}
return d;
}
void sched_trace_host_limit_hit(xce_sched_trace_rec_t* rec, const char* limit,
const char* scope)
{
struct sched_trace_tally_build_s* b;
bool failed = false;
int k;
sti_rollback_run_end(rec);
if (rec->scope_w == NULL) {
return; /* not a host scope: the cluster-wide check's rows */
}
rec->host_row_verdicts |= XCE_TRACE_V_LIMIT;
if ((rec->level != XCE_TRACE_LEVEL_SUMMARY) || rec->incomplete) {
return; /* full level shows the rows themselves (2.2) */
}
for (k = 0; k < rec->pos_nlimits; k++) {
b = &rec->pos_limits[k];
if (tally_str_eq(b->limit, limit) && tally_str_eq(b->scope, scope)) {
b->last_host = rec->host_seq; /* once per host, at its verdict */
return;
}
}
if (rec->pos_nlimits == rec->pos_limits_cap) {
int cap = (rec->pos_limits_cap > 0) ? rec->pos_limits_cap * 2 : 4;
void* p = realloc(rec->pos_limits, sizeof(*b) * (size_t)cap);
if (p == NULL) {
rec->incomplete = true;
return;
}
rec->pos_limits = (struct sched_trace_tally_build_s*)p;
rec->pos_limits_cap = cap;
}
b = &rec->pos_limits[rec->pos_nlimits];
b->limit = tally_strdup(limit, &failed);
b->scope = tally_strdup(scope, &failed);
b->count = 0;
b->last_host = rec->host_seq;
if (failed) {
free(b->limit);
free(b->scope);
rec->incomplete = true;
return;
}
rec->pos_nlimits++;
}
/* Appends one host to a ROLLBACK entry, growing its array in the arena. */
static void rollback_append(xce_sched_trace_rec_t* rec, sched_trace_entry_t* e,
const char* host)
{
if (e->u.rollback.nhosts == e->u.rollback.cap) {
int cap = (e->u.rollback.cap > 0) ? e->u.rollback.cap * 2 : 4;
const char** h = (const char**)sched_trace_arena_alloc(
rec, sizeof(char*) * (size_t)cap);
if (h == NULL) {
return;
}
if (e->u.rollback.nhosts > 0) {
memcpy(h, e->u.rollback.hosts,
sizeof(char*) * (size_t)e->u.rollback.nhosts);
}
e->u.rollback.hosts = h;
e->u.rollback.cap = cap;
}
e->u.rollback.hosts[e->u.rollback.nhosts++]
= sched_trace_arena_strdup(rec, host);
}
void sched_trace_rollback_host(xce_sched_trace_rec_t* rec, const char* host)
{
sched_trace_entry_t* e;
if (rec->rollback_open) {
/* the same run of released hosts: the same line */
if ((rec->rollback_e != NULL) && !rec->incomplete) {
rollback_append(rec, rec->rollback_e, host);
}
return;
}
e = sti_entry_add(rec, SCHED_TRACE_E_ROLLBACK);
rec->rollback_open = true; /* also when omitted: one line either way */
rec->rollback_e = e;
if (e != NULL) {
rollback_append(rec, e, host);
}
}
void sched_trace_search_end(xce_sched_trace_rec_t* rec, int rc)
{
sched_trace_entry_t* e;
sched_trace_search_stmt_t stmt = SCHED_TRACE_SEARCH_NO_STMT;
int t = rec->scope_tried;
int d = rec->scope_discarded;
int x = rec->scope_not_tried;
int g = t + d + x;
if (rc == 0) {
if (g == 0) {
stmt = SCHED_TRACE_SEARCH_NONE_GENERATED;
rec->counters.reqs_none_generated++;
} else if (d == g) {
stmt = SCHED_TRACE_SEARCH_ALL_DISCARDED;
rec->counters.reqs_all_discarded++;
} else if ((t > 0) && !rec->scope_plan_err) {
stmt = SCHED_TRACE_SEARCH_NONE_PLACED;
rec->counters.reqs_none_placed++;
}
}
/* the search is over: a later search in this requirement counts anew */
rec->scope_tried = 0;
rec->scope_discarded = 0;
rec->scope_not_tried = 0;
rec->scope_plan_err = false;
e = sti_entry_add(rec, SCHED_TRACE_E_SEARCH_END);
if (e == NULL) {
return;
}
e->u.search_end.generated = g;
e->u.search_end.tried = t;
e->u.search_end.discarded = d;
e->u.search_end.not_tried = x;
e->u.search_end.rc = rc;
e->u.search_end.stmt = stmt;
}

View file

@ -241,6 +241,18 @@ sched_core_isolation_test = executable(
+ (meson.current_source_dir() / '..' / 'src'
/ 'sched_trace_capture.c')
+ '"',
'-DXCE_SCHED_TRACE_SCAN_C_PATH="'
+ (meson.current_source_dir() / '..' / 'src'
/ 'sched_trace_scan.c')
+ '"',
'-DXCE_SCHED_TRACE_OUTCOME_C_PATH="'
+ (meson.current_source_dir() / '..' / 'src'
/ 'sched_trace_outcome.c')
+ '"',
'-DXCE_SCHED_TRACE_INTERNAL_H_PATH="'
+ (meson.current_source_dir() / '..' / 'src'
/ 'sched_trace_internal.h')
+ '"',
'-DXCE_CORE_LIB_PATH="'
+ sched_core_isolation_lib.full_path()
+ '"'],
@ -2520,10 +2532,12 @@ test('scheduler repl trace baseline test',
# scheduler-sim-decision-trace task 1.3: decision trace record model
# (src/sched_trace.c) unit tests -- selection matching, record header and
# per-record arena copying and size, record finishing. Links only the model
# source; libxce supplies the jobid helpers.
# sources (sched_trace.c and, since task 2.4, sched_trace_scan.c); libxce
# supplies the jobid helpers.
sched_trace_model_test = executable(
'sched_trace_model_test',
['test_trace_model.c', '../src/sched_trace.c'],
['test_trace_model.c', '../src/sched_trace.c',
'../src/sched_trace_scan.c'],
include_directories:[incdir],
dependencies: [scheduler_test_deps],
install: false)
@ -2537,7 +2551,8 @@ test('scheduler trace model test',
# src/decision.c; links the record model for its constructor and free.
sched_decision_trace_test = executable(
'sched_decision_trace_test',
['test_decision_trace.c', '../src/sched_trace.c'],
['test_decision_trace.c', '../src/sched_trace.c',
'../src/sched_trace_scan.c'],
include_directories:[incdir],
dependencies: [scheduler_test_deps],
install: false)
@ -2549,11 +2564,13 @@ test('scheduler decision trace test',
# scheduler-sim-decision-trace task 2.1: capture helpers (design.md
# "TraceCapture") over a fixture core and job -- selection facts,
# cluster-wide limits and attempt failure reasons. Links the record model
# and the capture source; libxce supplies the pend-reason helpers.
# and the capture sources (split in task 2.4); libxce supplies the
# pend-reason helpers.
sched_trace_capture_test = executable(
'sched_trace_capture_test',
['test_trace_capture.c', '../src/sched_trace.c',
'../src/sched_trace_capture.c'],
'../src/sched_trace_scan.c', '../src/sched_trace_capture.c',
'../src/sched_trace_outcome.c'],
include_directories:[incdir],
dependencies: [scheduler_test_deps],
install: false)

View file

@ -13,7 +13,9 @@
#include "../src/resource.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
/* scheduler-sim-live-allocation spec, task 1.3: sim.h only depends on
* core.h (+ stdbool/stdint/stdio, per its own header comment), so it is

View file

@ -111,7 +111,9 @@
#include "../src/driver.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
#include "../src/recording.c"
#include "../src/recording_events.c"
#include "../src/snapshot.c"

View file

@ -25,7 +25,9 @@
#include "../src/alloc_runner.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
#include "../src/plugins/parjob_allocator.c"
/* Link-satisfying stubs (same convention as test_seqjob_allocator.c). */

View file

@ -40,7 +40,9 @@
#include "../src/alloc_runner.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
#include "../src/plugins/seqjob_allocator.c"
/* ---------------------------------------------------------------------- */

View file

@ -38,7 +38,9 @@
#include "../src/alloc_runner.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
#include "../src/plugins/seqjob_allocator.c"
/* ---------------------------------------------------------------------- */

View file

@ -31,7 +31,9 @@
#include "../src/resource.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
#include "../src/schedobj.c"
/* ---------------------------------------------------------------------- */

View file

@ -40,7 +40,9 @@ static void* test_calloc(size_t n, size_t sz);
#include "../src/resource.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
#include "../src/schedobj.c"
/* ---------------------------------------------------------------------- */

View file

@ -36,7 +36,9 @@ static void* test_realloc(void* p, size_t n);
#include "../src/resource.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
#include "../src/schedobj.c"
/* ---------------------------------------------------------------------- */

View file

@ -72,7 +72,9 @@
#include "../src/resource.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
void kill_sched_job(void* arg) { (void)arg; }
void kill_sched_queue(void* arg) { (void)arg; }

View file

@ -64,11 +64,13 @@
* sidesteps that entirely by checking *usage*, not *visibility*).
*
* Decision trace sources (scheduler-sim-decision-trace spec, task 1.3,
* design.md "Invariant Cross-Check" dimension 2): sched_trace.c (record
* model) and sched_trace_capture.c (capture helpers) run on the allocator
* design.md "Invariant Cross-Check" dimension 2): sched_trace.c and
* sched_trace_scan.c (record model), sched_trace_capture.c and
* sched_trace_outcome.c (capture helpers) and their shared
* sched_trace_internal.h (split in task 2.4) run on the allocator
* thread inside the core's allocation chain, so they join layer 1's
* forbidden-headers and forbidden-symbols scans, plus a check that applies
* to these two files only: no stdio, no file I/O and no logging (a trace
* to these trace files only: no stdio, no file I/O and no logging (a trace
* record is pure data; only the simulator REPL renders or writes it). They
* are NOT part of layer 2: sched_core_isolation_lib covers the four core
* state-machine files, and the capture file references scheduler symbols
@ -117,6 +119,18 @@
#error \
"XCE_SCHED_TRACE_CAPTURE_C_PATH must be defined by meson.build to sched_trace_capture.c's absolute path"
#endif
#ifndef XCE_SCHED_TRACE_SCAN_C_PATH
#error \
"XCE_SCHED_TRACE_SCAN_C_PATH must be defined by meson.build to sched_trace_scan.c's absolute path"
#endif
#ifndef XCE_SCHED_TRACE_OUTCOME_C_PATH
#error \
"XCE_SCHED_TRACE_OUTCOME_C_PATH must be defined by meson.build to sched_trace_outcome.c's absolute path"
#endif
#ifndef XCE_SCHED_TRACE_INTERNAL_H_PATH
#error \
"XCE_SCHED_TRACE_INTERNAL_H_PATH must be defined by meson.build to sched_trace_internal.h's absolute path"
#endif
#ifndef XCE_CORE_LIB_PATH
#error \
"XCE_CORE_LIB_PATH must be defined by meson.build to the absolute path of core.c's isolated static_library() archive"
@ -217,6 +231,9 @@ static void test_core_source_paths_are_readable(void)
char* cc = read_whole_file(XCE_CORE_CLOCK_C_PATH);
char* st = read_whole_file(XCE_SCHED_TRACE_C_PATH);
char* sc = read_whole_file(XCE_SCHED_TRACE_CAPTURE_C_PATH);
char* ss = read_whole_file(XCE_SCHED_TRACE_SCAN_C_PATH);
char* so = read_whole_file(XCE_SCHED_TRACE_OUTCOME_C_PATH);
char* si = read_whole_file(XCE_SCHED_TRACE_INTERNAL_H_PATH);
TEST_ASSERT_NOT_NULL_MESSAGE(h, XCE_CORE_H_PATH);
TEST_ASSERT_NOT_NULL_MESSAGE(c, XCE_CORE_C_PATH);
@ -225,6 +242,9 @@ static void test_core_source_paths_are_readable(void)
TEST_ASSERT_NOT_NULL_MESSAGE(cc, XCE_CORE_CLOCK_C_PATH);
TEST_ASSERT_NOT_NULL_MESSAGE(st, XCE_SCHED_TRACE_C_PATH);
TEST_ASSERT_NOT_NULL_MESSAGE(sc, XCE_SCHED_TRACE_CAPTURE_C_PATH);
TEST_ASSERT_NOT_NULL_MESSAGE(ss, XCE_SCHED_TRACE_SCAN_C_PATH);
TEST_ASSERT_NOT_NULL_MESSAGE(so, XCE_SCHED_TRACE_OUTCOME_C_PATH);
TEST_ASSERT_NOT_NULL_MESSAGE(si, XCE_SCHED_TRACE_INTERNAL_H_PATH);
free(h);
free(c);
@ -233,6 +253,9 @@ static void test_core_source_paths_are_readable(void)
free(cc);
free(st);
free(sc);
free(ss);
free(so);
free(si);
}
/* Scans one file's text for every FORBIDDEN_INCLUDES entry, tagging each
@ -261,6 +284,9 @@ static void test_core_never_includes_forbidden_headers(void)
char* cc = read_whole_file(XCE_CORE_CLOCK_C_PATH);
char* st = read_whole_file(XCE_SCHED_TRACE_C_PATH);
char* sc = read_whole_file(XCE_SCHED_TRACE_CAPTURE_C_PATH);
char* ss = read_whole_file(XCE_SCHED_TRACE_SCAN_C_PATH);
char* so = read_whole_file(XCE_SCHED_TRACE_OUTCOME_C_PATH);
char* si = read_whole_file(XCE_SCHED_TRACE_INTERNAL_H_PATH);
TEST_ASSERT_NOT_NULL(h);
TEST_ASSERT_NOT_NULL(c);
@ -269,6 +295,9 @@ static void test_core_never_includes_forbidden_headers(void)
TEST_ASSERT_NOT_NULL(cc);
TEST_ASSERT_NOT_NULL(st);
TEST_ASSERT_NOT_NULL(sc);
TEST_ASSERT_NOT_NULL(ss);
TEST_ASSERT_NOT_NULL(so);
TEST_ASSERT_NOT_NULL(si);
assert_no_forbidden_includes(h, "core.h");
assert_no_forbidden_includes(c, "core.c");
@ -277,6 +306,9 @@ static void test_core_never_includes_forbidden_headers(void)
assert_no_forbidden_includes(cc, "core_clock.c");
assert_no_forbidden_includes(st, "sched_trace.c");
assert_no_forbidden_includes(sc, "sched_trace_capture.c");
assert_no_forbidden_includes(ss, "sched_trace_scan.c");
assert_no_forbidden_includes(so, "sched_trace_outcome.c");
assert_no_forbidden_includes(si, "sched_trace_internal.h");
free(h);
free(c);
@ -285,6 +317,9 @@ static void test_core_never_includes_forbidden_headers(void)
free(cc);
free(st);
free(sc);
free(ss);
free(so);
free(si);
}
/* Shared by test_core_never_calls_forbidden_symbols() -- see
@ -313,6 +348,9 @@ static void test_core_never_calls_forbidden_symbols(void)
char* cc = read_whole_file(XCE_CORE_CLOCK_C_PATH);
char* st = read_whole_file(XCE_SCHED_TRACE_C_PATH);
char* sc = read_whole_file(XCE_SCHED_TRACE_CAPTURE_C_PATH);
char* ss = read_whole_file(XCE_SCHED_TRACE_SCAN_C_PATH);
char* so = read_whole_file(XCE_SCHED_TRACE_OUTCOME_C_PATH);
char* si = read_whole_file(XCE_SCHED_TRACE_INTERNAL_H_PATH);
TEST_ASSERT_NOT_NULL(h);
TEST_ASSERT_NOT_NULL(c);
@ -321,6 +359,9 @@ static void test_core_never_calls_forbidden_symbols(void)
TEST_ASSERT_NOT_NULL(cc);
TEST_ASSERT_NOT_NULL(st);
TEST_ASSERT_NOT_NULL(sc);
TEST_ASSERT_NOT_NULL(ss);
TEST_ASSERT_NOT_NULL(so);
TEST_ASSERT_NOT_NULL(si);
assert_no_forbidden_symbols(h, "core.h");
assert_no_forbidden_symbols(c, "core.c");
@ -329,6 +370,9 @@ static void test_core_never_calls_forbidden_symbols(void)
assert_no_forbidden_symbols(cc, "core_clock.c");
assert_no_forbidden_symbols(st, "sched_trace.c");
assert_no_forbidden_symbols(sc, "sched_trace_capture.c");
assert_no_forbidden_symbols(ss, "sched_trace_scan.c");
assert_no_forbidden_symbols(so, "sched_trace_outcome.c");
assert_no_forbidden_symbols(si, "sched_trace_internal.h");
free(h);
free(c);
@ -337,6 +381,9 @@ static void test_core_never_calls_forbidden_symbols(void)
free(cc);
free(st);
free(sc);
free(ss);
free(so);
free(si);
}
/* core_clock.c is deliberately NOT scanned here -- it is the one
@ -386,8 +433,9 @@ static void test_core_never_calls_wallclock_functions(void)
/*
* No stdio, no file I/O, no logging in the decision trace sources
* (scheduler-sim-decision-trace design.md, TraceModel / TraceCapture: "No
* I/O and no tlog"). Applies to sched_trace.c and sched_trace_capture.c
* only.
* I/O and no tlog"). Applies to the trace sources only: sched_trace.c,
* sched_trace_scan.c, sched_trace_capture.c, sched_trace_outcome.c and
* sched_trace_internal.h.
*
* Comments, string literals and character literals are blanked before the
* scan, so prose such as "never calls fopen" in a comment is not a hit.
@ -650,15 +698,27 @@ static void test_trace_sources_do_no_stdio_or_file_io(void)
{
char* st = read_whole_file(XCE_SCHED_TRACE_C_PATH);
char* sc = read_whole_file(XCE_SCHED_TRACE_CAPTURE_C_PATH);
char* ss = read_whole_file(XCE_SCHED_TRACE_SCAN_C_PATH);
char* so = read_whole_file(XCE_SCHED_TRACE_OUTCOME_C_PATH);
char* si = read_whole_file(XCE_SCHED_TRACE_INTERNAL_H_PATH);
TEST_ASSERT_NOT_NULL(st);
TEST_ASSERT_NOT_NULL(sc);
TEST_ASSERT_NOT_NULL(ss);
TEST_ASSERT_NOT_NULL(so);
TEST_ASSERT_NOT_NULL(si);
assert_trace_source_has_no_io(st, "sched_trace.c");
assert_trace_source_has_no_io(sc, "sched_trace_capture.c");
assert_trace_source_has_no_io(ss, "sched_trace_scan.c");
assert_trace_source_has_no_io(so, "sched_trace_outcome.c");
assert_trace_source_has_no_io(si, "sched_trace_internal.h");
free(st);
free(sc);
free(ss);
free(so);
free(si);
}
/*

View file

@ -62,7 +62,9 @@
#include "../src/driver.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
void kill_sched_job(void* arg) { (void)arg; }
void kill_sched_queue(void* arg) { (void)arg; }

View file

@ -165,7 +165,9 @@
#include "../src/driver.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
#include "../src/plugin.c"
void kill_sched_job(void* arg) { (void)arg; }

View file

@ -60,7 +60,9 @@
#include "../src/driver.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
void kill_sched_job(void* arg) { (void)arg; }
void kill_sched_queue(void* arg) { (void)arg; }

View file

@ -70,7 +70,9 @@
#include "../src/host_iter.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
/* Link-satisfying stubs -- identical set to test_resource_accounting.c's
* own (see that file's header comment for the full rationale). None of

View file

@ -96,7 +96,9 @@
#include "../src/resource.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
#include "../src/schedobj.c"
/* ---------------------------------------------------------------------- */

View file

@ -97,7 +97,9 @@
#include "../src/driver.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
#include "../src/plugin.c"
#include "../src/recording.c"
#include "../src/recording_events.c"

View file

@ -92,7 +92,9 @@
#include "../src/resource.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
#include "../src/schedobj.c"
void plugin_unregister(void* arg) { (void)arg; }

View file

@ -87,7 +87,9 @@
#include "../src/driver.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
void kill_sched_job(void* arg) { (void)arg; }
void kill_sched_queue(void* arg) { (void)arg; }

View file

@ -52,7 +52,9 @@
#include "../src/alloc_runner.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
#include "../src/plugins/seqjob_allocator.c"
/* ---------------------------------------------------------------------- */

View file

@ -95,7 +95,9 @@
#include "../src/alloc_runner.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
#include "../src/plugins/seqjob_allocator.c"
/* ---------------------------------------------------------------------- */

View file

@ -208,7 +208,9 @@
#include "../src/resource.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
#include "../src/sim.c"
#include "../src/sim_loop.c"
#include "../src/sim_picker.c" /* bug-1567: sched_sim_driver_run() calls sim_bootstrap_picker() (sim_loop.c-cap sibling) as an extern -- undefined reference at link time otherwise */

View file

@ -202,7 +202,9 @@
#include "../src/resource.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
#include "../src/sim.c"
#include "../src/sim_loop.c"
#include "../src/sim_picker.c" /* bug-1567: sched_sim_driver_run() calls sim_bootstrap_picker() (sim_loop.c-cap sibling) as an extern -- undefined reference at link time otherwise */

View file

@ -191,7 +191,9 @@
#include "../src/driver.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
#include "../src/plugin.c"
#include "../src/plugins/builtin_fcfs.c"
#include "../src/recording.c"

View file

@ -139,7 +139,9 @@
#include "../src/resource.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
#include "../src/sim.c"
#include "../src/sim_loop.c"
#include "../src/sim_picker.c" /* bug-1567: sim_loop.c calls sim_bootstrap_picker() (sim_loop.c-cap sibling) as an extern -- undefined reference at link time otherwise */

View file

@ -146,7 +146,9 @@
#include "../src/resource.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
#include "../src/sim.c"
#include "../src/sim_loop.c"
#include "../src/sim_picker.c" /* bug-1567: sched_sim_driver_run() now calls sim_bootstrap_picker() (sim_loop.c-cap sibling) as an extern -- undefined reference at link time otherwise */

View file

@ -114,7 +114,9 @@
#include "../src/resource.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
#include "../src/sim.c"
#include "../src/sim_loop.c"
#include "../src/sim_picker.c" /* bug-1567: sched_sim_driver_run() calls sim_bootstrap_picker() (sim_loop.c-cap sibling) as an extern -- undefined reference at link time otherwise */

View file

@ -136,7 +136,9 @@
#include "../src/driver.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
#include "../src/plugins/builtin_fcfs.c"
#include "../src/recording.c"
#include "../src/recording_events.c"

View file

@ -101,7 +101,9 @@
#include "../src/resource.c"
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
#include "../src/sched_trace.c"
#include "../src/sched_trace_scan.c"
#include "../src/sched_trace_capture.c"
#include "../src/sched_trace_outcome.c"
#include "../src/snapshot.c"
#include "../src/snapshot_load.c"
#include "../src/snapshot_wire.c"

View file

@ -40,8 +40,9 @@
* - with 50 or fewer hosts the pend copy equals its source under
* job_preason_eq(), the equality libxce defines for pend reasons.
*
* Links src/sched_trace.c and src/sched_trace_capture.c, plus libxce for
* the pend-reason, resmgr and container helpers.
* Links the trace model (src/sched_trace.c, sched_trace_scan.c) and the
* capture sources (src/sched_trace_capture.c, sched_trace_outcome.c), plus
* libxce for the pend-reason, resmgr and container helpers.
*/
#include <stdio.h>
#include <stdlib.h>

View file

@ -32,7 +32,8 @@
* tallying hits per limit (5.9, 5.10);
* - consecutive released-host calls merge into one rollback entry (5.8).
*
* Links only src/sched_trace.c (plus libxce for the jobid helpers).
* Links only the model, src/sched_trace.c and src/sched_trace_scan.c (plus
* libxce for the jobid helpers).
*/
#include <stdio.h>
#include <stdlib.h>