fix(scheduler): sim resource over-commit, byte-unit display, sync job-state-at-capture
- bug-1799/1801: fix resInUse over-commit in sim replay -- sched_account_jobs's per-round re-derive pass now skips RECONCILING/UNAVAIL workers the same way core_reset_workers already does (bug-1751), closing the gap that let a worker's used slots exceed its own capacity across a resync replay - bug-1804: revise scheduler-sim-eager-reconciliation spec (user-directed) so a captured job's job->state is set to RUN synchronously at dispatch capture, mirroring sched_commit_decision's normal-mode semantics (bug-1624), instead of deferring until its separately-recorded JOB_STATE_CHANGE event replays - bug-1800: render bytes-valued CONSUMABLE resources (mem/swp/tmp) with the same human-scaled unit formatting already used for maxmem/maxswap/maxtmp - bug-1802/1803: add then revert a break-on-ANY-event breakpoint kind after live feedback that it over-fires inside busy sessions; replace it with a prescan-based "next non-dummy session" hint in info summary instead Full scheduler suite: 77/77 tests pass (asan profile). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
This commit is contained in:
parent
ffca959ed9
commit
25fb5c115a
22 changed files with 1827 additions and 303 deletions
|
|
@ -1714,6 +1714,404 @@ test_info_resource_units_mode_both_renders_raw_and_human_for_bytes_resource(
|
|||
"bytes_resource: PASS\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* User-reported (2026-09-11, live replay session): a CONSUMABLE byte-valued
|
||||
* per-host resource (mem/tmp/swp) rendered its value=/used= pair as a raw
|
||||
* `%.2f` byte count (e.g. "used=87960930222080.00"), unlike every other
|
||||
* bytes-valued field in this same listing (maxmem/maxswap/maxtmp, and mem/
|
||||
* tmp/swp themselves when NOT flagged consumable, via the Load Metrics
|
||||
* path) which already render human-scaled. repl_info_worker_one()'s own
|
||||
* pre-fix header comment explicitly scoped this out ("no bytes-valued
|
||||
* CONSUMABLE resource is exercised anywhere in this codebase's tests
|
||||
* today") -- this test (and its "raw"/swp/info-resource siblings below)
|
||||
* closes that gap. A non-bytes-valued consumable (slots/licenses) must keep
|
||||
* its existing pinned "%.2f"/"%.2f" shape unchanged -- see
|
||||
* test_info_worker_reports_consumable_value_and_used (bug-1727,
|
||||
* test_repl_breakpoints_inspection.c), left untouched by this fix.
|
||||
*/
|
||||
static void
|
||||
test_info_worker_units_mode_both_renders_raw_and_human_for_consumable_bytes_resource(
|
||||
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;
|
||||
char expected_value[64];
|
||||
char expected_used[64];
|
||||
char expected_line[160];
|
||||
double avail;
|
||||
double used;
|
||||
int rc;
|
||||
|
||||
avail = 8589934592.0; /* 8 GB */
|
||||
used = 4294967296.0; /* 4 GB */
|
||||
|
||||
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 = "mem";
|
||||
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 = avail;
|
||||
memset(&nv, 0, sizeof(nv));
|
||||
nv.name = "mem";
|
||||
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 = used;
|
||||
zarray_put(&(sw->resInUse), r->idx, dup_val(&used_val));
|
||||
|
||||
sim.repl_state = repl_state_ensure(&sim);
|
||||
TEST_ASSERT_NOT_NULL(sim.repl_state);
|
||||
sim.repl_state->units_mode = XCE_REPL_UNITS_BOTH;
|
||||
|
||||
captured = capture_dispatch(&sim, "info worker hostA");
|
||||
|
||||
repl_format_bytes_human(avail, expected_value, sizeof(expected_value));
|
||||
repl_format_bytes_human(used, expected_used, sizeof(expected_used));
|
||||
snprintf(expected_line, sizeof(expected_line), "value=%f (%s) used=%f (%s)",
|
||||
avail, expected_value, used, expected_used);
|
||||
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||
strstr(captured, expected_line),
|
||||
"a CONSUMABLE byte-valued resource (mem/tmp/swp) must render both "
|
||||
"value= and used= human-scaled under units_mode=both, the same "
|
||||
"as every other bytes-valued field in this listing");
|
||||
|
||||
free(captured);
|
||||
|
||||
printf(
|
||||
"test_info_worker_units_mode_both_renders_raw_and_human_for_"
|
||||
"consumable_bytes_resource: PASS\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* AC 6.2 'raw' counterpart for a CONSUMABLE byte-valued resource: under
|
||||
* units_mode=raw, value=/used= must show ONLY the bare wire numbers, no
|
||||
* "GB"/human suffix -- mirrors
|
||||
* test_info_worker_units_mode_raw_renders_bare_wire_value_without_mutating_stored_value's
|
||||
* contract, applied to the consumable value=/used= pair instead of a
|
||||
* single value= line.
|
||||
*/
|
||||
static void
|
||||
test_info_worker_units_mode_raw_renders_bare_values_for_consumable_bytes_resource(
|
||||
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;
|
||||
char expected_line[160];
|
||||
double avail;
|
||||
double used;
|
||||
int rc;
|
||||
|
||||
avail = 8589934592.0;
|
||||
used = 4294967296.0;
|
||||
|
||||
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 = "mem";
|
||||
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 = avail;
|
||||
memset(&nv, 0, sizeof(nv));
|
||||
nv.name = "mem";
|
||||
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 = used;
|
||||
zarray_put(&(sw->resInUse), r->idx, dup_val(&used_val));
|
||||
|
||||
sim.repl_state = repl_state_ensure(&sim);
|
||||
TEST_ASSERT_NOT_NULL(sim.repl_state);
|
||||
sim.repl_state->units_mode = XCE_REPL_UNITS_RAW;
|
||||
|
||||
captured = capture_dispatch(&sim, "info worker hostA");
|
||||
|
||||
snprintf(expected_line, sizeof(expected_line), "value=%f used=%f", avail,
|
||||
used);
|
||||
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||
strstr(captured, expected_line),
|
||||
"AC 6.2 'raw': a CONSUMABLE byte-valued resource must render bare "
|
||||
"wire values for both value= and used= under units_mode=raw");
|
||||
TEST_ASSERT_NULL_MESSAGE(
|
||||
strstr(captured, "GB"),
|
||||
"AC 6.2 'raw': must show no human-readable unit suffix");
|
||||
|
||||
free(captured);
|
||||
|
||||
printf(
|
||||
"test_info_worker_units_mode_raw_renders_bare_values_for_"
|
||||
"consumable_bytes_resource: PASS\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* AC 6.1's swp-is-megabytes special case, applied to the CONSUMABLE
|
||||
* value=/used= pair: both fields must be converted MB->bytes before
|
||||
* human-scaling, mirroring
|
||||
* test_info_worker_units_mode_converts_swp_megabytes_before_human_scaling's
|
||||
* contract for the Load Metrics (non-consumable) path.
|
||||
*/
|
||||
static void
|
||||
test_info_worker_units_mode_converts_swp_megabytes_for_consumable_resource(
|
||||
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;
|
||||
char expected_value_human[64];
|
||||
char expected_used_human[64];
|
||||
double avail_mb;
|
||||
double used_mb;
|
||||
int rc;
|
||||
|
||||
avail_mb = 16384.0; /* 16384 MB -> 16 GB */
|
||||
used_mb = 4096.0; /* 4096 MB -> 4 GB */
|
||||
|
||||
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 = "swp";
|
||||
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 = avail_mb;
|
||||
memset(&nv, 0, sizeof(nv));
|
||||
nv.name = "swp";
|
||||
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 = used_mb;
|
||||
zarray_put(&(sw->resInUse), r->idx, dup_val(&used_val));
|
||||
|
||||
sim.repl_state = repl_state_ensure(&sim);
|
||||
TEST_ASSERT_NOT_NULL(sim.repl_state);
|
||||
sim.repl_state->units_mode = XCE_REPL_UNITS_HUMAN;
|
||||
|
||||
captured = capture_dispatch(&sim, "info worker hostA");
|
||||
|
||||
repl_format_bytes_human(avail_mb * 1024.0 * 1024.0, expected_value_human,
|
||||
sizeof(expected_value_human));
|
||||
repl_format_bytes_human(used_mb * 1024.0 * 1024.0, expected_used_human,
|
||||
sizeof(expected_used_human));
|
||||
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||
strstr(captured, expected_value_human),
|
||||
"swp's value= must be converted MB->bytes before human-scaling, "
|
||||
"same as the Load Metrics path");
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||
strstr(captured, expected_used_human),
|
||||
"swp's used= must be converted MB->bytes before human-scaling too");
|
||||
|
||||
free(captured);
|
||||
|
||||
printf(
|
||||
"test_info_worker_units_mode_converts_swp_megabytes_for_"
|
||||
"consumable_resource: PASS\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* info resource's counterpart: a GLOBAL/SHARED CONSUMABLE byte-valued
|
||||
* resource must render its value=/used= pair human-scaled too, mirroring
|
||||
* test_info_resource_units_mode_both_renders_raw_and_human_for_bytes_resource
|
||||
* (the non-consumable case) and this file's own info-worker tests above.
|
||||
*/
|
||||
static void
|
||||
test_info_resource_units_mode_both_renders_raw_and_human_for_consumable_bytes_resource(
|
||||
void)
|
||||
{
|
||||
xce_sched_sim_driver_t sim;
|
||||
xce_core_event_t events[1];
|
||||
xce_resource_t wire_mem;
|
||||
xce_resource_t* r;
|
||||
xce_val_t val_mem;
|
||||
xce_resval_t rv_mem;
|
||||
xce_resval_t* rv_ptrs[1];
|
||||
xce_val_t* stored;
|
||||
xce_val_t used_val;
|
||||
char* captured;
|
||||
char expected_value[64];
|
||||
char expected_used[64];
|
||||
char expected_line[160];
|
||||
double avail;
|
||||
double used;
|
||||
int rc;
|
||||
|
||||
avail = 8589934592.0; /* 8 GB */
|
||||
used = 2147483648.0; /* 2 GB */
|
||||
|
||||
memset(&sim, 0, sizeof(sim));
|
||||
rc = xce_sched_core_init(&(sim.core));
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
memset(&wire_mem, 0, sizeof(wire_mem));
|
||||
wire_mem.name = "mem";
|
||||
wire_mem.valtype = XCE_VAL_NUMERIC;
|
||||
wire_mem.flags = XCE_RESF_SHARED | XCE_RESF_CONSUMABLE;
|
||||
r = makeResource(sim.core.resmgr, &wire_mem);
|
||||
TEST_ASSERT_NOT_NULL(r);
|
||||
|
||||
memset(&val_mem, 0, sizeof(val_mem));
|
||||
val_mem.which = Value_u_fvalue;
|
||||
val_mem.u.fvalue = avail;
|
||||
memset(&rv_mem, 0, sizeof(rv_mem));
|
||||
rv_mem.name = "mem";
|
||||
rv_mem.val = &val_mem;
|
||||
rv_ptrs[0] = &rv_mem;
|
||||
|
||||
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 = used;
|
||||
zarray_put(&(sim.core.sharedInUse), r->idx, dup_val(&used_val));
|
||||
|
||||
sim.repl_state = repl_state_ensure(&sim);
|
||||
TEST_ASSERT_NOT_NULL(sim.repl_state);
|
||||
sim.repl_state->units_mode = XCE_REPL_UNITS_BOTH;
|
||||
|
||||
captured = capture_dispatch(&sim, "info resource mem");
|
||||
|
||||
repl_format_bytes_human(avail, expected_value, sizeof(expected_value));
|
||||
repl_format_bytes_human(used, expected_used, sizeof(expected_used));
|
||||
snprintf(expected_line, sizeof(expected_line), "value=%f (%s) used=%f (%s)",
|
||||
avail, expected_value, used, expected_used);
|
||||
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||
strstr(captured, expected_line),
|
||||
"a GLOBAL/SHARED CONSUMABLE byte-valued resource must render both "
|
||||
"value= and used= human-scaled under units_mode=both");
|
||||
|
||||
stored = (xce_val_t*)zarray_get(&(sim.core.sharedInst), r->idx);
|
||||
TEST_ASSERT_NOT_NULL(stored);
|
||||
TEST_ASSERT_EQUAL_INT(Value_u_fvalue, stored->which);
|
||||
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(
|
||||
(float)avail, (float)stored->u.fvalue,
|
||||
"AC 6.4: displaying a resource under any units_mode must never "
|
||||
"mutate the underlying stored/wire value");
|
||||
|
||||
free(captured);
|
||||
|
||||
printf(
|
||||
"test_info_resource_units_mode_both_renders_raw_and_human_for_"
|
||||
"consumable_bytes_resource: PASS\n");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
|
|
@ -1724,6 +2122,9 @@ int main(void)
|
|||
RUN_TEST(test_info_worker_units_mode_human_renders_scaled_value_without_mutating_stored_value);
|
||||
RUN_TEST(test_info_worker_units_mode_both_renders_raw_and_human_without_mutating_stored_value);
|
||||
RUN_TEST(test_info_worker_units_mode_converts_swp_megabytes_before_human_scaling);
|
||||
RUN_TEST(test_info_worker_units_mode_both_renders_raw_and_human_for_consumable_bytes_resource);
|
||||
RUN_TEST(test_info_worker_units_mode_raw_renders_bare_values_for_consumable_bytes_resource);
|
||||
RUN_TEST(test_info_worker_units_mode_converts_swp_megabytes_for_consumable_resource);
|
||||
RUN_TEST(test_info_worker_assigned_job_listing_compresses_contiguous_array_indices);
|
||||
RUN_TEST(test_info_worker_assigned_job_listing_appends_slot_count_when_uniform);
|
||||
RUN_TEST(test_info_worker_assigned_job_listing_excludes_terminal_job_despite_stale_allocs);
|
||||
|
|
@ -1731,6 +2132,7 @@ int main(void)
|
|||
RUN_TEST(test_info_resource_groups_resources_into_labeled_table_sections_in_order);
|
||||
RUN_TEST(test_info_resource_load_metric_whole_number_omits_trailing_zeros);
|
||||
RUN_TEST(test_info_resource_units_mode_both_renders_raw_and_human_for_bytes_resource);
|
||||
RUN_TEST(test_info_resource_units_mode_both_renders_raw_and_human_for_consumable_bytes_resource);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue