feat(scheduler-sim-diagnostics): display effective host-scope note (Req 1.6)

repl_info_limit() now prints an explanatory note when a limit is implicitly
host-scoped (is_host_based_limit() > 0 due to a percent-based value on a
non-shared resource) but its own configuration doesn't show an explicit
per-host scope. The real discriminator turned out to be consumers[CONSUMER_
HOST]->per_consumer, not pointer-nullity as design.md's gloss said --
build_consumer() unconditionally allocates a consumer for all four
dimensions, so the struct field is never actually NULL in production. Fixed
using the same field is_host_based_limit() itself already falls back to;
verified via RED-phase reproduction that the literal design.md condition
never fires. design.md corrected to document the real condition.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
This commit is contained in:
Rongsong Shen 2026-09-17 11:43:12 +00:00
parent 117d01c77e
commit 5876716eee
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
4 changed files with 295 additions and 6 deletions

View file

@ -213,7 +213,7 @@ extern var limit_get_usage_instances(xce_sched_core_t* core);
- Single entry point `repl_info_limit(xce_sched_sim_driver_t* sim, const char* arg)`, called from `repl_cmd_info()` exactly like `repl_info_resource(sim, tokens[2])`.
- `arg == "all"`: iterate `limit_get_configured_limits()`; if `NULL`/empty, print the Requirement 1.7 "no limits configured" message and return; otherwise print each limit via the per-limit path below.
- `arg != "all"`: if `limit_get_configured_limits()` returns `NULL` (plugin not registered — a `NULL` handle is never passed to `key_find()`), or `key_find()` on the returned handle finds no match, print the Requirement 1.3 "no such limit `<name>`" message and return (no partial output). Requirement 1.7's "no limits configured" message is reserved for the `all` path specifically — its own acceptance criterion scopes it to "when an operator requests information for all limits" — so a `NULL`-handle result on the named-lookup path reports absence of *that name*, not absence of configuration in general.
- Per-limit output: `fmt_limit()` into a stack buffer, printed line-by-line with the existing `[sim] ` prefix convention; then, only if `is_host_based_limit(core->resmgr, limit) > 0` AND the limit's own `consumers[CONSUMER_HOST] == NULL`, print the Requirement 1.6 effective-host-scope note.
- Per-limit output: `fmt_limit()` into a stack buffer, printed line-by-line with the existing `[sim] ` prefix convention; then, only if `is_host_based_limit(core->resmgr, limit) > 0` AND the limit's own per-host consumer was not explicitly configured, print the Requirement 1.6 effective-host-scope note. **Implementation note (task 2.3, corrected from an earlier inaccurate gloss here):** `limit->consumers[CONSUMER_HOST]` is never literally `NULL` for a production-built limit — `build_consumer()` (`xcelimit.c`) unconditionally allocates a consumer for all four dimensions, defaulting to the built-in "all" group with `per_consumer = 0` when unconfigured. The real discriminator is `consumers[CONSUMER_HOST]->per_consumer` (the same field `is_host_based_limit()` itself falls back to, and the same one `fmt_consumer()` uses to choose `"HOST ="` vs `"PER_HOST ="`) — check `(host_consumer == NULL) || (host_consumer->per_consumer == 0)`, not pointer-nullity alone.
- Live usage: iterate `limit_get_usage_instances()`, filter by `vres->limit == matched_limit` (pointer equality). `build_vres_name()` (existing, `xcelimit.c`) never encodes host in the vresname — so there is exactly one `xce_vres_t` per matched (user, queue, project) combination *regardless of host-scoping*; host breakout is NOT "one `xce_vres_t` per host," it is a per-worker fan-out over that single `xce_vres_t`'s shared resource index. Concretely, for each matching `xce_vres_t`:
- If `vres->vres` is `XCE_RESF_GLOBAL`/`XCE_RESF_SHARED`: exactly one displayed instance, reading capacity from `core.sharedInst[vres->vres->idx]` and accounted usage from `core.sharedInUse[vres->vres->idx]` — the exact read `repl_info_resource_one()` already implements for a GLOBAL/SHARED resource (bug-1593).
- Otherwise (host-scoped, explicit or implicit per Requirement 1.6): one displayed instance *per worker that has a populated entry at `vres->vres->idx`* — iterate `core.workers`, and for each worker `w`, include it only if `zarray_get(&(w->resvals), vres->vres->idx)` is non-NULL (a worker `put_resvals()` has never written to, e.g. an unevaluated host, must not produce a fabricated zero-usage line, matching Requirement 2.2's no-fabrication rule at per-host granularity). For an included worker, capacity comes from `w->resvals[vres->vres->idx]` and accounted usage from `w->resInUse[vres->vres->idx]` — the sibling capacity/in-use zarray pair `sched_check_host_satisfy_()` (`resource.c`) already reads the identical way for ordinary consumable resources.

View file

@ -23,7 +23,7 @@
- Observable: a limit configured with an exclusion group ("all except X") shows both the inclusion and the exclusion in its output; `info limit all` against a fixture with multiple configured limits displays every one's setting
- _Requirements: 1.1, 1.2, 1.5_
- [ ] 2.3 Display effective host-scope for implicitly host-scoped limits
- [x] 2.3 Display effective host-scope for implicitly host-scoped limits
- After `fmt_limit()`'s output, call `is_host_based_limit(core->resmgr, limit)`; if it returns `> 0` AND 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_