feat(scheduler-sim-decision-trace): trace record model foundation
Task 1.3: trace types and constants, per-record arena, job selection matching, no-decision record finishing; empty capture module; both sources scanned by the core isolation test with a no-stdio check. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7f40ff27dc
commit
7d3c71fc34
8 changed files with 1234 additions and 1 deletions
206
src/scheduler/include/sched/trace.h
Normal file
206
src/scheduler/include/sched/trace.h
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
#if !defined(_SCHED_TRACE_H_)
|
||||
|
||||
#define _SCHED_TRACE_H_ 1
|
||||
|
||||
/*
|
||||
* trace.h - scheduling decision trace model (scheduler-sim-decision-trace
|
||||
* spec, design.md "TraceModel").
|
||||
*
|
||||
* A trace record explains one allocator decision for one traced job. It is
|
||||
* built on the allocator thread while the job is allocated, handed to the
|
||||
* simulator REPL on the decision, and rendered there. The record is
|
||||
* self-contained: every string and number it holds is copied into its own
|
||||
* per-record arena, so it never points into scheduler memory and stays
|
||||
* valid after the job, its schedobj or the core state it was captured from
|
||||
* change or go away.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* Requirement 1.8: two detail levels; summary is the default. */
|
||||
typedef enum {
|
||||
XCE_TRACE_LEVEL_SUMMARY = 0,
|
||||
XCE_TRACE_LEVEL_FULL
|
||||
} xce_sched_trace_level_t;
|
||||
|
||||
/* Requirement 5.10 verdict kinds, bit flags (a host may carry several). */
|
||||
typedef enum {
|
||||
XCE_TRACE_V_ACCEPTED = 1u << 0,
|
||||
XCE_TRACE_V_SELECT = 1u << 1, /* selection mismatch (pre-filter) */
|
||||
XCE_TRACE_V_SHORTFALL = 1u << 2, /* resource shortfall (per-host check) */
|
||||
XCE_TRACE_V_LIMIT = 1u << 3, /* limit exceeded (per-host check) */
|
||||
XCE_TRACE_V_CLAIMED = 1u << 4, /* already claimed by earlier position */
|
||||
XCE_TRACE_V_INTERNAL = 1u << 5 /* not evaluated: internal error */
|
||||
} xce_sched_trace_verdict_t;
|
||||
|
||||
/* Number of verdict kinds above: the size of every per-kind counter array,
|
||||
* indexed by the kind's bit position. */
|
||||
#define XCE_TRACE_V_NKINDS 6
|
||||
|
||||
/* Requirement 2.4 / 2.5 record kinds. */
|
||||
typedef enum {
|
||||
XCE_TRACE_REC_DECISION = 0,
|
||||
XCE_TRACE_REC_NOT_FOUND,
|
||||
XCE_TRACE_REC_NO_SCHEDOBJ,
|
||||
XCE_TRACE_REC_INTERNAL_ERROR, /* alloc_rc < 0 or decision build failure */
|
||||
XCE_TRACE_REC_PEND_NO_REASON,
|
||||
XCE_TRACE_REC_RECONSTRUCTED
|
||||
} xce_sched_trace_rec_kind_t;
|
||||
|
||||
/* One selector from `trace on job ...` (Requirements 1.2, 1.3). */
|
||||
typedef struct xce_sched_trace_sel_s {
|
||||
uint64_t key; /* plain jobid, array base id, or element id */
|
||||
bool whole_array; /* true: key is an array base, matches every element */
|
||||
} xce_sched_trace_sel_t;
|
||||
|
||||
/*
|
||||
* Settings snapshot published to the core for one session. Immutable during
|
||||
* the session except `position`, which only the allocator thread writes.
|
||||
* REPL-owned; a record copies what it needs and never keeps a pointer here.
|
||||
*/
|
||||
typedef struct xce_sched_trace_ctl_s {
|
||||
bool all;
|
||||
int nsel;
|
||||
xce_sched_trace_sel_t* sel;
|
||||
xce_sched_trace_level_t level;
|
||||
int line_bound; /* 50..100000 */
|
||||
uint64_t session_no; /* rs->session_count of this session */
|
||||
const char* policy_name; /* picker name, owned by the ctl */
|
||||
int position; /* allocator-owned, reset at session begin */
|
||||
} xce_sched_trace_ctl_t;
|
||||
|
||||
typedef struct xce_sched_trace_rec_s xce_sched_trace_rec_t;
|
||||
|
||||
#define XCE_TRACE_LINE_BOUND_DEFAULT 1000 /* Requirement 9.1 */
|
||||
#define XCE_TRACE_LINE_BOUND_MIN 50
|
||||
#define XCE_TRACE_LINE_BOUND_MAX 100000
|
||||
#define XCE_TRACE_OUTCOME_HOSTS 50 /* Requirement 9.4 */
|
||||
|
||||
typedef enum {
|
||||
XCE_TRACE_PATH_SINGLE, /* PRIMARY job: the single requirement */
|
||||
XCE_TRACE_PATH_COMPOUND, /* top-level COMPOUND container */
|
||||
XCE_TRACE_PATH_ALT, /* alternative i/n of an ALTER (a compound) */
|
||||
XCE_TRACE_PATH_MEMBER /* member j/m of the enclosing compound */
|
||||
} xce_sched_trace_path_kind_t;
|
||||
|
||||
/* Kept inside the record, not passed by hooks. */
|
||||
typedef struct {
|
||||
int depth; /* 0..2: container (COMPOUND|ALT) > MEMBER, or SINGLE */
|
||||
struct {
|
||||
xce_sched_trace_path_kind_t kind;
|
||||
int i;
|
||||
int n;
|
||||
} lvl[2];
|
||||
} sched_trace_path_t;
|
||||
|
||||
/* Requirement 4.7 reasons. */
|
||||
typedef enum {
|
||||
XCE_TRACE_NOPLAN_DECLINED, /* allocator accepts() declined */
|
||||
XCE_TRACE_NOPLAN_GENERATOR_FAILED, /* plan_init failed */
|
||||
XCE_TRACE_NOPLAN_NONE_GENERATED /* generator produced no plan */
|
||||
} xce_sched_trace_noplan_t;
|
||||
|
||||
typedef enum {
|
||||
XCE_TRACE_POS_FILLED,
|
||||
XCE_TRACE_POS_UNFILLED,
|
||||
XCE_TRACE_POS_SCAN_ERROR, /* host scan could not start or stopped (4.4) */
|
||||
XCE_TRACE_POS_INTERNAL /* host accepted but claim snapshot failed */
|
||||
} xce_sched_trace_pos_end_t;
|
||||
|
||||
/*
|
||||
* Decision-wide counters (Requirement 9.3). They stay exact when the line
|
||||
* budget truncates the stored entries.
|
||||
*/
|
||||
typedef struct {
|
||||
int plans_tried;
|
||||
int plans_discarded;
|
||||
int reqs_none_generated;
|
||||
int reqs_all_discarded;
|
||||
int reqs_none_placed;
|
||||
int host_evals[XCE_TRACE_V_NKINDS]; /* indexed by verdict bit position */
|
||||
int omitted_lines;
|
||||
} sched_trace_counters_t;
|
||||
|
||||
/* One arena chunk; the record frees the whole chain at once. */
|
||||
typedef struct sched_trace_chunk_s {
|
||||
struct sched_trace_chunk_s* next;
|
||||
size_t cap;
|
||||
size_t used;
|
||||
/* followed by `cap` bytes of storage */
|
||||
} sched_trace_chunk_t;
|
||||
|
||||
/*
|
||||
* Record layout. Read only by sched_trace*.c and the REPL renderer; every
|
||||
* other file treats the record as opaque. All pointers point into the
|
||||
* record's own arena.
|
||||
*/
|
||||
struct xce_sched_trace_rec_s {
|
||||
/* header */
|
||||
uint64_t jobid;
|
||||
xce_sched_trace_rec_kind_t kind;
|
||||
int err; /* no-decision kinds: the error, else 0 */
|
||||
xce_sched_trace_level_t level;
|
||||
uint64_t session_no;
|
||||
int position;
|
||||
int line_bound;
|
||||
bool incomplete; /* arena growth failed: appending stopped */
|
||||
|
||||
/* selection block */
|
||||
const char* policy_name; /* arena copy of ctl->policy_name, or NULL */
|
||||
|
||||
/* current requirement path (pushed and popped by the allocation chain) */
|
||||
sched_trace_path_t path;
|
||||
|
||||
sched_trace_counters_t counters;
|
||||
|
||||
/* per-record arena */
|
||||
sched_trace_chunk_t* arena;
|
||||
size_t arena_bytes; /* sum of chunk sizes, headers included */
|
||||
};
|
||||
|
||||
/* True iff `jobid` is selected by `ctl`: `all`, an exact plain-job or
|
||||
* element selector, or a whole-array selector whose array the element
|
||||
* belongs to (also for elements created after the command). NULL ctl:
|
||||
* false. */
|
||||
extern bool sched_trace_ctl_wants(const xce_sched_trace_ctl_t* ctl,
|
||||
uint64_t jobid);
|
||||
|
||||
/* New record for `jobid` with the header copied from `ctl`. NULL when `ctl`
|
||||
* is NULL or allocation fails (the job is then not traced). */
|
||||
extern xce_sched_trace_rec_t*
|
||||
sched_trace_rec_new(const xce_sched_trace_ctl_t* ctl, uint64_t jobid,
|
||||
xce_sched_trace_rec_kind_t kind);
|
||||
|
||||
/* Frees the record and its arena. NULL-safe. */
|
||||
extern void sched_trace_rec_free(xce_sched_trace_rec_t* rec);
|
||||
|
||||
/* Finish a record as a no-decision kind (NOT_FOUND, NO_SCHEDOBJ,
|
||||
* INTERNAL_ERROR, PEND_NO_REASON) before it is pushed via
|
||||
* make_trace_decision (2.4). Any other kind leaves the record unchanged. */
|
||||
extern void sched_trace_rec_finish(xce_sched_trace_rec_t* rec,
|
||||
xce_sched_trace_rec_kind_t kind, int err);
|
||||
|
||||
/* Bytes owned by the record: the record block plus its arena. 0 for NULL. */
|
||||
extern size_t sched_trace_rec_bytes(const xce_sched_trace_rec_t* rec);
|
||||
|
||||
/*
|
||||
* Arena copies. Each returns storage owned by the record, freed with it.
|
||||
* On allocation failure they set rec->incomplete and return NULL. A NULL
|
||||
* source (or rec) returns NULL without marking the record incomplete.
|
||||
*/
|
||||
extern void* sched_trace_arena_alloc(xce_sched_trace_rec_t* rec, size_t n);
|
||||
extern void* sched_trace_arena_memdup(xce_sched_trace_rec_t* rec,
|
||||
const void* src, size_t n);
|
||||
extern char* sched_trace_arena_strdup(xce_sched_trace_rec_t* rec,
|
||||
const char* s);
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue