feat(scheduler-sim-diagnostics): add fmt_limit_units() sibling (task 8.1)

Adds fmt_limit_units(buf, len, limit, units_mode) to src/lib/xcelimit.c,
a units-mode-aware sibling of fmt_limit() following the
fmt_jobid()/fmt_jobid_ex() extended-sibling pattern. Reuses fmt_limit()'s
exact consumer-dump loop untouched; only the final value line branches:
percent limits always render bare "%f%%" regardless of mode, byte-valued
(or "swp"-special-cased) absolute values render via
xce_format_units_value() (task 7), everything else keeps the
unconditional "%f".

fmt_limit() itself is now a one-line XCE_UNITS_RAW wrapper around
fmt_limit_units() -- provably byte-identical to its prior output for
every input shape, so its 5 existing direct unit tests needed zero
changes (the regression proof this refactor relies on).

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-20 05:09:09 +00:00
parent eb23c158ae
commit 9f54c5753f
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
4 changed files with 362 additions and 5 deletions

View file

@ -76,7 +76,7 @@
- _Boundary: Shared Units Formatter, REPL Limit Info Command (the retyped composer and its thin-wrapper callers live in `repl_info.c`/`repl_fmt.c`)_
- [ ] 8. Core: Human-readable configured-setting display
- [ ] 8.1 Add a display-mode-aware way to render a limit's configured setting
- [x] 8.1 Add a display-mode-aware way to render a limit's configured setting
- New entry point produces the same configured-setting output the existing formatter already produces, except a byte-valued, non-percentage resource's amount renders in human-readable form according to a supplied display mode
- The existing formatter's own output is provably unchanged for every input it can be given — proven by its complete pre-existing test coverage continuing to pass with zero modification
- Observable: the existing configured-setting formatter's test coverage passes unmodified, and a new test confirms the mode-aware variant renders a byte-valued resource's amount differently across each supported display mode while leaving a percentage or non-byte-valued resource's rendering untouched

View file

