feat(scheduler-sim-decision-trace): capture decision outcomes and reconstructed dispatches
Task 2.3: dispatch rows with limit charges listed separately and resolved through the job's matched limits (raw-number fallback), pend reason set deep-copied, 50 hosts in detail with the rest aggregated, and RECONSTRUCTED records holding the dispatch outcome. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
79c5a0cbda
commit
8efe142bad
4 changed files with 1269 additions and 68 deletions
|
|
@ -97,7 +97,7 @@
|
|||
- Done when capture tests pass under ASAN showing that limit rows are never reported as shortfalls, scope strings are correct, and each scope keeps the right rows.
|
||||
- _Requirements: 4.1, 4.2, 5.4, 5.5_
|
||||
|
||||
- [ ] 2.3 Capture decision outcomes and reconstructed dispatches, test first
|
||||
- [x] 2.3 Capture decision outcomes and reconstructed dispatches, test first
|
||||
- **Dispatch:** per-host allocation rows and shared rows, with limit charges listed separately.
|
||||
- A limit charge is identified by the limit flag, or by its name prefix when the resource is unknown.
|
||||
- It is resolved to its limit by name through the job's matched limits. An unresolved charge falls back to a raw number.
|
||||
|
|
@ -349,3 +349,4 @@
|
|||
- 2.1: capture reads struct fields only and calls no scheduler functions, so the white-box tests need no extra sources. Policy name = ctl policy_name, else core->job_pick_plugin; the queue picker override is ignored because election always uses the core picker. Cluster-wide limit scope is stored as "u=..,q=..,p=.." ("" when unscoped); 2.2 should reuse split_vres_name/copy_scope and append "h=<host>". 4.1 must render selection_set/glimits_set false as "unavailable" and an ATTEMPT_REASONS entry with ncodes==0.
|
||||
- 2.2: rows go through `sched_trace_row()`, a model addition not named in the design, as CHECK_ROW (cluster-wide check, all rows) or HOST_ROW (host scope, unmet rows only). Host rows are held until the host verdict is stored and then stored one level deeper, each counting against the budget; they are dropped at summary level, where the verdict bits and limit tally are still set. Rows arriving outside an open check (`rec->check_open`) or a host scope are dropped, so 3.3/3.4 must open a host scope before host_iter's pre-filter calls `sched_check_host_satisfy` and must call `sched_trace_check_end` on every exit path of the cluster-wide check. 4.1 must render a NULL row name by its index, `limited_res == NULL` as a raw amount, and HOST_ROW at host depth + 1. Capture now calls librtti `vget()`, which every test already links.
|
||||
- 2.2 OPEN for 6.2: if `limit_row()` runs out of memory, `sched_trace_check_rows` returns early, so that row's LIMIT bit and later rows' SHORTFALL bits are lost. The verdict derived from rows then becomes SELECT and the Req 9.3 counts are off. This needs fault injection to test, so fix it in 6.2: keep setting the verdict bits and continue.
|
||||
- 2.3: the outcome capture functions are `sched_trace_capture_dispatch`, `sched_trace_capture_pend` and `sched_trace_rec_reconstructed`, declared in trace.h; 3.x and 5.3 call them. Limit charges are classified by the limit flag or a `limit=` prefix and resolved via glimits (shared) or the worker's hlimits (host, worker looked up by host name). An unresolved charge has `limited_res == NULL`, which is the raw-number fallback. Shared pend entries carry host "" and are treated as host-less. For 4.1: limit totals over omitted hosts have a scope without the host, and a NULL host name yields the scope "h=". Follow-up (low): capture's `key_find`/`vget` on resmgr/workers run without the RTTI read lock that `sched_get_worker` takes. That is safe while the sim runs in lock-step; the 2.4 split may wrap them.
|
||||
|
|
|
|||
|
|
@ -371,6 +371,68 @@ typedef struct sched_trace_entry_s {
|
|||
} u;
|
||||
} sched_trace_entry_t;
|
||||
|
||||
/*
|
||||
* Decision outcome (6.1-6.4, 9.4), filled by TraceCapture and rendered
|
||||
* outside the line budget. All pointers point into the record's arena.
|
||||
*/
|
||||
typedef enum {
|
||||
SCHED_TRACE_OUTCOME_NONE = 0,
|
||||
SCHED_TRACE_OUTCOME_DISPATCH,
|
||||
SCHED_TRACE_OUTCOME_PEND
|
||||
} sched_trace_outcome_kind_t;
|
||||
|
||||
/* One allocated amount: a resource row, or a limit charge (6.3: a charge is
|
||||
* never a resource row). A charge names its limit and scope instance (a
|
||||
* host charge has "h=<host>" appended); `limited_res` is the resource the
|
||||
* limit limits, NULL when no matched limit names the charge, in which case
|
||||
* the amount is a raw number. In an aggregate the scope has no host part. */
|
||||
typedef struct {
|
||||
const char* res; /* allocated resource name (a charge: the vres name;
|
||||
* NULL in a limit aggregate) */
|
||||
double value; /* amount, or the total in an aggregate */
|
||||
const char* limit; /* charges only */
|
||||
const char* scope; /* charges only */
|
||||
const char* limited_res; /* charges only; NULL: unknown (raw number) */
|
||||
} sched_trace_alloc_row_t;
|
||||
|
||||
/* One allocated host kept in detail (6.1), in allocation order. */
|
||||
typedef struct {
|
||||
const char* host;
|
||||
int nres;
|
||||
sched_trace_alloc_row_t* res;
|
||||
int ncharges;
|
||||
sched_trace_alloc_row_t* charges;
|
||||
} sched_trace_host_out_t;
|
||||
|
||||
/* Dispatch outcome: the first XCE_TRACE_OUTCOME_HOSTS hosts in detail, the
|
||||
* job's shared rows and charges (6.2), and the omitted hosts' totals per
|
||||
* resource (limits excluded) and per limit, in the order first seen (9.4). */
|
||||
typedef struct {
|
||||
int nhosts; /* kept in detail */
|
||||
sched_trace_host_out_t* hosts;
|
||||
int nshared;
|
||||
sched_trace_alloc_row_t* shared;
|
||||
int nshared_charges;
|
||||
sched_trace_alloc_row_t* shared_charges;
|
||||
int omitted_hosts;
|
||||
int nagg_res;
|
||||
sched_trace_alloc_row_t* agg_res;
|
||||
int nagg_charges;
|
||||
sched_trace_alloc_row_t* agg_charges;
|
||||
} sched_trace_dispatch_out_t;
|
||||
|
||||
/* Pend outcome (6.4): a copy of the decision's pend-reason set holding the
|
||||
* main reason, every entry that names no host and every entry of the first
|
||||
* XCE_TRACE_OUTCOME_HOSTS distinct hosts, in source order (equal to the
|
||||
* source under job_preason_eq() when no host was omitted). Entries of the
|
||||
* hosts after them are counted per pend-reason code (9.4). */
|
||||
typedef struct {
|
||||
xce_job_preason_t* reasons; /* NULL: the decision had none */
|
||||
int omitted_hosts;
|
||||
int ncodes;
|
||||
sched_trace_reason_count_t* omitted_codes; /* first-seen order */
|
||||
} sched_trace_pend_out_t;
|
||||
|
||||
/* One arena chunk; the record frees the whole chain at once. */
|
||||
typedef struct sched_trace_chunk_s {
|
||||
struct sched_trace_chunk_s* next;
|
||||
|
|
@ -416,6 +478,11 @@ struct xce_sched_trace_rec_s {
|
|||
|
||||
sched_trace_counters_t counters;
|
||||
|
||||
/* outcome (6.x, 9.4), outside the line budget */
|
||||
sched_trace_outcome_kind_t outcome;
|
||||
sched_trace_dispatch_out_t dispatch; /* outcome DISPATCH */
|
||||
sched_trace_pend_out_t pend; /* outcome PEND */
|
||||
|
||||
/* stored entries, in the order made; at most line_bound of them */
|
||||
sched_trace_entry_t* entries;
|
||||
sched_trace_entry_t* entries_tail;
|
||||
|
|
@ -677,4 +744,37 @@ extern void sched_trace_check_rows(xce_sched_trace_rec_t* rec,
|
|||
extern void sched_trace_capture_attempt_reasons(xce_sched_trace_rec_t* rec,
|
||||
const xce_pendreason_t* reason);
|
||||
|
||||
/*
|
||||
* A dispatch decision's allocation as the record's outcome (6.1-6.3, 9.4).
|
||||
* Each row is a limit charge when its resource, found by name in
|
||||
* core->resmgr, has XCE_RESF_LIMIT, or, when the name is not registered,
|
||||
* when it starts with "limit=". A charge is resolved by vres name through
|
||||
* the job's matched limits: obj->glimits for shared rows,
|
||||
* obj->hlimits[w->idx] for a host's rows (w found by host name in
|
||||
* core->workers); unresolved, its name and scope are parsed from the
|
||||
* resource name and its limited resource is unknown. NULL obj: nothing
|
||||
* resolves. A NULL alloc stores an empty dispatch. `core` is only read.
|
||||
*/
|
||||
extern void sched_trace_capture_dispatch(xce_sched_trace_rec_t* rec,
|
||||
xce_sched_core_t* core,
|
||||
const xce_sched_obj_t* obj,
|
||||
const xce_job_alloc_t* alloc);
|
||||
|
||||
/* A pend decision's pend-reason set as the record's outcome (6.4, 9.4). */
|
||||
extern void sched_trace_capture_pend(xce_sched_trace_rec_t* rec,
|
||||
const xce_pendreason_t* reason);
|
||||
|
||||
/*
|
||||
* A RECONSTRUCTED record (2.5) for a dispatch the simulator rebuilt from a
|
||||
* recorded allocation, built on the sim thread: the header from `ctl` and
|
||||
* `allocs` as its dispatch outcome, captured as above (`obj` is the job's
|
||||
* schedobj, NULL when it has none). NULL when ctl is NULL or the record
|
||||
* cannot be allocated.
|
||||
*/
|
||||
extern xce_sched_trace_rec_t*
|
||||
sched_trace_rec_reconstructed(const xce_sched_trace_ctl_t* ctl,
|
||||
xce_sched_core_t* core, uint64_t jobid,
|
||||
const xce_sched_obj_t* obj,
|
||||
const xce_job_alloc_t* allocs);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -16,10 +16,12 @@
|
|||
*
|
||||
* Task 2.1: selection facts (2.2), cluster-wide limits (2.3) and attempt
|
||||
* 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.
|
||||
* 4.2, 5.4, 5.5). Task 2.3: the dispatch and pend outcomes (6.1-6.4, 9.4)
|
||||
* and the reconstructed-dispatch record (2.5). These read struct fields and
|
||||
* call no scheduler function (resources and workers are looked up with
|
||||
* librtti's vget()/key_find(), which every scheduler target links), so
|
||||
* every white-box test that #includes this file links without extra
|
||||
* sources.
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
|
@ -69,21 +71,34 @@ static bool split_vres_name(const char* vname, const char* known,
|
|||
return true;
|
||||
}
|
||||
|
||||
/* Arena copy of a vres scope part, "u=a/q=b" -> "u=a,q=b". */
|
||||
static char* copy_scope(xce_sched_trace_rec_t* rec, const char* scope)
|
||||
/* Arena copy of a vres scope part, "u=a/q=b" -> "u=a,q=b", with
|
||||
* ",h=<host>" ("h=<host>" when the scope is empty) appended for a non-NULL
|
||||
* host: host-limit vres names do not encode the host. */
|
||||
static char* copy_scope(xce_sched_trace_rec_t* rec, const char* scope,
|
||||
const char* host)
|
||||
{
|
||||
size_t n = strlen(scope);
|
||||
size_t hn = (host != NULL) ? strlen(host) : 0;
|
||||
char* out;
|
||||
char* p;
|
||||
size_t i;
|
||||
|
||||
out = (char*)sched_trace_arena_alloc(rec, n + 1);
|
||||
out = (char*)sched_trace_arena_alloc(rec, n + hn + 4);
|
||||
if (out == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
for (i = 0; i < n; i++) {
|
||||
out[i] = (scope[i] == '/') ? ',' : scope[i];
|
||||
for (i = 0, p = out; i < n; i++) {
|
||||
*p++ = (scope[i] == '/') ? ',' : scope[i];
|
||||
}
|
||||
out[n] = '\0';
|
||||
if (host != NULL) {
|
||||
if (n > 0) {
|
||||
*p++ = ',';
|
||||
}
|
||||
memcpy(p, "h=", 2);
|
||||
memcpy(p + 2, host, hn);
|
||||
p += 2 + hn;
|
||||
}
|
||||
*p = '\0';
|
||||
return out;
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +150,7 @@ static bool limit_ref(xce_sched_trace_rec_t* rec, const xce_vres_t* vres,
|
|||
} else {
|
||||
out->limit = copy_prefix(rec, name, name_len);
|
||||
}
|
||||
out->scope = copy_scope(rec, scope);
|
||||
out->scope = copy_scope(rec, scope, NULL);
|
||||
return (out->limit != NULL) && (out->scope != NULL);
|
||||
}
|
||||
|
||||
|
|
@ -216,35 +231,38 @@ static float row_uncomit_at(const xvec_t* uncomitted, int idx)
|
|||
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)
|
||||
/* Worker `idx`'s matched host limits, obj->hlimits[idx] (sparse: NULL
|
||||
* when the worker has none). Read directly: zarray_get() takes a
|
||||
* non-const array. */
|
||||
static const xlist_t* worker_limits(const xce_sched_obj_t* obj, int idx)
|
||||
{
|
||||
const zarray_t* hl;
|
||||
int idx;
|
||||
const zarray_t* hl = &obj->hlimits;
|
||||
|
||||
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)
|
||||
/* The current scope's matched limit list: obj->glimits in the cluster-wide
|
||||
* check, the worker's host limits in a host scope. */
|
||||
static const xlist_t* scope_limits(const xce_sched_trace_rec_t* rec)
|
||||
{
|
||||
const xlist_t* list = scope_limits(rec);
|
||||
int i;
|
||||
if (rec->scope_obj == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
if (rec->scope_w == NULL) {
|
||||
return &rec->scope_obj->glimits;
|
||||
}
|
||||
return worker_limits(rec->scope_obj, rec->scope_w->idx);
|
||||
}
|
||||
|
||||
/* The matched limit in `list` whose vres resource is `r` or is named
|
||||
* `name`, or NULL. Read directly: xlist_get() takes a non-const list. */
|
||||
static const xce_vres_t* list_vres(const xlist_t* list,
|
||||
const xce_resource_t* r, const char* name)
|
||||
{
|
||||
int i;
|
||||
|
||||
if ((list == NULL) || (list->data == NULL)) {
|
||||
return NULL;
|
||||
|
|
@ -255,15 +273,45 @@ static const xce_vres_t* scope_vres(const xce_sched_trace_rec_t* rec,
|
|||
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))) {
|
||||
if (((r != NULL) && (v->vres == r))
|
||||
|| ((v->vres->name != NULL) && (name != NULL)
|
||||
&& (strcmp(v->vres->name, name) == 0))) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* A limit resource named `resname`, resolved: the limit name (not
|
||||
* NUL-terminated), the vres-form scope part and the limited resource. */
|
||||
typedef struct {
|
||||
const char* name;
|
||||
size_t name_len;
|
||||
const char* scope;
|
||||
const char* limited_res; /* NULL: no matched limit names it */
|
||||
} charge_ref_t;
|
||||
|
||||
/* Resolves `resname` through its matched limit `v` (NULL: none, so the
|
||||
* limit name and scope are parsed from the name, and the limited resource
|
||||
* is unknown). A name without the "limit=" prefix names the limit
|
||||
* itself. */
|
||||
static void charge_resolve(const xce_vres_t* v, const char* resname,
|
||||
charge_ref_t* c)
|
||||
{
|
||||
const char* known = ((v != NULL) && (v->limit != NULL)) ? v->limit->name
|
||||
: NULL;
|
||||
|
||||
if (!split_vres_name(resname, known, &c->name, &c->name_len, &c->scope)) {
|
||||
c->name = (known != NULL) ? known : resname;
|
||||
c->name_len = (c->name != NULL) ? strlen(c->name) : 0;
|
||||
c->scope = "";
|
||||
} else if (known != NULL) {
|
||||
c->name = known;
|
||||
c->name_len = strlen(known);
|
||||
}
|
||||
c->limited_res = ((v != NULL) && (v->res != NULL)) ? v->res->name : 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
|
||||
|
|
@ -273,49 +321,34 @@ static const xce_vres_t* scope_vres(const xce_sched_trace_rec_t* rec,
|
|||
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;
|
||||
charge_ref_t c;
|
||||
const char* host = NULL;
|
||||
size_t host_len = 0;
|
||||
size_t scope_len;
|
||||
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);
|
||||
}
|
||||
charge_resolve(list_vres(scope_limits(rec), r, r->name), r->name, &c);
|
||||
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);
|
||||
scope_len = strlen(c.scope);
|
||||
|
||||
/* "<name>\0<scope>[,]h=<host>\0" */
|
||||
*buf = (char*)malloc(name_len + scope_len + host_len + 5);
|
||||
*buf = (char*)malloc(c.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;
|
||||
memcpy(p, (c.name != NULL) ? c.name : "", c.name_len);
|
||||
p[c.name_len] = '\0';
|
||||
row->limit = p;
|
||||
p += c.name_len + 1;
|
||||
row->scope = p;
|
||||
for (k = 0; k < scope_len; k++) {
|
||||
*p++ = (scope[k] == '/') ? ',' : scope[k];
|
||||
*p++ = (c.scope[k] == '/') ? ',' : c.scope[k];
|
||||
}
|
||||
if (host != NULL) {
|
||||
if (scope_len > 0) {
|
||||
|
|
@ -328,10 +361,7 @@ static bool limit_row(const xce_sched_trace_rec_t* rec, const xce_resource_t* r,
|
|||
}
|
||||
*p = '\0';
|
||||
|
||||
row->limited_res = NULL;
|
||||
if ((v != NULL) && (v->res != NULL)) {
|
||||
row->limited_res = v->res->name;
|
||||
}
|
||||
row->limited_res = c.limited_res;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -508,3 +538,528 @@ void sched_trace_capture_attempt_reasons(xce_sched_trace_rec_t* rec,
|
|||
free((void*)hosts);
|
||||
free((void*)res);
|
||||
}
|
||||
|
||||
/* ---- dispatch outcome (6.1-6.3, 9.4) ---------------------------------- */
|
||||
|
||||
/* True iff the allocated resource `resname` is a limit charge: its
|
||||
* registered resource has XCE_RESF_LIMIT, or, when the name is not
|
||||
* registered in this replay, it has the "limit=" prefix. */
|
||||
static bool is_limit_charge(xce_sched_core_t* core, const char* resname)
|
||||
{
|
||||
xce_resource_t* r = NULL;
|
||||
|
||||
if ((core != NULL) && (core->resmgr != NULL)
|
||||
&& (key_find(core->resmgr, (void*)resname, (void**)&r) >= 0)
|
||||
&& (r != NULL)) {
|
||||
return (r->flags & XCE_RESF_LIMIT) != 0;
|
||||
}
|
||||
return strncmp(resname, VRES_PREFIX, VRES_PREFIX_LEN) == 0;
|
||||
}
|
||||
|
||||
/* The host limits matched to the job on the worker named `host`, or NULL
|
||||
* (no schedobj, unknown worker, none matched). */
|
||||
static const xlist_t* host_limits(xce_sched_core_t* core,
|
||||
const xce_sched_obj_t* obj, const char* host)
|
||||
{
|
||||
xce_sched_worker_t* w = NULL;
|
||||
|
||||
if ((obj == NULL) || (core == NULL) || (core->workers == NULL)
|
||||
|| (host == NULL)
|
||||
|| (key_find(core->workers, (void*)host, (void**)&w) < 0)
|
||||
|| (w == NULL)) {
|
||||
return NULL;
|
||||
}
|
||||
return worker_limits(obj, w->idx);
|
||||
}
|
||||
|
||||
/* Stores allocated amount `a` as a limit charge, resolved through `limits`;
|
||||
* a host charge (`host` non-NULL) gets the host appended to its scope. */
|
||||
static bool charge_row(xce_sched_trace_rec_t* rec, const xlist_t* limits,
|
||||
const xce_alloc_t* a, const char* host,
|
||||
sched_trace_alloc_row_t* out)
|
||||
{
|
||||
charge_ref_t c;
|
||||
|
||||
charge_resolve(list_vres(limits, NULL, a->resname), a->resname, &c);
|
||||
out->res = sched_trace_arena_strdup(rec, a->resname);
|
||||
out->value = a->value;
|
||||
out->limit = copy_prefix(rec, c.name, c.name_len);
|
||||
out->scope = copy_scope(rec, c.scope, host);
|
||||
out->limited_res = sched_trace_arena_strdup(rec, c.limited_res);
|
||||
return (out->res != NULL) && (out->limit != NULL) && (out->scope != NULL)
|
||||
&& ((c.limited_res == NULL) || (out->limited_res != NULL));
|
||||
}
|
||||
|
||||
/* One allocation list (a host's, or the shared one) as resource rows and
|
||||
* limit charges, each in allocation order. False on an arena failure. */
|
||||
static bool alloc_rows(xce_sched_trace_rec_t* rec, xce_sched_core_t* core,
|
||||
const xlist_t* limits, int n, xce_alloc_t** allocs,
|
||||
const char* host, int* nres,
|
||||
sched_trace_alloc_row_t** res, int* ncharges,
|
||||
sched_trace_alloc_row_t** charges)
|
||||
{
|
||||
size_t sz = sizeof(**res) * (size_t)n;
|
||||
int i;
|
||||
|
||||
if ((n <= 0) || (allocs == NULL)) {
|
||||
return true;
|
||||
}
|
||||
*res = (sched_trace_alloc_row_t*)sched_trace_arena_alloc(rec, sz);
|
||||
*charges = (sched_trace_alloc_row_t*)sched_trace_arena_alloc(rec, sz);
|
||||
if ((*res == NULL) || (*charges == NULL)) {
|
||||
return false;
|
||||
}
|
||||
for (i = 0; i < n; i++) {
|
||||
const xce_alloc_t* a = allocs[i];
|
||||
sched_trace_alloc_row_t* row;
|
||||
|
||||
if ((a == NULL) || (a->resname == NULL)) {
|
||||
continue;
|
||||
}
|
||||
if (is_limit_charge(core, a->resname)) {
|
||||
if (!charge_row(rec, limits, a, host, &(*charges)[(*ncharges)++])) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
row = &(*res)[(*nres)++];
|
||||
row->res = sched_trace_arena_strdup(rec, a->resname);
|
||||
row->value = a->value;
|
||||
if (row->res == NULL) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* A total over the omitted hosts, keyed by resource name or, for a charge,
|
||||
* by limit name and scope. Scratch memory; the strings are the sources'. */
|
||||
typedef struct {
|
||||
const char* res; /* resource total; NULL: a limit total */
|
||||
charge_ref_t c; /* limit total */
|
||||
double value;
|
||||
} agg_t;
|
||||
|
||||
static void agg_add(agg_t* agg, int* n, const char* res,
|
||||
const charge_ref_t* c, double value)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < *n; i++) {
|
||||
if ((res != NULL)
|
||||
? (strcmp(agg[i].res, res) == 0)
|
||||
: ((agg[i].c.name_len == c->name_len)
|
||||
&& (strncmp(agg[i].c.name, c->name, c->name_len) == 0)
|
||||
&& (strcmp(agg[i].c.scope, c->scope) == 0))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == *n) {
|
||||
memset(&agg[i], 0, sizeof(agg[i]));
|
||||
agg[i].res = res;
|
||||
if (c != NULL) {
|
||||
agg[i].c = *c;
|
||||
}
|
||||
(*n)++;
|
||||
}
|
||||
agg[i].value += value;
|
||||
if ((c != NULL) && (agg[i].c.limited_res == NULL)) {
|
||||
agg[i].c.limited_res = c->limited_res;
|
||||
}
|
||||
}
|
||||
|
||||
/* Copies `n` totals into the arena as rows (a charge's scope has no
|
||||
* host part: it totals hosts). */
|
||||
static bool agg_store(xce_sched_trace_rec_t* rec, const agg_t* agg, int n,
|
||||
int* nout, sched_trace_alloc_row_t** out)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (n == 0) {
|
||||
return true;
|
||||
}
|
||||
*out = (sched_trace_alloc_row_t*)sched_trace_arena_alloc(
|
||||
rec, sizeof(**out) * (size_t)n);
|
||||
if (*out == NULL) {
|
||||
return false;
|
||||
}
|
||||
for (i = 0; i < n; i++) {
|
||||
sched_trace_alloc_row_t* row = &(*out)[i];
|
||||
|
||||
row->value = agg[i].value;
|
||||
if (agg[i].res != NULL) {
|
||||
row->res = sched_trace_arena_strdup(rec, agg[i].res);
|
||||
if (row->res == NULL) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
row->limit = copy_prefix(rec, agg[i].c.name, agg[i].c.name_len);
|
||||
row->scope = copy_scope(rec, agg[i].c.scope, NULL);
|
||||
row->limited_res = sched_trace_arena_strdup(rec, agg[i].c.limited_res);
|
||||
if ((row->limit == NULL) || (row->scope == NULL)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*nout = n;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Totals of the hosts from index `from` on (9.4): per resource without the
|
||||
* limit charges, and per limit. False on an allocation failure. */
|
||||
static bool aggregate_hosts(xce_sched_trace_rec_t* rec, xce_sched_core_t* core,
|
||||
const xce_sched_obj_t* obj,
|
||||
const xce_job_alloc_t* alloc, int from)
|
||||
{
|
||||
sched_trace_dispatch_out_t* d = &rec->dispatch;
|
||||
size_t total = 0;
|
||||
agg_t* res;
|
||||
agg_t* chg;
|
||||
int nres = 0;
|
||||
int nchg = 0;
|
||||
bool ok;
|
||||
int i;
|
||||
int k;
|
||||
|
||||
for (i = from; i < alloc->nhosts; i++) {
|
||||
if ((alloc->allocs[i] != NULL) && (alloc->allocs[i]->allocs != NULL)
|
||||
&& (alloc->allocs[i]->nallocs > 0)) {
|
||||
total += (size_t)alloc->allocs[i]->nallocs;
|
||||
}
|
||||
}
|
||||
res = (agg_t*)malloc(sizeof(*res) * (total + 1));
|
||||
chg = (agg_t*)malloc(sizeof(*chg) * (total + 1));
|
||||
ok = (res != NULL) && (chg != NULL);
|
||||
|
||||
for (i = from; ok && (i < alloc->nhosts); i++) {
|
||||
const xce_host_alloc_t* h = alloc->allocs[i];
|
||||
const xlist_t* limits;
|
||||
|
||||
if ((h == NULL) || (h->allocs == NULL)) {
|
||||
continue;
|
||||
}
|
||||
limits = host_limits(core, obj, h->host);
|
||||
for (k = 0; k < h->nallocs; k++) {
|
||||
const xce_alloc_t* a = h->allocs[k];
|
||||
charge_ref_t c;
|
||||
|
||||
if ((a == NULL) || (a->resname == NULL)) {
|
||||
continue;
|
||||
}
|
||||
if (!is_limit_charge(core, a->resname)) {
|
||||
agg_add(res, &nres, a->resname, NULL, a->value);
|
||||
continue;
|
||||
}
|
||||
charge_resolve(list_vres(limits, NULL, a->resname), a->resname,
|
||||
&c);
|
||||
agg_add(chg, &nchg, NULL, &c, a->value);
|
||||
}
|
||||
}
|
||||
ok = ok && agg_store(rec, res, nres, &d->nagg_res, &d->agg_res)
|
||||
&& agg_store(rec, chg, nchg, &d->nagg_charges, &d->agg_charges);
|
||||
free(res);
|
||||
free(chg);
|
||||
return ok;
|
||||
}
|
||||
|
||||
void sched_trace_capture_dispatch(xce_sched_trace_rec_t* rec,
|
||||
xce_sched_core_t* core,
|
||||
const xce_sched_obj_t* obj,
|
||||
const xce_job_alloc_t* alloc)
|
||||
{
|
||||
sched_trace_dispatch_out_t* d = &rec->dispatch;
|
||||
int from = -1;
|
||||
int nh;
|
||||
int i;
|
||||
|
||||
memset(d, 0, sizeof(*d));
|
||||
rec->outcome = SCHED_TRACE_OUTCOME_DISPATCH;
|
||||
if (alloc == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!alloc_rows(rec, core, (obj != NULL) ? &obj->glimits : NULL,
|
||||
alloc->nres_shared, alloc->allocs_shared, NULL,
|
||||
&d->nshared, &d->shared, &d->nshared_charges,
|
||||
&d->shared_charges)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
nh = (alloc->allocs != NULL) ? alloc->nhosts : 0;
|
||||
if (nh > 0) {
|
||||
int keep = (nh < XCE_TRACE_OUTCOME_HOSTS) ? nh : XCE_TRACE_OUTCOME_HOSTS;
|
||||
|
||||
d->hosts = (sched_trace_host_out_t*)sched_trace_arena_alloc(
|
||||
rec, sizeof(*d->hosts) * (size_t)keep);
|
||||
if (d->hosts == NULL) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < nh; i++) {
|
||||
const xce_host_alloc_t* h = alloc->allocs[i];
|
||||
sched_trace_host_out_t* ho;
|
||||
|
||||
if (h == NULL) {
|
||||
continue;
|
||||
}
|
||||
if (d->nhosts == XCE_TRACE_OUTCOME_HOSTS) {
|
||||
if (from < 0) {
|
||||
from = i;
|
||||
}
|
||||
d->omitted_hosts++;
|
||||
continue;
|
||||
}
|
||||
ho = &d->hosts[d->nhosts++];
|
||||
ho->host = sched_trace_arena_strdup(rec, h->host);
|
||||
if (((h->host != NULL) && (ho->host == NULL))
|
||||
|| !alloc_rows(rec, core, host_limits(core, obj, h->host),
|
||||
h->nallocs, h->allocs,
|
||||
(h->host != NULL) ? h->host : "", &ho->nres,
|
||||
&ho->res, &ho->ncharges, &ho->charges)) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
if ((from >= 0) && !aggregate_hosts(rec, core, obj, alloc, from)) {
|
||||
goto fail;
|
||||
}
|
||||
return;
|
||||
|
||||
fail:
|
||||
rec->incomplete = true; /* design "Error Strategy" */
|
||||
}
|
||||
|
||||
xce_sched_trace_rec_t*
|
||||
sched_trace_rec_reconstructed(const xce_sched_trace_ctl_t* ctl,
|
||||
xce_sched_core_t* core, uint64_t jobid,
|
||||
const xce_sched_obj_t* obj,
|
||||
const xce_job_alloc_t* allocs)
|
||||
{
|
||||
xce_sched_trace_rec_t* rec;
|
||||
|
||||
rec = sched_trace_rec_new(ctl, jobid, XCE_TRACE_REC_RECONSTRUCTED);
|
||||
if (rec != NULL) {
|
||||
sched_trace_capture_dispatch(rec, core, obj, allocs);
|
||||
}
|
||||
return rec;
|
||||
}
|
||||
|
||||
/* ---- pend outcome (6.4, 9.4) ------------------------------------------ */
|
||||
|
||||
/* A set of host names, open addressing over a power-of-two table. Scratch
|
||||
* memory; the names are the source's. */
|
||||
typedef struct {
|
||||
const char** slot;
|
||||
size_t mask;
|
||||
int n;
|
||||
} host_set_t;
|
||||
|
||||
static bool host_set_init(host_set_t* s, int nmax)
|
||||
{
|
||||
size_t cap = 16;
|
||||
|
||||
while (cap < (size_t)nmax * 2 + 2) {
|
||||
cap <<= 1;
|
||||
}
|
||||
s->slot = (const char**)calloc(cap, sizeof(*s->slot));
|
||||
s->mask = cap - 1;
|
||||
s->n = 0;
|
||||
return s->slot != NULL;
|
||||
}
|
||||
|
||||
/* True iff `h` is in the set; when absent and `insert`, adds it. */
|
||||
static bool host_set_has(host_set_t* s, const char* h, bool insert)
|
||||
{
|
||||
size_t i = 2166136261u; /* FNV-1a */
|
||||
const unsigned char* p;
|
||||
|
||||
for (p = (const unsigned char*)h; *p != '\0'; p++) {
|
||||
i = (i ^ *p) * 16777619u;
|
||||
}
|
||||
for (i &= s->mask; s->slot[i] != NULL; i = (i + 1) & s->mask) {
|
||||
if (strcmp(s->slot[i], h) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (insert) {
|
||||
s->slot[i] = h;
|
||||
s->n++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
host_set_t kept; /* the first 50 distinct hosts */
|
||||
host_set_t omitted; /* the hosts after them */
|
||||
sched_trace_reason_count_t* codes; /* omitted entries per code */
|
||||
int ncodes;
|
||||
} pend_build_t;
|
||||
|
||||
/* True iff an entry naming `host` is kept: it names no host, or one of the
|
||||
* first 50 distinct hosts. Otherwise it is counted under its code. */
|
||||
static bool pend_keep(pend_build_t* b, const char* host, int code)
|
||||
{
|
||||
if ((host == NULL) || (host[0] == '\0')
|
||||
|| host_set_has(&b->kept, host, false)) {
|
||||
return true;
|
||||
}
|
||||
if (b->kept.n < XCE_TRACE_OUTCOME_HOSTS) {
|
||||
(void)host_set_has(&b->kept, host, true);
|
||||
return true;
|
||||
}
|
||||
(void)host_set_has(&b->omitted, host, true);
|
||||
count_code(b->codes, &b->ncodes, code);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Arena copies of the kept host entries (NULL entries stay NULL). */
|
||||
static bool copy_host_reasons(xce_sched_trace_rec_t* rec,
|
||||
const xce_job_preason_t* r, const bool* keep,
|
||||
int nh, xce_job_preason_t* c)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nh; i++) {
|
||||
c->nhost_reasons += keep[i] ? 1 : 0;
|
||||
}
|
||||
if (c->nhost_reasons == 0) {
|
||||
return true;
|
||||
}
|
||||
c->host_based_reasons = (xce_host_reason_t**)sched_trace_arena_alloc(
|
||||
rec, sizeof(*c->host_based_reasons) * (size_t)c->nhost_reasons);
|
||||
if (c->host_based_reasons == NULL) {
|
||||
return false;
|
||||
}
|
||||
c->nhost_reasons = 0;
|
||||
for (i = 0; i < nh; i++) {
|
||||
const xce_host_reason_t* src = r->host_based_reasons[i];
|
||||
xce_host_reason_t* dst = NULL;
|
||||
|
||||
if (!keep[i]) {
|
||||
continue;
|
||||
}
|
||||
if (src != NULL) {
|
||||
dst = (xce_host_reason_t*)sched_trace_arena_alloc(rec, sizeof(*dst));
|
||||
if (dst == NULL) {
|
||||
return false;
|
||||
}
|
||||
dst->reason = src->reason;
|
||||
dst->host = sched_trace_arena_strdup(rec, src->host);
|
||||
if ((src->host != NULL) && (dst->host == NULL)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
c->host_based_reasons[c->nhost_reasons++] = dst;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Arena copies of the kept resource entries (NULL entries stay NULL). */
|
||||
static bool copy_res_reasons(xce_sched_trace_rec_t* rec,
|
||||
const xce_job_preason_t* r, const bool* keep,
|
||||
int nr, xce_job_preason_t* c)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nr; i++) {
|
||||
c->nres_reasons += keep[i] ? 1 : 0;
|
||||
}
|
||||
if (c->nres_reasons == 0) {
|
||||
return true;
|
||||
}
|
||||
c->res_based_reasons = (xce_res_reason_t**)sched_trace_arena_alloc(
|
||||
rec, sizeof(*c->res_based_reasons) * (size_t)c->nres_reasons);
|
||||
if (c->res_based_reasons == NULL) {
|
||||
return false;
|
||||
}
|
||||
c->nres_reasons = 0;
|
||||
for (i = 0; i < nr; i++) {
|
||||
const xce_res_reason_t* src = r->res_based_reasons[i];
|
||||
xce_res_reason_t* dst = NULL;
|
||||
|
||||
if (!keep[i]) {
|
||||
continue;
|
||||
}
|
||||
if (src != NULL) {
|
||||
dst = (xce_res_reason_t*)sched_trace_arena_alloc(rec, sizeof(*dst));
|
||||
if (dst == NULL) {
|
||||
return false;
|
||||
}
|
||||
dst->reason = src->reason;
|
||||
dst->host = sched_trace_arena_strdup(rec, src->host);
|
||||
dst->resource = sched_trace_arena_strdup(rec, src->resource);
|
||||
if (((src->host != NULL) && (dst->host == NULL))
|
||||
|| ((src->resource != NULL) && (dst->resource == NULL))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
c->res_based_reasons[c->nres_reasons++] = dst;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void sched_trace_capture_pend(xce_sched_trace_rec_t* rec,
|
||||
const xce_pendreason_t* reason)
|
||||
{
|
||||
sched_trace_pend_out_t* p = &rec->pend;
|
||||
const xce_job_preason_t* r = (reason != NULL) ? reason->reasons : NULL;
|
||||
pend_build_t b;
|
||||
bool* keep;
|
||||
int nh;
|
||||
int nr;
|
||||
int i;
|
||||
bool ok;
|
||||
|
||||
memset(p, 0, sizeof(*p));
|
||||
rec->outcome = SCHED_TRACE_OUTCOME_PEND;
|
||||
if (r == NULL) {
|
||||
return;
|
||||
}
|
||||
nh = ((r->host_based_reasons != NULL) && (r->nhost_reasons > 0))
|
||||
? r->nhost_reasons : 0;
|
||||
nr = ((r->res_based_reasons != NULL) && (r->nres_reasons > 0))
|
||||
? r->nres_reasons : 0;
|
||||
|
||||
/* which entries are kept, in one pass over the source order */
|
||||
memset(&b, 0, sizeof(b));
|
||||
keep = (bool*)malloc(sizeof(*keep) * (size_t)(nh + nr + 1));
|
||||
b.codes = (sched_trace_reason_count_t*)malloc(sizeof(*b.codes)
|
||||
* (size_t)(nh + nr + 1));
|
||||
ok = (keep != NULL) && (b.codes != NULL)
|
||||
&& host_set_init(&b.kept, XCE_TRACE_OUTCOME_HOSTS)
|
||||
&& host_set_init(&b.omitted, nh + nr);
|
||||
for (i = 0; ok && (i < nh); i++) {
|
||||
const xce_host_reason_t* e = r->host_based_reasons[i];
|
||||
|
||||
keep[i] = (e == NULL) || pend_keep(&b, e->host, e->reason);
|
||||
}
|
||||
for (i = 0; ok && (i < nr); i++) {
|
||||
const xce_res_reason_t* e = r->res_based_reasons[i];
|
||||
|
||||
keep[nh + i] = (e == NULL) || pend_keep(&b, e->host, e->reason);
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
p->reasons = (xce_job_preason_t*)sched_trace_arena_alloc(
|
||||
rec, sizeof(*p->reasons));
|
||||
ok = (p->reasons != NULL);
|
||||
}
|
||||
if (ok) {
|
||||
p->reasons->main_reason = r->main_reason;
|
||||
ok = copy_host_reasons(rec, r, keep, nh, p->reasons)
|
||||
&& copy_res_reasons(rec, r, keep + nh, nr, p->reasons);
|
||||
}
|
||||
if (ok && (b.ncodes > 0)) {
|
||||
p->omitted_codes = (sched_trace_reason_count_t*)sched_trace_arena_memdup(
|
||||
rec, b.codes, sizeof(*b.codes) * (size_t)b.ncodes);
|
||||
ok = (p->omitted_codes != NULL);
|
||||
p->ncodes = ok ? b.ncodes : 0;
|
||||
}
|
||||
p->omitted_hosts = b.omitted.n;
|
||||
if (!ok) {
|
||||
rec->incomplete = true; /* design "Error Strategy" */
|
||||
}
|
||||
free(keep);
|
||||
free(b.codes);
|
||||
free((void*)b.kept.slot);
|
||||
free((void*)b.omitted.slot);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,19 @@
|
|||
* unmet rows, stored after the host's verdict line, and they set its
|
||||
* SHORTFALL and/or LIMIT verdict.
|
||||
*
|
||||
* Task 2.3 covers the decision outcomes (Requirements 6.1-6.4, 9.4) and the
|
||||
* reconstructed-dispatch record (2.5), over the same fixture plus a workers
|
||||
* table:
|
||||
* - a dispatch keeps per-host and shared resource rows apart from the
|
||||
* limit charges, each charge resolved through the job's matched limits
|
||||
* (or, unresolved, parsed from its name with an unknown limited
|
||||
* resource: the raw-number fallback);
|
||||
* - past 50 hosts, the rest become per-resource and per-limit totals and
|
||||
* an omitted-host count; a pend keeps the first 50 hosts' entries and
|
||||
* the host-less ones and counts the rest per pend-reason code;
|
||||
* - with 50 or fewer hosts the pend copy equals its source under
|
||||
* job_preason_eq(), the equality libxce defines for pend reasons.
|
||||
*
|
||||
* Links src/sched_trace.c and src/sched_trace_capture.c, plus libxce for
|
||||
* the pend-reason, resmgr and container helpers.
|
||||
*/
|
||||
|
|
@ -525,6 +538,7 @@ typedef struct {
|
|||
xce_vres_t vres_lic;
|
||||
xce_vres_t vres_host;
|
||||
xce_vres_t vres_hcap;
|
||||
xce_sched_worker_t w_bare; /* n02: registered, no matched host limits */
|
||||
} rows_fx_t;
|
||||
|
||||
#define ROWS_HOST_IDX 2
|
||||
|
|
@ -593,6 +607,25 @@ static rows_fx_t* rows_fx_new(void)
|
|||
|
||||
fx->w.name = "n01";
|
||||
fx->w.idx = ROWS_HOST_IDX;
|
||||
|
||||
/* workers by name, for the dispatch capture's host-limit lookup */
|
||||
{
|
||||
kvmgr_params_t wk_params = { .key_type = KVMGR_KEY_STRING,
|
||||
.nslots = 16,
|
||||
.delta = 8,
|
||||
.datakiller = NULL };
|
||||
|
||||
fx->core.workers = makeInstance(&xkvmgr_type, &wk_params);
|
||||
TEST_ASSERT_NOT_NULL(fx->core.workers);
|
||||
fx->w_bare.name = "n02";
|
||||
TEST_ASSERT_TRUE(key_add(fx->core.workers, (void*)fx->w.name, &fx->w)
|
||||
>= 0);
|
||||
TEST_ASSERT_TRUE(key_add(fx->core.workers, (void*)fx->w_bare.name,
|
||||
&fx->w_bare)
|
||||
>= 0);
|
||||
fx->w.idx = ROWS_HOST_IDX; /* key_add's slot is not the idx */
|
||||
fx->w_bare.idx = ROWS_HOST_IDX + 1;
|
||||
}
|
||||
return fx;
|
||||
}
|
||||
|
||||
|
|
@ -1023,6 +1056,510 @@ static void test_check_rows_host_rows_count_against_budget(void)
|
|||
rows_fx_free(fx);
|
||||
}
|
||||
|
||||
/* ---- decision outcomes (6.1-6.4, 9.4) and reconstructed records (2.5) - */
|
||||
|
||||
typedef struct {
|
||||
const char* res;
|
||||
double value;
|
||||
} t_av_t;
|
||||
|
||||
static xce_alloc_t* t_alloc(const char* res, double value)
|
||||
{
|
||||
xce_alloc_t* a = (xce_alloc_t*)calloc(1, sizeof(*a));
|
||||
|
||||
TEST_ASSERT_NOT_NULL(a);
|
||||
a->resname = strdup(res);
|
||||
a->reserve = strdup("");
|
||||
a->value = value;
|
||||
return a;
|
||||
}
|
||||
|
||||
static xce_alloc_t** t_allocs(int n, const t_av_t* av)
|
||||
{
|
||||
xce_alloc_t** v = (xce_alloc_t**)calloc((size_t)n + 1, sizeof(*v));
|
||||
int i;
|
||||
|
||||
TEST_ASSERT_NOT_NULL(v);
|
||||
for (i = 0; i < n; i++) {
|
||||
v[i] = t_alloc(av[i].res, av[i].value);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
static xce_host_alloc_t* t_host(const char* host, int n, const t_av_t* av)
|
||||
{
|
||||
xce_host_alloc_t* h = (xce_host_alloc_t*)calloc(1, sizeof(*h));
|
||||
|
||||
TEST_ASSERT_NOT_NULL(h);
|
||||
h->host = strdup(host);
|
||||
h->nallocs = n;
|
||||
h->allocs = t_allocs(n, av);
|
||||
return h;
|
||||
}
|
||||
|
||||
static void t_free_allocs(int n, xce_alloc_t** v)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; (v != NULL) && (i < n); i++) {
|
||||
if (v[i] != NULL) {
|
||||
free(v[i]->resname);
|
||||
free(v[i]->reserve);
|
||||
free(v[i]);
|
||||
}
|
||||
}
|
||||
free(v);
|
||||
}
|
||||
|
||||
static void t_free_job(xce_job_alloc_t* j)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < j->nhosts; i++) {
|
||||
t_free_allocs(j->allocs[i]->nallocs, j->allocs[i]->allocs);
|
||||
free(j->allocs[i]->host);
|
||||
free(j->allocs[i]);
|
||||
}
|
||||
free(j->allocs);
|
||||
t_free_allocs(j->nres_shared, j->allocs_shared);
|
||||
free(j);
|
||||
}
|
||||
|
||||
static xce_job_alloc_t* t_job(int nhosts)
|
||||
{
|
||||
xce_job_alloc_t* j = (xce_job_alloc_t*)calloc(1, sizeof(*j));
|
||||
|
||||
TEST_ASSERT_NOT_NULL(j);
|
||||
j->jobid = 0x77;
|
||||
j->nhosts = nhosts;
|
||||
j->allocs = (xce_host_alloc_t**)calloc((size_t)nhosts + 1,
|
||||
sizeof(*j->allocs));
|
||||
TEST_ASSERT_NOT_NULL(j->allocs);
|
||||
return j;
|
||||
}
|
||||
|
||||
static void assert_alloc_row(const sched_trace_alloc_row_t* row,
|
||||
const char* res, double value)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_STRING(res, row->res);
|
||||
TEST_ASSERT_EQUAL_FLOAT((float)value, (float)row->value);
|
||||
TEST_ASSERT_NULL(row->limit);
|
||||
TEST_ASSERT_NULL(row->scope);
|
||||
TEST_ASSERT_NULL(row->limited_res);
|
||||
}
|
||||
|
||||
static void assert_charge(const sched_trace_alloc_row_t* row, const char* res,
|
||||
double value, const char* limit, const char* scope,
|
||||
const char* limited)
|
||||
{
|
||||
if (res == NULL) {
|
||||
TEST_ASSERT_NULL(row->res);
|
||||
} else {
|
||||
TEST_ASSERT_EQUAL_STRING(res, row->res);
|
||||
}
|
||||
TEST_ASSERT_EQUAL_FLOAT((float)value, (float)row->value);
|
||||
TEST_ASSERT_EQUAL_STRING(limit, row->limit);
|
||||
TEST_ASSERT_EQUAL_STRING(scope, row->scope);
|
||||
if (limited == NULL) {
|
||||
TEST_ASSERT_NULL(row->limited_res);
|
||||
} else {
|
||||
TEST_ASSERT_EQUAL_STRING(limited, row->limited_res);
|
||||
}
|
||||
}
|
||||
|
||||
/* Per-host rows and shared rows, limit charges listed apart: resolved
|
||||
* through obj->hlimits[w->idx] (w by host name) and obj->glimits; a
|
||||
* registered limit flag or, for an unregistered name, the "limit=" prefix
|
||||
* makes a charge; an unresolved charge keeps its parsed name and scope and
|
||||
* an unknown limited resource, so it renders as a raw number (6.1-6.3). */
|
||||
static void test_dispatch_separates_limit_charges(void)
|
||||
{
|
||||
rows_fx_t* fx = rows_fx_new();
|
||||
xce_sched_trace_ctl_t ctl = make_ctl("fcfs");
|
||||
xce_sched_trace_rec_t* rec;
|
||||
xce_job_alloc_t* job = t_job(2);
|
||||
const t_av_t h1[] = { { "mem", 8 }, { "limit=host_slots/u=alice", 4 },
|
||||
{ "slots", 4 }, { "limit=hcap", 4 } };
|
||||
const t_av_t h2[] = { { "mem", 2 }, { "limit=host_slots/u=alice", 1 },
|
||||
{ "gpu", 1 }, { "limit=ghost/u=alice", 1 } };
|
||||
const t_av_t sh[] = { { "lic", 1 },
|
||||
{ "limit=lic_cap/u=alice/q=normal", 1 },
|
||||
{ "limit=site_total/p=proj1", 1 } };
|
||||
const sched_trace_dispatch_out_t* d;
|
||||
|
||||
job->allocs[0] = t_host("n01", 4, h1);
|
||||
job->allocs[1] = t_host("n02", 4, h2);
|
||||
job->nres_shared = 3;
|
||||
job->allocs_shared = t_allocs(3, sh);
|
||||
|
||||
rec = sched_trace_rec_new(&ctl, 0x77, XCE_TRACE_REC_DECISION);
|
||||
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_OUTCOME_NONE, rec->outcome);
|
||||
sched_trace_capture_dispatch(rec, &fx->core, &fx->obj, job);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_OUTCOME_DISPATCH, rec->outcome);
|
||||
TEST_ASSERT_EQUAL_INT(0, rec->nentries); /* outside the line budget */
|
||||
d = &rec->dispatch;
|
||||
TEST_ASSERT_EQUAL_INT(2, d->nhosts);
|
||||
TEST_ASSERT_EQUAL_INT(0, d->omitted_hosts);
|
||||
TEST_ASSERT_EQUAL_INT(0, d->nagg_res);
|
||||
TEST_ASSERT_EQUAL_INT(0, d->nagg_charges);
|
||||
|
||||
/* n01: resources in allocation order, charges resolved through its
|
||||
* host limits, the host appended to the scope */
|
||||
TEST_ASSERT_EQUAL_STRING("n01", d->hosts[0].host);
|
||||
TEST_ASSERT_EQUAL_INT(2, d->hosts[0].nres);
|
||||
assert_alloc_row(&d->hosts[0].res[0], "mem", 8);
|
||||
assert_alloc_row(&d->hosts[0].res[1], "slots", 4);
|
||||
TEST_ASSERT_EQUAL_INT(2, d->hosts[0].ncharges);
|
||||
assert_charge(&d->hosts[0].charges[0], "limit=host_slots/u=alice", 4,
|
||||
"host_slots", "u=alice,h=n01", "slots");
|
||||
assert_charge(&d->hosts[0].charges[1], "limit=hcap", 4, "hcap", "h=n01",
|
||||
"slots");
|
||||
|
||||
/* n02 has no matched host limits: the charge is unresolved; "gpu" is
|
||||
* not registered and has no limit prefix: a resource; "limit=ghost..."
|
||||
* is not registered: a charge by its prefix */
|
||||
TEST_ASSERT_EQUAL_STRING("n02", d->hosts[1].host);
|
||||
TEST_ASSERT_EQUAL_INT(2, d->hosts[1].nres);
|
||||
assert_alloc_row(&d->hosts[1].res[0], "mem", 2);
|
||||
assert_alloc_row(&d->hosts[1].res[1], "gpu", 1);
|
||||
TEST_ASSERT_EQUAL_INT(2, d->hosts[1].ncharges);
|
||||
assert_charge(&d->hosts[1].charges[0], "limit=host_slots/u=alice", 1,
|
||||
"host_slots", "u=alice,h=n02", NULL);
|
||||
assert_charge(&d->hosts[1].charges[1], "limit=ghost/u=alice", 1, "ghost",
|
||||
"u=alice,h=n02", NULL);
|
||||
|
||||
/* shared: once for the job, charges resolved through glimits */
|
||||
TEST_ASSERT_EQUAL_INT(1, d->nshared);
|
||||
assert_alloc_row(&d->shared[0], "lic", 1);
|
||||
TEST_ASSERT_EQUAL_INT(2, d->nshared_charges);
|
||||
assert_charge(&d->shared_charges[0], "limit=lic_cap/u=alice/q=normal", 1,
|
||||
"lic_cap", "u=alice,q=normal", "lic");
|
||||
assert_charge(&d->shared_charges[1], "limit=site_total/p=proj1", 1,
|
||||
"site_total", "p=proj1", NULL);
|
||||
|
||||
/* a self-contained copy */
|
||||
TEST_ASSERT_TRUE(in_arena(rec, d->hosts));
|
||||
TEST_ASSERT_TRUE(in_arena(rec, d->hosts[0].host));
|
||||
TEST_ASSERT_TRUE(in_arena(rec, d->hosts[0].res[0].res));
|
||||
TEST_ASSERT_TRUE(in_arena(rec, d->hosts[0].charges[0].limit));
|
||||
TEST_ASSERT_TRUE(in_arena(rec, d->hosts[0].charges[0].scope));
|
||||
TEST_ASSERT_TRUE(in_arena(rec, d->hosts[0].charges[0].limited_res));
|
||||
TEST_ASSERT_TRUE(in_arena(rec, d->shared_charges[0].limited_res));
|
||||
t_free_job(job);
|
||||
rows_fx_free(fx);
|
||||
TEST_ASSERT_EQUAL_STRING("n01", d->hosts[0].host);
|
||||
TEST_ASSERT_EQUAL_STRING("u=alice,h=n01", d->hosts[0].charges[0].scope);
|
||||
TEST_ASSERT_EQUAL_STRING("lic", d->shared_charges[0].limited_res);
|
||||
TEST_ASSERT_FALSE(rec->incomplete);
|
||||
sched_trace_rec_free(rec);
|
||||
}
|
||||
|
||||
/* No schedobj (a job this replay never evaluated): every charge is still
|
||||
* listed as a charge, by its flag, with the name and scope parsed from the
|
||||
* resource name and the amount left as a raw number (design "TraceCapture",
|
||||
* the raw-number fallback). */
|
||||
static void test_dispatch_raw_number_fallback(void)
|
||||
{
|
||||
rows_fx_t* fx = rows_fx_new();
|
||||
xce_sched_trace_ctl_t ctl = make_ctl("fcfs");
|
||||
xce_sched_trace_rec_t* rec;
|
||||
xce_job_alloc_t* job = t_job(1);
|
||||
const t_av_t h1[] = { { "limit=host_slots/u=alice", 3 }, { "mem", 1 } };
|
||||
const t_av_t sh[] = { { "limit=lic_cap/u=alice/q=normal", 2 } };
|
||||
const sched_trace_dispatch_out_t* d;
|
||||
|
||||
job->allocs[0] = t_host("n01", 2, h1);
|
||||
job->nres_shared = 1;
|
||||
job->allocs_shared = t_allocs(1, sh);
|
||||
|
||||
rec = sched_trace_rec_new(&ctl, 0x77, XCE_TRACE_REC_DECISION);
|
||||
sched_trace_capture_dispatch(rec, &fx->core, NULL, job);
|
||||
d = &rec->dispatch;
|
||||
TEST_ASSERT_EQUAL_INT(1, d->hosts[0].nres);
|
||||
assert_alloc_row(&d->hosts[0].res[0], "mem", 1);
|
||||
TEST_ASSERT_EQUAL_INT(1, d->hosts[0].ncharges);
|
||||
assert_charge(&d->hosts[0].charges[0], "limit=host_slots/u=alice", 3,
|
||||
"host_slots", "u=alice,h=n01", NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, d->nshared);
|
||||
TEST_ASSERT_EQUAL_INT(1, d->nshared_charges);
|
||||
assert_charge(&d->shared_charges[0], "limit=lic_cap/u=alice/q=normal", 2,
|
||||
"lic_cap", "u=alice,q=normal", NULL);
|
||||
sched_trace_rec_free(rec);
|
||||
|
||||
/* no resmgr entry and no prefix either way: no core at all */
|
||||
rec = sched_trace_rec_new(&ctl, 0x77, XCE_TRACE_REC_DECISION);
|
||||
sched_trace_capture_dispatch(rec, NULL, NULL, job);
|
||||
d = &rec->dispatch;
|
||||
TEST_ASSERT_EQUAL_INT(1, d->hosts[0].nres);
|
||||
TEST_ASSERT_EQUAL_INT(1, d->hosts[0].ncharges);
|
||||
assert_charge(&d->hosts[0].charges[0], "limit=host_slots/u=alice", 3,
|
||||
"host_slots", "u=alice,h=n01", NULL);
|
||||
sched_trace_rec_free(rec);
|
||||
|
||||
t_free_job(job);
|
||||
rows_fx_free(fx);
|
||||
}
|
||||
|
||||
/* A reconstructed dispatch (2.5): a RECONSTRUCTED record with the session
|
||||
* header, no entries and the dispatch outcome, its charges resolved the same
|
||||
* way. */
|
||||
static void test_reconstructed_record_holds_dispatch(void)
|
||||
{
|
||||
rows_fx_t* fx = rows_fx_new();
|
||||
xce_sched_trace_ctl_t ctl = make_ctl("fcfs");
|
||||
xce_sched_trace_rec_t* rec;
|
||||
xce_job_alloc_t* job = t_job(1);
|
||||
const t_av_t h1[] = { { "slots", 2 }, { "limit=host_slots/u=alice", 2 } };
|
||||
|
||||
job->allocs[0] = t_host("n01", 2, h1);
|
||||
|
||||
TEST_ASSERT_NULL(
|
||||
sched_trace_rec_reconstructed(NULL, &fx->core, 0x99, &fx->obj, job));
|
||||
rec = sched_trace_rec_reconstructed(&ctl, &fx->core, 0x99, &fx->obj, job);
|
||||
TEST_ASSERT_NOT_NULL(rec);
|
||||
TEST_ASSERT_EQUAL_INT(XCE_TRACE_REC_RECONSTRUCTED, rec->kind);
|
||||
TEST_ASSERT_EQUAL_UINT64(0x99, rec->jobid);
|
||||
TEST_ASSERT_EQUAL_UINT64(2, rec->session_no);
|
||||
TEST_ASSERT_EQUAL_INT(0, rec->nentries);
|
||||
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_OUTCOME_DISPATCH, rec->outcome);
|
||||
TEST_ASSERT_EQUAL_INT(1, rec->dispatch.nhosts);
|
||||
assert_alloc_row(&rec->dispatch.hosts[0].res[0], "slots", 2);
|
||||
assert_charge(&rec->dispatch.hosts[0].charges[0],
|
||||
"limit=host_slots/u=alice", 2, "host_slots", "u=alice,h=n01",
|
||||
"slots");
|
||||
t_free_job(job);
|
||||
rows_fx_free(fx);
|
||||
TEST_ASSERT_EQUAL_STRING("n01", rec->dispatch.hosts[0].host);
|
||||
sched_trace_rec_free(rec);
|
||||
|
||||
/* a job with no schedobj: still built, charges unresolved */
|
||||
fx = rows_fx_new();
|
||||
job = t_job(1);
|
||||
job->allocs[0] = t_host("n01", 2, h1);
|
||||
rec = sched_trace_rec_reconstructed(&ctl, &fx->core, 0x99, NULL, job);
|
||||
TEST_ASSERT_NOT_NULL(rec);
|
||||
TEST_ASSERT_NULL(rec->dispatch.hosts[0].charges[0].limited_res);
|
||||
sched_trace_rec_free(rec);
|
||||
t_free_job(job);
|
||||
rows_fx_free(fx);
|
||||
}
|
||||
|
||||
/* More than 50 hosts: the first 50 in detail, the rest as an omitted-host
|
||||
* count, per-resource totals without the limits, and per-limit totals with
|
||||
* the host-free scope (9.4). */
|
||||
static void test_dispatch_aggregates_hosts_past_50(void)
|
||||
{
|
||||
rows_fx_t* fx = rows_fx_new();
|
||||
xce_sched_trace_ctl_t ctl = make_ctl("fcfs");
|
||||
xce_sched_trace_rec_t* rec;
|
||||
int nh;
|
||||
|
||||
for (nh = XCE_TRACE_OUTCOME_HOSTS + 1; nh <= XCE_TRACE_OUTCOME_HOSTS + 3;
|
||||
nh += 2) {
|
||||
xce_job_alloc_t* job = t_job(nh);
|
||||
const sched_trace_dispatch_out_t* d;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nh; i++) {
|
||||
char name[16];
|
||||
t_av_t av[3] = { { "mem", 2 + i },
|
||||
{ "limit=host_slots/u=alice", 1 },
|
||||
{ "slots", 1 } };
|
||||
|
||||
snprintf(name, sizeof(name), "h%02d", i);
|
||||
job->allocs[i] = t_host(name, 3, av);
|
||||
}
|
||||
rec = sched_trace_rec_new(&ctl, 0x77, XCE_TRACE_REC_DECISION);
|
||||
sched_trace_capture_dispatch(rec, &fx->core, &fx->obj, job);
|
||||
d = &rec->dispatch;
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(XCE_TRACE_OUTCOME_HOSTS, d->nhosts);
|
||||
TEST_ASSERT_EQUAL_STRING("h49", d->hosts[49].host);
|
||||
TEST_ASSERT_EQUAL_INT(1, d->hosts[49].ncharges);
|
||||
TEST_ASSERT_EQUAL_INT(nh - XCE_TRACE_OUTCOME_HOSTS, d->omitted_hosts);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(2, d->nagg_res);
|
||||
if (nh == XCE_TRACE_OUTCOME_HOSTS + 1) {
|
||||
assert_alloc_row(&d->agg_res[0], "mem", 52); /* h50 only */
|
||||
} else {
|
||||
assert_alloc_row(&d->agg_res[0], "mem", 52 + 53 + 54);
|
||||
}
|
||||
assert_alloc_row(&d->agg_res[1], "slots", nh - XCE_TRACE_OUTCOME_HOSTS);
|
||||
TEST_ASSERT_EQUAL_INT(1, d->nagg_charges);
|
||||
assert_charge(&d->agg_charges[0], NULL, nh - XCE_TRACE_OUTCOME_HOSTS,
|
||||
"host_slots", "u=alice", NULL);
|
||||
TEST_ASSERT_TRUE(in_arena(rec, d->agg_res[0].res));
|
||||
TEST_ASSERT_TRUE(in_arena(rec, d->agg_charges[0].limit));
|
||||
t_free_job(job);
|
||||
TEST_ASSERT_EQUAL_STRING("mem", d->agg_res[0].res);
|
||||
sched_trace_rec_free(rec);
|
||||
}
|
||||
|
||||
/* exactly 50: nothing omitted */
|
||||
{
|
||||
xce_job_alloc_t* job = t_job(XCE_TRACE_OUTCOME_HOSTS);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < XCE_TRACE_OUTCOME_HOSTS; i++) {
|
||||
char name[16];
|
||||
t_av_t av[1] = { { "mem", 1 } };
|
||||
|
||||
snprintf(name, sizeof(name), "h%02d", i);
|
||||
job->allocs[i] = t_host(name, 1, av);
|
||||
}
|
||||
rec = sched_trace_rec_new(&ctl, 0x77, XCE_TRACE_REC_DECISION);
|
||||
sched_trace_capture_dispatch(rec, &fx->core, &fx->obj, job);
|
||||
TEST_ASSERT_EQUAL_INT(XCE_TRACE_OUTCOME_HOSTS, rec->dispatch.nhosts);
|
||||
TEST_ASSERT_EQUAL_INT(0, rec->dispatch.omitted_hosts);
|
||||
TEST_ASSERT_EQUAL_INT(0, rec->dispatch.nagg_res);
|
||||
sched_trace_rec_free(rec);
|
||||
t_free_job(job);
|
||||
}
|
||||
rows_fx_free(fx);
|
||||
}
|
||||
|
||||
static void test_dispatch_null_alloc_is_empty(void)
|
||||
{
|
||||
xce_sched_trace_ctl_t ctl = make_ctl("fcfs");
|
||||
xce_sched_trace_rec_t* rec = sched_trace_rec_new(&ctl, 0x77,
|
||||
XCE_TRACE_REC_DECISION);
|
||||
|
||||
sched_trace_capture_dispatch(rec, NULL, NULL, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_OUTCOME_DISPATCH, rec->outcome);
|
||||
TEST_ASSERT_EQUAL_INT(0, rec->dispatch.nhosts);
|
||||
TEST_ASSERT_EQUAL_INT(0, rec->dispatch.nshared);
|
||||
TEST_ASSERT_FALSE(rec->incomplete);
|
||||
sched_trace_rec_free(rec);
|
||||
}
|
||||
|
||||
/* 50 or fewer hosts: the pend copy is the decision's set, equal under the
|
||||
* scheduler's own pend-reason equality, deep-copied into the arena (6.4). */
|
||||
static void test_pend_copy_equals_source(void)
|
||||
{
|
||||
xce_sched_trace_ctl_t ctl = make_ctl("fcfs");
|
||||
xce_sched_trace_rec_t* rec = sched_trace_rec_new(&ctl, 0x55,
|
||||
XCE_TRACE_REC_DECISION);
|
||||
xce_pendreason_t* r = make_pendreason(0x55);
|
||||
char* why = NULL;
|
||||
int i;
|
||||
|
||||
add_job_preason(r->reasons, PREASON_BAD_RESREQ, NULL, NULL); /* main */
|
||||
for (i = 0; i < XCE_TRACE_OUTCOME_HOSTS; i++) {
|
||||
char name[16];
|
||||
|
||||
snprintf(name, sizeof(name), "h%02d", i);
|
||||
add_job_preason(r->reasons, PREASON_MATCH_FAIL, name, NULL);
|
||||
if ((i % 7) == 0) {
|
||||
add_job_preason(r->reasons, PREASON_NO_RESOURCE, name, "mem");
|
||||
}
|
||||
}
|
||||
add_job_preason(r->reasons, PREASON_NO_RESOURCE, NULL, "lic"); /* shared */
|
||||
|
||||
sched_trace_capture_pend(rec, r);
|
||||
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_OUTCOME_PEND, rec->outcome);
|
||||
TEST_ASSERT_EQUAL_INT(0, rec->nentries);
|
||||
TEST_ASSERT_NOT_NULL(rec->pend.reasons);
|
||||
TEST_ASSERT_TRUE_MESSAGE(job_preason_eq(rec->pend.reasons, r->reasons,
|
||||
&why),
|
||||
(why != NULL) ? why : "");
|
||||
TEST_ASSERT_EQUAL_INT(0, rec->pend.omitted_hosts);
|
||||
TEST_ASSERT_EQUAL_INT(0, rec->pend.ncodes);
|
||||
|
||||
/* no aliasing: every level is the record's own */
|
||||
TEST_ASSERT_TRUE(in_arena(rec, rec->pend.reasons));
|
||||
TEST_ASSERT_TRUE(in_arena(rec, rec->pend.reasons->host_based_reasons));
|
||||
TEST_ASSERT_TRUE(in_arena(rec, rec->pend.reasons->host_based_reasons[0]));
|
||||
TEST_ASSERT_TRUE(
|
||||
in_arena(rec, rec->pend.reasons->host_based_reasons[0]->host));
|
||||
TEST_ASSERT_TRUE(in_arena(rec, rec->pend.reasons->res_based_reasons[0]));
|
||||
TEST_ASSERT_TRUE(
|
||||
in_arena(rec, rec->pend.reasons->res_based_reasons[0]->resource));
|
||||
free_pendreason(r);
|
||||
TEST_ASSERT_EQUAL_INT(PREASON_BAD_RESREQ, rec->pend.reasons->main_reason);
|
||||
TEST_ASSERT_EQUAL_STRING("h49",
|
||||
rec->pend.reasons->host_based_reasons[49]->host);
|
||||
sched_trace_rec_free(rec);
|
||||
|
||||
/* main reason only, and an empty set: still equal */
|
||||
r = make_pendreason(0x55);
|
||||
rec = sched_trace_rec_new(&ctl, 0x55, XCE_TRACE_REC_DECISION);
|
||||
sched_trace_capture_pend(rec, r);
|
||||
TEST_ASSERT_TRUE(job_preason_eq(rec->pend.reasons, r->reasons, NULL));
|
||||
sched_trace_rec_free(rec);
|
||||
free_pendreason(r);
|
||||
}
|
||||
|
||||
/* More than 50 distinct hosts: the main reason, host-less entries and
|
||||
* every entry of the first 50 hosts (also later ones) are kept; the other
|
||||
* hosts' entries are counted per pend-reason code (9.4). */
|
||||
static void test_pend_aggregates_hosts_past_50(void)
|
||||
{
|
||||
xce_sched_trace_ctl_t ctl = make_ctl("fcfs");
|
||||
xce_sched_trace_rec_t* rec = sched_trace_rec_new(&ctl, 0x55,
|
||||
XCE_TRACE_REC_DECISION);
|
||||
xce_pendreason_t* r = make_pendreason(0x55);
|
||||
const xce_job_preason_t* c;
|
||||
int i;
|
||||
|
||||
add_job_preason(r->reasons, PREASON_BAD_RESREQ, NULL, NULL);
|
||||
for (i = 0; i < XCE_TRACE_OUTCOME_HOSTS + 1; i++) {
|
||||
char name[16];
|
||||
|
||||
snprintf(name, sizeof(name), "h%02d", i);
|
||||
add_job_preason(r->reasons, PREASON_MATCH_FAIL, name, NULL);
|
||||
}
|
||||
/* resource entries: a kept host, the omitted host, a host-less one */
|
||||
add_job_preason(r->reasons, PREASON_NO_RESOURCE, "h03", "mem");
|
||||
add_job_preason(r->reasons, PREASON_NO_RESOURCE, "h50", "mem");
|
||||
add_job_preason(r->reasons, PREASON_NO_RESOURCE, NULL, "lic");
|
||||
|
||||
sched_trace_capture_pend(rec, r);
|
||||
c = rec->pend.reasons;
|
||||
TEST_ASSERT_NOT_NULL(c);
|
||||
TEST_ASSERT_EQUAL_INT(PREASON_BAD_RESREQ, c->main_reason);
|
||||
TEST_ASSERT_EQUAL_INT(XCE_TRACE_OUTCOME_HOSTS, c->nhost_reasons);
|
||||
TEST_ASSERT_EQUAL_STRING("h00", c->host_based_reasons[0]->host);
|
||||
TEST_ASSERT_EQUAL_STRING("h49", c->host_based_reasons[49]->host);
|
||||
TEST_ASSERT_EQUAL_INT(2, c->nres_reasons);
|
||||
TEST_ASSERT_EQUAL_STRING("h03", c->res_based_reasons[0]->host);
|
||||
TEST_ASSERT_EQUAL_STRING("mem", c->res_based_reasons[0]->resource);
|
||||
TEST_ASSERT_EQUAL_STRING("", c->res_based_reasons[1]->host); /* shared */
|
||||
TEST_ASSERT_EQUAL_STRING("lic", c->res_based_reasons[1]->resource);
|
||||
TEST_ASSERT_EQUAL_INT(PREASON_NO_RESOURCE, c->res_based_reasons[1]->reason);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(1, rec->pend.omitted_hosts);
|
||||
TEST_ASSERT_EQUAL_INT(2, rec->pend.ncodes);
|
||||
TEST_ASSERT_EQUAL_INT(PREASON_MATCH_FAIL, rec->pend.omitted_codes[0].code);
|
||||
TEST_ASSERT_EQUAL_INT(1, rec->pend.omitted_codes[0].count);
|
||||
TEST_ASSERT_EQUAL_INT(PREASON_NO_RESOURCE, rec->pend.omitted_codes[1].code);
|
||||
TEST_ASSERT_EQUAL_INT(1, rec->pend.omitted_codes[1].count);
|
||||
TEST_ASSERT_TRUE(in_arena(rec, rec->pend.omitted_codes));
|
||||
free_pendreason(r);
|
||||
TEST_ASSERT_EQUAL_STRING("h49", c->host_based_reasons[49]->host);
|
||||
sched_trace_rec_free(rec);
|
||||
}
|
||||
|
||||
static void test_pend_null_reason_has_no_set(void)
|
||||
{
|
||||
xce_sched_trace_ctl_t ctl = make_ctl("fcfs");
|
||||
xce_sched_trace_rec_t* rec = sched_trace_rec_new(&ctl, 0x55,
|
||||
XCE_TRACE_REC_DECISION);
|
||||
xce_pendreason_t empty;
|
||||
|
||||
sched_trace_capture_pend(rec, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(SCHED_TRACE_OUTCOME_PEND, rec->outcome);
|
||||
TEST_ASSERT_NULL(rec->pend.reasons);
|
||||
sched_trace_rec_free(rec);
|
||||
|
||||
memset(&empty, 0, sizeof(empty));
|
||||
rec = sched_trace_rec_new(&ctl, 0x55, XCE_TRACE_REC_DECISION);
|
||||
sched_trace_capture_pend(rec, &empty);
|
||||
TEST_ASSERT_NULL(rec->pend.reasons);
|
||||
TEST_ASSERT_FALSE(rec->incomplete);
|
||||
sched_trace_rec_free(rec);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
xstring_table_init(1024);
|
||||
|
|
@ -1048,5 +1585,13 @@ int main(void)
|
|||
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);
|
||||
RUN_TEST(test_dispatch_separates_limit_charges);
|
||||
RUN_TEST(test_dispatch_raw_number_fallback);
|
||||
RUN_TEST(test_reconstructed_record_holds_dispatch);
|
||||
RUN_TEST(test_dispatch_aggregates_hosts_past_50);
|
||||
RUN_TEST(test_dispatch_null_alloc_is_empty);
|
||||
RUN_TEST(test_pend_copy_equals_source);
|
||||
RUN_TEST(test_pend_aggregates_hosts_past_50);
|
||||
RUN_TEST(test_pend_null_reason_has_no_set);
|
||||
return UNITY_END();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue