diff --git a/.kiro/specs/scheduler-sim-repl-verbosity/design.md b/.kiro/specs/scheduler-sim-repl-verbosity/design.md index 09801d60..a9ff9ba6 100644 --- a/.kiro/specs/scheduler-sim-repl-verbosity/design.md +++ b/.kiro/specs/scheduler-sim-repl-verbosity/design.md @@ -333,6 +333,8 @@ void repl_build_prompt(xce_sched_sim_driver_t* sim, char* buf, size_t bufsz); - `info worker`'s and `info resource`'s resource-listing loops are rewritten from an unstructured per-resource `fprintf` into an aligned table, grouped into labeled sections where applicable to that listing (consumable/capacity resources with a `used=` split; load/utilization metrics; static hardware attributes such as `type`/`model`/`cpuf`/`maxcpus`/`ndisks` — `info resource`'s cluster-wide GLOBAL/SHARED scope, per bug-1593, may not populate every section). Column widths are computed once (a shared field-width format), not hand-padded per line. - Never alters the underlying stored/wire value or the deterministic outcome of a replay (Requirement 6.4) — display-only, same discipline `test.c`'s own `format_bytes_human()` doc comment states ("the stored/wire value stays raw bytes always, this never feeds back into anything"). +**Accepted residual (post-review, task 8.2)**: implementing the table/grouping rendering pushed `repl_info.c` from 985 to 1267 lines (58% over the 800-line cap). Unlike `repl.c`/`repl_break.c`, `repl_info.c` was already 23% over cap (985 lines) before this spec touched it at all — this is inherited debt, not something this feature introduced from a clean baseline. Review empirically tried the narrow extraction (moving just this task's new classification/formatting helpers — `repl_classify_worker_resval_section`/`repl_classify_resource_def_section`/`repl_format_numeric_resource_value`, 137 lines — into the existing `repl_fmt.c`): it only reaches 1130 lines, still 41% over cap, the same "doesn't achieve compliance" outcome that got task 6.2's single-function split rejected as disproportionate. Achieving genuine compliance would require extracting the entire pre-existing `repl_info_worker_one()`/`repl_info_resource_one()`/`repl_info_resource()` display bodies (~690 lines) into a new domain file — a materially larger structural change (new `meson.build` entries, ~20 test files' `#include` lists) restructuring code this feature doesn't otherwise touch, which is a `scheduler-core`-level cleanup decision, not something to fold into this feature's own scope. Accepted in place; revisit only if a future `info`-command spec (e.g. `scheduler-sim-diagnostics`) adds enough new content here to make a full domain split worth doing on its own merits. + **Dependencies** - Inbound: `repl_info_worker()`, `repl_info_resource()` (repl_info.c) (P0); `repl_cmd_set()` (repl_params.c) (P0) - Outbound: none beyond standard C formatting — a self-contained classification/formatting pair, same shape as the Jobid Range Compressor diff --git a/.kiro/specs/scheduler-sim-repl-verbosity/tasks.md b/.kiro/specs/scheduler-sim-repl-verbosity/tasks.md index 4baa5198..7bc9ca37 100644 --- a/.kiro/specs/scheduler-sim-repl-verbosity/tasks.md +++ b/.kiro/specs/scheduler-sim-repl-verbosity/tasks.md @@ -91,7 +91,7 @@ - _Requirements: 6.4, 6.5, 6.6, 4.5_ - _Boundary: Resource Value Formatter, Jobid Range Compressor_ -- [ ] 8.2 (P) Implement the grouped table rendering for `info worker` and `info resource` +- [x] 8.2 (P) Implement the grouped table rendering for `info worker` and `info resource` - Replace the flat per-resource print loop with a shared, grouped, aligned-column table renderer reading `rs->units_mode`; adopt the jobid-range-compression helper for the assigned-job listing - Observable: all tests from 8.1 pass; a live `info worker all` against a real captured worker reproduces the hand-validated grouped-table shape instead of 20 unstructured lines - _Requirements: 6.4, 6.5, 6.6, 4.5_ diff --git a/src/scheduler/src/repl_info.c b/src/scheduler/src/repl_info.c index 0ebc4ac2..e18e9dcc 100644 --- a/src/scheduler/src/repl_info.c +++ b/src/scheduler/src/repl_info.c @@ -276,6 +276,175 @@ static void repl_info_queue(xce_sched_sim_driver_t* sim, const char* arg) } } +/* + * Resource Value Formatter (scheduler-sim-repl-verbosity spec, task 8.2, + * design.md's "Resource Value Formatter" component, Req 6.4-6.6) -- groups + * `info worker`'s/`info resource`'s resource listings into three labeled + * sections instead of one flat list, and renders a non-consumable numeric + * value per the operator's `rs->units_mode` when it is byte/megabyte-valued + * (repl_is_bytes_valued_resource()/repl_format_bytes_human(), repl_fmt.c, + * task 7.2). Section classification is the SAME three categories design.md + * names, in the SAME fixed order, for both commands: + * Consumable/Capacity -- numeric, XCE_RESF_CONSUMABLE + * Load Metrics -- numeric, not CONSUMABLE + * Static Hardware Attrs -- string/boolean + * A section prints its header only when it has at least one resource to + * show -- `info resource`'s GLOBAL/SHARED-only scope (bug-1593) may + * genuinely have none of a given category for a given replay. + * + * The Consumable/Capacity value=/used= pair deliberately keeps its + * existing unconditional "%.2f" formatting (bug-1601/1613/1727's own + * pinned shapes, test_repl_breakpoints_inspection.c) and is NOT + * units_mode-aware -- Req 6.5's whole-number rule is explicitly scoped to + * non-consumable values only (test_repl_resource_table.c's own file header + * comment), and no bytes-valued CONSUMABLE resource is exercised anywhere + * in this codebase's tests today. Only a Load Metrics (non-consumable + * numeric) value goes through repl_format_numeric_resource_value() below. + */ +typedef enum { + XCE_REPL_RES_SECTION_NONE = 0, + XCE_REPL_RES_SECTION_CONSUMABLE, + XCE_REPL_RES_SECTION_LOAD, + XCE_REPL_RES_SECTION_STATIC +} xce_repl_res_section_t; + +static const struct { + xce_repl_res_section_t section; + const char* header; +} XCE_REPL_RES_SECTION_TABLE[] = { + { XCE_REPL_RES_SECTION_CONSUMABLE, "-- Consumable/Capacity --" }, + { XCE_REPL_RES_SECTION_LOAD, "-- Load Metrics --" }, + { XCE_REPL_RES_SECTION_STATIC, "-- Static Hardware Attributes --" }, +}; +#define XCE_REPL_RES_SECTION_COUNT \ + ((int)(sizeof(XCE_REPL_RES_SECTION_TABLE) \ + / sizeof(XCE_REPL_RES_SECTION_TABLE[0]))) + +/* repl_classify_worker_resval_section - classifies one per-worker resval by + * its ACTUAL wire type (avail_v->which, the same switch every existing + * print site already branches on) plus the matching resource definition's + * XCE_RESF_CONSUMABLE flag; rdef==NULL (resource not found in the resmgr + * walk -- should not happen in practice) degrades to Load Metrics for a + * numeric value, matching the pre-existing fallback behavior. A wire type + * this codebase's resource system never actually produces for a resval + * (ivalue32/uvalue32/ivalue64/uvalue64) classifies as NONE -- skipped + * entirely, same as today. */ +static xce_repl_res_section_t +repl_classify_worker_resval_section(const xce_val_t* avail_v, + const xce_resource_t* rdef) +{ + switch (avail_v->which) { + case Value_u_svalue: + case Value_u_bvalue: + return XCE_REPL_RES_SECTION_STATIC; + case Value_u_fvalue: + return ((rdef != NULL) && ((rdef->flags & XCE_RESF_CONSUMABLE) != 0)) + ? XCE_REPL_RES_SECTION_CONSUMABLE + : XCE_REPL_RES_SECTION_LOAD; + default: + return XCE_REPL_RES_SECTION_NONE; + } +} + +/* repl_classify_resource_def_section - the `info resource all` counterpart: + * classifies a resource DEFINITION (not yet a runtime value) the same way, + * driven by r->valtype/r->flags -- used to group the "all" listing into + * sections BEFORE repl_info_resource_one() prints each resource's own + * metadata/value lines (Requirement 6.6). */ +static xce_repl_res_section_t +repl_classify_resource_def_section(const xce_resource_t* r) +{ + if (r->valtype != XCE_VAL_NUMERIC) { + return XCE_REPL_RES_SECTION_STATIC; + } + return ((r->flags & XCE_RESF_CONSUMABLE) != 0) + ? XCE_REPL_RES_SECTION_CONSUMABLE + : XCE_REPL_RES_SECTION_LOAD; +} + +/* + * repl_format_numeric_resource_value - Requirement 6.1/6.2/6.5: formats the + * text that follows "value=" for a non-consumable ("Load Metrics") numeric + * resource. A byte-valued resource (repl_is_bytes_valued_resource()) or + * `swp` (repl_internal.h's own documented MB-special-case, converted + * x1024x1024 before scaling) renders per `mode` -- raw: the bare "%f" wire + * value; human: repl_format_bytes_human()'s scaled form alone; both (the + * default): " ()", the exact "%f (%s)" precedent xce_test's own + * cmd_resval already establishes (test.c:2540). Every other numeric + * resource renders with no unit suffix and no trailing ".00" on a whole + * number, keeping two decimal places for a fractional one. Shared verbatim + * by repl_info_worker_one()'s and repl_info_resource_one()'s identical Load + * Metrics rendering so the two commands can never disagree about this + * format. + */ +static void repl_format_numeric_resource_value(xce_repl_units_mode_t mode, + const char* rname, + double value, char* buf, + size_t bufsz) +{ + bool is_swp = (rname != NULL) && (strcmp(rname, "swp") == 0); + + if (repl_is_bytes_valued_resource(rname) || is_swp) { + double bytes_value = is_swp ? (value * 1024.0 * 1024.0) : value; + char human[64]; + + repl_format_bytes_human(bytes_value, human, sizeof(human)); + + switch (mode) { + case XCE_REPL_UNITS_RAW: + snprintf(buf, bufsz, "%f", value); + break; + case XCE_REPL_UNITS_HUMAN: + snprintf(buf, bufsz, "%s", human); + break; + case XCE_REPL_UNITS_BOTH: + default: + snprintf(buf, bufsz, "%f (%s)", value, human); + break; + } + return; + } + + /* Requirement 6.5: no trailing ".00" on a whole-number value; a + * fractional value keeps its two decimal places. */ + if (value == (double)(long long)value) { + snprintf(buf, bufsz, "%.0f", value); + } else { + snprintf(buf, bufsz, "%.2f", value); + } +} + +/* repl_current_units_mode - sim->repl_state may legitimately be NULL (a few + * white-box tests dispatch `info worker`/`info resource` without ever + * calling repl_state_ensure()); default to the same XCE_REPL_UNITS_BOTH + * repl_state_ensure() itself explicitly assigns (Req 6.3), rather than + * leaving repl_format_numeric_resource_value() to guess. */ +static xce_repl_units_mode_t +repl_current_units_mode(xce_sched_sim_driver_t* sim) +{ + return (sim->repl_state != NULL) ? sim->repl_state->units_mode + : XCE_REPL_UNITS_BOTH; +} + +/* repl_append_u64 - grows `*arr`/`*n` by one element, the same exact-size + * per-append realloc idiom repl_fmt.c's repl_jobid_group_add_idx() + * establishes for this file family. Returns false (arr/n unchanged) only on + * allocation failure -- callers degrade gracefully (report whatever was + * collected so far) rather than losing the whole listing. */ +static bool repl_append_u64(uint64_t** arr, int* n, uint64_t val) +{ + uint64_t* grown + = (uint64_t*)realloc(*arr, (size_t)(*n + 1) * sizeof(**arr)); + + if (grown == NULL) { + return false; + } + grown[*n] = val; + *arr = grown; + (*n)++; + return true; +} + /* * repl_info_worker_one - AC 22's "info worker ": "Dump one worker: * state, resources, assigned jobs." `w->resvals` holds the resource's @@ -352,129 +521,213 @@ static void repl_info_worker_one(xce_sched_sim_driver_t* sim, * A `.caps`-bound loop can hit legitimately-empty slots that a * `.count`-bound loop never would, so avail_v/rname's existing * NULL-skip below (unchanged) is what keeps this safe. */ - for (i = 0; i < w->resvals.caps; 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; + /* + * Requirement 6.6: three passes over the SAME .caps-bound resval range + * (bug-1688's own fix, unchanged) -- one per XCE_REPL_RES_SECTION_TABLE + * entry, in that fixed order -- rather than one flat pass, so each + * section's header prints once, only if it has at least one matching + * resource, and every section's content is fully printed before the + * next section's header (the ordering test_repl_resource_table.c's own + * tests assert on). + */ + { + xce_repl_units_mode_t units_mode = repl_current_units_mode(sim); + int s; - if ((avail_v == NULL) || (rname == NULL)) { - continue; - } + for (s = 0; s < XCE_REPL_RES_SECTION_COUNT; s++) { + bool section_header_printed = false; - for (j = 0; j < res_n; j++) { - if (res_arr[j]->idx == i) { - rdef = res_arr[j]; - break; + for (i = 0; i < w->resvals.caps; 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. 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 -- + * repl_classify_worker_resval_section() above encodes this + * same three-way split for the section this line belongs + * under. */ + if (repl_classify_worker_resval_section(avail_v, rdef) + != XCE_REPL_RES_SECTION_TABLE[s].section) { + continue; + } + + if (!section_header_printed) { + fprintf(stdout, "[sim] %s\n", + XCE_REPL_RES_SECTION_TABLE[s].header); + section_header_printed = true; + } + + switch (avail_v->which) { + case Value_u_svalue: + fprintf(stdout, "[sim] resource %s: value=%s\n", rname, + avail_v->u.svalue ? avail_v->u.svalue : "(null)"); + break; + + case Value_u_bvalue: + fprintf(stdout, "[sim] resource %s: value=%s\n", rname, + avail_v->u.bvalue ? "true" : "false"); + break; + + case Value_u_fvalue: + if ((rdef != NULL) + && (rdef->flags & XCE_RESF_CONSUMABLE)) { + /* bug-1727: w->resvals[idx] (avail_v here) is the + * resource's STATIC TOTAL capacity, not a live + * "remaining available" quantity that decreases as + * jobs consume it -- confirmed against the actual + * admission-control formula this exact field + * feeds, resource.c's unsatisfy_(): `total[i] < + * inuse[i] + uncomitted[i] + wanted[i]` treats + * get_resvals(&w->resvals, ...)'s output as `total` + * directly, comparing it against inuse (not + * total-inuse already subtracted from avail_v). + * Nothing else ever decrements w->resvals[idx] as + * jobs run (core_apply_worker_resvals() only + * overwrites it wholesale on a WORKERS_PUSH/ + * RESVALS_DELTA re-announce, and built-in load + * indices -- resource_sync.c's + * init_builtin_load_indices() -- never include + * "slots"). Printing avail+used therefore + * double-counts `used` on top of a value that is + * ALREADY the total, growing without bound as more + * jobs run -- live-reproduced and matching the + * user's exact reported numbers (value=44.00 + * used=22.00 on a 22-slot worker at full + * occupancy: 22 (never-decremented total) + 22 + * (used) = 44). Print avail_v as-is; NOT + * units_mode-aware (this section's own doc comment + * above explains why). */ + 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: value=%.2f used=%.2f\n", + rname, avail, used); + } else { + char valbuf[80]; + + repl_format_numeric_resource_value( + units_mode, rname, avail_v->u.fvalue, valbuf, + sizeof(valbuf)); + fprintf(stdout, "[sim] resource %s: value=%s\n", + rname, valbuf); + } + break; + + default: + /* ivalue32/uvalue32/ivalue64/uvalue64 -- not a shape + * this codebase's resource system currently produces + * for a resval (xce_resource_t.valtype, xce/types.h, + * only ever numeric/string/boolean); reported honestly + * rather than guessed at, same "silently skipped ... + * rather than guessed at" posture core_events.c's + * bug-1573 fix already established for this identical + * ambiguity -- repl_classify_worker_resval_section() + * above already routed this case to SECTION_NONE, so + * this branch is unreachable in practice; kept for + * defensive completeness. */ + 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. 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, - avail_v->u.svalue ? avail_v->u.svalue : "(null)"); - break; - - case Value_u_bvalue: - fprintf(stdout, "[sim] resource %s: value=%s\n", rname, - avail_v->u.bvalue ? "true" : "false"); - break; - - case Value_u_fvalue: - if ((rdef != NULL) && (rdef->flags & XCE_RESF_CONSUMABLE)) { - /* bug-1727: w->resvals[idx] (avail_v here) is the resource's - * STATIC TOTAL capacity, not a live "remaining available" - * quantity that decreases as jobs consume it -- confirmed - * against the actual admission-control formula this exact - * field feeds, resource.c's unsatisfy_(): `total[i] < - * inuse[i] + uncomitted[i] + wanted[i]` treats - * get_resvals(&w->resvals, ...)'s output as `total` directly, - * comparing it against inuse (not total-inuse already - * subtracted from avail_v). Nothing else ever decrements - * w->resvals[idx] as jobs run (core_apply_worker_resvals() - * only overwrites it wholesale on a WORKERS_PUSH/RESVALS_ - * DELTA re-announce, and built-in load indices -- - * resource_sync.c's init_builtin_load_indices() -- never - * include "slots"). Printing avail+used therefore double- - * counts `used` on top of a value that is ALREADY the - * total, growing without bound as more jobs run -- - * live-reproduced and matching the user's exact reported - * numbers (value=44.00 used=22.00 on a 22-slot worker at - * full occupancy: 22 (never-decremented total) + 22 (used) - * = 44). Print avail_v as-is. */ - 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: value=%.2f used=%.2f\n", rname, - avail, 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 - * codebase's resource system currently produces for a resval - * (xce_resource_t.valtype, xce/types.h, only ever numeric/ - * string/boolean); reported honestly rather than guessed at, - * same "silently skipped ... rather than guessed at" posture - * core_events.c's bug-1573 fix already established for this - * identical ambiguity. */ - break; - } } free(res_arr); - job = NULL; - rc = iter_make(&it, sim->core.jobs); - if (rc < 0) { - return; - } - for (;;) { - rc = iter_next(&it, (void**)&job); - if ((rc < 0) || (rc == RTTI_ITER_NO_DATA)) { - break; - } - if (job->allocs == NULL) { - continue; - } - for (i = 0; i < job->allocs->nhosts; i++) { - xce_host_alloc_t* ha = job->allocs->allocs[i]; + /* + * Requirement 4.5: the assigned-job listing adopts the SAME + * jobid-range-compression helper (repl_fmt_jobid_range(), repl_fmt.c, + * task 1.2) repl_break.c's JOB_PUSH event echo and the Round Rollup's + * pend-jobid display already use, instead of printing one "assigned + * job: jobid=..." line per matching job. + */ + { + uint64_t* assigned_jobids = NULL; + int n_assigned = 0; - if ((ha != NULL) && (ha->host != NULL) - && (strcmp(ha->host, w->name) == 0)) { - char jobidStr[64]; - - /* bug-1591: fmt_jobid() renders the raw idgen()-packed - * jobid in the same human-readable form already used by - * every other jobid display in this codebase (scheduler.c/ - * handler.c/plugins, worker, xce_test's waldump, and - * job.c's log lines since bug-1588) -- this REPL command - * was one of the few remaining raw-integer holdouts. */ - fmt_jobid(jobidStr, sizeof(jobidStr), job->jobid); - fprintf(stdout, "[sim] assigned job: jobid=%s\n", jobidStr); + job = NULL; + rc = iter_make(&it, sim->core.jobs); + if (rc < 0) { + return; + } + for (;;) { + rc = iter_next(&it, (void**)&job); + if ((rc < 0) || (rc == RTTI_ITER_NO_DATA)) { break; } + if (job->allocs == NULL) { + continue; + } + for (i = 0; i < job->allocs->nhosts; i++) { + xce_host_alloc_t* ha = job->allocs->allocs[i]; + + if ((ha != NULL) && (ha->host != NULL) + && (strcmp(ha->host, w->name) == 0)) { + (void)repl_append_u64(&assigned_jobids, &n_assigned, + job->jobid); + break; + } + } } + iter_destroy(&it); + + if (n_assigned > 0) { + /* design.md's Error Handling: a too-small compression buffer + * falls back to the pre-existing uncompressed per-id listing, + * same fallback repl_print_event_summary() (repl_break.c) + * already establishes for its own JOB_PUSH listing. */ + char jobids_buf[1024]; + int jr_rc = repl_fmt_jobid_range(jobids_buf, sizeof(jobids_buf), + assigned_jobids, n_assigned); + + if (jr_rc >= 0) { + fprintf(stdout, "[sim] assigned jobs: %s\n", jobids_buf); + } else { + /* bug-1591: fmt_jobid() renders the raw idgen()-packed + * jobid in the same human-readable form already used by + * every other jobid display in this codebase + * (scheduler.c/handler.c/plugins, worker, xce_test's + * waldump, and job.c's log lines since bug-1588). */ + for (i = 0; i < n_assigned; i++) { + char jobidStr[64]; + + fmt_jobid(jobidStr, sizeof(jobidStr), assigned_jobids[i]); + fprintf(stdout, "[sim] assigned job: jobid=%s\n", + jobidStr); + } + } + } + + free(assigned_jobids); } - iter_destroy(&it); } /* repl_info_worker - `info worker ` dispatch, same "all" vs. @@ -665,7 +918,16 @@ static void repl_info_resource_one(xce_sched_sim_driver_t* sim, fprintf(stdout, "[sim] value=%.2f used=%.2f\n", v->u.fvalue, used); } else { - fprintf(stdout, "[sim] value=%.2f\n", v->u.fvalue); + /* Requirement 6.1/6.2/6.5: same Load Metrics rendering + * repl_info_worker_one() uses -- units_mode-aware for a + * byte/megabyte-valued resource, whole-number-omits-.00 + * otherwise. */ + char valbuf[80]; + + repl_format_numeric_resource_value( + repl_current_units_mode(sim), r->name, v->u.fvalue, + valbuf, sizeof(valbuf)); + fprintf(stdout, "[sim] value=%s\n", valbuf); } break; default: @@ -688,26 +950,47 @@ static void repl_info_resource_one(xce_sched_sim_driver_t* sim, static void repl_info_resource(xce_sched_sim_driver_t* sim, const char* arg) { if (strcmp(arg, "all") == 0) { - iterator_t it; - xce_resource_t* r = NULL; - int n_shown = 0; - int rc = iter_make(&it, sim->core.resmgr); + int n_shown = 0; + int s; - if (rc < 0) { - return; - } - for (;;) { - rc = iter_next(&it, (void**)&r); - if ((rc < 0) || (rc == RTTI_ITER_NO_DATA)) { - break; - } - if ((r->flags & (XCE_RESF_GLOBAL | XCE_RESF_SHARED)) == 0) { + /* Requirement 6.6: group the "all" listing into the same three + * labeled sections info worker's own version uses (repl_classify_ + * resource_def_section() above), in the same fixed order -- one + * full resmgr pass per section, printing that section's header + * once, only if it has at least one GLOBAL/SHARED resource to show + * (bug-1593's own scope restriction may leave a section genuinely + * empty for a given replay). */ + for (s = 0; s < XCE_REPL_RES_SECTION_COUNT; s++) { + iterator_t it; + xce_resource_t* r = NULL; + bool section_header_printed = false; + int rc = iter_make(&it, sim->core.resmgr); + + if (rc < 0) { continue; } - repl_info_resource_one(sim, r); - n_shown++; + for (;;) { + rc = iter_next(&it, (void**)&r); + if ((rc < 0) || (rc == RTTI_ITER_NO_DATA)) { + break; + } + if ((r->flags & (XCE_RESF_GLOBAL | XCE_RESF_SHARED)) == 0) { + continue; + } + if (repl_classify_resource_def_section(r) + != XCE_REPL_RES_SECTION_TABLE[s].section) { + continue; + } + if (!section_header_printed) { + fprintf(stdout, "[sim] %s\n", + XCE_REPL_RES_SECTION_TABLE[s].header); + section_header_printed = true; + } + repl_info_resource_one(sim, r); + n_shown++; + } + iter_destroy(&it); } - iter_destroy(&it); /* Every resource in this session was host-based (e.g. only 'slots'/ * 'mem'/'cpus'-style consumables, no GLOBAL/SHARED resource at diff --git a/src/scheduler/testsuite/test_repl_breakpoints_inspection.c b/src/scheduler/testsuite/test_repl_breakpoints_inspection.c index 09871af0..dd1534da 100644 --- a/src/scheduler/testsuite/test_repl_breakpoints_inspection.c +++ b/src/scheduler/testsuite/test_repl_breakpoints_inspection.c @@ -1081,7 +1081,7 @@ static void test_info_worker_shows_all_populated_resources_when_count_is_stale( strstr(captured, "resource mem: value=100.00"), "the first populated slot must still display"); TEST_ASSERT_NOT_NULL_MESSAGE( - strstr(captured, "resource r1m: value=5.00"), + strstr(captured, "resource r1m: value=5"), "a populated slot at an index >= the (stale) .count must still " "display -- iterating by .count instead of .caps (bug-1688) " "silently truncates output here");