feat(scheduler-sim-decision-trace): capture decision outcomes and reconstructed dispatches

Task 2.3: dispatch rows with limit charges listed separately and
resolved through the job's matched limits (raw-number fallback), pend
reason set deep-copied, 50 hosts in detail with the rest aggregated,
and RECONSTRUCTED records holding the dispatch outcome.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-10-08 09:58:21 +00:00
parent 79c5a0cbda
commit 8efe142bad
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
4 changed files with 1269 additions and 68 deletions

View file

@ -371,6 +371,68 @@ typedef struct sched_trace_entry_s {
} u;
} sched_trace_entry_t;
/*
* Decision outcome (6.1-6.4, 9.4), filled by TraceCapture and rendered
* outside the line budget. All pointers point into the record's arena.
*/
typedef enum {
SCHED_TRACE_OUTCOME_NONE = 0,
SCHED_TRACE_OUTCOME_DISPATCH,
SCHED_TRACE_OUTCOME_PEND
} sched_trace_outcome_kind_t;
/* One allocated amount: a resource row, or a limit charge (6.3: a charge is
* never a resource row). A charge names its limit and scope instance (a
* host charge has "h=<host>" appended); `limited_res` is the resource the
* limit limits, NULL when no matched limit names the charge, in which case
* the amount is a raw number. In an aggregate the scope has no host part. */
typedef struct {
const char* res; /* allocated resource name (a charge: the vres name;
* NULL in a limit aggregate) */
double value; /* amount, or the total in an aggregate */
const char* limit; /* charges only */
const char* scope; /* charges only */
const char* limited_res; /* charges only; NULL: unknown (raw number) */
} sched_trace_alloc_row_t;
/* One allocated host kept in detail (6.1), in allocation order. */
typedef struct {
const char* host;
int nres;
sched_trace_alloc_row_t* res;
int ncharges;
sched_trace_alloc_row_t* charges;
} sched_trace_host_out_t;
/* Dispatch outcome: the first XCE_TRACE_OUTCOME_HOSTS hosts in detail, the
* job's shared rows and charges (6.2), and the omitted hosts' totals per
* resource (limits excluded) and per limit, in the order first seen (9.4). */
typedef struct {
int nhosts; /* kept in detail */
sched_trace_host_out_t* hosts;
int nshared;
sched_trace_alloc_row_t* shared;
int nshared_charges;
sched_trace_alloc_row_t* shared_charges;
int omitted_hosts;
int nagg_res;
sched_trace_alloc_row_t* agg_res;
int nagg_charges;
sched_trace_alloc_row_t* agg_charges;
} sched_trace_dispatch_out_t;
/* Pend outcome (6.4): a copy of the decision's pend-reason set holding the
* main reason, every entry that names no host and every entry of the first
* XCE_TRACE_OUTCOME_HOSTS distinct hosts, in source order (equal to the
* source under job_preason_eq() when no host was omitted). Entries of the
* hosts after them are counted per pend-reason code (9.4). */
typedef struct {
xce_job_preason_t* reasons; /* NULL: the decision had none */
int omitted_hosts;
int ncodes;
sched_trace_reason_count_t* omitted_codes; /* first-seen order */
} sched_trace_pend_out_t;
/* One arena chunk; the record frees the whole chain at once. */
typedef struct sched_trace_chunk_s {
struct sched_trace_chunk_s* next;
@ -416,6 +478,11 @@ struct xce_sched_trace_rec_s {
sched_trace_counters_t counters;
/* outcome (6.x, 9.4), outside the line budget */
sched_trace_outcome_kind_t outcome;
sched_trace_dispatch_out_t dispatch; /* outcome DISPATCH */
sched_trace_pend_out_t pend; /* outcome PEND */
/* stored entries, in the order made; at most line_bound of them */
sched_trace_entry_t* entries;
sched_trace_entry_t* entries_tail;
@ -677,4 +744,37 @@ extern void sched_trace_check_rows(xce_sched_trace_rec_t* rec,
extern void sched_trace_capture_attempt_reasons(xce_sched_trace_rec_t* rec,
const xce_pendreason_t* reason);
/*
* A dispatch decision's allocation as the record's outcome (6.1-6.3, 9.4).
* Each row is a limit charge when its resource, found by name in
* core->resmgr, has XCE_RESF_LIMIT, or, when the name is not registered,
* when it starts with "limit=". A charge is resolved by vres name through
* the job's matched limits: obj->glimits for shared rows,
* obj->hlimits[w->idx] for a host's rows (w found by host name in
* core->workers); unresolved, its name and scope are parsed from the
* resource name and its limited resource is unknown. NULL obj: nothing
* resolves. A NULL alloc stores an empty dispatch. `core` is only read.
*/
extern 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);
/* A pend decision's pend-reason set as the record's outcome (6.4, 9.4). */
extern void sched_trace_capture_pend(xce_sched_trace_rec_t* rec,
const xce_pendreason_t* reason);
/*
* A RECONSTRUCTED record (2.5) for a dispatch the simulator rebuilt from a
* recorded allocation, built on the sim thread: the header from `ctl` and
* `allocs` as its dispatch outcome, captured as above (`obj` is the job's
* schedobj, NULL when it has none). NULL when ctl is NULL or the record
* cannot be allocated.
*/
extern 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);
#endif