feat(scheduler-sim-decision-trace): capture shortfall and limit rows from the satisfaction check

Task 2.2: each checked row carries name, flags, value type, total,
in-use, reserved, requested and met (same comparison as unsatisfy_());
limit rows resolve through the scope's matched limits and never count as
shortfalls; the cluster-wide check keeps every row, a host only unmet
rows, which set its shortfall/limit verdict.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-10-08 09:35:00 +00:00
parent 90d417bb1b
commit 258267fa48
Signed by untrusted user: rshen
GPG key ID: 80533536B0EB651C
6 changed files with 977 additions and 38 deletions

View file

@ -157,8 +157,7 @@ typedef struct {
* 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 and host rows are added by the task that
* captures them (TraceCapture).
* budgeted lines.
*/
typedef enum {
SCHED_TRACE_E_RESREQ_BEGIN = 0, /* requirement named by the path */
@ -184,6 +183,8 @@ typedef enum {
SCHED_TRACE_E_POS_SUMMARY, /* summary level: the position end with
* its verdict counts (5.9) */
SCHED_TRACE_E_ROLLBACK, /* released hosts, merged (5.8) */
SCHED_TRACE_E_CHECK_ROW, /* cluster-wide check row (4.1, 4.2) */
SCHED_TRACE_E_HOST_ROW, /* unmet row under a host (5.4, 5.5) */
SCHED_TRACE_E_NTYPES
} sched_trace_entry_type_t;
@ -222,6 +223,32 @@ typedef struct {
const char* scope; /* arena copy */
} sched_trace_limit_ref_t;
/*
* One row of the satisfaction check (resource.c unsatisfy_()): one
* requested resource compared against its total. `met` is the check's own
* verdict, !(total < inuse + reserved + requested). A row whose resource
* has XCE_RESF_LIMIT is a limit row: `limit`, `scope` and `limited_res`
* name the limit, the scope instance it applies to ("u=..,q=..,p=..", with
* ",h=<host>" or "h=<host>" appended in a host scope) and the resource it
* limits (NULL when no matched limit names it). Every other row is a
* resource row, its limit fields NULL. Stored strings are arena copies.
*/
typedef struct {
const char* res; /* resource name; NULL when the index is unknown */
int idx; /* resource index */
int flags; /* XCE_RESF_* */
int valtype;
float total;
float inuse;
float reserved; /* provisionally reserved by earlier parts of
* this job's evaluation (uncommitted) */
float requested;
bool met;
const char* limit; /* limit rows only */
const char* scope; /* limit rows only */
const char* limited_res; /* limit rows only; NULL: unknown */
} sched_trace_row_t;
/* One requested resource of a single requirement or member (3.2). */
typedef struct {
const char* name; /* arena copy */
@ -335,6 +362,7 @@ typedef struct sched_trace_entry_s {
sched_trace_limit_tally_t* limits; /* hits per limit, in the
* order first hit */
} pos_summary;
sched_trace_row_t row; /* CHECK_ROW, HOST_ROW */
struct { /* ROLLBACK: consecutive released hosts, in call order */
int nhosts;
int cap;
@ -404,6 +432,13 @@ struct xce_sched_trace_rec_s {
bool scan_err_set;
unsigned host_row_verdicts; /* SHORTFALL/LIMIT from the current host's
* unmet rows (set by the row capture) */
bool check_open; /* between check_begin and check_end */
/* the current host's unmet rows (full level), held in builder memory
* (malloc'd, strings strdup'd) until its verdict line is stored, then
* stored after it; dropped when the host scope ends any other way */
sched_trace_row_t* host_rows;
int host_nrows;
int host_rows_cap;
/* plans of the current requirement's search (reset by resreq_begin
* and after search_end) */
int scope_tried;
@ -564,6 +599,17 @@ extern void sched_trace_host_verdict(xce_sched_trace_rec_t* rec,
extern void sched_trace_host_limit_hit(xce_sched_trace_rec_t* rec,
const char* limit, const char* scope);
/*
* One satisfaction check row (2.2), copied. In a host scope (between
* host_begin and host_verdict) it is a HOST_ROW, held until the host's
* verdict line and stored right after it, one level deeper; at summary
* level it is not stored at all. Otherwise it is a CHECK_ROW, stored now.
* Which rows to keep, and the host verdict bits they set, are the caller's
* (TraceCapture's) decision.
*/
extern void sched_trace_row(xce_sched_trace_rec_t* rec,
const sched_trace_row_t* row);
/* One host whose claim a failed plan released. Consecutive calls merge
* into one ROLLBACK entry, "released hosts: n01, n02" (5.8). */
extern void sched_trace_rollback_host(xce_sched_trace_rec_t* rec,
@ -602,6 +648,27 @@ extern void sched_trace_capture_selection(xce_sched_trace_rec_t* rec,
extern void sched_trace_capture_glimits(xce_sched_trace_rec_t* rec,
const xce_sched_obj_t* obj);
/*
* The satisfaction check's rows (4.1, 4.2, 5.4, 5.5), called from
* resource.c unsatisfy_() with its inputs: `nres` rows of resource index,
* total, in-use and requested, and the dense uncommitted vector keyed by
* resource index. Each row's resource is resolved through core->resmgr.
* A limit row (XCE_RESF_LIMIT) is resolved to its limit through the
* scope's matched limits: obj->glimits in the cluster-wide check scope,
* obj->hlimits[w->idx] in a host scope; without a match, the limit name
* and scope come from the resource's own name. The cluster-wide scope
* keeps every row. A host scope keeps only unmet rows: an unmet resource
* row sets the host's SHORTFALL verdict, an unmet limit row its LIMIT
* verdict (via sched_trace_host_limit_hit), never SHORTFALL. Outside both
* scopes nothing is recorded. `core` is only read.
*/
extern void sched_trace_check_rows(xce_sched_trace_rec_t* rec,
xce_sched_core_t* core, int nres,
const int* idxs, const float* total,
const float* inuse,
const xvec_t* uncomitted,
const float* wanted);
/* A failed compound attempt's reasons (3.5), from its pend-reason set before
* the scheduler replaces obj->reason: one ATTEMPT_REASONS entry with a
* count per pend-reason code (main reason, host and resource reasons, in

View file

@ -78,6 +78,20 @@ static void pos_limits_clear(xce_sched_trace_rec_t* rec)
rec->pos_nlimits = 0;
}
/* Drops the current host's held rows (keeps the array). */
static void host_rows_clear(xce_sched_trace_rec_t* rec)
{
int k;
for (k = 0; k < rec->host_nrows; k++) {
free((void*)rec->host_rows[k].res);
free((void*)rec->host_rows[k].limit);
free((void*)rec->host_rows[k].scope);
free((void*)rec->host_rows[k].limited_res);
}
rec->host_nrows = 0;
}
/* Starts the current position's tallies over. */
static void pos_tally_reset(xce_sched_trace_rec_t* rec)
{
@ -234,6 +248,8 @@ void sched_trace_rec_free(xce_sched_trace_rec_t* rec)
pos_limits_clear(rec);
free(rec->pos_limits);
host_rows_clear(rec);
free(rec->host_rows);
c = rec->arena;
while (c != NULL) {
@ -545,8 +561,9 @@ void sched_trace_check_begin(xce_sched_trace_rec_t* rec,
const xce_sched_obj_t* obj)
{
rollback_run_end(rec);
rec->scope_obj = obj;
rec->scope_w = NULL;
rec->scope_obj = obj;
rec->scope_w = NULL;
rec->check_open = true;
}
void sched_trace_check_end(xce_sched_trace_rec_t* rec, int rc,
@ -554,7 +571,8 @@ void sched_trace_check_end(xce_sched_trace_rec_t* rec, int rc,
{
sched_trace_entry_t* e;
rec->scope_obj = NULL;
rec->scope_obj = NULL;
rec->check_open = false;
e = entry_add(rec, SCHED_TRACE_E_CHECK_END);
if (e == NULL) {
@ -761,6 +779,7 @@ void sched_trace_host_begin(xce_sched_trace_rec_t* rec,
rec->scope_obj = obj;
rec->scope_w = w;
rec->host_row_verdicts = 0;
host_rows_clear(rec); /* a scope that ended without its verdict */
}
/* Counts one host evaluation: decision-wide per verdict kind, and at
@ -786,6 +805,103 @@ static void host_count(xce_sched_trace_rec_t* rec, unsigned verdicts)
}
}
/* Copies a row's strings into the arena (NULL stays NULL). */
static void row_to_arena(xce_sched_trace_rec_t* rec, sched_trace_row_t* dst,
const sched_trace_row_t* src)
{
*dst = *src;
dst->res = sched_trace_arena_strdup(rec, src->res);
dst->limit = sched_trace_arena_strdup(rec, src->limit);
dst->scope = sched_trace_arena_strdup(rec, src->scope);
dst->limited_res = sched_trace_arena_strdup(rec, src->limited_res);
}
/* Stores the current host's held rows after its verdict line, one level
* below it, then drops them. Each counts against the budget. */
static void host_rows_flush(xce_sched_trace_rec_t* rec)
{
int k;
for (k = 0; k < rec->host_nrows; k++) {
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_HOST_ROW);
if (e == NULL) {
continue;
}
e->depth++;
row_to_arena(rec, &e->u.row, &rec->host_rows[k]);
}
host_rows_clear(rec);
}
/* NULL-preserving heap copy for held rows; sets *failed on failure. */
static const char* row_strdup(const char* s, bool* failed)
{
char* d;
if (s == NULL) {
return NULL;
}
d = strdup(s);
if (d == NULL) {
*failed = true;
}
return d;
}
void sched_trace_row(xce_sched_trace_rec_t* rec, const sched_trace_row_t* row)
{
sched_trace_entry_t* e;
sched_trace_row_t* h;
bool failed = false;
rollback_run_end(rec);
if (row == NULL) {
return;
}
if (rec->scope_w == NULL) {
/* the cluster-wide check: stored now, before its check-end line */
e = entry_add(rec, SCHED_TRACE_E_CHECK_ROW);
if (e != NULL) {
row_to_arena(rec, &e->u.row, row);
}
return;
}
/* a host scope: no host lines at summary level; at full level the row
* waits for the host's verdict line, which it belongs under */
if ((rec->level == XCE_TRACE_LEVEL_SUMMARY) || rec->incomplete) {
return;
}
if (rec->host_nrows == rec->host_rows_cap) {
int cap = (rec->host_rows_cap > 0) ? rec->host_rows_cap * 2 : 4;
void* p = realloc(rec->host_rows, sizeof(*h) * (size_t)cap);
if (p == NULL) {
rec->incomplete = true; /* design "Error Strategy" */
return;
}
rec->host_rows = (sched_trace_row_t*)p;
rec->host_rows_cap = cap;
}
h = &rec->host_rows[rec->host_nrows];
*h = *row;
h->res = row_strdup(row->res, &failed);
h->limit = row_strdup(row->limit, &failed);
h->scope = row_strdup(row->scope, &failed);
h->limited_res = row_strdup(row->limited_res, &failed);
if (failed) {
free((void*)h->res);
free((void*)h->limit);
free((void*)h->scope);
free((void*)h->limited_res);
rec->incomplete = true;
return;
}
rec->host_nrows++;
}
void sched_trace_host_verdict(xce_sched_trace_rec_t* rec, unsigned verdicts,
int err)
{
@ -820,17 +936,19 @@ void sched_trace_host_verdict(xce_sched_trace_rec_t* rec, unsigned verdicts,
rollback_run_end(rec);
if (rec->level == XCE_TRACE_LEVEL_SUMMARY) {
host_rows_clear(rec);
return; /* tallied for the position summary, no host line */
}
e = entry_add(rec, SCHED_TRACE_E_HOST);
if (e == NULL) {
return;
if (e != NULL) {
e->u.host.host = sched_trace_arena_strdup(rec, name);
e->u.host.verdicts = verdicts;
e->u.host.phase = phase;
e->u.host.err = err;
}
e->u.host.host = sched_trace_arena_strdup(rec, name);
e->u.host.verdicts = verdicts;
e->u.host.phase = phase;
e->u.host.err = err;
/* the host's unmet rows, under its verdict line (5.4, 5.5) */
host_rows_flush(rec);
}
void sched_trace_commit(xce_sched_trace_rec_t* rec)

View file

@ -15,9 +15,11 @@
* formatting them is the renderer's job.
*
* Task 2.1: selection facts (2.2), cluster-wide limits (2.3) and attempt
* failure reasons (3.5). These read struct fields only and call no
* scheduler function, so every white-box test that #includes this file
* links without extra sources.
* failure reasons (3.5). Task 2.2: the satisfaction check's rows (4.1,
* 4.2, 5.4, 5.5). These read struct fields and call no scheduler function
* (the row capture resolves resources with librtti's vget(), which every
* scheduler target links), so every white-box test that #includes this
* file links without extra sources.
*/
#include <stdlib.h>
#include <string.h>
@ -200,6 +202,202 @@ void sched_trace_capture_glimits(xce_sched_trace_rec_t* rec,
}
}
/* ---- satisfaction check rows (4.1, 4.2, 5.4, 5.5) --------------------- */
/* The uncommitted amount of resource index `idx`: the same read as
* resource.c uncomit_at_() (the vector is dense, keyed by resource index;
* past its end or NULL means nothing is uncommitted). */
static float row_uncomit_at(const xvec_t* uncomitted, int idx)
{
if ((uncomitted == NULL) || (uncomitted->data == NULL) || (idx < 0)
|| (idx >= uncomitted->count)) {
return 0.0f;
}
return ((const float*)uncomitted->data)[idx];
}
/* The current scope's matched limit list: obj->glimits in the cluster-wide
* check, obj->hlimits[w->idx] in a host scope (sparse: NULL when the
* worker has none). Read directly: zarray_get()/xlist_get() take
* non-const containers. */
static const xlist_t* scope_limits(const xce_sched_trace_rec_t* rec)
{
const zarray_t* hl;
int idx;
if (rec->scope_obj == NULL) {
return NULL;
}
if (rec->scope_w == NULL) {
return &rec->scope_obj->glimits;
}
hl = &rec->scope_obj->hlimits;
idx = rec->scope_w->idx;
if ((hl->data == NULL) || (idx < 0) || (idx >= hl->caps)) {
return NULL;
}
return (const xlist_t*)hl->data[idx];
}
/* The matched limit whose vres resource is `r`, or NULL. */
static const xce_vres_t* scope_vres(const xce_sched_trace_rec_t* rec,
const xce_resource_t* r)
{
const xlist_t* list = scope_limits(rec);
int i;
if ((list == NULL) || (list->data == NULL)) {
return NULL;
}
for (i = 0; i < list->count; i++) {
const xce_vres_t* v = (const xce_vres_t*)list->data[i];
if ((v == NULL) || (v->vres == NULL)) {
continue;
}
if ((v->vres == r)
|| ((v->vres->name != NULL) && (r->name != NULL)
&& (strcmp(v->vres->name, r->name) == 0))) {
return v;
}
}
return NULL;
}
/*
* Fills a limit row's limit name, scope instance and limited resource for
* the limit vres resource `r`, composing the strings in a scratch buffer
* returned through *buf (the caller frees it once the model has copied the
* row). Returns false on allocation failure.
*/
static bool limit_row(const xce_sched_trace_rec_t* rec, const xce_resource_t* r,
sched_trace_row_t* row, char** buf)
{
const xce_vres_t* v = scope_vres(rec, r);
const char* known = NULL;
const char* host = NULL;
const char* name;
size_t name_len;
const char* scope;
size_t scope_len;
size_t host_len = 0;
char* p;
size_t k;
if ((v != NULL) && (v->limit != NULL)) {
known = v->limit->name;
}
if (!split_vres_name(r->name, known, &name, &name_len, &scope)) {
/* not a "limit=" name: the resource names the limit itself */
name = (known != NULL) ? known : r->name;
name_len = (name != NULL) ? strlen(name) : 0;
scope = "";
} else if (known != NULL) {
name = known;
name_len = strlen(known);
}
if (rec->scope_w != NULL) {
/* host-limit vres names do not encode the host */
host = (rec->scope_w->name != NULL) ? rec->scope_w->name : "";
host_len = strlen(host);
}
scope_len = strlen(scope);
/* "<name>\0<scope>[,]h=<host>\0" */
*buf = (char*)malloc(name_len + scope_len + host_len + 5);
if (*buf == NULL) {
return false;
}
p = *buf;
memcpy(p, (name != NULL) ? name : "", name_len);
p[name_len] = '\0';
row->limit = p;
p += name_len + 1;
row->scope = p;
for (k = 0; k < scope_len; k++) {
*p++ = (scope[k] == '/') ? ',' : scope[k];
}
if (host != NULL) {
if (scope_len > 0) {
*p++ = ',';
}
memcpy(p, "h=", 2);
p += 2;
memcpy(p, host, host_len);
p += host_len;
}
*p = '\0';
row->limited_res = NULL;
if ((v != NULL) && (v->res != NULL)) {
row->limited_res = v->res->name;
}
return true;
}
void sched_trace_check_rows(xce_sched_trace_rec_t* rec, xce_sched_core_t* core,
int nres, const int* idxs, const float* total,
const float* inuse, const xvec_t* uncomitted,
const float* wanted)
{
bool host_scope = (rec->scope_w != NULL);
int i;
/* a row belongs to the cluster-wide check or to one host's check;
* outside both there is nothing to attribute it to */
if ((!host_scope && !rec->check_open) || (nres <= 0) || (idxs == NULL)
|| (total == NULL) || (inuse == NULL) || (wanted == NULL)) {
return;
}
for (i = 0; i < nres; i++) {
xce_resource_t* r = NULL;
sched_trace_row_t row;
char* buf = NULL;
float uncomit = row_uncomit_at(uncomitted, idxs[i]);
memset(&row, 0, sizeof(row));
row.idx = idxs[i];
row.total = total[i];
row.inuse = inuse[i];
row.reserved = uncomit;
row.requested = wanted[i];
/* the check's own comparison, resource.c unsatisfy_() */
row.met = !(total[i] < inuse[i] + uncomit + wanted[i]);
/* a host scope keeps only the rows that rejected the host */
if (host_scope && row.met) {
continue;
}
if ((core != NULL) && (idxs[i] >= 0)
&& (vget(core->resmgr, idxs[i], (void**)&r) < 0)) {
r = NULL;
}
if (r != NULL) {
row.res = r->name;
row.flags = r->flags;
row.valtype = r->valtype;
}
if ((r != NULL) && ((r->flags & XCE_RESF_LIMIT) != 0)) {
if (!limit_row(rec, r, &row, &buf)) {
rec->incomplete = true; /* design "Error Strategy" */
return;
}
/* a limit rejection, never a shortfall (4.2, 5.5) */
if (host_scope) {
sched_trace_host_limit_hit(rec, row.limit, row.scope);
}
} else if (host_scope) {
rec->host_row_verdicts |= XCE_TRACE_V_SHORTFALL; /* 5.4 */
}
sched_trace_row(rec, &row);
free(buf);
}
}
/* ---- attempt failure reasons (3.5) ------------------------------------ */
static void count_code(sched_trace_reason_count_t* codes, int* ncodes,

View file

@ -13,8 +13,22 @@
* structures afterwards leaves the record unchanged (design "Invariants":
* a record never references scheduler-owned memory).
*
* Task 2.2 covers the satisfaction check's rows (Requirements 4.1, 4.2,
* 5.4, 5.5), over a fixture core whose resmgr registers plain resources and
* limit vres resources, with a cluster-wide limit in the schedobj's
* glimits and a host-scoped limit in its hlimits for the test worker:
* - every row copies the resource name, flags, value type, total,
* in-use, provisionally reserved, requested and met;
* - a row whose resource has XCE_RESF_LIMIT is a limit row, resolved
* through the scope's matched limits to (limit name, scope instance,
* limited resource), "h=<host>" appended in a host scope; it is never
* reported as a shortfall;
* - the cluster-wide scope keeps every row; a host scope keeps only the
* unmet rows, stored after the host's verdict line, and they set its
* SHORTFALL and/or LIMIT verdict.
*
* Links src/sched_trace.c and src/sched_trace_capture.c, plus libxce for
* the pend-reason helpers.
* the pend-reason, resmgr and container helpers.
*/
#include <stdio.h>
#include <stdlib.h>
@ -23,9 +37,13 @@
#include <unity/unity.h>
#include "core.h"
#include "kvmgr.h"
#include "preason.h"
#include "resobj.h"
#include "sched/trace.h"
#include "xlist.h"
#include "xstring.h"
#include "zarray.h"
void setUp(void) { return; }
void tearDown(void) { return; }
@ -486,8 +504,528 @@ static void test_attempt_reasons_counts_against_the_budget(void)
sched_trace_rec_free(rec);
}
/* ---- satisfaction check rows (4.1, 4.2, 5.4, 5.5) -------------------- */
typedef struct {
xce_sched_core_t core;
xce_sched_obj_t obj;
xce_sched_worker_t w;
xlist_t hl; /* the worker's matched host limits */
/* registered resources (resmgr copies) */
xce_resource_t* mem;
xce_resource_t* lic;
xce_resource_t* slots;
xce_resource_t* lim_lic; /* cluster-wide limit vres, scoped */
xce_resource_t* lim_orphan; /* limit vres matched by no limit */
xce_resource_t* lim_host; /* host limit vres, scoped */
xce_resource_t* lim_hcap; /* host limit vres, no consumer scope */
xce_limit_t limit_lic;
xce_limit_t limit_host;
xce_limit_t limit_hcap;
xce_vres_t vres_lic;
xce_vres_t vres_host;
xce_vres_t vres_hcap;
} rows_fx_t;
#define ROWS_HOST_IDX 2
static xce_resource_t* reg_res(var resmgr, const char* name, int flags,
int valtype)
{
xce_resource_t res;
xce_resource_t* r;
memset(&res, 0, sizeof(res));
res.name = (char*)name;
res.flags = flags;
res.valtype = valtype;
r = makeResource(resmgr, &res);
TEST_ASSERT_NOT_NULL(r);
return r;
}
static rows_fx_t* rows_fx_new(void)
{
kvmgr_params_t res_params = { .key_type = KVMGR_KEY_STRING,
.nslots = 16,
.delta = 8,
.datakiller = NULL };
rows_fx_t* fx = (rows_fx_t*)calloc(1, sizeof(*fx));
TEST_ASSERT_NOT_NULL(fx);
fx->core.resmgr = makeInstance(&xkvmgr_type, &res_params);
TEST_ASSERT_NOT_NULL(fx->core.resmgr);
fx->mem = reg_res(fx->core.resmgr, "mem", XCE_RESF_CONSUMABLE, 1);
fx->lic = reg_res(fx->core.resmgr, "lic",
XCE_RESF_SHARED | XCE_RESF_CONSUMABLE, 1);
fx->slots = reg_res(fx->core.resmgr, "slots", XCE_RESF_CONSUMABLE, 2);
fx->lim_lic = reg_res(fx->core.resmgr, "limit=lic_cap/u=alice/q=normal",
XCE_RESF_LIMIT | XCE_RESF_SHARED, 1);
fx->lim_orphan = reg_res(fx->core.resmgr, "limit=site_total/p=proj1",
XCE_RESF_LIMIT | XCE_RESF_SHARED, 1);
fx->lim_host = reg_res(fx->core.resmgr, "limit=host_slots/u=alice",
XCE_RESF_LIMIT, 2);
fx->lim_hcap = reg_res(fx->core.resmgr, "limit=hcap", XCE_RESF_LIMIT,
2);
fx->limit_lic.name = "lic_cap";
fx->limit_host.name = "host_slots";
fx->limit_hcap.name = "hcap";
fx->vres_lic.vres = fx->lim_lic;
fx->vres_lic.res = fx->lic;
fx->vres_lic.limit = &fx->limit_lic;
fx->vres_host.vres = fx->lim_host;
fx->vres_host.res = fx->slots;
fx->vres_host.limit = &fx->limit_host;
fx->vres_hcap.vres = fx->lim_hcap;
fx->vres_hcap.res = fx->slots;
fx->vres_hcap.limit = &fx->limit_hcap;
xlist_init(&fx->obj.glimits, 4);
xlist_store(&fx->obj.glimits, &fx->vres_lic);
xlist_init(&fx->hl, 4);
xlist_store(&fx->hl, &fx->vres_host);
xlist_store(&fx->hl, &fx->vres_hcap);
zarray_init(&fx->obj.hlimits, 8, NULL);
TEST_ASSERT_EQUAL_INT(0, zarray_put(&fx->obj.hlimits, ROWS_HOST_IDX,
&fx->hl));
fx->w.name = "n01";
fx->w.idx = ROWS_HOST_IDX;
return fx;
}
static void rows_fx_free(rows_fx_t* fx)
{
zarray_fini(&fx->obj.hlimits);
xlist_fini(&fx->hl);
xlist_fini(&fx->obj.glimits);
free(fx);
}
/* A dense uncommitted vector keyed by resource index (as schedobj.c fills
* the context's), all zero except `idx` = `val`. */
static void make_uncomit(xvec_t* v, float* buf, int n, int idx, float val)
{
memset(buf, 0, sizeof(float) * (size_t)n);
if ((idx >= 0) && (idx < n)) {
buf[idx] = val;
}
v->count = n;
v->elsize = sizeof(float);
v->data = buf;
}
static sched_trace_entry_t* entry_n(xce_sched_trace_rec_t* rec, int k)
{
sched_trace_entry_t* e = rec->entries;
while ((e != NULL) && (k-- > 0)) {
e = e->next;
}
TEST_ASSERT_NOT_NULL(e);
return e;
}
static void assert_resource_row(const sched_trace_entry_t* e, int type,
const char* res, int flags, int valtype,
float total, float inuse, float reserved,
float requested, bool met)
{
TEST_ASSERT_EQUAL_INT(type, e->type);
TEST_ASSERT_EQUAL_STRING(res, e->u.row.res);
TEST_ASSERT_EQUAL_INT(flags, e->u.row.flags);
TEST_ASSERT_EQUAL_INT(valtype, e->u.row.valtype);
TEST_ASSERT_EQUAL_FLOAT(total, e->u.row.total);
TEST_ASSERT_EQUAL_FLOAT(inuse, e->u.row.inuse);
TEST_ASSERT_EQUAL_FLOAT(reserved, e->u.row.reserved);
TEST_ASSERT_EQUAL_FLOAT(requested, e->u.row.requested);
TEST_ASSERT_EQUAL(met, e->u.row.met);
}
static void assert_limit_row(const sched_trace_entry_t* e, const char* limit,
const char* scope, const char* limited)
{
TEST_ASSERT_TRUE((e->u.row.flags & XCE_RESF_LIMIT) != 0);
TEST_ASSERT_EQUAL_STRING(limit, e->u.row.limit);
TEST_ASSERT_EQUAL_STRING(scope, e->u.row.scope);
if (limited == NULL) {
TEST_ASSERT_NULL(e->u.row.limited_res);
} else {
TEST_ASSERT_EQUAL_STRING(limited, e->u.row.limited_res);
}
}
/* Cluster-wide scope: every row is kept, met or not, and limit rows are
* resolved through obj->glimits (4.1, 4.2). */
static void test_check_rows_cluster_scope_keeps_every_row(void)
{
rows_fx_t* fx = rows_fx_new();
xce_sched_trace_ctl_t ctl = make_ctl("fcfs");
xce_sched_trace_rec_t* rec;
float ubuf[16];
xvec_t uncomit;
int idxs[4] = { fx->mem->idx, fx->lic->idx, fx->lim_lic->idx,
fx->lim_orphan->idx };
float total[4] = { 64, 10, 5, 3 };
float inuse[4] = { 60, 9, 4, 0 };
float wanted[4] = { 8, 1, 2, 1 };
sched_trace_entry_t* e;
int k;
make_uncomit(&uncomit, ubuf, 16, fx->lim_lic->idx, 1);
rec = sched_trace_rec_new(&ctl, 0x77, XCE_TRACE_REC_DECISION);
sched_trace_check_begin(rec, &fx->obj);
sched_trace_check_rows(rec, &fx->core, 4, idxs, total, inuse, &uncomit,
wanted);
sched_trace_check_end(rec, 0, false);
TEST_ASSERT_EQUAL_INT(5, rec->nentries);
/* mem: 64 < 60 + 0 + 8: not met, a resource row */
e = entry_n(rec, 0);
assert_resource_row(e, SCHED_TRACE_E_CHECK_ROW, "mem", XCE_RESF_CONSUMABLE,
1, 64, 60, 0, 8, false);
TEST_ASSERT_NULL(e->u.row.limit);
TEST_ASSERT_EQUAL_INT(fx->mem->idx, e->u.row.idx);
/* lic: 10 < 9 + 0 + 1 is false: met, kept anyway */
e = entry_n(rec, 1);
assert_resource_row(e, SCHED_TRACE_E_CHECK_ROW, "lic",
XCE_RESF_SHARED | XCE_RESF_CONSUMABLE, 1, 10, 9, 0, 1,
true);
TEST_ASSERT_NULL(e->u.row.limit);
/* the cluster-wide limit: 5 < 4 + 1 + 2, reserved from the dense
* vector at its own index; a limit row, not a shortfall */
e = entry_n(rec, 2);
assert_resource_row(e, SCHED_TRACE_E_CHECK_ROW,
"limit=lic_cap/u=alice/q=normal",
XCE_RESF_LIMIT | XCE_RESF_SHARED, 1, 5, 4, 1, 2, false);
assert_limit_row(e, "lic_cap", "u=alice,q=normal", "lic");
/* a limit vres in no matched limit: name and scope parsed from its
* resource name, limited resource unknown */
e = entry_n(rec, 3);
assert_resource_row(e, SCHED_TRACE_E_CHECK_ROW, "limit=site_total/p=proj1",
XCE_RESF_LIMIT | XCE_RESF_SHARED, 1, 3, 0, 0, 1, true);
assert_limit_row(e, "site_total", "p=proj1", NULL);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_CHECK_END, entry_n(rec, 4)->type);
/* rows sit at the check's level, with the check-end line */
TEST_ASSERT_EQUAL_INT(entry_n(rec, 4)->depth, entry_n(rec, 0)->depth);
/* no host verdict and no limit tally from the cluster-wide check */
TEST_ASSERT_EQUAL_UINT(0, rec->host_row_verdicts);
TEST_ASSERT_EQUAL_INT(0, rec->pos_nlimits);
/* copies: the record survives its sources */
for (k = 0; k < 4; k++) {
e = entry_n(rec, k);
TEST_ASSERT_TRUE(in_arena(rec, e->u.row.res));
if (e->u.row.limit != NULL) {
TEST_ASSERT_TRUE(in_arena(rec, e->u.row.limit));
TEST_ASSERT_TRUE(in_arena(rec, e->u.row.scope));
}
}
TEST_ASSERT_TRUE(in_arena(rec, entry_n(rec, 2)->u.row.limited_res));
total[0] = 1;
inuse[0] = 1;
ubuf[fx->lim_lic->idx] = 9;
TEST_ASSERT_EQUAL_FLOAT(64, entry_n(rec, 0)->u.row.total);
TEST_ASSERT_EQUAL_FLOAT(1, entry_n(rec, 2)->u.row.reserved);
rows_fx_free(fx);
TEST_ASSERT_EQUAL_STRING("lic_cap", entry_n(rec, 2)->u.row.limit);
TEST_ASSERT_FALSE(rec->incomplete);
sched_trace_rec_free(rec);
}
/* Host scope: only unmet rows are kept, after the host's verdict line,
* one level below it; the limit row resolves through obj->hlimits with
* the host appended to its scope, and sets LIMIT, never SHORTFALL (5.4,
* 5.5). */
static void test_check_rows_host_scope_keeps_unmet_rows(void)
{
rows_fx_t* fx = rows_fx_new();
xce_sched_trace_ctl_t ctl = make_ctl("fcfs");
xce_sched_trace_rec_t* rec;
float ubuf[16];
xvec_t uncomit;
int idxs[4] = { fx->mem->idx, fx->slots->idx, fx->lim_host->idx,
fx->lim_hcap->idx };
float total[4] = { 64, 8, 10, 100 };
float inuse[4] = { 60, 2, 9, 3 };
float wanted[4] = { 8, 4, 2, 4 };
sched_trace_entry_t* e;
make_uncomit(&uncomit, ubuf, 16, fx->mem->idx, 0.5f);
rec = sched_trace_rec_new(&ctl, 0x77, XCE_TRACE_REC_DECISION);
sched_trace_host_begin(rec, &fx->obj, &fx->w);
sched_trace_check_rows(rec, &fx->core, 4, idxs, total, inuse, &uncomit,
wanted);
/* the rows wait for the verdict: nothing stored yet */
TEST_ASSERT_EQUAL_INT(0, rec->nentries);
TEST_ASSERT_EQUAL_UINT(XCE_TRACE_V_SHORTFALL | XCE_TRACE_V_LIMIT,
rec->host_row_verdicts);
sched_trace_host_verdict(rec, 0, 0);
TEST_ASSERT_EQUAL_INT(3, rec->nentries);
e = entry_n(rec, 0);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_HOST, e->type);
TEST_ASSERT_EQUAL_STRING("n01", 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);
/* mem: 64 < 60 + 0.5 + 8 */
e = entry_n(rec, 1);
assert_resource_row(e, SCHED_TRACE_E_HOST_ROW, "mem", XCE_RESF_CONSUMABLE,
1, 64, 60, 0.5f, 8, false);
TEST_ASSERT_NULL(e->u.row.limit);
TEST_ASSERT_EQUAL_INT(entry_n(rec, 0)->depth + 1, e->depth);
/* slots (met) and hcap (met) are not kept; host_slots is */
e = entry_n(rec, 2);
assert_resource_row(e, SCHED_TRACE_E_HOST_ROW, "limit=host_slots/u=alice",
XCE_RESF_LIMIT, 2, 10, 9, 0, 2, false);
assert_limit_row(e, "host_slots", "u=alice,h=n01", "slots");
TEST_ASSERT_EQUAL_INT(entry_n(rec, 0)->depth + 1, e->depth);
TEST_ASSERT_TRUE(in_arena(rec, e->u.row.res));
TEST_ASSERT_TRUE(in_arena(rec, e->u.row.scope));
TEST_ASSERT_TRUE(in_arena(rec, e->u.row.limited_res));
/* one evaluation, counted under each kind it matched */
TEST_ASSERT_EQUAL_INT(1, rec->counters.host_evals[2]); /* SHORTFALL */
TEST_ASSERT_EQUAL_INT(1, rec->counters.host_evals[3]); /* LIMIT */
rows_fx_free(fx);
TEST_ASSERT_EQUAL_STRING("u=alice,h=n01", entry_n(rec, 2)->u.row.scope);
sched_trace_rec_free(rec);
}
/* A host whose only unmet row is a limit is a limit rejection alone: the
* limit is never also reported as a resource shortfall (5.5). A limit with
* no consumer scope gets the host as its whole scope. */
static void test_check_rows_host_limit_only_is_not_a_shortfall(void)
{
rows_fx_t* fx = rows_fx_new();
xce_sched_trace_ctl_t ctl = make_ctl("fcfs");
xce_sched_trace_rec_t* rec;
int idxs[2] = { fx->slots->idx, fx->lim_hcap->idx };
float total[2] = { 8, 4 };
float inuse[2] = { 2, 3 };
float wanted[2] = { 4, 4 };
sched_trace_entry_t* e;
rec = sched_trace_rec_new(&ctl, 0x77, XCE_TRACE_REC_DECISION);
sched_trace_host_begin(rec, &fx->obj, &fx->w);
sched_trace_check_rows(rec, &fx->core, 2, idxs, total, inuse, NULL,
wanted);
sched_trace_host_verdict(rec, 0, 0);
TEST_ASSERT_EQUAL_INT(2, rec->nentries);
TEST_ASSERT_EQUAL_UINT(XCE_TRACE_V_LIMIT, entry_n(rec, 0)->u.host.verdicts);
e = entry_n(rec, 1);
assert_resource_row(e, SCHED_TRACE_E_HOST_ROW, "limit=hcap",
XCE_RESF_LIMIT, 2, 4, 3, 0, 4, false);
assert_limit_row(e, "hcap", "h=n01", "slots");
TEST_ASSERT_EQUAL_INT(0, rec->counters.host_evals[2]); /* SHORTFALL */
TEST_ASSERT_EQUAL_INT(1, rec->counters.host_evals[3]); /* LIMIT */
sched_trace_rec_free(rec);
/* a shortfall alone is a shortfall alone */
{
int i2[1] = { fx->mem->idx };
float t2[1] = { 4 };
float u2[1] = { 3 };
float w2[1] = { 2 };
rec = sched_trace_rec_new(&ctl, 0x77, XCE_TRACE_REC_DECISION);
sched_trace_host_begin(rec, &fx->obj, &fx->w);
sched_trace_check_rows(rec, &fx->core, 1, i2, t2, u2, NULL, w2);
sched_trace_host_verdict(rec, 0, 0);
TEST_ASSERT_EQUAL_INT(2, rec->nentries);
TEST_ASSERT_EQUAL_UINT(XCE_TRACE_V_SHORTFALL,
entry_n(rec, 0)->u.host.verdicts);
TEST_ASSERT_NULL(entry_n(rec, 1)->u.row.limit);
sched_trace_rec_free(rec);
}
rows_fx_free(fx);
}
/* Every row met: nothing kept, and the verdict falls back to the per-host
* check's own selection branch (the model's "from rows" rule). */
static void test_check_rows_host_all_met_keeps_nothing(void)
{
rows_fx_t* fx = rows_fx_new();
xce_sched_trace_ctl_t ctl = make_ctl("fcfs");
xce_sched_trace_rec_t* rec;
int idxs[2] = { fx->mem->idx, fx->lim_host->idx };
float total[2] = { 64, 10 };
float inuse[2] = { 0, 0 };
float wanted[2] = { 8, 2 };
rec = sched_trace_rec_new(&ctl, 0x77, XCE_TRACE_REC_DECISION);
sched_trace_host_begin(rec, &fx->obj, &fx->w);
sched_trace_check_rows(rec, &fx->core, 2, idxs, total, inuse, NULL,
wanted);
TEST_ASSERT_EQUAL_UINT(0, rec->host_row_verdicts);
sched_trace_host_verdict(rec, XCE_TRACE_V_ACCEPTED, 0);
TEST_ASSERT_EQUAL_INT(1, rec->nentries);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_HOST, entry_n(rec, 0)->type);
sched_trace_rec_free(rec);
rows_fx_free(fx);
}
/* A worker with no matched host limits: the limit row still resolves its
* name and scope from the vres resource name, host appended. */
static void test_check_rows_host_limit_without_matched_limits(void)
{
rows_fx_t* fx = rows_fx_new();
xce_sched_trace_ctl_t ctl = make_ctl("fcfs");
xce_sched_worker_t w2;
xce_sched_trace_rec_t* rec;
int idxs[1] = { fx->lim_host->idx };
float total[1] = { 1 };
float inuse[1] = { 1 };
float wanted[1] = { 1 };
sched_trace_entry_t* e;
memset(&w2, 0, sizeof(w2));
w2.name = "n77";
w2.idx = 7; /* no hlimits list at this index */
rec = sched_trace_rec_new(&ctl, 0x77, XCE_TRACE_REC_DECISION);
sched_trace_host_begin(rec, &fx->obj, &w2);
sched_trace_check_rows(rec, &fx->core, 1, idxs, total, inuse, NULL,
wanted);
sched_trace_host_verdict(rec, 0, 0);
TEST_ASSERT_EQUAL_INT(2, rec->nentries);
TEST_ASSERT_EQUAL_UINT(XCE_TRACE_V_LIMIT, entry_n(rec, 0)->u.host.verdicts);
e = entry_n(rec, 1);
assert_limit_row(e, "host_slots", "u=alice,h=n77", NULL);
sched_trace_rec_free(rec);
rows_fx_free(fx);
}
/* Summary level: no host lines and no rows, but each limit a host hit is
* tallied with its scope instance for the position summary (5.9). */
static void test_check_rows_host_summary_tallies_limit(void)
{
rows_fx_t* fx = rows_fx_new();
xce_sched_trace_ctl_t ctl = make_ctl("fcfs");
xce_sched_trace_rec_t* rec;
int idxs[2] = { fx->mem->idx, fx->lim_host->idx };
float total[2] = { 64, 10 };
float inuse[2] = { 60, 9 };
float wanted[2] = { 8, 2 };
sched_trace_entry_t* e;
ctl.level = XCE_TRACE_LEVEL_SUMMARY;
rec = sched_trace_rec_new(&ctl, 0x77, XCE_TRACE_REC_DECISION);
sched_trace_pos_begin(rec, 1, 1);
sched_trace_host_begin(rec, &fx->obj, &fx->w);
sched_trace_check_rows(rec, &fx->core, 2, idxs, total, inuse, NULL,
wanted);
sched_trace_host_verdict(rec, 0, 0);
sched_trace_pos_end(rec, XCE_TRACE_POS_UNFILLED, NULL);
TEST_ASSERT_EQUAL_INT(2, rec->nentries);
e = entry_n(rec, 1);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_POS_SUMMARY, e->type);
TEST_ASSERT_EQUAL_INT(1, e->u.pos_summary.counts[2]); /* SHORTFALL */
TEST_ASSERT_EQUAL_INT(1, e->u.pos_summary.counts[3]); /* LIMIT */
TEST_ASSERT_EQUAL_INT(1, e->u.pos_summary.nlimits);
TEST_ASSERT_EQUAL_STRING("host_slots", e->u.pos_summary.limits[0].limit);
TEST_ASSERT_EQUAL_STRING("u=alice,h=n01", e->u.pos_summary.limits[0].scope);
TEST_ASSERT_EQUAL_INT(1, e->u.pos_summary.limits[0].count);
sched_trace_rec_free(rec);
rows_fx_free(fx);
}
/* Rows outside any check scope are not attributed to anything; a resource
* index the resmgr does not know is kept as an unnamed resource row. */
static void test_check_rows_scope_edges(void)
{
rows_fx_t* fx = rows_fx_new();
xce_sched_trace_ctl_t ctl = make_ctl("fcfs");
xce_sched_trace_rec_t* rec;
int idxs[1] = { fx->mem->idx };
int unknown[1] = { 99 };
float total[1] = { 1 };
float inuse[1] = { 1 };
float wanted[1] = { 1 };
sched_trace_entry_t* e;
rec = sched_trace_rec_new(&ctl, 0x77, XCE_TRACE_REC_DECISION);
sched_trace_check_rows(rec, &fx->core, 1, idxs, total, inuse, NULL,
wanted);
TEST_ASSERT_EQUAL_INT(0, rec->nentries);
TEST_ASSERT_EQUAL_UINT(0, rec->host_row_verdicts);
sched_trace_check_begin(rec, &fx->obj);
sched_trace_check_end(rec, 1, true);
sched_trace_check_rows(rec, &fx->core, 1, idxs, total, inuse, NULL,
wanted);
TEST_ASSERT_EQUAL_INT(1, rec->nentries); /* the check end only */
sched_trace_check_begin(rec, &fx->obj);
sched_trace_check_rows(rec, &fx->core, 1, unknown, total, inuse, NULL,
wanted);
TEST_ASSERT_EQUAL_INT(2, rec->nentries);
e = entry_n(rec, 1);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_CHECK_ROW, e->type);
TEST_ASSERT_NULL(e->u.row.res);
TEST_ASSERT_EQUAL_INT(99, e->u.row.idx);
TEST_ASSERT_EQUAL_INT(0, e->u.row.flags);
TEST_ASSERT_NULL(e->u.row.limit);
TEST_ASSERT_FALSE(e->u.row.met);
sched_trace_check_end(rec, 0, false);
sched_trace_rec_free(rec);
rows_fx_free(fx);
}
/* Host rows count against the line budget like every other line. */
static void test_check_rows_host_rows_count_against_budget(void)
{
rows_fx_t* fx = rows_fx_new();
xce_sched_trace_ctl_t ctl = make_ctl("fcfs");
xce_sched_trace_rec_t* rec;
int idxs[2] = { fx->mem->idx, fx->lim_host->idx };
float total[2] = { 1, 1 };
float inuse[2] = { 1, 1 };
float wanted[2] = { 1, 1 };
int i;
ctl.line_bound = XCE_TRACE_LINE_BOUND_MIN;
rec = sched_trace_rec_new(&ctl, 0x77, XCE_TRACE_REC_DECISION);
for (i = 0; i < XCE_TRACE_LINE_BOUND_MIN - 2; i++) {
sched_trace_alt_skipped(rec, 1, 1);
}
sched_trace_host_begin(rec, &fx->obj, &fx->w);
sched_trace_check_rows(rec, &fx->core, 2, idxs, total, inuse, NULL,
wanted);
sched_trace_host_verdict(rec, 0, 0);
TEST_ASSERT_EQUAL_INT(XCE_TRACE_LINE_BOUND_MIN, rec->nentries);
TEST_ASSERT_EQUAL_INT(1, rec->counters.omitted_lines);
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_E_HOST_ROW, rec->entries_tail->type);
TEST_ASSERT_EQUAL_STRING("mem", rec->entries_tail->u.row.res);
/* a host scope that ends without its verdict leaves no rows behind */
sched_trace_rec_free(rec);
ctl = make_ctl("fcfs");
rec = sched_trace_rec_new(&ctl, 0x77, XCE_TRACE_REC_DECISION);
sched_trace_host_begin(rec, &fx->obj, &fx->w);
sched_trace_check_rows(rec, &fx->core, 2, idxs, total, inuse, NULL,
wanted);
sched_trace_host_begin(rec, &fx->obj, &fx->w);
sched_trace_host_verdict(rec, XCE_TRACE_V_CLAIMED, 0);
TEST_ASSERT_EQUAL_INT(1, rec->nentries);
/* and a record freed with rows still pending frees them (checked only
* under ASAN with detect_leaks=1; the suite runs with leaks off) */
sched_trace_host_begin(rec, &fx->obj, &fx->w);
sched_trace_check_rows(rec, &fx->core, 2, idxs, total, inuse, NULL,
wanted);
sched_trace_rec_free(rec);
rows_fx_free(fx);
}
int main(void)
{
xstring_table_init(1024);
UNITY_BEGIN();
RUN_TEST(test_selection_copies_ordering_keys);
RUN_TEST(test_selection_policy_ignores_queue_override);
@ -502,5 +1040,13 @@ int main(void)
RUN_TEST(test_attempt_reasons_main_reason_only);
RUN_TEST(test_attempt_reasons_none_recorded);
RUN_TEST(test_attempt_reasons_counts_against_the_budget);
RUN_TEST(test_check_rows_cluster_scope_keeps_every_row);
RUN_TEST(test_check_rows_host_scope_keeps_unmet_rows);
RUN_TEST(test_check_rows_host_limit_only_is_not_a_shortfall);
RUN_TEST(test_check_rows_host_all_met_keeps_nothing);
RUN_TEST(test_check_rows_host_limit_without_matched_limits);
RUN_TEST(test_check_rows_host_summary_tallies_limit);
RUN_TEST(test_check_rows_scope_edges);
RUN_TEST(test_check_rows_host_rows_count_against_budget);
return UNITY_END();
}

View file

@ -1092,8 +1092,12 @@ static int emit_every_kind(xce_sched_trace_rec_t* rec)
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 };
sched_trace_row_t row;
make_primary(&fp, 1, 1);
memset(&row, 0, sizeof(row));
row.res = "mem";
row.total = 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);
@ -1102,29 +1106,31 @@ static int emit_every_kind(xce_sched_trace_rec_t* rec)
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_row(rec, &row); /* 4 */
sched_trace_check_end(rec, 1, true); /* 5 */
sched_trace_no_plan(rec, XCE_TRACE_NOPLAN_DECLINED); /* 6 */
sched_trace_plan_discarded(rec, &plan); /* 7 */
sched_trace_plan_not_tried(rec, &plan, -12); /* 8 */
sched_trace_plan_begin(rec, &plan); /* 9 */
sched_trace_pos_begin(rec, 1, 1); /* 10 */
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_rollback_host(rec, hname); /* 13 */
sched_trace_row(rec, &row); /* 11, held */
sched_trace_host_verdict(rec, XCE_TRACE_V_ACCEPTED, 0); /* 12 */
sched_trace_pos_end(rec, XCE_TRACE_POS_FILLED, hname); /* 13 */
sched_trace_plan_end(rec, 1, 0); /* 14 */
sched_trace_rollback_host(rec, hname); /* 15 */
sched_trace_rollback_host(rec, hname); /* merged */
sched_trace_commit(rec); /* 14 */
sched_trace_finalize_failed(rec, -5); /* 15 */
sched_trace_search_end(rec, 1); /* 16 */
sched_trace_resreq_end(rec, 1, 0); /* 17 */
sched_trace_commit(rec); /* 16 */
sched_trace_finalize_failed(rec, -5); /* 17 */
sched_trace_search_end(rec, 1); /* 18 */
sched_trace_resreq_end(rec, 1, 0); /* 19 */
sched_trace_path_pop(rec);
sched_trace_member_failed(rec, 1, 1, -12); /* 18 */
sched_trace_attempt_reasons(rec, &rc, 1, NULL, 0, NULL, 0); /* 19 */
sched_trace_resreq_end(rec, 0, 0); /* 20 */
sched_trace_member_failed(rec, 1, 1, -12); /* 20 */
sched_trace_attempt_reasons(rec, &rc, 1, NULL, 0, NULL, 0); /* 21 */
sched_trace_resreq_end(rec, 0, 0); /* 22 */
sched_trace_path_pop(rec);
sched_trace_alt_result(rec, 0, 2); /* 21 */
return 21;
sched_trace_alt_result(rec, 0, 2); /* 23 */
return 23;
}
static void test_every_entry_kind_is_one_entry(void)
@ -1152,14 +1158,16 @@ static void test_every_entry_kind_is_one_entry(void)
TEST_ASSERT_EQUAL_INT(0, seen[SCHED_TRACE_E_POS_SUMMARY]);
sched_trace_rec_free(rec);
/* summary level: the position summary replaces the host lines and the
* position end, so the same calls make one entry fewer */
/* summary level: the position summary replaces the host lines (the
* host verdict and its row) and the position end, so the same calls
* make two entries fewer */
rec = new_rec_level(XCE_TRACE_LINE_BOUND_DEFAULT, XCE_TRACE_LEVEL_SUMMARY);
TEST_ASSERT_NOT_NULL(rec);
TEST_ASSERT_EQUAL_INT(n, emit_every_kind(rec));
TEST_ASSERT_EQUAL_INT(n - 1, rec->nentries);
TEST_ASSERT_EQUAL_INT(n - 2, rec->nentries);
for (e = rec->entries; e != NULL; e = e->next) {
TEST_ASSERT_TRUE(e->type != SCHED_TRACE_E_HOST);
TEST_ASSERT_TRUE(e->type != SCHED_TRACE_E_HOST_ROW);
TEST_ASSERT_TRUE(e->type != SCHED_TRACE_E_POS_END);
seen[e->type]++;
}