diff --git a/src/scheduler/src/repl_info.c b/src/scheduler/src/repl_info.c index f6391dee..710e28c9 100644 --- a/src/scheduler/src/repl_info.c +++ b/src/scheduler/src/repl_info.c @@ -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 ": "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 " 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 `'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 `'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"); diff --git a/src/scheduler/testsuite/test_repl_breakpoints_inspection.c b/src/scheduler/testsuite/test_repl_breakpoints_inspection.c index 4f63546a..e37619ce 100644 --- a/src/scheduler/testsuite/test_repl_breakpoints_inspection.c +++ b/src/scheduler/testsuite/test_repl_breakpoints_inspection.c @@ -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);