fix(scheduler): gate resource value/used display on XCE_RESF_CONSUMABLE
repl_info_worker_one() ("info worker <name>") unconditionally showed a
total=/available= pair for every numeric per-worker resource, regardless
of whether it was actually consumable -- misleading for a static
per-worker attribute nothing ever allocates against.
repl_info_resource_one() ("info resource <name|all>") never reported a
consumable GLOBAL/SHARED resource's in-use amount at all, even though
core->sharedInUse (the cluster-wide mirror of a worker's resInUse)
already exists and is populated by the real resource-accounting code.
Both now gate on XCE_RESF_CONSUMABLE: consumable resources report
value=<capacity> used=<in-use>, non-consumable resources keep a single
value=<current> line. Also added the resource definition's missing
ordtype= field.
Add test_info_resource_reports_consumable_value_and_used and
test_info_worker_reports_consumable_value_and_used; update 3 pre-existing
tests whose asserted output shape changed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
51063c1dc8
commit
9473361683
2 changed files with 250 additions and 48 deletions
|
|
@ -278,18 +278,21 @@ static void repl_info_queue(xce_sched_sim_driver_t* sim, const char* arg)
|
|||
|
||||
/*
|
||||
* repl_info_worker_one - AC 22's "info worker <name>": "Dump one worker:
|
||||
* state, resources (total + available), assigned jobs." `w->resvals` holds
|
||||
* currently-AVAILABLE values (the same zarray the picker/allocator
|
||||
* accounting path reads/resets each session, core_events.c); `w->resInUse`
|
||||
* holds the currently-committed amount at the same index -- "total" below
|
||||
* is therefore computed as available+inUse, mirroring how this codebase's
|
||||
* own resource accounting treats the pair as a conservation invariant
|
||||
* (nothing elsewhere sums them into a literal "total" field to reuse).
|
||||
* Resource names are resolved via find_resource_name_by_idx() against a
|
||||
* resmgr walk built the same way xce_sched_core_save_snapshot() builds its
|
||||
* own `res_arr` (snapshot.c) -- not a new lookup mechanism. "assigned jobs"
|
||||
* scans core->jobs for any per-host allocation naming this worker
|
||||
* (xce_job_alloc_t.allocs[]->host, xce/types.h).
|
||||
* state, resources, assigned jobs." `w->resvals` holds currently-AVAILABLE
|
||||
* values (the same zarray the picker/allocator accounting path reads/resets
|
||||
* each session, core_events.c); `w->resInUse` holds the currently-committed
|
||||
* amount at the same index. Only a resource flagged XCE_RESF_CONSUMABLE has
|
||||
* a capacity/in-use split worth reporting -- its value= is available+inUse
|
||||
* (a conservation invariant this codebase's own resource accounting already
|
||||
* relies on) and used= is inUse; a non-consumable numeric resource (a
|
||||
* static per-worker attribute nothing ever allocates against) gets a single
|
||||
* value= line instead, mirroring repl_info_resource_one()'s identical
|
||||
* CONSUMABLE gate for the cluster-wide case. Resource names/flags are
|
||||
* resolved against a resmgr walk built the same way
|
||||
* xce_sched_core_save_snapshot() builds its own `res_arr` (snapshot.c) --
|
||||
* not a new lookup mechanism. "assigned jobs" scans core->jobs for any
|
||||
* per-host allocation naming this worker (xce_job_alloc_t.allocs[]->host,
|
||||
* xce/types.h).
|
||||
*/
|
||||
static void repl_info_worker_one(xce_sched_sim_driver_t* sim,
|
||||
xce_sched_worker_t* w)
|
||||
|
|
@ -319,27 +322,34 @@ static void repl_info_worker_one(xce_sched_sim_driver_t* sim,
|
|||
}
|
||||
|
||||
for (i = 0; i < w->resvals.count; i++) {
|
||||
xce_val_t* avail_v = (xce_val_t*)zarray_get(&(w->resvals), i);
|
||||
xce_val_t* used_v = (xce_val_t*)zarray_get(&(w->resInUse), i);
|
||||
const char* rname = find_resource_name_by_idx(res_arr, res_n, i);
|
||||
xce_val_t* avail_v = (xce_val_t*)zarray_get(&(w->resvals), i);
|
||||
xce_val_t* used_v = (xce_val_t*)zarray_get(&(w->resInUse), i);
|
||||
const char* rname = find_resource_name_by_idx(res_arr, res_n, i);
|
||||
xce_resource_t* rdef = NULL;
|
||||
int j;
|
||||
|
||||
if ((avail_v == NULL) || (rname == NULL)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (j = 0; j < res_n; j++) {
|
||||
if (res_arr[j]->idx == i) {
|
||||
rdef = res_arr[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* bug-1590: xce_val_t.which (a Value_u_which wire tag,
|
||||
* controller.capnp.h) tells us the value's REAL type -- bug-1568
|
||||
* fixed comparing it against the wrong enum for the numeric case,
|
||||
* but left every non-numeric resource (Value_u_svalue/
|
||||
* Value_u_bvalue -- e.g. this worker's `type`/`model` string
|
||||
* resources, or any boolean-typed resource) silently coerced to
|
||||
* 0.0 by the old unconditional %.2f formatting below. "total"/
|
||||
* "available" (a consumable-quantity concept: how much of this
|
||||
* worker's capacity is free vs. in use) is only meaningful for a
|
||||
* NUMERIC resource -- a string or boolean resource is a single
|
||||
* static-ish per-worker attribute with no "in use" counterpart, so
|
||||
* it gets its own "value=" line instead of a fabricated
|
||||
* total/available pair. */
|
||||
* 0.0 by the old unconditional %.2f formatting below. A value/used
|
||||
* split is only meaningful for a CONSUMABLE numeric resource -- a
|
||||
* string/boolean resource, or a non-consumable numeric one (a
|
||||
* static per-worker attribute nothing ever allocates against), gets
|
||||
* its own single "value=" line instead. */
|
||||
switch (avail_v->which) {
|
||||
case Value_u_svalue:
|
||||
fprintf(stdout, "[sim] resource %s: value=%s\n", rname,
|
||||
|
|
@ -351,17 +361,22 @@ static void repl_info_worker_one(xce_sched_sim_driver_t* sim,
|
|||
avail_v->u.bvalue ? "true" : "false");
|
||||
break;
|
||||
|
||||
case Value_u_fvalue: {
|
||||
double avail = avail_v->u.fvalue;
|
||||
double used = ((used_v != NULL) && (used_v->which == Value_u_fvalue))
|
||||
? used_v->u.fvalue
|
||||
: 0.0;
|
||||
case Value_u_fvalue:
|
||||
if ((rdef != NULL) && (rdef->flags & XCE_RESF_CONSUMABLE)) {
|
||||
double avail = avail_v->u.fvalue;
|
||||
double used = ((used_v != NULL) &&
|
||||
(used_v->which == Value_u_fvalue))
|
||||
? used_v->u.fvalue
|
||||
: 0.0;
|
||||
|
||||
fprintf(stdout,
|
||||
"[sim] resource %s: total=%.2f available=%.2f\n", rname,
|
||||
avail + used, avail);
|
||||
fprintf(stdout,
|
||||
"[sim] resource %s: value=%.2f used=%.2f\n", rname,
|
||||
avail + used, used);
|
||||
} else {
|
||||
fprintf(stdout, "[sim] resource %s: value=%.2f\n", rname,
|
||||
avail_v->u.fvalue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
/* ivalue32/uvalue32/ivalue64/uvalue64 -- not a shape this
|
||||
|
|
@ -487,21 +502,27 @@ static void repl_resource_flags_str(int flags, char* buf, size_t bufsz)
|
|||
/*
|
||||
* repl_info_resource_one - AC 22 amendment (task 12.5, bug-1593): "info
|
||||
* resource <name>" reports the resource DEFINITION's own metadata (desc,
|
||||
* value type, XCE_RESF_* flags, reporting interval) -- never a per-host
|
||||
* aggregation, which is repl_info_worker_one()'s job. A resource flagged
|
||||
* XCE_RESF_GLOBAL or XCE_RESF_SHARED additionally gets its current value,
|
||||
* read directly from the core's single defining pool (`core->sharedInst`,
|
||||
* indexed by `r->idx` -- the same pool RESVALS_FULL/RESVALS_DELTA event
|
||||
* handling and snapshot save/load already treat as authoritative for these
|
||||
* resources, core_events.c/snapshot.c). A host-based resource (the common
|
||||
* case -- cpus, mem, ...) has no single cluster-wide value to report here;
|
||||
* its per-host values remain `info worker <name>`'s job.
|
||||
* value type, order type, XCE_RESF_* flags, reporting interval) -- never a
|
||||
* per-host aggregation, which is repl_info_worker_one()'s job. A resource
|
||||
* flagged XCE_RESF_GLOBAL or XCE_RESF_SHARED additionally gets its current
|
||||
* value, read directly from the core's single defining pool
|
||||
* (`core->sharedInst`, indexed by `r->idx` -- the same pool RESVALS_FULL/
|
||||
* RESVALS_DELTA event handling and snapshot save/load already treat as
|
||||
* authoritative for these resources, core_events.c/snapshot.c). A host-based
|
||||
* resource (the common case -- cpus, mem, ...) has no single cluster-wide
|
||||
* value to report here; its per-host values remain `info worker <name>`'s
|
||||
* job. If the resource is additionally XCE_RESF_CONSUMABLE, the numeric
|
||||
* value line also reports how much is currently in use, read from
|
||||
* `core->sharedInUse` at the same index (mirrors repl_info_worker_one()'s
|
||||
* resInUse-derived split below) -- a non-consumable numeric resource has no
|
||||
* "used" counterpart, so it keeps a single value= line.
|
||||
*/
|
||||
static void repl_info_resource_one(xce_sched_sim_driver_t* sim,
|
||||
xce_resource_t* r)
|
||||
{
|
||||
char flags_str[160];
|
||||
const char* valtype_str;
|
||||
const char* ordtype_str;
|
||||
|
||||
switch (r->valtype) {
|
||||
case XCE_VAL_STRING:
|
||||
|
|
@ -515,12 +536,27 @@ static void repl_info_resource_one(xce_sched_sim_driver_t* sim,
|
|||
valtype_str = "numeric";
|
||||
break;
|
||||
}
|
||||
|
||||
switch (r->ordtype) {
|
||||
case XCE_ORD_INCR:
|
||||
ordtype_str = "incr";
|
||||
break;
|
||||
case XCE_ORD_DECR:
|
||||
ordtype_str = "decr";
|
||||
break;
|
||||
case XCE_ORD_NA:
|
||||
default:
|
||||
ordtype_str = "na";
|
||||
break;
|
||||
}
|
||||
|
||||
repl_resource_flags_str(r->flags, flags_str, sizeof(flags_str));
|
||||
|
||||
fprintf(stdout,
|
||||
"[sim] resource %s: desc=%s valtype=%s flags=%s interval=%d\n",
|
||||
"[sim] resource %s: desc=%s valtype=%s ordtype=%s flags=%s "
|
||||
"interval=%d\n",
|
||||
r->name, (r->desc != NULL) ? r->desc : "(none)", valtype_str,
|
||||
flags_str, r->interval);
|
||||
ordtype_str, flags_str, r->interval);
|
||||
|
||||
if ((r->flags & (XCE_RESF_GLOBAL | XCE_RESF_SHARED)) == 0) {
|
||||
fprintf(stdout, "[sim] host-based resource -- see 'info worker "
|
||||
|
|
@ -551,7 +587,28 @@ static void repl_info_resource_one(xce_sched_sim_driver_t* sim,
|
|||
v->u.bvalue ? "true" : "false");
|
||||
break;
|
||||
case Value_u_fvalue:
|
||||
fprintf(stdout, "[sim] value=%.2f\n", v->u.fvalue);
|
||||
/* A consumable resource has a capacity/in-use split worth
|
||||
* reporting; a non-consumable numeric resource (e.g. a static
|
||||
* cluster-wide attribute) has no "used" counterpart, so it gets
|
||||
* a single value= line like the string/boolean cases above. */
|
||||
if (r->flags & XCE_RESF_CONSUMABLE) {
|
||||
xce_val_t* used_v = NULL;
|
||||
double used = 0.0;
|
||||
|
||||
if ((r->idx >= 0) &&
|
||||
(r->idx < sim->core.sharedInUse.count)) {
|
||||
used_v = (xce_val_t*)zarray_get(&(sim->core.sharedInUse),
|
||||
r->idx);
|
||||
}
|
||||
if ((used_v != NULL) && (used_v->which == Value_u_fvalue)) {
|
||||
used = used_v->u.fvalue;
|
||||
}
|
||||
|
||||
fprintf(stdout, "[sim] value=%.2f used=%.2f\n",
|
||||
v->u.fvalue + used, used);
|
||||
} else {
|
||||
fprintf(stdout, "[sim] value=%.2f\n", v->u.fvalue);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
fprintf(stdout, "[sim] value=(unrecognized wire type)\n");
|
||||
|
|
|
|||
|
|
@ -732,11 +732,12 @@ static void test_info_worker_reports_resource_values(void)
|
|||
captured = capture_dispatch(&sim, "info worker hostA");
|
||||
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||
strstr(captured, "resource mem: total=42.50 available=42.50"),
|
||||
strstr(captured, "resource mem: value=42.50"),
|
||||
"a worker's real numeric resource value must be displayed, not "
|
||||
"the XCE_VAL_NUMERIC/Value_u_fvalue type-tag mismatch's 0.00 "
|
||||
"(bug-1568)");
|
||||
TEST_ASSERT_NULL_MESSAGE(strstr(captured, "total=0.00 available=0.00"),
|
||||
"(bug-1568); a non-consumable resource (no flags set here) gets a "
|
||||
"single value= line, not a value=/used= pair");
|
||||
TEST_ASSERT_NULL_MESSAGE(strstr(captured, "value=0.00"),
|
||||
"must not fall back to the zero default for a "
|
||||
"genuinely-set numeric value");
|
||||
|
||||
|
|
@ -745,6 +746,82 @@ static void test_info_worker_reports_resource_values(void)
|
|||
printf("test_info_worker_reports_resource_values: PASS\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* New requirement (2026-08-20): a per-worker resource additionally flagged
|
||||
* XCE_RESF_CONSUMABLE must report both value (available+inUse) and used
|
||||
* (inUse); a non-consumable numeric resource keeps a single value= line
|
||||
* (test_info_worker_reports_resource_values above covers that case).
|
||||
*/
|
||||
static void test_info_worker_reports_consumable_value_and_used(void)
|
||||
{
|
||||
xce_sched_sim_driver_t sim;
|
||||
xce_core_event_t events[1];
|
||||
xce_resource_t wire_resource;
|
||||
xce_resource_t* r;
|
||||
xce_worker_t wire_worker;
|
||||
xce_worker_t* worker_ptrs[1];
|
||||
xce_val_t val;
|
||||
xce_name_val_t nv;
|
||||
xce_name_val_t* nv_ptrs[1];
|
||||
xce_val_t used_val;
|
||||
xce_sched_worker_t* sw;
|
||||
char* captured;
|
||||
int rc;
|
||||
|
||||
memset(&sim, 0, sizeof(sim));
|
||||
rc = xce_sched_core_init(&(sim.core));
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
memset(&wire_resource, 0, sizeof(wire_resource));
|
||||
wire_resource.name = "slots";
|
||||
wire_resource.valtype = XCE_VAL_NUMERIC;
|
||||
wire_resource.flags = XCE_RESF_CONSUMABLE;
|
||||
r = makeResource(sim.core.resmgr, &wire_resource);
|
||||
TEST_ASSERT_NOT_NULL(r);
|
||||
|
||||
memset(&val, 0, sizeof(val));
|
||||
val.which = Value_u_fvalue;
|
||||
val.u.fvalue = 5.0;
|
||||
memset(&nv, 0, sizeof(nv));
|
||||
nv.name = "slots";
|
||||
nv.val = &val;
|
||||
nv_ptrs[0] = &nv;
|
||||
|
||||
memset(&wire_worker, 0, sizeof(wire_worker));
|
||||
wire_worker.name = "hostA";
|
||||
wire_worker.status = 0;
|
||||
wire_worker.nresvals_to_sched = 1;
|
||||
wire_worker.resvals_to_sched = nv_ptrs;
|
||||
worker_ptrs[0] = &wire_worker;
|
||||
|
||||
memset(events, 0, sizeof(events));
|
||||
events[0].type = XCE_CORE_EVENT_WORKERS_PUSH;
|
||||
events[0].u.workers_push.nworkers = 1;
|
||||
events[0].u.workers_push.workers = worker_ptrs;
|
||||
|
||||
rc = xce_core_push_events(&(sim.core), events, 1);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
sw = sched_get_worker(&(sim.core), "hostA");
|
||||
TEST_ASSERT_NOT_NULL(sw);
|
||||
|
||||
memset(&used_val, 0, sizeof(used_val));
|
||||
used_val.which = Value_u_fvalue;
|
||||
used_val.u.fvalue = 2.0;
|
||||
zarray_put(&(sw->resInUse), r->idx, dup_val(&used_val));
|
||||
|
||||
captured = capture_dispatch(&sim, "info worker hostA");
|
||||
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||
strstr(captured, "resource slots: value=7.00 used=2.00"),
|
||||
"a CONSUMABLE worker resource must report value=avail+used and "
|
||||
"used=inUse");
|
||||
|
||||
free(captured);
|
||||
|
||||
printf("test_info_worker_reports_consumable_value_and_used: PASS\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* bug-1590: repl_info_worker_one() forced EVERY resource value through the
|
||||
* numeric "total=%.2f available=%.2f" format, silently coercing any
|
||||
|
|
@ -893,7 +970,8 @@ static void test_info_resource_reports_string_value(void)
|
|||
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||
strstr(captured, "resource license_pool: desc=(none) "
|
||||
"valtype=string flags=GLOBAL interval=0"),
|
||||
"valtype=string ordtype=incr flags=GLOBAL "
|
||||
"interval=0"),
|
||||
"a GLOBAL resource's definition metadata must be printed "
|
||||
"(bug-1593)");
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||
|
|
@ -912,6 +990,71 @@ static void test_info_resource_reports_string_value(void)
|
|||
printf("test_info_resource_reports_string_value: PASS\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* New requirement (2026-08-20): a GLOBAL/SHARED resource additionally
|
||||
* flagged XCE_RESF_CONSUMABLE must report both its value (capacity,
|
||||
* available+inUse) and how much is currently in use, read from
|
||||
* core->sharedInUse -- the cluster-wide mirror of repl_info_worker_one()'s
|
||||
* resInUse-derived value/used split.
|
||||
*/
|
||||
static void test_info_resource_reports_consumable_value_and_used(void)
|
||||
{
|
||||
xce_sched_sim_driver_t sim;
|
||||
xce_core_event_t events[1];
|
||||
xce_resource_t wire_resource;
|
||||
xce_resource_t* r;
|
||||
xce_val_t avail_val;
|
||||
xce_val_t used_val;
|
||||
xce_resval_t rv;
|
||||
xce_resval_t* rv_ptrs[1];
|
||||
char* captured;
|
||||
int rc;
|
||||
|
||||
memset(&sim, 0, sizeof(sim));
|
||||
rc = xce_sched_core_init(&(sim.core));
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
memset(&wire_resource, 0, sizeof(wire_resource));
|
||||
wire_resource.name = "licenses";
|
||||
wire_resource.valtype = XCE_VAL_NUMERIC;
|
||||
wire_resource.flags = XCE_RESF_SHARED | XCE_RESF_CONSUMABLE;
|
||||
r = makeResource(sim.core.resmgr, &wire_resource);
|
||||
TEST_ASSERT_NOT_NULL(r);
|
||||
|
||||
memset(&avail_val, 0, sizeof(avail_val));
|
||||
avail_val.which = Value_u_fvalue;
|
||||
avail_val.u.fvalue = 8.0;
|
||||
|
||||
memset(&rv, 0, sizeof(rv));
|
||||
rv.name = "licenses";
|
||||
rv.val = &avail_val;
|
||||
rv_ptrs[0] = &rv;
|
||||
|
||||
memset(events, 0, sizeof(events));
|
||||
events[0].type = XCE_CORE_EVENT_RESVALS_FULL;
|
||||
events[0].u.resvals_full.n = 1;
|
||||
events[0].u.resvals_full.resvals = rv_ptrs;
|
||||
|
||||
rc = xce_core_push_events(&(sim.core), events, 1);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
memset(&used_val, 0, sizeof(used_val));
|
||||
used_val.which = Value_u_fvalue;
|
||||
used_val.u.fvalue = 3.0;
|
||||
zarray_put(&(sim.core.sharedInUse), r->idx, dup_val(&used_val));
|
||||
|
||||
captured = capture_dispatch(&sim, "info resource licenses");
|
||||
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||
strstr(captured, "value=11.00 used=3.00"),
|
||||
"a CONSUMABLE global resource must report value=avail+used and "
|
||||
"used=inUse, sourced from core->sharedInUse");
|
||||
|
||||
free(captured);
|
||||
|
||||
printf("test_info_resource_reports_consumable_value_and_used: PASS\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* bug-1593 (task 12.5): `info resource all` must list only resources
|
||||
* flagged XCE_RESF_GLOBAL/XCE_RESF_SHARED -- a host-based resource (no
|
||||
|
|
@ -1005,7 +1148,7 @@ static void test_info_resource_named_lookup_reports_host_based_note(void)
|
|||
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||
strstr(captured, "resource cpus: desc=(none) valtype=numeric "
|
||||
"flags=(none) interval=0"),
|
||||
"ordtype=incr flags=(none) interval=0"),
|
||||
"a host-based resource's metadata must still be reported by a "
|
||||
"named lookup (bug-1593)");
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||
|
|
@ -1661,8 +1804,10 @@ int main(void)
|
|||
RUN_TEST(test_info_job_unknown_reports_not_found);
|
||||
RUN_TEST(test_info_queue_reports_status_and_queued_jobs);
|
||||
RUN_TEST(test_info_worker_reports_resource_values);
|
||||
RUN_TEST(test_info_worker_reports_consumable_value_and_used);
|
||||
RUN_TEST(test_info_worker_reports_string_and_boolean_resource_values);
|
||||
RUN_TEST(test_info_resource_reports_string_value);
|
||||
RUN_TEST(test_info_resource_reports_consumable_value_and_used);
|
||||
RUN_TEST(test_info_resource_all_lists_only_global_shared_resources);
|
||||
RUN_TEST(test_info_resource_named_lookup_reports_host_based_note);
|
||||
RUN_TEST(test_info_resource_all_reports_when_none_are_global_shared);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue