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

@ -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]++;
}