@ -9,6 +9,7 @@
#include "group.h"
#include "kvmgr.h"
#include "utils.h"
#include "xce/types.h"
extern int build_consumer(xce_consumer_t**, var, xce_consumer_type_t, int,
@ -20,6 +21,14 @@ extern int build_vres_name(char**, xce_limit_t*, char*, char*, char*);
extern int is_host_based_limit(var, xce_limit_t*);
extern int fmt_consumer(char*, int, xce_consumer_t*);
extern int fmt_limit(char*, int, xce_limit_t*);
/* fmt_limit_units() -- scheduler-sim-diagnostics spec, Requirement 6, task
* 8.1. Sibling of fmt_limit() (the fmt_jobid()/fmt_jobid_ex() pattern):
* identical consumer-dump loop, but the final configured-value line renders
* a byte-valued, non-percentage resource's amount via xce_format_units_value()
* according to the supplied units_mode instead of always the bare "%f".
* fmt_limit() itself is now a one-line wrapper: fmt_limit_units(buf, len,
* limit, XCE_UNITS_RAW) -- byte-identical to its own pre-existing output. */
extern int fmt_limit_units(char*, int, xce_limit_t*, xce_units_mode_t);
extern void kill_limit(xce_limit_t*);
extern int instantiate_limit(xce_vres_t**, var, xce_limit_t*, char*, char*,
char*);

View file

@ -360,12 +360,299 @@ static void test_match_limit2(void)
destroy(umgr);
}
/* ---------------------------------------------------------------------
* fmt_limit_units() -- scheduler-sim-diagnostics spec, Requirement 6,
* task 8.1. fmt_limit() itself becomes a one-line XCE_UNITS_RAW wrapper
* around this new sibling entry point; the five tests above (unmodified)
* are the regression proof that the wrapper is byte-identical to
* fmt_limit()'s pre-existing output. These new tests cover
* fmt_limit_units()'s own additional behavior: byte-valued resources
* rendered per mode, percentage-based and non-byte-valued resources left
* unaffected by mode, and the "swp" MB->bytes special case mirroring
* repl_format_numeric_resource_value()'s existing convention.
* ------------------------------------------------------------------- */
static void test_fmt_limit_units_byte_valued_resource_renders_per_mode(void)
{
var umgr;
var qmgr;
var hmgr;
var pmgr;
xce_limit_t* limit;
int rc;
xce_limit_config_t limit_config = {
.name = "limit_mem",
.per_user = 0,
.per_queue = 0,
.per_project = 0,
.per_host = 0,
.users = NULL,
.queues = NULL,
.hosts = NULL,
.projects = NULL,
.val
= &(xce_limit_val_t) { .resname = "mem", .percent = 0,
/* 1 GiB, exactly representable as float --
* avoids float-rounding ambiguity in the
* expected-string literals below. */
.val = 1073741824.0 }
};
char buf[1024];
groupmgr_init(&umgr, 4, 4);
groupmgr_init(&qmgr, 4, 4);
groupmgr_init(&hmgr, 4, 4);
groupmgr_init(&pmgr, 4, 4);
rc = build_limit(&limit, umgr, qmgr, hmgr, pmgr, &limit_config);
TEST_ASSERT_TRUE(rc == 1);
memset(buf, 0x0, sizeof(buf));
rc = fmt_limit_units(buf, sizeof(buf) - 1, limit, XCE_UNITS_RAW);
TEST_ASSERT_TRUE(rc > 0);
TEST_ASSERT_NOT_NULL_MESSAGE(
strstr(buf, "\nmem = 1073741824.000000\n"),
"RAW mode must render the bare %f value for a byte-valued resource");
memset(buf, 0x0, sizeof(buf));
rc = fmt_limit_units(buf, sizeof(buf) - 1, limit, XCE_UNITS_HUMAN);
TEST_ASSERT_TRUE(rc > 0);
TEST_ASSERT_NOT_NULL_MESSAGE(
strstr(buf, "\nmem = 1.00 GB\n"),
"HUMAN mode must render the human-scaled value alone");
memset(buf, 0x0, sizeof(buf));
rc = fmt_limit_units(buf, sizeof(buf) - 1, limit, XCE_UNITS_BOTH);
TEST_ASSERT_TRUE(rc > 0);
TEST_ASSERT_NOT_NULL_MESSAGE(
strstr(buf, "\nmem = 1073741824.000000 (1.00 GB)\n"),
"BOTH mode must render raw and human forms together");
kill_limit(limit);
destroy(pmgr);
destroy(hmgr);
destroy(qmgr);
destroy(umgr);
}
static void test_fmt_limit_units_percent_limit_ignores_units_mode(void)
{
var umgr;
var qmgr;
var hmgr;
var pmgr;
xce_limit_t* limit;
int rc;
xce_limit_config_t limit_config = {
.name = "limit_mem_pct",
.per_user = 0,
.per_queue = 0,
.per_project = 0,
.per_host = 0,
.users = NULL,
.queues = NULL,
.hosts = NULL,
.projects = NULL,
.val
= &(xce_limit_val_t) { .resname = "mem", .percent = 1, .val = 80.0 }
};
char buf[1024];
xce_units_mode_t modes[] = { XCE_UNITS_RAW, XCE_UNITS_HUMAN,
XCE_UNITS_BOTH };
size_t i;
groupmgr_init(&umgr, 4, 4);
groupmgr_init(&qmgr, 4, 4);
groupmgr_init(&hmgr, 4, 4);
groupmgr_init(&pmgr, 4, 4);
rc = build_limit(&limit, umgr, qmgr, hmgr, pmgr, &limit_config);
TEST_ASSERT_TRUE(rc == 1);
for (i = 0; i < (sizeof(modes) / sizeof(modes[0])); i++) {
memset(buf, 0x0, sizeof(buf));
rc = fmt_limit_units(buf, sizeof(buf) - 1, limit, modes[i]);
TEST_ASSERT_TRUE(rc > 0);
TEST_ASSERT_NOT_NULL_MESSAGE(
strstr(buf, "\nmem = 80.000000%\n"),
"a percentage-based limit's value must render identically "
"regardless of units_mode (Requirement 6 AC3) -- a percentage "
"is never byte-scaled");
}
kill_limit(limit);
destroy(pmgr);
destroy(hmgr);
destroy(qmgr);
destroy(umgr);
}
static void test_fmt_limit_units_non_byte_valued_resource_ignores_units_mode(
void)
{
var umgr;
var qmgr;
var hmgr;
var pmgr;
xce_limit_t* limit;
int rc;
xce_limit_config_t limit_config = {
.name = "limit_slots",
.per_user = 0,
.per_queue = 0,
.per_project = 0,
.per_host = 0,
.users = NULL,
.queues = NULL,
.hosts = NULL,
.projects = NULL,
.val
= &(xce_limit_val_t) { .resname = "slots", .percent = 0, .val = 3.0 }
};
char buf[1024];
xce_units_mode_t modes[] = { XCE_UNITS_RAW, XCE_UNITS_HUMAN,
XCE_UNITS_BOTH };
size_t i;
groupmgr_init(&umgr, 4, 4);
groupmgr_init(&qmgr, 4, 4);
groupmgr_init(&hmgr, 4, 4);
groupmgr_init(&pmgr, 4, 4);
rc = build_limit(&limit, umgr, qmgr, hmgr, pmgr, &limit_config);
TEST_ASSERT_TRUE(rc == 1);
for (i = 0; i < (sizeof(modes) / sizeof(modes[0])); i++) {
memset(buf, 0x0, sizeof(buf));
rc = fmt_limit_units(buf, sizeof(buf) - 1, limit, modes[i]);
TEST_ASSERT_TRUE(rc > 0);
TEST_ASSERT_NOT_NULL_MESSAGE(
strstr(buf, "\nslots = 3.000000\n"),
"a non-byte-valued resource's value must render identically "
"regardless of units_mode (Requirement 6 AC3)");
}
kill_limit(limit);
destroy(pmgr);
destroy(hmgr);
destroy(qmgr);
destroy(umgr);
}
static void test_fmt_limit_units_swp_resource_applies_mb_to_bytes_conversion(
void)
{
var umgr;
var qmgr;
var hmgr;
var pmgr;
xce_limit_t* limit;
int rc;
xce_limit_config_t limit_config = {
.name = "limit_swp",
.per_user = 0,
.per_queue = 0,
.per_project = 0,
.per_host = 0,
.users = NULL,
.queues = NULL,
.hosts = NULL,
.projects = NULL,
.val
= &(xce_limit_val_t) { .resname = "swp", .percent = 0,
/* 1024 MB == 1 GiB once converted to bytes */
.val = 1024.0 }
};
char buf[1024];
groupmgr_init(&umgr, 4, 4);
groupmgr_init(&qmgr, 4, 4);
groupmgr_init(&hmgr, 4, 4);
groupmgr_init(&pmgr, 4, 4);
rc = build_limit(&limit, umgr, qmgr, hmgr, pmgr, &limit_config);
TEST_ASSERT_TRUE(rc == 1);
memset(buf, 0x0, sizeof(buf));
rc = fmt_limit_units(buf, sizeof(buf) - 1, limit, XCE_UNITS_HUMAN);
TEST_ASSERT_TRUE(rc > 0);
TEST_ASSERT_NOT_NULL_MESSAGE(
strstr(buf, "\nswp = 1.00 GB\n"),
"swp's wire unit is MB, so fmt_limit_units() must apply the same "
"MB-to-bytes conversion repl_format_numeric_resource_value() "
"already applies before scaling for display");
kill_limit(limit);
destroy(pmgr);
destroy(hmgr);
destroy(qmgr);
destroy(umgr);
}
static void test_fmt_limit_wrapper_matches_fmt_limit_units_raw_mode(void)
{
var umgr;
var qmgr;
var hmgr;
var pmgr;
xce_limit_t* limit;
int rc1, rc2;
xce_limit_config_t limit_config = {
.name = "limit_mem_wrapper",
.per_user = 0,
.per_queue = 0,
.per_project = 0,
.per_host = 0,
.users = NULL,
.queues = NULL,
.hosts = NULL,
.projects = NULL,
.val
= &(xce_limit_val_t) { .resname = "mem", .percent = 0,
.val = 1073741824.0 }
};
char buf1[1024];
char buf2[1024];
groupmgr_init(&umgr, 4, 4);
groupmgr_init(&qmgr, 4, 4);
groupmgr_init(&hmgr, 4, 4);
groupmgr_init(&pmgr, 4, 4);
rc1 = build_limit(&limit, umgr, qmgr, hmgr, pmgr, &limit_config);
TEST_ASSERT_TRUE(rc1 == 1);
memset(buf1, 0x0, sizeof(buf1));
memset(buf2, 0x0, sizeof(buf2));
rc1 = fmt_limit(buf1, sizeof(buf1) - 1, limit);
rc2 = fmt_limit_units(buf2, sizeof(buf2) - 1, limit, XCE_UNITS_RAW);
TEST_ASSERT_TRUE(rc1 > 0);
TEST_ASSERT_TRUE(rc2 > 0);
TEST_ASSERT_EQUAL_STRING_MESSAGE(
buf2, buf1,
"fmt_limit() must remain a byte-identical XCE_UNITS_RAW wrapper "
"around fmt_limit_units()");
kill_limit(limit);
destroy(pmgr);
destroy(hmgr);
destroy(qmgr);
destroy(umgr);
}
void test_limit(void)
{
printf("\n>> limit test\n\n");
xstring_table_init(139);
RUN_TEST(test_build_consumer1);
RUN_TEST(test_build_consumer2);
RUN_TEST(test_build_consumer_empty_config_defaults_to_all);
@ -374,6 +661,12 @@ void test_limit(void)
RUN_TEST(test_match_limit_ok2);
RUN_TEST(test_match_limit1);
RUN_TEST(test_match_limit2);
RUN_TEST(test_fmt_limit_units_byte_valued_resource_renders_per_mode);
RUN_TEST(test_fmt_limit_units_percent_limit_ignores_units_mode);
RUN_TEST(
test_fmt_limit_units_non_byte_valued_resource_ignores_units_mode);
RUN_TEST(test_fmt_limit_units_swp_resource_applies_mb_to_bytes_conversion);
RUN_TEST(test_fmt_limit_wrapper_matches_fmt_limit_units_raw_mode);
xstring_table_fini();
}

