feat(scheduler-sim-diagnostics): display configured limit setting via fmt_limit()
repl_info_limit() now prints a matched limit's configured setting on both the all and named-lookup success paths: fmt_limit() (existing libxce formatter, unmodified) into a stack buffer, re-split and printed through a new repl_print_limit_dump() helper matching this file's existing [sim]-prefix- per-line convention. Inclusion and exclusion group membership both render since fmt_group() already handles both, adopted as-is per design.md's explicit reuse decision. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
This commit is contained in:
parent
d3df8fb29f
commit
117d01c77e
3 changed files with 199 additions and 15 deletions
|
|
@ -17,7 +17,7 @@
|
||||||
- _Requirements: 1.3, 1.7, 4.1, 4.2_
|
- _Requirements: 1.3, 1.7, 4.1, 4.2_
|
||||||
- _Depends: 1_
|
- _Depends: 1_
|
||||||
|
|
||||||
- [ ] 2.2 Display configured setting including scope membership
|
- [x] 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 via `fprintf(stdout, ...)`, never bare `printf()` (`test_no_bare_printf.c` regression-gates this)
|
- 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 via `fprintf(stdout, ...)`, never bare `printf()` (`test_no_bare_printf.c` regression-gates this)
|
||||||
- Inclusion and exclusion group membership both render (`fmt_group()` already handles this, adopted as-is)
|
- 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 all` against a fixture with multiple configured limits displays every one's setting
|
- 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
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
#include "repl_internal.h"
|
#include "repl_internal.h"
|
||||||
#include "resreq.h"
|
#include "resreq.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
#include "xcelimit.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* repl_info.c - state inspection (scheduler-core spec, task 12.2, Req 5 AC
|
* repl_info.c - state inspection (scheduler-core spec, task 12.2, Req 5 AC
|
||||||
|
|
@ -1155,14 +1156,52 @@ static void repl_info_resource(xce_sched_sim_driver_t* sim, const char* arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* repl_info_limit - `info limit <name|all>` name/all resolution
|
* repl_print_limit_dump - print fmt_limit()'s (src/lib/xcelimit.c) fully
|
||||||
* (scheduler-sim-diagnostics spec, Requirement Set 1, task 2.1, Req 1.3,
|
* verbatim, library-owned buffer for a matched limit's configured setting
|
||||||
* 1.7, 4.1, 4.2). THIS task implements only the name/all resolution and
|
* (task 2.2, Req 1.1/1.2/1.5) using this file's own existing "[sim] "
|
||||||
* the two absence messages -- printing a matched limit's configured
|
* per-line prefix convention for a multi-line info dump -- the same
|
||||||
* setting (fmt_limit(), Req 1.1/1.2/1.5) is task 2.2's job, and printing
|
* convention repl_info_break() and repl_info_resource_one() (both above)
|
||||||
* its live usage instances (Req 2.1-2.3, 3.1-3.4) is task 3's job. Not yet
|
* already use. fmt_limit() embeds '\n' between its logical lines (limit
|
||||||
* wired into repl_cmd_info() (task 4 adds that branch) -- static and
|
* name, one line per configured consumer dimension via fmt_consumer()/
|
||||||
* currently uncalled from production code; exercised directly by
|
* fmt_group(), then the constrained resource/value line) rather than
|
||||||
|
* returning an array of strings, so each line is re-split here and printed
|
||||||
|
* individually with its own "[sim] " prefix rather than emitting the whole
|
||||||
|
* buffer behind a single leading prefix (design.md's REPL Limit Info
|
||||||
|
* Command Responsibilities: "fmt_limit() into a stack buffer, printed
|
||||||
|
* line-by-line with the existing [sim] prefix convention").
|
||||||
|
*
|
||||||
|
* Per design.md's/research.md's explicit Boundary Commitments, this
|
||||||
|
* function only re-prefixes fmt_limit()'s own output -- it does not
|
||||||
|
* reimplement, reformat, or second-guess anything fmt_limit()/
|
||||||
|
* fmt_consumer()/fmt_group() decided, including their exclusion-group "~"
|
||||||
|
* rendering (Req 1.5).
|
||||||
|
*/
|
||||||
|
static void repl_print_limit_dump(const char* buf)
|
||||||
|
{
|
||||||
|
const char* line = buf;
|
||||||
|
|
||||||
|
while (*line != '\0') {
|
||||||
|
const char* nl = strchr(line, '\n');
|
||||||
|
int len = (nl != NULL) ? (int)(nl - line) : (int)strlen(line);
|
||||||
|
|
||||||
|
fprintf(stdout, "[sim] %.*s\n", len, line);
|
||||||
|
|
||||||
|
if (nl == NULL) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
line = nl + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* repl_info_limit - `info limit <name|all>` name/all resolution (task 2.1)
|
||||||
|
* plus, once a genuine match is found, printing that limit's configured
|
||||||
|
* setting via the library's own fmt_limit()/fmt_group() formatters (task
|
||||||
|
* 2.2, Req 1.1/1.2/1.5) -- scheduler-sim-diagnostics spec, Requirement Set
|
||||||
|
* 1. Printing the Req 1.6 effective-host-scope note is task 2.3's job, and
|
||||||
|
* printing live usage instances (Req 2.1-2.3, 3.1-3.4) is task 3's job.
|
||||||
|
* Not yet wired into repl_cmd_info() (task 4 adds that branch) -- static
|
||||||
|
* and currently uncalled from production code; exercised directly by
|
||||||
* test_repl_limit_table.c per this task's own scope.
|
* test_repl_limit_table.c per this task's own scope.
|
||||||
*
|
*
|
||||||
* Requirement 1.7's "no limits configured" message is reserved for the
|
* Requirement 1.7's "no limits configured" message is reserved for the
|
||||||
|
|
@ -1202,9 +1241,16 @@ static void repl_info_limit(xce_sched_sim_driver_t* sim, const char* arg)
|
||||||
if ((rc < 0) || (rc == RTTI_ITER_NO_DATA) || (limit == NULL)) {
|
if ((rc < 0) || (rc == RTTI_ITER_NO_DATA) || (limit == NULL)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/* task 2.2 prints this limit's configured setting here
|
{
|
||||||
* (fmt_limit()); task 3 adds its live usage. This task's own
|
char limit_buf[1024];
|
||||||
* scope stops at proving resolution reaches a genuine match. */
|
int fmt_rc = fmt_limit(limit_buf, sizeof(limit_buf), limit);
|
||||||
|
|
||||||
|
if (fmt_rc >= 0) {
|
||||||
|
repl_print_limit_dump(limit_buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* task 2.3 adds the Req 1.6 effective-host-scope note here;
|
||||||
|
* task 3 adds this limit's live usage. */
|
||||||
n_shown++;
|
n_shown++;
|
||||||
}
|
}
|
||||||
iter_destroy(&it);
|
iter_destroy(&it);
|
||||||
|
|
@ -1226,9 +1272,16 @@ static void repl_info_limit(xce_sched_sim_driver_t* sim, const char* arg)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* task 2.2 prints the matched limit's configured setting here
|
{
|
||||||
* (fmt_limit()); task 3 adds its live usage. This task's own scope
|
char limit_buf[1024];
|
||||||
* stops at proving resolution reaches a genuine match. */
|
int fmt_rc = fmt_limit(limit_buf, sizeof(limit_buf), match);
|
||||||
|
|
||||||
|
if (fmt_rc >= 0) {
|
||||||
|
repl_print_limit_dump(limit_buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* task 2.3 adds the Req 1.6 effective-host-scope note here; task 3
|
||||||
|
* adds this limit's live usage. */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -378,6 +378,51 @@ static void add_configured_limit(xce_sched_plugin_t* plugin, const char* name)
|
||||||
free_limit_config(limits[0]);
|
free_limit_config(limits[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* add_configured_limit_with_host_exclusion - task 2.2's own Requirement 1.5
|
||||||
|
* fixture: an "all except X" exclusion group on the HOST dimension, the
|
||||||
|
* same shape test_limit.c's own test_build_consumer2() already establishes
|
||||||
|
* for the USER dimension (.members=["all"], .ex_members=[<excluded>]).
|
||||||
|
* Unlike add_configured_limit()/make_limit_config() (which leaves every
|
||||||
|
* consumer's xce_group_config_t* NULL, so build_consumer() falls back to
|
||||||
|
* the builtin "all" group for all four dimensions), this drives the real
|
||||||
|
* limit_update_config() with an explicit `.hosts` group config.
|
||||||
|
*
|
||||||
|
* All storage here (val, hosts_cfg, limit_cfg, the `limits` array) lives on
|
||||||
|
* this function's own stack frame; limit_update_config() -> build_limit()
|
||||||
|
* -> build_consumer() -> makeGroup() synchronously copies every member
|
||||||
|
* name into the real, persisted xce_group_t (and build_limit() itself
|
||||||
|
* strdup()s the limit/resource names) before this function returns, so
|
||||||
|
* nothing here needs to outlive the call -- the identical lifetime
|
||||||
|
* assumption test_limit.c's own test_build_limit_ok1()-style compound
|
||||||
|
* literals already rely on.
|
||||||
|
*/
|
||||||
|
static void add_configured_limit_with_host_exclusion(
|
||||||
|
xce_sched_plugin_t* plugin, const char* name, const char* included,
|
||||||
|
const char* excluded)
|
||||||
|
{
|
||||||
|
xce_limit_val_t val = { .resname = "mem",
|
||||||
|
.percent = 0,
|
||||||
|
.val = 10.0 };
|
||||||
|
xce_group_config_t hosts_cfg
|
||||||
|
= { .members = { .count = 1, .data = (char*[]) { (char*)included } },
|
||||||
|
.ex_members = { .count = 1,
|
||||||
|
.data = (char*[]) { (char*)excluded } } };
|
||||||
|
xce_limit_config_t limit_cfg = { .name = (char*)name,
|
||||||
|
.hosts = &hosts_cfg,
|
||||||
|
.val = &val };
|
||||||
|
xce_limit_config_t* limits[1] = { &limit_cfg };
|
||||||
|
xce_conf4sched_t config;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
memset(&config, 0, sizeof(config));
|
||||||
|
config.nlimits = 1;
|
||||||
|
config.limits = limits;
|
||||||
|
|
||||||
|
rc = limit_update_config(plugin, &config);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------------------------------------------------------------------- */
|
/* ---------------------------------------------------------------------- */
|
||||||
/* Tests */
|
/* Tests */
|
||||||
/* ---------------------------------------------------------------------- */
|
/* ---------------------------------------------------------------------- */
|
||||||
|
|
@ -602,6 +647,89 @@ test_info_limit_named_does_not_report_no_such_limit_for_configured_name(void)
|
||||||
"configured_name: PASS\n");
|
"configured_name: PASS\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Task 2.2's own Observable #1 (Requirement 1.1, 1.5): a matched limit's
|
||||||
|
* configured setting is actually printed -- for the first time,
|
||||||
|
* repl_info_limit() finding a real match must emit something beyond
|
||||||
|
* silence. A limit configured with an exclusion group ("all except X" on
|
||||||
|
* the HOST dimension) must show BOTH the inclusion member and the
|
||||||
|
* exclusion member (fmt_group()'s "~" prefix) in the same displayed scope
|
||||||
|
* line, not just the presence of some output.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
test_info_limit_named_shows_both_inclusion_and_exclusion_membership(void)
|
||||||
|
{
|
||||||
|
xce_sched_sim_driver_t sim;
|
||||||
|
xce_sched_plugin_t plugin;
|
||||||
|
xce_scheduler_t group_sched;
|
||||||
|
char* captured;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
memset(&sim, 0, sizeof(sim));
|
||||||
|
rc = xce_sched_core_init(&(sim.core));
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||||
|
|
||||||
|
register_limit_plugin(&sim, &plugin, &group_sched);
|
||||||
|
add_configured_limit_with_host_exclusion(&plugin, "host_limit1", "all",
|
||||||
|
"excluded_host");
|
||||||
|
|
||||||
|
captured = capture_info_limit(&sim, "host_limit1");
|
||||||
|
|
||||||
|
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||||
|
strstr(captured, "limit: host_limit1"),
|
||||||
|
"the matched limit's fmt_limit() dump must include its name line "
|
||||||
|
"(proves real fmt_limit() output, not a stub)");
|
||||||
|
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||||
|
strstr(captured, "HOST = [all ~excluded_host]"),
|
||||||
|
"Requirement 1.5: the HOST scope line must show BOTH the inclusion "
|
||||||
|
"member ('all') and the exclusion member ('~excluded_host', "
|
||||||
|
"fmt_group()'s own exclusion prefix) together, distinguishing "
|
||||||
|
"included members from explicitly excluded ones");
|
||||||
|
|
||||||
|
free(captured);
|
||||||
|
|
||||||
|
printf("test_info_limit_named_shows_both_inclusion_and_exclusion_"
|
||||||
|
"membership: PASS\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Task 2.2's own Observable #2 (Requirement 1.2): `info limit all` against
|
||||||
|
* a fixture with MULTIPLE configured limits must display every one's
|
||||||
|
* configured setting, not just the first match or a truncated listing.
|
||||||
|
*/
|
||||||
|
static void test_info_limit_all_displays_every_configured_limits_setting(void)
|
||||||
|
{
|
||||||
|
xce_sched_sim_driver_t sim;
|
||||||
|
xce_sched_plugin_t plugin;
|
||||||
|
xce_scheduler_t group_sched;
|
||||||
|
char* captured;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
memset(&sim, 0, sizeof(sim));
|
||||||
|
rc = xce_sched_core_init(&(sim.core));
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||||
|
|
||||||
|
register_limit_plugin(&sim, &plugin, &group_sched);
|
||||||
|
add_configured_limit(&plugin, "limitA");
|
||||||
|
add_configured_limit(&plugin, "limitB");
|
||||||
|
|
||||||
|
captured = capture_info_limit(&sim, "all");
|
||||||
|
|
||||||
|
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||||
|
strstr(captured, "limit: limitA"),
|
||||||
|
"Requirement 1.2: 'info limit all' must display limitA's "
|
||||||
|
"configured setting");
|
||||||
|
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||||
|
strstr(captured, "limit: limitB"),
|
||||||
|
"Requirement 1.2: 'info limit all' must display limitB's "
|
||||||
|
"configured setting too -- every configured limit, not just one");
|
||||||
|
|
||||||
|
free(captured);
|
||||||
|
|
||||||
|
printf("test_info_limit_all_displays_every_configured_limits_setting: "
|
||||||
|
"PASS\n");
|
||||||
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
UNITY_BEGIN();
|
UNITY_BEGIN();
|
||||||
|
|
@ -617,5 +745,8 @@ int main(void)
|
||||||
test_info_limit_named_reports_no_such_limit_when_plugin_never_registered_not_no_limits_configured);
|
test_info_limit_named_reports_no_such_limit_when_plugin_never_registered_not_no_limits_configured);
|
||||||
RUN_TEST(
|
RUN_TEST(
|
||||||
test_info_limit_named_does_not_report_no_such_limit_for_configured_name);
|
test_info_limit_named_does_not_report_no_such_limit_for_configured_name);
|
||||||
|
RUN_TEST(
|
||||||
|
test_info_limit_named_shows_both_inclusion_and_exclusion_membership);
|
||||||
|
RUN_TEST(test_info_limit_all_displays_every_configured_limits_setting);
|
||||||
return UNITY_END();
|
return UNITY_END();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue