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:
parent
90d417bb1b
commit
258267fa48
6 changed files with 977 additions and 38 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]++;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue