repl_info_limit() (repl_info.c, not yet wired into repl_cmd_info() -- that's task 4) resolves "all" vs a specific name against limit_get_configured_limits(): a NULL/empty handle on the all path reports "no limits configured" (Req 1.7); a NULL handle or key_find() miss on the named path reports "no such limit '<name>'" (Req 1.3) and never collapses into the all-only message. New white-box test_repl_limit_table.c exercises both paths directly, including a dedicated regression guard for the NULL-scoping rule. Making repl_info.c genuinely call limit_get_configured_limits() broke the link for ~24 other white-box test files that #include repl_info.c without also linking plugins/limit.c -- fixed with the same link-satisfying-stub idiom this file family already uses elsewhere (all return NULL, verified behavior- preserving since none of these fixtures register the limit plugin). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
9.2 KiB
Implementation Plan: scheduler-sim-diagnostics (Requirement Set 1 — Limit Inspection)
-
1. Add read-only accessors exposing the limit plugin's configured-limits and live-usage-instances tables
- Two new functions (
limit_get_configured_limits(),limit_get_usage_instances()) returning raw kvmgr handles (orNULLwhen the plugin is absent), declared inplugin.halongsideregister_limit(), defined inplugins/limit.c - Never allocate, mutate, or trigger plugin hooks — a pure read of already-existing pointers
- New/extended unit tests in
test_limit_plugin.ccover: plugin registered with configured limits (handle returned, iterable), plugin registered with zero limits, plugin not registered (NULL) - Observable: a test that registers the limit plugin with a known limit and confirms the accessor's returned handle, when iterated via
iter_make()/iter_next(), yields that same limit; a sibling test confirmsNULLwhen the plugin was never registered - Requirements: 1.1, 1.2, 1.4, 2.1, 3.1
- Boundary: Limit Plugin Accessor
- Two new functions (
-
2. Core: Limit lookup and configured-setting display
-
2.1 Implement name/all resolution with correct absence messaging
- New white-box test file
test_repl_limit_table.ccreated (sibling oftest_repl_resource_table.c, own#includeset coveringplugins/limit.candresource.cfor real — not stubbed — perdesign.md's build-wiring risk note citing bug-1762's precedent) repl_info_limit(sim, arg):arg == "all"iterateslimit_get_configured_limits();NULL/empty prints "no limits configured" (Requirement 1.7);arg != "all"does akey_find()by name (not a scan) on the same handle, printing "no such limit ''" (Requirement 1.3) for aNULLhandle or a miss — aNULLhandle on this path is never treated as "no limits configured," matching that message's ownall-only scoping- Wired as a direct call for now (
repl_dispatch()/repl_cmd_info()wiring itself deferred to task 4); tests in this task callrepl_info_limit()directly, not throughrepl_dispatch() - Observable: REPL output shows "no such limit 'foo'" for an unconfigured name against a fixture with other limits configured, and "no limits configured" for
allagainst a fixture with zero configured limits - Requirements: 1.3, 1.7, 4.1, 4.2
- Depends: 1
- New white-box test file
-
2.2 Display configured setting including scope membership
- Matched limit's name, constrained resource, value/percent, and per-dimension scope print via the existing
fmt_limit()library formatter into a stack buffer, line-prefixed with the existing[sim]convention; all output viafprintf(stdout, ...), never bareprintf()(test_no_bare_printf.cregression-gates this) - Inclusion and exclusion group membership both render (
fmt_group()already handles this, adopted as-is) - Observable: a limit configured with an exclusion group ("all except X") shows both the inclusion and the exclusion in its output;
info limit allagainst a fixture with multiple configured limits displays every one's setting - Requirements: 1.1, 1.2, 1.5
- Matched limit's name, constrained resource, value/percent, and per-dimension scope print via the existing
-
2.3 Display effective host-scope for implicitly host-scoped limits
- After
fmt_limit()'s output, callis_host_based_limit(core->resmgr, limit); if it returns> 0AND the limit's own per-host consumer is not configured, print an explanatory host-scope note - Observable: two REPL fixtures — one percent-based limit on a per-host resource without explicit per-host scoping (note appears), one absolute-value or explicitly-global-scoped limit (note does not appear)
- Requirements: 1.6
- After
-
3. Core: Live accounted-usage display
-
3.1 Display usage for cluster-wide (non-host-scoped) limit instances
- Iterate
limit_get_usage_instances(), filter byvres->limit == matched_limit; for a GLOBAL/SHAREDvres, read capacity fromcore.sharedInst[idx]and accounted usage fromcore.sharedInUse[idx] - Existence-of-instance and zero-usage are distinct: zero matching
xce_vres_t(no capacity entry at all) prints "no active usage instance yet" (Requirement 2.2); a matchedxce_vres_twhosesharedInUse[idx]isNULL/absent instead displays a genuineaccounted=0(it has been evaluated and has a real capacity, just nothing charged against it yet) — existence is gated on the capacity read, never on the in-use read - Observable: a global limit that has matched at least one job shows an accounted/capacity pair reflecting current state; a configured-but-never-evaluated limit shows the explicit no-instance message instead; a matched-but-never-charged limit shows
accounted=0rather than the no-instance message - Requirements: 1.4, 2.1, 2.2, 2.3, 3.3
- Depends: 1
- Iterate
-
3.2 Display per-host usage for host-scoped limit instances
- For a host-scoped
vres(explicit or implicit per 2.3), iteratecore.workers; include a worker only if it has a populated capacity entry (w->resvals[idx]non-NULL) — read capacity fromw->resvals[idx]and accounted usage fromw->resInUse[idx](same existence-vs-zero-usage distinction as 3.1, applied per worker); label each instance with its scope values (user/queue/project from the vresname, plus host) - A limit varying by more than one scope dimension at once (e.g. per-user and per-host together) shows each distinct combination as its own labeled instance
- Observable: a limit matched against jobs on two distinct hosts shows two separate per-host usage lines each labeled with its own scope values, and a third configured-but-untouched host shows no line at all
- Requirements: 1.4, 2.1, 2.2, 2.3, 3.1, 3.2, 3.4
- For a host-scoped
-
4. Register
info limitas a recognizedinfosubcommand- New
else if (strcmp(tokens[1], "limit") == 0)branch inrepl_cmd_info()(repl_info.c) routes torepl_info_limit(); usage-string literal updated to listlimitalongside the existing subcommands test_repl_limit_table.cgains a full dispatch-round-trip test (viarepl_dispatch(), not a direct call) for bothinfo limit <name>andinfo limit all- Observable:
info limit <name>andinfo limit allboth work when typed as real REPL input (throughrepl_dispatch()), andhelp/usage output lists the new subcommand - Requirements: 4.1, 4.2
- Depends: 2.1, 3.1, 3.2
- New
-
5. Verify no mutation, replay determinism, and existing-subcommand regression
- Test: displaying limit information never alters configuration, accounting state, or scheduling decisions (assert config/accounting untouched before vs. after)
- Test: a replay run twice — once with
info limitissued mid-session, once without — produces identical scheduling decisions and identical final accounted state - Full existing
infosubcommand test suite (job/queue/worker/resource/summary) re-run to confirm zero output changes from this addition - Observable: the two new determinism/no-mutation tests pass, and the full pre-existing info-command test suite shows zero diffs
- Requirements: 5.1, 5.2, 5.3
- Depends: 4
Parallel Execution Note
No (P) markers are used anywhere in this plan. Tasks 2.1–2.3 and 3.1–3.2 all incrementally edit the same function (repl_info_limit()) in the same file (repl_info.c) and largely share the same new test file (test_repl_limit_table.c) — the parallel-analysis rules' "no conflicting files or shared mutable resources" criterion rules out marking any of them concurrent. Task 1 is the sole Foundation task (rarely (P) by rule, and has no peer to run alongside regardless). Tasks 4 and 5 are Integration/Validation work that structurally depends on Core being complete.
Implementation Notes
- Task 1:
test_limit_plugin.c's pre-existingfixture_init()never populatedcore->plugins(plugin_register()is stubbed to a no-op in that white-box TU). Closed with two new test-local fixture helpers that hand-build a realcore->pluginskvmgr andkey_add()the plugin under its real"xce_sched_limit"name — necessary to exercise the accessors' realkey_find()path, not a workaround.test_repl_limit_table.c(tasks 2.1+) is a different white-box TU with its own include set (per design.md's build-wiring risk note) — it should link realplugin.c/core.cmachinery whereregister_limit()/xce_sched_core_init()actually populatecore->plugins, so it likely does NOT need this same hand-built-kvmgr workaround; confirm this when starting 2.1 rather than assuming the same gap exists there. - Task 2.1: Making
repl_info.cgenuinely calllimit_get_configured_limits()broke the link for ~24 pre-existing white-box test files that#include "../src/repl_info.c"without also linkingplugins/limit.c— this codebase's white-box test convention means any new cross-file call from an included production file ripples to every file that includes the caller but not the callee. Fixed with an identical 5-line link-satisfying stub (return NULL) added to each, matching the established "duplicate, don't share" stub idiom already used throughout this file family; verified behavior-preserving since none of those 24 fixtures register the limit plugin (the real function would also returnNULLthere). Task 3 (which callslimit_get_usage_instances()) will very likely hit the identical ripple across an overlapping or identical file set — expect it, and apply the same fix pattern rather than treating it as a surprise.