View file

@ -151,7 +151,37 @@ void kill_consumer(xce_consumer_t* c)
FREEUP(c);
}
int fmt_limit(char* buf, int len, xce_limit_t* limit)
/*
* fmt_limit_units() -- scheduler-sim-diagnostics spec, Requirement 6, task
* 8.1. Sibling of fmt_limit() carrying one extra parameter, following this
* codebase's established "add a sibling rather than break an existing
* signature" pattern (fmt_jobid()/fmt_jobid_ex(),
* limit_seed_config()/limit_seed_config_impl()).
*
* The consumer-dump loop is IDENTICAL to fmt_limit()'s -- untouched. Only
* the final configured-value line's formatting differs:
* - percent != 0: always the bare "%f%%" rendering, regardless of
* units_mode -- a percentage is never byte-scaled (Requirement 6 AC3).
* - percent == 0, resource is byte-valued (xce_is_bytes_valued_resource())
* or the "swp" special case (its wire unit is MB, not bytes -- the same
* check repl_format_numeric_resource_value() already applies, converted
* via *1024.0*1024.0 before formatting): render via
* xce_format_units_value(units_mode, bytes_value, ...).
* - otherwise (non-byte-valued, e.g. "slots"): keep today's exact "%f"
* output unconditionally -- units_mode has zero effect (Requirement 6
* AC3).
*
* fmt_limit() itself is a one-line wrapper around this function in
* XCE_UNITS_RAW mode. That is provably byte-identical to fmt_limit()'s own
* pre-existing output for every percent/non-percent, byte-valued/
* non-byte-valued shape: xce_format_units_value(XCE_UNITS_RAW, v, ...)
* always renders the bare "%f" of v (see its own implementation), matching
* what the old inline fmt_limit() body produced. This is why
* test_limit.c's five pre-existing fmt_limit() call sites needed zero
* changes -- they are the regression proof.
*/
int fmt_limit_units(char* buf, int len, xce_limit_t* limit,
xce_units_mode_t units_mode)
{
int pos = 0;
int i, rc;
@ -181,8 +211,28 @@ int fmt_limit(char* buf, int len, xce_limit_t* limit)
}
}
rc = snprintf(buf + pos, len - pos, "\n%s = %f%s\n", limit->val.resname,
limit->val.val, (limit->val.percent != 0) ? "%" : "");
if (limit->val.percent != 0) {
rc = snprintf(buf + pos, len - pos, "\n%s = %f%%\n",
limit->val.resname, limit->val.val);
} else {
bool is_swp = (limit->val.resname != NULL)
&& (strcmp(limit->val.resname, "swp") == 0);
if (xce_is_bytes_valued_resource(limit->val.resname) || is_swp) {
double bytes_value = is_swp
? ((double)limit->val.val * 1024.0 * 1024.0)
: (double)limit->val.val;
char valbuf[128];
xce_format_units_value(units_mode, bytes_value, valbuf,
sizeof(valbuf));
rc = snprintf(buf + pos, len - pos, "\n%s = %s\n",
limit->val.resname, valbuf);
} else {
rc = snprintf(buf + pos, len - pos, "\n%s = %f\n",
limit->val.resname, limit->val.val);
}
}
if (rc < 0) {
return rc;
}
@ -190,6 +240,11 @@ int fmt_limit(char* buf, int len, xce_limit_t* limit)
return strlen(buf);
}
int fmt_limit(char* buf, int len, xce_limit_t* limit)
{
return fmt_limit_units(buf, len, limit, XCE_UNITS_RAW);
}
void kill_limit(xce_limit_t* limit)
{
int i;