From 4f951f2b52ff6cb87a1868ba6eda3dbf6037b71f Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Thu, 8 Oct 2026 08:05:44 +0000 Subject: [PATCH] feat(scheduler-sim-decision-trace): trace entry sequence and line budget Task 1.4: requirement path push/pop with balanced begin/end entries carrying the full nesting path, one entry per traced event kind, and a line budget that stops storing at the bound and counts omitted lines. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../scheduler-sim-decision-trace/tasks.md | 3 +- src/scheduler/include/sched/alloc_iter.h | 6 +- src/scheduler/include/sched/trace.h | 251 ++++++ src/scheduler/src/sched_trace.c | 465 ++++++++++ src/scheduler/testsuite/test_trace_model.c | 844 ++++++++++++++++++ 5 files changed, 1567 insertions(+), 2 deletions(-) diff --git a/.kiro/specs/scheduler-sim-decision-trace/tasks.md b/.kiro/specs/scheduler-sim-decision-trace/tasks.md index 2a3c0edb..272f3cf2 100644 --- a/.kiro/specs/scheduler-sim-decision-trace/tasks.md +++ b/.kiro/specs/scheduler-sim-decision-trace/tasks.md @@ -32,7 +32,7 @@ - Done when model unit tests pass under ASAN for selection matching, arena copying and size, and record finishing, and the isolation test passes with the new files scanned. - _Requirements: 1.2, 1.3, 1.8, 2.4, 9.1, 11.2_ -- [ ] 1.4 Build the trace entry sequence and the line budget, test first +- [x] 1.4 Build the trace entry sequence and the line budget, test first - Path push and pop, with balanced requirement begin and end entries. Each entry stores its full nesting path: single, compound container, alternative i/n, or member j/m. - Entry kinds, each rendering as exactly one line: - requirement begin and end; @@ -340,3 +340,4 @@ - 1.3: `sched_trace_ctl_wants` whole-array selectors match elements only, not the bare key. A bare id in `trace on job ` can't be told apart from an array base for a job that doesn't exist yet, so 4.3's parser must emit both `{key,false}` and `{key,true}`, or the matcher must also accept `jobid == key`. - 1.3: design's record header lists a "file tag", but `xce_sched_trace_ctl_t` has no such field; the 4.2/4.6 store/emit tasks must set it or drop it explicitly. - 1.3: trace sources (sched_trace.c, sched_trace_capture.c) may not include (core isolation test); capture copies raw strings/numbers and leaves formatting to the renderer. +- 1.4: `sched_trace_host_verdict(rec, 0, err)` means "derive from rows" (rec->host_row_verdicts shortfall/limit, else SELECT, phase per-host); an explicit SELECT is the pre-filter phase. 2.2's row capture must set host_row_verdicts and 3.3/3.4 hooks must follow this. `sched_trace_pos_scan_error` stores no entry; it turns the next UNFILLED position end into SCAN_ERROR. Plan numbers restart per requirement. trace.h forward-declares `struct xce_alloc_plan_s` (tag added in alloc_iter.h) to avoid the core.h include loop. `resreq_begin` may store a NULL req name for a NULL rusage; 4.1's renderer must tolerate it. diff --git a/src/scheduler/include/sched/alloc_iter.h b/src/scheduler/include/sched/alloc_iter.h index b960f5b4..c15380c8 100644 --- a/src/scheduler/include/sched/alloc_iter.h +++ b/src/scheduler/include/sched/alloc_iter.h @@ -38,8 +38,12 @@ * Produced by xce_alloc_plan_iter_t.next(); the returned pointer is valid * only until the following next() or fini() call on that same iterator * (it is not independently heap-owned by the caller). + * + * Tagged so sched/trace.h can forward-declare it without including this + * header (which pulls in scheduler.h -> core.h, and core.h will carry the + * trace record pointer). */ -typedef struct { +typedef struct xce_alloc_plan_s { int nbuckets; /* number of hosts this plan needs (== * len(bucket_slots)) */ int* bucket_slots; /* partition of maxTasks; bucket_slots[i] = slots diff --git a/src/scheduler/include/sched/trace.h b/src/scheduler/include/sched/trace.h index 3d4cd349..7c04e6b0 100644 --- a/src/scheduler/include/sched/trace.h +++ b/src/scheduler/include/sched/trace.h @@ -26,6 +26,13 @@ #include #include +#include "resreq.h" +#include "sched/types.h" + +/* Defined in sched/alloc_iter.h, which is not included here: it pulls in + * scheduler.h and so core.h. */ +typedef struct xce_alloc_plan_s xce_alloc_plan_t; + /* Requirement 1.8: two detail levels; summary is the default. */ typedef enum { XCE_TRACE_LEVEL_SUMMARY = 0, @@ -130,6 +137,140 @@ typedef struct { int omitted_lines; } sched_trace_counters_t; +/* + * Entry types (design.md "Logical Data Model"). Every stored entry renders + * as exactly one line in the budgeted sections (requirement, cluster-wide + * check, plan and host), so the number of stored entries is the number of + * budgeted lines. The check rows, host rows, search-end, position-summary + * and rollback entries are added by the tasks that capture them. + */ +typedef enum { + SCHED_TRACE_E_RESREQ_BEGIN = 0, /* requirement named by the path */ + SCHED_TRACE_E_RESREQ_END, /* its outcome; one per begin */ + SCHED_TRACE_E_ALT_SKIPPED, /* alternative with no content (3.6) */ + SCHED_TRACE_E_ALT_RESULT, /* chosen alternative, or none (3.4) */ + SCHED_TRACE_E_MEMBER_FAILED, /* merge failed after member j/m (3.3) */ + SCHED_TRACE_E_ATTEMPT_REASONS, /* failure reasons of an attempt (3.5) */ + SCHED_TRACE_E_CHECK_END, /* cluster-wide check result (4.8) */ + SCHED_TRACE_E_NO_PLAN, /* no plan generated, and why (4.7) */ + SCHED_TRACE_E_PLAN_BEGIN, /* plan tried (4.3) */ + SCHED_TRACE_E_PLAN_DISCARDED, /* plan generated, discarded (4.9) */ + SCHED_TRACE_E_PLAN_NOT_TRIED, /* plan generated, internal error (4.4)*/ + SCHED_TRACE_E_PLAN_END, /* plan result (4.4) */ + SCHED_TRACE_E_POS_BEGIN, /* plan position (4.4) */ + SCHED_TRACE_E_POS_END, /* filled / unfilled / scan error / + * internal (4.4) */ + SCHED_TRACE_E_HOST, /* host verdict (5.1-5.7, 5.10) */ + SCHED_TRACE_E_COMMIT, /* committed plan (4.5) */ + SCHED_TRACE_E_FINALIZE_FAILED, /* allocation build failed (4.5) */ + SCHED_TRACE_E_NTYPES +} sched_trace_entry_type_t; + +/* Where a host verdict came from (5.2). Only the selection, shortfall and + * limit kinds carry a phase; the other kinds use NONE. */ +typedef enum { + SCHED_TRACE_PHASE_NONE = 0, + SCHED_TRACE_PHASE_PREFILTER, /* host-selection pre-filter */ + SCHED_TRACE_PHASE_PERHOST /* per-host resource and limit check */ +} sched_trace_phase_t; + +/* One requested resource of a single requirement or member (3.2). */ +typedef struct { + const char* name; /* arena copy */ + float value; + int per; /* rusage_per_type_t */ +} sched_trace_req_t; + +/* One pend-reason code of an attempt's failure reasons and how often it + * occurred (3.5). */ +typedef struct { + int code; + int count; +} sched_trace_reason_count_t; + +/* + * One stored entry. All pointers point into the record's arena. `depth` is + * the nesting level the renderer indents by: a requirement, plan or + * position begin is stored at the current level and opens the next one; + * its end closes it and is stored at the begin's level. `path` is the full + * requirement path in effect when the entry was made (3.1). + */ +typedef struct sched_trace_entry_s { + struct sched_trace_entry_s* next; + sched_trace_entry_type_t type; + int depth; + sched_trace_path_t path; + union { + struct { /* RESREQ_BEGIN */ + bool container; /* COMPOUND or ALT: no primary */ + int nmembers; /* container only */ + int min_tasks; + int max_tasks; + int nreq; + sched_trace_req_t* req; + } resreq_begin; + struct { /* RESREQ_END */ + int rc; /* > 0 satisfied, 0 not, < 0 error */ + int not_tried_after; /* members after this one, not tried */ + } resreq_end; + struct { /* ALT_SKIPPED, ALT_RESULT (i == 0: none chosen) */ + int i; + int n; + } alt; + struct { /* MEMBER_FAILED */ + int j; + int m; + int err; + } member_failed; + struct { /* ATTEMPT_REASONS */ + int ncodes; + sched_trace_reason_count_t* codes; + int nhosts; + const char** hosts; + int nres; + const char** res; + } reasons; + struct { /* CHECK_END */ + int rc; + bool plans_follow; + } check_end; + struct { /* NO_PLAN */ + xce_sched_trace_noplan_t why; + } no_plan; + struct { /* PLAN_BEGIN, PLAN_DISCARDED, PLAN_NOT_TRIED */ + int plan_no; /* 1-based, in generation order per requirement */ + int nbuckets; /* hosts the plan needs */ + int* slots; /* tasks per position, nbuckets of them */ + int err; /* PLAN_NOT_TRIED only */ + } plan; + struct { /* PLAN_END */ + int plan_no; + int rc; + int failed_pos; /* the position that could not be filled */ + } plan_end; + struct { /* POS_BEGIN */ + int pos; + int slots; + } pos_begin; + struct { /* POS_END */ + int pos; + xce_sched_trace_pos_end_t how; + const char* host; /* filled: the accepted host */ + int err; /* scan error */ + } pos_end; + struct { /* HOST */ + const char* host; + unsigned verdicts; /* xce_sched_trace_verdict_t bits */ + sched_trace_phase_t phase; + int err; + } host; + struct { /* COMMIT, FINALIZE_FAILED */ + int plan_no; /* the last plan begun */ + int err; /* FINALIZE_FAILED only */ + } commit; + } u; +} sched_trace_entry_t; + /* One arena chunk; the record frees the whole chain at once. */ typedef struct sched_trace_chunk_s { struct sched_trace_chunk_s* next; @@ -162,6 +303,27 @@ struct xce_sched_trace_rec_s { sched_trace_counters_t counters; + /* stored entries, in the order made; at most line_bound of them */ + sched_trace_entry_t* entries; + sched_trace_entry_t* entries_tail; + int nentries; + + /* builder state (allocator thread only, while the record is built) */ + int depth; /* nesting level of the next entry */ + int path_overflow; /* pushes beyond the two path levels, not kept */ + int resreq_open; /* requirement begins without their end yet */ + int plan_no; /* plans generated so far in this requirement */ + int cur_plan_no; /* the last plan begun */ + int cur_pos; /* the position begun last */ + int scan_err; /* pos_scan_error() for the current position */ + bool scan_err_set; + unsigned host_row_verdicts; /* SHORTFALL/LIMIT from the current host's + * unmet rows (set by the row capture) */ + /* scope pointers, kept only until the matching scope ends and never + * dereferenced after it (TraceCapture's limit lookup) */ + const xce_sched_obj_t* scope_obj; + const xce_sched_worker_t* scope_w; + /* per-record arena */ sched_trace_chunk_t* arena; size_t arena_bytes; /* sum of chunk sizes, headers included */ @@ -203,4 +365,93 @@ extern void* sched_trace_arena_memdup(xce_sched_trace_rec_t* rec, extern char* sched_trace_arena_strdup(xce_sched_trace_rec_t* rec, const char* s); +/* + * Entry sequence (design.md "TraceModel"). `rec` is never NULL (the + * SCHED_TRACE gate guarantees it). Calls nest in the order the allocator + * makes them: every path_push has one path_pop and every *_begin one + * *_end. Entries past rec->line_bound are not stored and each adds one to + * counters.omitted_lines (9.1, 9.2); the builder state (path, nesting, + * plan numbers, scope pointers) is kept up to date either way. + */ + +/* Requirement path: SINGLE 1/1, COMPOUND 1/1 or ALT i/n as the container + * level, MEMBER j/m below it. Not an entry. */ +extern void sched_trace_path_push(xce_sched_trace_rec_t* rec, + xce_sched_trace_path_kind_t kind, int i, + int n); +extern void sched_trace_path_pop(xce_sched_trace_rec_t* rec); + +/* Begin and end of the requirement the current path names. `primary` is + * NULL for a container, whose `nmembers` is its member count. */ +extern void sched_trace_resreq_begin(xce_sched_trace_rec_t* rec, + const xce_primary_resreq_t* primary, + int nmembers); +extern void sched_trace_resreq_end(xce_sched_trace_rec_t* rec, int rc, + int not_tried_after); + +/* Merge failure after member j of m: members j+1..m not tried (3.3). */ +extern void sched_trace_member_failed(xce_sched_trace_rec_t* rec, int j, + int m, int err); +/* Alternative i/n chosen, later ones not tried (3.4); 0: all failed. */ +extern void sched_trace_alt_result(xce_sched_trace_rec_t* rec, int chosen_i, + int n); +/* Alternative i/n skipped: it has no requirement content (3.6). */ +extern void sched_trace_alt_skipped(xce_sched_trace_rec_t* rec, int i, + int n); +/* Failure reasons of a compound attempt: per-code counts and the hosts and + * resources they name, all copied (3.5). TraceCapture builds the arrays + * from the attempt's pend-reason set. */ +extern void +sched_trace_attempt_reasons(xce_sched_trace_rec_t* rec, + const sched_trace_reason_count_t* codes, + int ncodes, const char* const* hosts, int nhosts, + const char* const* res, int nres); + +/* Cluster-wide check scope. check_begin stores no entry; check_end stores + * the result, which on failure says no placement plans were tried (4.8). */ +extern void sched_trace_check_begin(xce_sched_trace_rec_t* rec, + const xce_sched_obj_t* obj); +extern void sched_trace_check_end(xce_sched_trace_rec_t* rec, int rc, + bool plans_follow); + +extern void sched_trace_no_plan(xce_sched_trace_rec_t* rec, + xce_sched_trace_noplan_t why); + +/* Plans, numbered in generation order within the current requirement. */ +extern void sched_trace_plan_begin(xce_sched_trace_rec_t* rec, + const xce_alloc_plan_t* plan); +extern void sched_trace_plan_discarded(xce_sched_trace_rec_t* rec, + const xce_alloc_plan_t* plan); +extern void sched_trace_plan_not_tried(xce_sched_trace_rec_t* rec, + const xce_alloc_plan_t* plan, int err); +extern void sched_trace_plan_end(xce_sched_trace_rec_t* rec, int rc, + int failed_pos); + +/* Plan positions. A pos_scan_error() stores no entry: it turns the + * position's following UNFILLED end into SCAN_ERROR with that error. */ +extern void sched_trace_pos_begin(xce_sched_trace_rec_t* rec, int pos, + int slots); +extern void sched_trace_pos_end(xce_sched_trace_rec_t* rec, + xce_sched_trace_pos_end_t how, + const char* host); +extern void sched_trace_pos_scan_error(xce_sched_trace_rec_t* rec, int err); + +/* + * Per-host scope: host_begin stores no entry; host_verdict stores the + * host's verdict and ends the scope. An explicit SELECT verdict comes from + * the host-selection pre-filter. `verdicts == 0` means "from the rows": the + * SHORTFALL/LIMIT bits the current host's unmet rows set, or SELECT when + * there were none (the per-host check's own selection branch); either way + * the phase is the per-host check (5.2). + */ +extern void sched_trace_host_begin(xce_sched_trace_rec_t* rec, + const xce_sched_obj_t* obj, + const xce_sched_worker_t* w); +extern void sched_trace_host_verdict(xce_sched_trace_rec_t* rec, + unsigned verdicts, int err); + +/* The plan begun last was committed, or building its allocation failed. */ +extern void sched_trace_commit(xce_sched_trace_rec_t* rec); +extern void sched_trace_finalize_failed(xce_sched_trace_rec_t* rec, int err); + #endif diff --git a/src/scheduler/src/sched_trace.c b/src/scheduler/src/sched_trace.c index 39aad53e..c8ff2697 100644 --- a/src/scheduler/src/sched_trace.c +++ b/src/scheduler/src/sched_trace.c @@ -11,12 +11,20 @@ * sim thread for a reconstructed record) and is immutable once attached to * a decision, so nothing here locks. * + * The entry sequence: the allocation chain's hooks append typed entries, + * each of which renders as exactly one line. At most `line_bound` entries + * are stored; every entry past that only adds one to the omitted-line + * count, and allocates nothing. The builder state (requirement path, + * nesting level, plan numbers, scope pointers) is kept whether or not an + * entry is stored, so a truncated record still nests correctly. + * * No I/O and no logging: test_core_isolation.c scans this file for stdio * and file I/O. */ #include #include +#include "sched/alloc_iter.h" #include "sched/trace.h" #include "utils.h" @@ -229,3 +237,460 @@ size_t sched_trace_rec_bytes(const xce_sched_trace_rec_t* rec) return sizeof(*rec) + rec->arena_bytes; } + +/* ---- entry sequence ---------------------------------------------------- */ + +/* + * Appends an entry of `type` at the current nesting level with the current + * requirement path, or returns NULL when it is not stored: past the line + * 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* e; + + if (rec->incomplete) { + return NULL; + } + if (rec->nentries >= rec->line_bound) { + rec->counters.omitted_lines++; + return NULL; + } + + e = (sched_trace_entry_t*)sched_trace_arena_alloc(rec, sizeof(*e)); + if (e == NULL) { + return NULL; + } + e->next = NULL; + e->type = type; + e->depth = rec->depth; + e->path = rec->path; + + if (rec->entries_tail == NULL) { + rec->entries = e; + } else { + rec->entries_tail->next = e; + } + rec->entries_tail = e; + rec->nentries++; + return e; +} + +/* 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* e = 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) +{ + if (rec->depth > 0) { + rec->depth--; + } + return entry_add(rec, type); +} + +/* Arena copy of `n` ints; NULL for none. */ +static int* copy_ints(xce_sched_trace_rec_t* rec, const int* src, int n) +{ + if ((src == NULL) || (n <= 0)) { + return NULL; + } + return (int*)sched_trace_arena_memdup(rec, src, sizeof(int) * (size_t)n); +} + +/* Arena copy of `n` strings (each copied too); NULL for none. */ +static const char** copy_strs(xce_sched_trace_rec_t* rec, + const char* const* src, int n) +{ + const char** out; + int i; + + if ((src == NULL) || (n <= 0)) { + return NULL; + } + out = (const char**)sched_trace_arena_alloc(rec, sizeof(char*) * (size_t)n); + if (out == NULL) { + return NULL; + } + for (i = 0; i < n; i++) { + out[i] = sched_trace_arena_strdup(rec, src[i]); + } + return out; +} + +void sched_trace_path_push(xce_sched_trace_rec_t* rec, + xce_sched_trace_path_kind_t kind, int i, int n) +{ + sched_trace_path_t* p = &rec->path; + + if (p->depth >= 2) { + /* deeper than container > member: not kept, but balanced */ + rec->path_overflow++; + return; + } + p->lvl[p->depth].kind = kind; + p->lvl[p->depth].i = i; + p->lvl[p->depth].n = n; + p->depth++; +} + +void sched_trace_path_pop(xce_sched_trace_rec_t* rec) +{ + if (rec->path_overflow > 0) { + rec->path_overflow--; + return; + } + if (rec->path.depth > 0) { + rec->path.depth--; + } +} + +void sched_trace_resreq_begin(xce_sched_trace_rec_t* rec, + const xce_primary_resreq_t* primary, + int nmembers) +{ + sched_trace_entry_t* e; + int n; + int k; + + rec->resreq_open++; + rec->plan_no = 0; /* plans are numbered per requirement */ + rec->cur_plan_no = 0; + + e = scope_begin(rec, SCHED_TRACE_E_RESREQ_BEGIN); + if (e == NULL) { + return; + } + + if (primary == NULL) { + e->u.resreq_begin.container = true; + e->u.resreq_begin.nmembers = nmembers; + return; + } + + e->u.resreq_begin.container = false; + e->u.resreq_begin.min_tasks = primary->minTasks; + e->u.resreq_begin.max_tasks = primary->maxTasks; + + n = primary->rusages.count; + if ((n <= 0) || (primary->rusages.data == NULL)) { + return; + } + e->u.resreq_begin.req = (sched_trace_req_t*)sched_trace_arena_alloc( + rec, sizeof(sched_trace_req_t) * (size_t)n); + if (e->u.resreq_begin.req == NULL) { + return; + } + for (k = 0; k < n; k++) { + const rusage_value_t* v + = (const rusage_value_t*)primary->rusages.data[k]; + sched_trace_req_t* r = &e->u.resreq_begin.req[k]; + + if (v == NULL) { + continue; + } + r->name = sched_trace_arena_strdup(rec, v->res); + r->value = v->value; + r->per = (int)v->per_flag; + } + e->u.resreq_begin.nreq = n; +} + +void sched_trace_resreq_end(xce_sched_trace_rec_t* rec, int rc, + int not_tried_after) +{ + sched_trace_entry_t* e; + + if (rec->resreq_open > 0) { + rec->resreq_open--; + } + + e = scope_end(rec, SCHED_TRACE_E_RESREQ_END); + if (e == NULL) { + return; + } + e->u.resreq_end.rc = rc; + e->u.resreq_end.not_tried_after = not_tried_after; +} + +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); + + if (e == NULL) { + return; + } + e->u.member_failed.j = j; + e->u.member_failed.m = m; + e->u.member_failed.err = err; +} + +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); + + if (e == NULL) { + return; + } + e->u.alt.i = chosen_i; + e->u.alt.n = 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); + + if (e == NULL) { + return; + } + e->u.alt.i = i; + e->u.alt.n = n; +} + +void sched_trace_attempt_reasons(xce_sched_trace_rec_t* rec, + const sched_trace_reason_count_t* codes, + 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); + + if (e == NULL) { + return; + } + if ((codes != NULL) && (ncodes > 0)) { + e->u.reasons.codes = (sched_trace_reason_count_t*) + sched_trace_arena_memdup(rec, codes, + sizeof(*codes) * (size_t)ncodes); + if (e->u.reasons.codes != NULL) { + e->u.reasons.ncodes = ncodes; + } + } + e->u.reasons.hosts = copy_strs(rec, hosts, nhosts); + if (e->u.reasons.hosts != NULL) { + e->u.reasons.nhosts = nhosts; + } + e->u.reasons.res = copy_strs(rec, res, nres); + if (e->u.reasons.res != NULL) { + e->u.reasons.nres = nres; + } +} + +void sched_trace_check_begin(xce_sched_trace_rec_t* rec, + const xce_sched_obj_t* obj) +{ + rec->scope_obj = obj; + rec->scope_w = NULL; +} + +void sched_trace_check_end(xce_sched_trace_rec_t* rec, int rc, + bool plans_follow) +{ + sched_trace_entry_t* e; + + rec->scope_obj = NULL; + + e = entry_add(rec, SCHED_TRACE_E_CHECK_END); + if (e == NULL) { + return; + } + e->u.check_end.rc = rc; + e->u.check_end.plans_follow = plans_follow; +} + +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); + + if (e == NULL) { + return; + } + e->u.no_plan.why = why; +} + +/* Fills a PLAN_BEGIN / PLAN_DISCARDED / PLAN_NOT_TRIED payload. */ +static void plan_fill(xce_sched_trace_rec_t* rec, sched_trace_entry_t* e, + const xce_alloc_plan_t* plan, int plan_no, int err) +{ + e->u.plan.plan_no = plan_no; + e->u.plan.err = err; + if (plan == NULL) { + return; + } + e->u.plan.nbuckets = plan->nbuckets; + e->u.plan.slots = copy_ints(rec, plan->bucket_slots, plan->nbuckets); +} + +void sched_trace_plan_begin(xce_sched_trace_rec_t* rec, + const xce_alloc_plan_t* plan) +{ + sched_trace_entry_t* e; + + rec->cur_plan_no = ++rec->plan_no; + e = scope_begin(rec, SCHED_TRACE_E_PLAN_BEGIN); + if (e != NULL) { + plan_fill(rec, e, plan, rec->cur_plan_no, 0); + } +} + +void sched_trace_plan_discarded(xce_sched_trace_rec_t* rec, + const xce_alloc_plan_t* plan) +{ + sched_trace_entry_t* e; + + rec->plan_no++; + e = entry_add(rec, SCHED_TRACE_E_PLAN_DISCARDED); + if (e != NULL) { + plan_fill(rec, e, plan, rec->plan_no, 0); + } +} + +void sched_trace_plan_not_tried(xce_sched_trace_rec_t* rec, + const xce_alloc_plan_t* plan, int err) +{ + sched_trace_entry_t* e; + + rec->plan_no++; + e = entry_add(rec, SCHED_TRACE_E_PLAN_NOT_TRIED); + if (e != NULL) { + plan_fill(rec, e, plan, rec->plan_no, err); + } +} + +void sched_trace_plan_end(xce_sched_trace_rec_t* rec, int rc, int failed_pos) +{ + sched_trace_entry_t* e = scope_end(rec, SCHED_TRACE_E_PLAN_END); + + if (e == NULL) { + return; + } + e->u.plan_end.plan_no = rec->cur_plan_no; + 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; + + 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) +{ + rec->scan_err = err; + rec->scan_err_set = true; +} + +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; + + 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) +{ + rec->scope_obj = obj; + rec->scope_w = w; + rec->host_row_verdicts = 0; +} + +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; + } + + /* the host scope ends here */ + rec->scope_obj = NULL; + rec->scope_w = NULL; + rec->host_row_verdicts = 0; + + e = entry_add(rec, SCHED_TRACE_E_HOST); + if (e == NULL) { + return; + } + 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; +} + +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; +} diff --git a/src/scheduler/testsuite/test_trace_model.c b/src/scheduler/testsuite/test_trace_model.c index dd145b3b..fe219fdc 100644 --- a/src/scheduler/testsuite/test_trace_model.c +++ b/src/scheduler/testsuite/test_trace_model.c @@ -13,6 +13,14 @@ * - finishing a record as a no-decision kind (Requirement 2.4); * - freeing a NULL record is a no-op. * + * Task 1.4 adds the entry sequence and the line budget: + * - requirement path push and pop, balanced requirement begin and end + * entries, each storing its full nesting path (3.1, 3.3, 3.4, 3.6); + * - every entry kind, one stored entry (= one rendered line) each, with + * its payload copied (3.3-3.6, 4.3-4.5, 4.7, 4.8, 5.10); + * - the line budget at bound-1, bound and bound+1, with the exact + * omitted count (9.1, 9.2). + * * Links only src/sched_trace.c (plus libxce for the jobid helpers). */ #include @@ -20,6 +28,7 @@ #include +#include "sched/alloc_iter.h" #include "sched/trace.h" #include "utils.h" @@ -406,6 +415,827 @@ static void test_finish_rejects_non_no_decision_kinds(void) sched_trace_rec_free(rec); } +/* ---- entry sequence (task 1.4) ---------------------------------------- */ + +static xce_sched_trace_rec_t* new_rec(int line_bound) +{ + xce_sched_trace_ctl_t ctl = make_ctl(NULL, 0); + + ctl.level = XCE_TRACE_LEVEL_FULL; + ctl.line_bound = line_bound; + return sched_trace_rec_new(&ctl, PLAIN_ID(5), XCE_TRACE_REC_DECISION); +} + +static int count_entries(const xce_sched_trace_rec_t* rec) +{ + const sched_trace_entry_t* e; + int n = 0; + + for (e = rec->entries; e != NULL; e = e->next) { + n++; + } + return n; +} + +static const sched_trace_entry_t* entry_at(const xce_sched_trace_rec_t* rec, + int k) +{ + const sched_trace_entry_t* e = rec->entries; + + while ((e != NULL) && (k > 0)) { + e = e->next; + k--; + } + TEST_ASSERT_NOT_NULL_MESSAGE(e, "entry index out of range"); + return e; +} + +static void assert_path1(const sched_trace_entry_t* e, + xce_sched_trace_path_kind_t kind, int i, int n) +{ + TEST_ASSERT_EQUAL_INT(1, e->path.depth); + TEST_ASSERT_EQUAL_INT(kind, e->path.lvl[0].kind); + TEST_ASSERT_EQUAL_INT(i, e->path.lvl[0].i); + TEST_ASSERT_EQUAL_INT(n, e->path.lvl[0].n); +} + +/* A primary with two requested resources; the xlist points at the + * caller's arrays (no heap). */ +typedef struct { + xce_primary_resreq_t p; + rusage_value_t v[2]; + void* ptrs[2]; + char name0[16]; + char name1[16]; +} fixture_primary_t; + +static void make_primary(fixture_primary_t* f, int min_tasks, int max_tasks) +{ + memset(f, 0, sizeof(*f)); + strcpy(f->name0, "mem"); + strcpy(f->name1, "license"); + f->v[0].res = f->name0; + f->v[0].value = 8192.0f; + f->v[0].per_flag = PER_TASK; + f->v[1].res = f->name1; + f->v[1].value = 2.0f; + f->v[1].per_flag = PER_JOB; + f->ptrs[0] = &f->v[0]; + f->ptrs[1] = &f->v[1]; + f->p.minTasks = min_tasks; + f->p.maxTasks = max_tasks; + f->p.rusages.count = 2; + f->p.rusages.caps = 2; + f->p.rusages.delta = 1; + f->p.rusages.data = f->ptrs; +} + +static void test_single_requirement_path_and_balance(void) +{ + xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT); + fixture_primary_t fp; + const sched_trace_entry_t* b; + const sched_trace_entry_t* e; + + TEST_ASSERT_NOT_NULL(rec); + TEST_ASSERT_NULL(rec->entries); + TEST_ASSERT_EQUAL_INT(0, rec->nentries); + make_primary(&fp, 2, 4); + + sched_trace_path_push(rec, XCE_TRACE_PATH_SINGLE, 1, 1); + TEST_ASSERT_EQUAL_INT(1, rec->path.depth); + sched_trace_resreq_begin(rec, &fp.p, 0); + sched_trace_resreq_end(rec, 1, 0); + sched_trace_path_pop(rec); + + TEST_ASSERT_EQUAL_INT(0, rec->path.depth); + TEST_ASSERT_EQUAL_INT(0, rec->resreq_open); + TEST_ASSERT_EQUAL_INT(0, rec->depth); + TEST_ASSERT_EQUAL_INT(2, rec->nentries); + TEST_ASSERT_EQUAL_INT(2, count_entries(rec)); + + b = entry_at(rec, 0); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_RESREQ_BEGIN, b->type); + TEST_ASSERT_EQUAL_INT(0, b->depth); + assert_path1(b, XCE_TRACE_PATH_SINGLE, 1, 1); + TEST_ASSERT_FALSE(b->u.resreq_begin.container); + TEST_ASSERT_EQUAL_INT(2, b->u.resreq_begin.min_tasks); + TEST_ASSERT_EQUAL_INT(4, b->u.resreq_begin.max_tasks); + TEST_ASSERT_EQUAL_INT(2, b->u.resreq_begin.nreq); + TEST_ASSERT_EQUAL_STRING("mem", b->u.resreq_begin.req[0].name); + TEST_ASSERT_EQUAL_FLOAT(8192.0f, b->u.resreq_begin.req[0].value); + TEST_ASSERT_EQUAL_INT(PER_TASK, b->u.resreq_begin.req[0].per); + TEST_ASSERT_EQUAL_STRING("license", b->u.resreq_begin.req[1].name); + TEST_ASSERT_EQUAL_FLOAT(2.0f, b->u.resreq_begin.req[1].value); + TEST_ASSERT_EQUAL_INT(PER_JOB, b->u.resreq_begin.req[1].per); + + e = entry_at(rec, 1); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_RESREQ_END, e->type); + TEST_ASSERT_EQUAL_INT(0, e->depth); /* same level as its begin */ + assert_path1(e, XCE_TRACE_PATH_SINGLE, 1, 1); + TEST_ASSERT_EQUAL_INT(1, e->u.resreq_end.rc); + TEST_ASSERT_EQUAL_INT(0, e->u.resreq_end.not_tried_after); + TEST_ASSERT_TRUE(rec->entries_tail == e); + + /* the requested resources are copies, not the scheduler's strings */ + TEST_ASSERT_TRUE(b->u.resreq_begin.req[0].name != fp.name0); + strcpy(fp.name0, "XXX"); + fp.v[1].value = 99.0f; + TEST_ASSERT_EQUAL_STRING("mem", b->u.resreq_begin.req[0].name); + TEST_ASSERT_EQUAL_FLOAT(2.0f, b->u.resreq_begin.req[1].value); + + sched_trace_rec_free(rec); +} + +static void test_alternative_member_nesting_stores_full_path(void) +{ + xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT); + fixture_primary_t fp; + const sched_trace_entry_t* e; + int k; + + TEST_ASSERT_NOT_NULL(rec); + make_primary(&fp, 1, 1); + + /* alternative 2/3 (a compound of 2 members): member 1/2 fails, so + * member 2/2 is not tried */ + sched_trace_path_push(rec, XCE_TRACE_PATH_ALT, 2, 3); + sched_trace_resreq_begin(rec, NULL, 2); + sched_trace_path_push(rec, XCE_TRACE_PATH_MEMBER, 1, 2); + TEST_ASSERT_EQUAL_INT(2, rec->path.depth); + sched_trace_resreq_begin(rec, &fp.p, 0); + sched_trace_resreq_end(rec, 0, 1); + sched_trace_path_pop(rec); + sched_trace_resreq_end(rec, 0, 0); + sched_trace_path_pop(rec); + + TEST_ASSERT_EQUAL_INT(0, rec->path.depth); + TEST_ASSERT_EQUAL_INT(0, rec->resreq_open); + TEST_ASSERT_EQUAL_INT(0, rec->depth); + TEST_ASSERT_EQUAL_INT(4, count_entries(rec)); + + e = entry_at(rec, 0); /* container begin */ + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_RESREQ_BEGIN, e->type); + TEST_ASSERT_EQUAL_INT(0, e->depth); + assert_path1(e, XCE_TRACE_PATH_ALT, 2, 3); + TEST_ASSERT_TRUE(e->u.resreq_begin.container); + TEST_ASSERT_EQUAL_INT(2, e->u.resreq_begin.nmembers); + TEST_ASSERT_EQUAL_INT(0, e->u.resreq_begin.nreq); + + for (k = 1; k <= 2; k++) { /* member begin and end: two-level path */ + e = entry_at(rec, k); + TEST_ASSERT_EQUAL_INT(1, e->depth); + TEST_ASSERT_EQUAL_INT(2, e->path.depth); + TEST_ASSERT_EQUAL_INT(XCE_TRACE_PATH_ALT, e->path.lvl[0].kind); + TEST_ASSERT_EQUAL_INT(2, e->path.lvl[0].i); + TEST_ASSERT_EQUAL_INT(3, e->path.lvl[0].n); + TEST_ASSERT_EQUAL_INT(XCE_TRACE_PATH_MEMBER, e->path.lvl[1].kind); + TEST_ASSERT_EQUAL_INT(1, e->path.lvl[1].i); + TEST_ASSERT_EQUAL_INT(2, e->path.lvl[1].n); + } + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_RESREQ_BEGIN, entry_at(rec, 1)->type); + TEST_ASSERT_FALSE(entry_at(rec, 1)->u.resreq_begin.container); + e = entry_at(rec, 2); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_RESREQ_END, e->type); + TEST_ASSERT_EQUAL_INT(0, e->u.resreq_end.rc); + TEST_ASSERT_EQUAL_INT(1, e->u.resreq_end.not_tried_after); + + e = entry_at(rec, 3); /* container end */ + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_RESREQ_END, e->type); + TEST_ASSERT_EQUAL_INT(0, e->depth); + assert_path1(e, XCE_TRACE_PATH_ALT, 2, 3); + + sched_trace_rec_free(rec); +} + +static void test_compound_container_path(void) +{ + xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT); + fixture_primary_t fp; + const sched_trace_entry_t* e; + int j; + int nbegin = 0; + int nend = 0; + + TEST_ASSERT_NOT_NULL(rec); + make_primary(&fp, 1, 2); + + sched_trace_path_push(rec, XCE_TRACE_PATH_COMPOUND, 1, 1); + sched_trace_resreq_begin(rec, NULL, 3); + for (j = 1; j <= 3; j++) { + sched_trace_path_push(rec, XCE_TRACE_PATH_MEMBER, j, 3); + sched_trace_resreq_begin(rec, &fp.p, 0); + sched_trace_resreq_end(rec, 1, 0); + sched_trace_path_pop(rec); + } + sched_trace_resreq_end(rec, 1, 0); + sched_trace_path_pop(rec); + + TEST_ASSERT_EQUAL_INT(8, count_entries(rec)); + e = entry_at(rec, 0); + assert_path1(e, XCE_TRACE_PATH_COMPOUND, 1, 1); + TEST_ASSERT_TRUE(e->u.resreq_begin.container); + TEST_ASSERT_EQUAL_INT(3, e->u.resreq_begin.nmembers); + e = entry_at(rec, 5); /* member 3/3 begin */ + TEST_ASSERT_EQUAL_INT(2, e->path.depth); + TEST_ASSERT_EQUAL_INT(XCE_TRACE_PATH_COMPOUND, e->path.lvl[0].kind); + TEST_ASSERT_EQUAL_INT(3, e->path.lvl[1].i); + TEST_ASSERT_EQUAL_INT(3, e->path.lvl[1].n); + + /* balanced: one end per begin, nesting back to the top */ + for (e = rec->entries; e != NULL; e = e->next) { + nbegin += (e->type == SCHED_TRACE_E_RESREQ_BEGIN); + nend += (e->type == SCHED_TRACE_E_RESREQ_END); + } + TEST_ASSERT_EQUAL_INT(4, nbegin); + TEST_ASSERT_EQUAL_INT(nbegin, nend); + TEST_ASSERT_EQUAL_INT(0, rec->resreq_open); + TEST_ASSERT_EQUAL_INT(0, rec->depth); + sched_trace_rec_free(rec); +} + +static void test_path_push_beyond_two_levels_stays_balanced(void) +{ + xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT); + + TEST_ASSERT_NOT_NULL(rec); + sched_trace_path_push(rec, XCE_TRACE_PATH_ALT, 1, 2); + sched_trace_path_push(rec, XCE_TRACE_PATH_MEMBER, 1, 1); + sched_trace_path_push(rec, XCE_TRACE_PATH_MEMBER, 1, 1); /* not kept */ + TEST_ASSERT_EQUAL_INT(2, rec->path.depth); + TEST_ASSERT_EQUAL_INT(XCE_TRACE_PATH_MEMBER, rec->path.lvl[1].kind); + sched_trace_path_pop(rec); /* pops the one not kept */ + TEST_ASSERT_EQUAL_INT(2, rec->path.depth); + sched_trace_path_pop(rec); + TEST_ASSERT_EQUAL_INT(1, rec->path.depth); + sched_trace_path_pop(rec); + TEST_ASSERT_EQUAL_INT(0, rec->path.depth); + sched_trace_path_pop(rec); /* unbalanced pop: stays at the top */ + TEST_ASSERT_EQUAL_INT(0, rec->path.depth); + TEST_ASSERT_EQUAL_INT(0, rec->nentries); /* the path is not an entry */ + sched_trace_rec_free(rec); +} + +static void test_requirement_level_entries(void) +{ + xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT); + const sched_trace_entry_t* e; + sched_trace_reason_count_t codes[2] = { { 3, 4 }, { 7, 1 } }; + char h0[8] = "n01"; + char h1[8] = "n02"; + char r0[16] = "license"; + const char* hosts[2] = { h0, h1 }; + const char* res[1] = { r0 }; + + TEST_ASSERT_NOT_NULL(rec); + sched_trace_path_push(rec, XCE_TRACE_PATH_ALT, 1, 3); + sched_trace_alt_skipped(rec, 1, 3); + sched_trace_path_pop(rec); + sched_trace_path_push(rec, XCE_TRACE_PATH_ALT, 2, 3); + sched_trace_resreq_begin(rec, NULL, 2); + sched_trace_member_failed(rec, 1, 2, -12); + sched_trace_attempt_reasons(rec, codes, 2, hosts, 2, res, 1); + sched_trace_resreq_end(rec, -12, 0); + sched_trace_path_pop(rec); + sched_trace_alt_result(rec, 3, 3); + sched_trace_alt_result(rec, 0, 3); + + TEST_ASSERT_EQUAL_INT(7, count_entries(rec)); + + e = entry_at(rec, 0); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_ALT_SKIPPED, e->type); + TEST_ASSERT_EQUAL_INT(1, e->u.alt.i); + TEST_ASSERT_EQUAL_INT(3, e->u.alt.n); + TEST_ASSERT_EQUAL_INT(0, e->depth); + + e = entry_at(rec, 2); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_MEMBER_FAILED, e->type); + TEST_ASSERT_EQUAL_INT(1, e->depth); /* inside the alternative */ + assert_path1(e, XCE_TRACE_PATH_ALT, 2, 3); + TEST_ASSERT_EQUAL_INT(1, e->u.member_failed.j); + TEST_ASSERT_EQUAL_INT(2, e->u.member_failed.m); + TEST_ASSERT_EQUAL_INT(-12, e->u.member_failed.err); + + e = entry_at(rec, 3); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_ATTEMPT_REASONS, e->type); + TEST_ASSERT_EQUAL_INT(1, e->depth); + TEST_ASSERT_EQUAL_INT(2, e->u.reasons.ncodes); + TEST_ASSERT_EQUAL_INT(3, e->u.reasons.codes[0].code); + TEST_ASSERT_EQUAL_INT(4, e->u.reasons.codes[0].count); + TEST_ASSERT_EQUAL_INT(7, e->u.reasons.codes[1].code); + TEST_ASSERT_EQUAL_INT(1, e->u.reasons.codes[1].count); + TEST_ASSERT_EQUAL_INT(2, e->u.reasons.nhosts); + TEST_ASSERT_EQUAL_INT(1, e->u.reasons.nres); + TEST_ASSERT_TRUE(e->u.reasons.codes != codes); + TEST_ASSERT_TRUE(e->u.reasons.hosts[0] != h0); + codes[0].count = 99; + strcpy(h0, "zz"); + strcpy(r0, "zz"); + TEST_ASSERT_EQUAL_INT(4, e->u.reasons.codes[0].count); + TEST_ASSERT_EQUAL_STRING("n01", e->u.reasons.hosts[0]); + TEST_ASSERT_EQUAL_STRING("n02", e->u.reasons.hosts[1]); + TEST_ASSERT_EQUAL_STRING("license", e->u.reasons.res[0]); + + e = entry_at(rec, 4); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_RESREQ_END, e->type); + TEST_ASSERT_EQUAL_INT(-12, e->u.resreq_end.rc); + + e = entry_at(rec, 5); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_ALT_RESULT, e->type); + TEST_ASSERT_EQUAL_INT(3, e->u.alt.i); + TEST_ASSERT_EQUAL_INT(3, e->u.alt.n); + TEST_ASSERT_EQUAL_INT(0, e->path.depth); /* after the pop */ + e = entry_at(rec, 6); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_ALT_RESULT, e->type); + TEST_ASSERT_EQUAL_INT(0, e->u.alt.i); /* all alternatives failed */ + + sched_trace_rec_free(rec); +} + +static void test_attempt_reasons_empty_lists(void) +{ + xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT); + const sched_trace_entry_t* e; + + TEST_ASSERT_NOT_NULL(rec); + sched_trace_attempt_reasons(rec, NULL, 0, NULL, 0, NULL, 0); + TEST_ASSERT_EQUAL_INT(1, rec->nentries); + e = entry_at(rec, 0); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_ATTEMPT_REASONS, e->type); + TEST_ASSERT_EQUAL_INT(0, e->u.reasons.ncodes); + TEST_ASSERT_EQUAL_INT(0, e->u.reasons.nhosts); + TEST_ASSERT_EQUAL_INT(0, e->u.reasons.nres); + TEST_ASSERT_FALSE(rec->incomplete); + sched_trace_rec_free(rec); +} + +/* A plan of `n` positions with `slots` tasks each, slot array owned by the + * caller. */ +static xce_alloc_plan_t make_plan(int* slots, int n) +{ + xce_alloc_plan_t plan; + + memset(&plan, 0, sizeof(plan)); + plan.nbuckets = n; + plan.bucket_slots = slots; + return plan; +} + +static void test_check_and_plan_entries(void) +{ + xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT); + fixture_primary_t fp; + const sched_trace_entry_t* e; + int s1[2] = { 3, 1 }; + int s2[1] = { 4 }; + int s3[2] = { 2, 2 }; + int s4[4] = { 1, 1, 1, 1 }; + xce_alloc_plan_t p1 = make_plan(s1, 2); + xce_alloc_plan_t p2 = make_plan(s2, 1); + xce_alloc_plan_t p3 = make_plan(s3, 2); + xce_alloc_plan_t p4 = make_plan(s4, 4); + const xce_sched_obj_t* obj = (const xce_sched_obj_t*)&fp; + + TEST_ASSERT_NOT_NULL(rec); + make_primary(&fp, 4, 4); + sched_trace_path_push(rec, XCE_TRACE_PATH_SINGLE, 1, 1); + sched_trace_resreq_begin(rec, &fp.p, 0); /* 0 */ + sched_trace_check_begin(rec, obj); /* no entry */ + TEST_ASSERT_TRUE(rec->scope_obj == obj); + sched_trace_check_end(rec, 1, true); /* 1 */ + TEST_ASSERT_NULL(rec->scope_obj); + sched_trace_plan_discarded(rec, &p1); /* 2: plan 1 */ + sched_trace_plan_begin(rec, &p2); /* 3: plan 2 */ + sched_trace_plan_end(rec, 0, 1); /* 4 */ + sched_trace_plan_begin(rec, &p3); /* 5: plan 3 */ + sched_trace_plan_end(rec, 1, 0); /* 6 */ + sched_trace_commit(rec); /* 7 */ + sched_trace_plan_not_tried(rec, &p4, -12); /* 8: plan 4 */ + sched_trace_finalize_failed(rec, -5); /* 9 */ + sched_trace_no_plan(rec, XCE_TRACE_NOPLAN_NONE_GENERATED); /* 10 */ + sched_trace_resreq_end(rec, 1, 0); /* 11 */ + sched_trace_path_pop(rec); + + TEST_ASSERT_EQUAL_INT(12, count_entries(rec)); + + e = entry_at(rec, 1); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_CHECK_END, e->type); + TEST_ASSERT_EQUAL_INT(1, e->depth); /* inside the requirement */ + assert_path1(e, XCE_TRACE_PATH_SINGLE, 1, 1); + TEST_ASSERT_EQUAL_INT(1, e->u.check_end.rc); + TEST_ASSERT_TRUE(e->u.check_end.plans_follow); + + e = entry_at(rec, 2); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_PLAN_DISCARDED, e->type); + TEST_ASSERT_EQUAL_INT(1, e->depth); + TEST_ASSERT_EQUAL_INT(1, e->u.plan.plan_no); + TEST_ASSERT_EQUAL_INT(2, e->u.plan.nbuckets); + TEST_ASSERT_EQUAL_INT(3, e->u.plan.slots[0]); + TEST_ASSERT_EQUAL_INT(1, e->u.plan.slots[1]); + TEST_ASSERT_TRUE(e->u.plan.slots != s1); + + e = entry_at(rec, 3); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_PLAN_BEGIN, e->type); + TEST_ASSERT_EQUAL_INT(1, e->depth); + TEST_ASSERT_EQUAL_INT(2, e->u.plan.plan_no); + TEST_ASSERT_EQUAL_INT(1, e->u.plan.nbuckets); + TEST_ASSERT_EQUAL_INT(4, e->u.plan.slots[0]); + + e = entry_at(rec, 4); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_PLAN_END, e->type); + TEST_ASSERT_EQUAL_INT(1, e->depth); /* same level as its begin */ + TEST_ASSERT_EQUAL_INT(2, e->u.plan_end.plan_no); + TEST_ASSERT_EQUAL_INT(0, e->u.plan_end.rc); + TEST_ASSERT_EQUAL_INT(1, e->u.plan_end.failed_pos); + + e = entry_at(rec, 6); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_PLAN_END, e->type); + TEST_ASSERT_EQUAL_INT(3, e->u.plan_end.plan_no); + TEST_ASSERT_EQUAL_INT(1, e->u.plan_end.rc); + + e = entry_at(rec, 7); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_COMMIT, e->type); + TEST_ASSERT_EQUAL_INT(1, e->depth); + TEST_ASSERT_EQUAL_INT(3, e->u.commit.plan_no); /* the last plan begun */ + + e = entry_at(rec, 8); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_PLAN_NOT_TRIED, e->type); + TEST_ASSERT_EQUAL_INT(4, e->u.plan.plan_no); + TEST_ASSERT_EQUAL_INT(4, e->u.plan.nbuckets); + TEST_ASSERT_EQUAL_INT(-12, e->u.plan.err); + + e = entry_at(rec, 9); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_FINALIZE_FAILED, e->type); + TEST_ASSERT_EQUAL_INT(3, e->u.commit.plan_no); + TEST_ASSERT_EQUAL_INT(-5, e->u.commit.err); + + e = entry_at(rec, 10); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_NO_PLAN, e->type); + TEST_ASSERT_EQUAL_INT(XCE_TRACE_NOPLAN_NONE_GENERATED, e->u.no_plan.why); + + e = entry_at(rec, 11); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_RESREQ_END, e->type); + TEST_ASSERT_EQUAL_INT(0, e->depth); + TEST_ASSERT_EQUAL_INT(0, rec->depth); + + /* plan numbers restart with the next requirement */ + sched_trace_path_push(rec, XCE_TRACE_PATH_SINGLE, 1, 1); + sched_trace_resreq_begin(rec, &fp.p, 0); + sched_trace_plan_begin(rec, &p2); + TEST_ASSERT_EQUAL_INT(1, rec->entries_tail->u.plan.plan_no); + sched_trace_plan_end(rec, 1, 0); + sched_trace_resreq_end(rec, 1, 0); + sched_trace_path_pop(rec); + + sched_trace_rec_free(rec); +} + +static void test_check_failed_and_no_plan_reasons(void) +{ + xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT); + + TEST_ASSERT_NOT_NULL(rec); + sched_trace_check_begin(rec, NULL); + sched_trace_check_end(rec, 0, false); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_CHECK_END, rec->entries_tail->type); + TEST_ASSERT_EQUAL_INT(0, rec->entries_tail->u.check_end.rc); + TEST_ASSERT_FALSE(rec->entries_tail->u.check_end.plans_follow); + sched_trace_no_plan(rec, XCE_TRACE_NOPLAN_DECLINED); + TEST_ASSERT_EQUAL_INT(XCE_TRACE_NOPLAN_DECLINED, + rec->entries_tail->u.no_plan.why); + sched_trace_no_plan(rec, XCE_TRACE_NOPLAN_GENERATOR_FAILED); + TEST_ASSERT_EQUAL_INT(XCE_TRACE_NOPLAN_GENERATOR_FAILED, + rec->entries_tail->u.no_plan.why); + TEST_ASSERT_EQUAL_INT(3, rec->nentries); + sched_trace_rec_free(rec); +} + +static xce_sched_worker_t make_worker(char* name, int idx) +{ + xce_sched_worker_t w; + + memset(&w, 0, sizeof(w)); + w.name = name; + w.idx = idx; + return w; +} + +static void test_position_and_host_entries(void) +{ + xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT); + const sched_trace_entry_t* e; + char n1[8] = "n01"; + char n2[8] = "n02"; + char n3[8] = "n03"; + char n4[8] = "n04"; + char n5[8] = "n05"; + xce_sched_worker_t w1 = make_worker(n1, 0); + xce_sched_worker_t w2 = make_worker(n2, 1); + xce_sched_worker_t w3 = make_worker(n3, 2); + xce_sched_worker_t w4 = make_worker(n4, 3); + xce_sched_worker_t w5 = make_worker(n5, 4); + int s[2] = { 4, 2 }; + xce_alloc_plan_t plan = make_plan(s, 2); + const xce_sched_obj_t* obj = (const xce_sched_obj_t*)&plan; + + TEST_ASSERT_NOT_NULL(rec); + sched_trace_plan_begin(rec, &plan); /* 0, depth 0 */ + sched_trace_pos_begin(rec, 1, 4); /* 1, depth 1 */ + sched_trace_host_begin(rec, obj, &w1); /* no entry */ + TEST_ASSERT_TRUE(rec->scope_obj == obj); + TEST_ASSERT_TRUE(rec->scope_w == &w1); + sched_trace_host_verdict(rec, XCE_TRACE_V_SELECT, 0); /* 2 */ + TEST_ASSERT_NULL(rec->scope_w); /* the host scope ended */ + TEST_ASSERT_NULL(rec->scope_obj); + sched_trace_host_begin(rec, obj, &w2); + sched_trace_host_verdict(rec, 0, 0); /* from rows: none -> SELECT */ + sched_trace_host_begin(rec, obj, &w3); + rec->host_row_verdicts = XCE_TRACE_V_SHORTFALL | XCE_TRACE_V_LIMIT; + sched_trace_host_verdict(rec, 0, 0); /* from rows */ + TEST_ASSERT_EQUAL_UINT(0, rec->host_row_verdicts); + sched_trace_host_begin(rec, obj, &w4); + sched_trace_host_verdict(rec, XCE_TRACE_V_CLAIMED, 0); + sched_trace_host_begin(rec, obj, &w5); + sched_trace_host_verdict(rec, XCE_TRACE_V_INTERNAL, -5); + sched_trace_host_begin(rec, obj, &w3); + sched_trace_host_verdict(rec, XCE_TRACE_V_ACCEPTED, 0); + sched_trace_pos_end(rec, XCE_TRACE_POS_FILLED, n3); /* 8 */ + sched_trace_pos_begin(rec, 2, 2); /* 9 */ + sched_trace_host_begin(rec, obj, &w1); + sched_trace_host_verdict(rec, XCE_TRACE_V_SELECT, 0); /* 10 */ + sched_trace_pos_scan_error(rec, -5); /* no entry */ + sched_trace_pos_end(rec, XCE_TRACE_POS_UNFILLED, NULL); /* 11 */ + sched_trace_pos_begin(rec, 2, 2); /* 12 */ + sched_trace_pos_end(rec, XCE_TRACE_POS_UNFILLED, NULL); /* 13 */ + sched_trace_pos_begin(rec, 2, 2); /* 14 */ + sched_trace_pos_end(rec, XCE_TRACE_POS_INTERNAL, n2); /* 15 */ + sched_trace_plan_end(rec, 0, 2); /* 16 */ + + TEST_ASSERT_EQUAL_INT(17, count_entries(rec)); + TEST_ASSERT_EQUAL_INT(0, rec->depth); + + e = entry_at(rec, 1); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_POS_BEGIN, e->type); + TEST_ASSERT_EQUAL_INT(1, e->depth); + TEST_ASSERT_EQUAL_INT(1, e->u.pos_begin.pos); + TEST_ASSERT_EQUAL_INT(4, e->u.pos_begin.slots); + + e = entry_at(rec, 2); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_HOST, e->type); + TEST_ASSERT_EQUAL_INT(2, e->depth); + TEST_ASSERT_EQUAL_STRING("n01", e->u.host.host); + TEST_ASSERT_TRUE(e->u.host.host != n1); + TEST_ASSERT_EQUAL_UINT(XCE_TRACE_V_SELECT, e->u.host.verdicts); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_PHASE_PREFILTER, e->u.host.phase); + + e = entry_at(rec, 3); + TEST_ASSERT_EQUAL_STRING("n02", e->u.host.host); + TEST_ASSERT_EQUAL_UINT(XCE_TRACE_V_SELECT, e->u.host.verdicts); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_PHASE_PERHOST, e->u.host.phase); + + e = entry_at(rec, 4); + TEST_ASSERT_EQUAL_STRING("n03", e->u.host.host); + TEST_ASSERT_EQUAL_UINT(XCE_TRACE_V_SHORTFALL | XCE_TRACE_V_LIMIT, + e->u.host.verdicts); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_PHASE_PERHOST, e->u.host.phase); + + e = entry_at(rec, 5); + TEST_ASSERT_EQUAL_UINT(XCE_TRACE_V_CLAIMED, e->u.host.verdicts); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_PHASE_NONE, e->u.host.phase); + + e = entry_at(rec, 6); + TEST_ASSERT_EQUAL_STRING("n05", e->u.host.host); + TEST_ASSERT_EQUAL_UINT(XCE_TRACE_V_INTERNAL, e->u.host.verdicts); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_PHASE_NONE, e->u.host.phase); + TEST_ASSERT_EQUAL_INT(-5, e->u.host.err); + + e = entry_at(rec, 7); + TEST_ASSERT_EQUAL_UINT(XCE_TRACE_V_ACCEPTED, e->u.host.verdicts); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_PHASE_NONE, e->u.host.phase); + + e = entry_at(rec, 8); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_POS_END, e->type); + TEST_ASSERT_EQUAL_INT(1, e->depth); /* same level as its begin */ + TEST_ASSERT_EQUAL_INT(1, e->u.pos_end.pos); + TEST_ASSERT_EQUAL_INT(XCE_TRACE_POS_FILLED, e->u.pos_end.how); + TEST_ASSERT_EQUAL_STRING("n03", e->u.pos_end.host); + TEST_ASSERT_TRUE(e->u.pos_end.host != n3); + + e = entry_at(rec, 11); /* UNFILLED after a scan error */ + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_POS_END, e->type); + TEST_ASSERT_EQUAL_INT(2, e->u.pos_end.pos); + TEST_ASSERT_EQUAL_INT(XCE_TRACE_POS_SCAN_ERROR, e->u.pos_end.how); + TEST_ASSERT_EQUAL_INT(-5, e->u.pos_end.err); + TEST_ASSERT_NULL(e->u.pos_end.host); + + e = entry_at(rec, 13); /* the scan error belonged to the earlier one */ + TEST_ASSERT_EQUAL_INT(XCE_TRACE_POS_UNFILLED, e->u.pos_end.how); + TEST_ASSERT_EQUAL_INT(0, e->u.pos_end.err); + + e = entry_at(rec, 15); + TEST_ASSERT_EQUAL_INT(XCE_TRACE_POS_INTERNAL, e->u.pos_end.how); + TEST_ASSERT_EQUAL_STRING("n02", e->u.pos_end.host); + + e = entry_at(rec, 16); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_PLAN_END, e->type); + TEST_ASSERT_EQUAL_INT(0, e->depth); + TEST_ASSERT_EQUAL_INT(2, e->u.plan_end.failed_pos); + + /* the host names are copies */ + strcpy(n1, "zz"); + strcpy(n3, "zz"); + TEST_ASSERT_EQUAL_STRING("n01", entry_at(rec, 2)->u.host.host); + TEST_ASSERT_EQUAL_STRING("n03", entry_at(rec, 8)->u.pos_end.host); + + sched_trace_rec_free(rec); +} + +static void test_pos_end_scan_error_direct(void) +{ + xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT); + + TEST_ASSERT_NOT_NULL(rec); + sched_trace_pos_begin(rec, 1, 1); + sched_trace_pos_end(rec, XCE_TRACE_POS_SCAN_ERROR, NULL); + TEST_ASSERT_EQUAL_INT(2, rec->nentries); + TEST_ASSERT_EQUAL_INT(XCE_TRACE_POS_SCAN_ERROR, + rec->entries_tail->u.pos_end.how); + TEST_ASSERT_EQUAL_INT(1, rec->entries_tail->u.pos_end.pos); + sched_trace_rec_free(rec); +} + +/* Drives one call of every entry kind; returns the number of entries it + * makes (every call below stores exactly one entry). */ +static int emit_every_kind(xce_sched_trace_rec_t* rec) +{ + static int slots[1] = { 1 }; + static char hname[] = "n01"; + static fixture_primary_t fp; + xce_alloc_plan_t plan = make_plan(slots, 1); + xce_sched_worker_t w = make_worker(hname, 0); + sched_trace_reason_count_t rc = { 1, 1 }; + + make_primary(&fp, 1, 1); + sched_trace_path_push(rec, XCE_TRACE_PATH_ALT, 1, 2); + sched_trace_alt_skipped(rec, 1, 2); /* 1 */ + sched_trace_path_pop(rec); + sched_trace_path_push(rec, XCE_TRACE_PATH_ALT, 2, 2); + sched_trace_resreq_begin(rec, NULL, 1); /* 2 */ + sched_trace_path_push(rec, XCE_TRACE_PATH_MEMBER, 1, 1); + sched_trace_resreq_begin(rec, &fp.p, 0); /* 3 */ + sched_trace_check_begin(rec, NULL); + sched_trace_check_end(rec, 1, true); /* 4 */ + sched_trace_no_plan(rec, XCE_TRACE_NOPLAN_DECLINED); /* 5 */ + sched_trace_plan_discarded(rec, &plan); /* 6 */ + sched_trace_plan_not_tried(rec, &plan, -12); /* 7 */ + sched_trace_plan_begin(rec, &plan); /* 8 */ + sched_trace_pos_begin(rec, 1, 1); /* 9 */ + sched_trace_host_begin(rec, NULL, &w); + sched_trace_host_verdict(rec, XCE_TRACE_V_ACCEPTED, 0); /* 10 */ + sched_trace_pos_end(rec, XCE_TRACE_POS_FILLED, hname); /* 11 */ + sched_trace_plan_end(rec, 1, 0); /* 12 */ + sched_trace_commit(rec); /* 13 */ + sched_trace_finalize_failed(rec, -5); /* 14 */ + sched_trace_resreq_end(rec, 1, 0); /* 15 */ + sched_trace_path_pop(rec); + sched_trace_member_failed(rec, 1, 1, -12); /* 16 */ + sched_trace_attempt_reasons(rec, &rc, 1, NULL, 0, NULL, 0); /* 17 */ + sched_trace_resreq_end(rec, 0, 0); /* 18 */ + sched_trace_path_pop(rec); + sched_trace_alt_result(rec, 0, 2); /* 19 */ + return 19; +} + +static void test_every_entry_kind_is_one_entry(void) +{ + xce_sched_trace_rec_t* rec = new_rec(XCE_TRACE_LINE_BOUND_DEFAULT); + const sched_trace_entry_t* e; + int seen[SCHED_TRACE_E_NTYPES]; + int n; + int t; + + TEST_ASSERT_NOT_NULL(rec); + memset(seen, 0, sizeof(seen)); + n = emit_every_kind(rec); + TEST_ASSERT_EQUAL_INT(n, rec->nentries); + TEST_ASSERT_EQUAL_INT(n, count_entries(rec)); + TEST_ASSERT_EQUAL_INT(0, rec->counters.omitted_lines); + TEST_ASSERT_EQUAL_INT(0, rec->depth); + TEST_ASSERT_EQUAL_INT(0, rec->resreq_open); + TEST_ASSERT_EQUAL_INT(0, rec->path.depth); + TEST_ASSERT_FALSE(rec->incomplete); + for (e = rec->entries; e != NULL; e = e->next) { + TEST_ASSERT_TRUE(e->type < SCHED_TRACE_E_NTYPES); + seen[e->type]++; + } + for (t = 0; t < SCHED_TRACE_E_NTYPES; t++) { + TEST_ASSERT_TRUE_MESSAGE(seen[t] > 0, "entry kind never stored"); + } + sched_trace_rec_free(rec); +} + +/* ---- line budget (9.1, 9.2) ------------------------------------------- */ + +/* Appends `k` entries (each alt_skipped call is one line). */ +static void emit_lines(xce_sched_trace_rec_t* rec, int k) +{ + int i; + + for (i = 1; i <= k; i++) { + sched_trace_alt_skipped(rec, i, k); + } +} + +static void check_budget(int bound, int k, int want_stored, int want_omitted) +{ + xce_sched_trace_rec_t* rec = new_rec(bound); + + TEST_ASSERT_NOT_NULL(rec); + emit_lines(rec, k); + TEST_ASSERT_EQUAL_INT(want_stored, rec->nentries); + TEST_ASSERT_EQUAL_INT(want_stored, count_entries(rec)); + TEST_ASSERT_EQUAL_INT(want_omitted, rec->counters.omitted_lines); + TEST_ASSERT_FALSE(rec->incomplete); + /* the stored ones are the first ones, in order */ + if (want_stored > 0) { + TEST_ASSERT_EQUAL_INT(1, rec->entries->u.alt.i); + TEST_ASSERT_EQUAL_INT(want_stored, rec->entries_tail->u.alt.i); + } + sched_trace_rec_free(rec); +} + +static void test_budget_edges(void) +{ + const int b = XCE_TRACE_LINE_BOUND_MIN; + + check_budget(b, b - 1, b - 1, 0); + check_budget(b, b, b, 0); + check_budget(b, b + 1, b, 1); + check_budget(b, b + 37, b, 37); + check_budget(XCE_TRACE_LINE_BOUND_DEFAULT, + XCE_TRACE_LINE_BOUND_DEFAULT - 1, + XCE_TRACE_LINE_BOUND_DEFAULT - 1, 0); + check_budget(XCE_TRACE_LINE_BOUND_DEFAULT, XCE_TRACE_LINE_BOUND_DEFAULT, + XCE_TRACE_LINE_BOUND_DEFAULT, 0); + check_budget(XCE_TRACE_LINE_BOUND_DEFAULT, + XCE_TRACE_LINE_BOUND_DEFAULT + 1, + XCE_TRACE_LINE_BOUND_DEFAULT, 1); +} + +/* Past the bound nothing more is stored or allocated, every kind counts + * one omitted line, and the builder state stays balanced. */ +static void test_budget_counts_every_kind_and_keeps_state(void) +{ + const int b = XCE_TRACE_LINE_BOUND_MIN; + xce_sched_trace_rec_t* rec = new_rec(b); + size_t bytes; + int n; + + TEST_ASSERT_NOT_NULL(rec); + emit_lines(rec, b); + TEST_ASSERT_EQUAL_INT(b, rec->nentries); + bytes = sched_trace_rec_bytes(rec); + + n = emit_every_kind(rec); + TEST_ASSERT_EQUAL_INT(b, rec->nentries); + TEST_ASSERT_EQUAL_INT(n, rec->counters.omitted_lines); + TEST_ASSERT_EQUAL_UINT(bytes, sched_trace_rec_bytes(rec)); + TEST_ASSERT_EQUAL_INT(0, rec->depth); + TEST_ASSERT_EQUAL_INT(0, rec->resreq_open); + TEST_ASSERT_EQUAL_INT(0, rec->path.depth); + TEST_ASSERT_NULL(rec->scope_w); + TEST_ASSERT_NULL(rec->scope_obj); + /* path push/pop and the scope-only calls are not lines */ + sched_trace_path_push(rec, XCE_TRACE_PATH_SINGLE, 1, 1); + sched_trace_check_begin(rec, NULL); + sched_trace_pos_scan_error(rec, -5); + sched_trace_path_pop(rec); + TEST_ASSERT_EQUAL_INT(n, rec->counters.omitted_lines); + sched_trace_rec_free(rec); +} + +/* An entry that straddles the bound: the begin is stored, its end is not, + * and the nesting level still returns to the top. */ +static void test_budget_cut_inside_a_scope(void) +{ + const int b = XCE_TRACE_LINE_BOUND_MIN; + xce_sched_trace_rec_t* rec = new_rec(b); + int s[1] = { 1 }; + xce_alloc_plan_t plan = make_plan(s, 1); + + TEST_ASSERT_NOT_NULL(rec); + emit_lines(rec, b - 1); + sched_trace_plan_begin(rec, &plan); /* the b-th line, stored */ + sched_trace_pos_begin(rec, 1, 1); /* omitted */ + sched_trace_pos_end(rec, XCE_TRACE_POS_UNFILLED, NULL); + sched_trace_plan_end(rec, 0, 1); + TEST_ASSERT_EQUAL_INT(b, rec->nentries); + TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_PLAN_BEGIN, rec->entries_tail->type); + TEST_ASSERT_EQUAL_INT(3, rec->counters.omitted_lines); + TEST_ASSERT_EQUAL_INT(0, rec->depth); + sched_trace_rec_free(rec); +} + int main(void) { UNITY_BEGIN(); @@ -428,5 +1258,19 @@ int main(void) RUN_TEST(test_rec_free_null_is_noop); RUN_TEST(test_finish_records_kind_and_error); RUN_TEST(test_finish_rejects_non_no_decision_kinds); + RUN_TEST(test_single_requirement_path_and_balance); + RUN_TEST(test_alternative_member_nesting_stores_full_path); + RUN_TEST(test_compound_container_path); + RUN_TEST(test_path_push_beyond_two_levels_stays_balanced); + RUN_TEST(test_requirement_level_entries); + RUN_TEST(test_attempt_reasons_empty_lists); + RUN_TEST(test_check_and_plan_entries); + RUN_TEST(test_check_failed_and_no_plan_reasons); + RUN_TEST(test_position_and_host_entries); + RUN_TEST(test_pos_end_scan_error_direct); + RUN_TEST(test_every_entry_kind_is_one_entry); + RUN_TEST(test_budget_edges); + RUN_TEST(test_budget_counts_every_kind_and_keeps_state); + RUN_TEST(test_budget_cut_inside_a_scope); return UNITY_END(); }