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) <noreply@anthropic.com>
This commit is contained in:
parent
383e2a90e4
commit
4f951f2b52
5 changed files with 1567 additions and 2 deletions
|
|
@ -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 <id>` 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 <stdio.h> (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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -26,6 +26,13 @@
|
|||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
||||
|
|
|
|||
|
|
@ -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 <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 <stdlib.h>
|
||||
|
|
@ -20,6 +28,7 @@
|
|||
|
||||
#include <unity/unity.h>
|
||||
|
||||
#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();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue