feat(scheduler-sim-diagnostics): display live usage for cluster-wide limit instances
repl_info_limit() now displays accounted-usage/capacity for GLOBAL/SHARED limit instances: iterates limit_get_usage_instances(), filters by vres->limit == matched_limit, reads capacity from core.sharedInst[idx] and accounted usage from core.sharedInUse[idx]. Existence-of-instance and zero-usage are kept distinct per design.md: no matching instance (or one with no populated capacity) prints "no active usage instance yet"; a matched-but-never-charged instance correctly shows accounted=0 instead. Self-caught during implementation and independently confirmed by review: a negative or stale vres->idx (possible per add_vres()'s allocate-then-assign pattern) is not safely rejected by zarray_get_ex()'s idx>=caps check alone -- the new code adds an explicit idx<0 guard rather than copying repl_info_resource_one()'s existing .count-based pre-check (which is itself imprecise for a sparsely-populated zarray, a pre-existing issue left out of scope here). Calling limit_get_usage_instances() for the first time from repl_info.c reproduced the same ~24-file link-stub ripple task 2.1 established for limit_get_configured_limits() -- fixed with the identical pattern. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
This commit is contained in:
parent
5876716eee
commit
6148cb0569
27 changed files with 677 additions and 3 deletions
|
|
@ -513,6 +513,91 @@ static void add_configured_percent_limit_explicit_per_host(
|
|||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Live-usage fixture helpers (task 3.1, Requirement 2.1/2.2/2.3/3.3/1.4) */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* instantiate_and_register_global_vres - drives the real, already-linked
|
||||
* instantiate_limit() (xcelimit.c, part of libxce -- NOT plugins/limit.c,
|
||||
* so no new link dependency) directly against `matched_limit` to create a
|
||||
* genuine xce_vres_t exactly the way match_job_global_limit_()
|
||||
* (plugins/limit.c) would on a real job match, then key_add()s it into
|
||||
* `plugin`'s privdata->instances -- this TU has direct access to
|
||||
* limit_plugin_data_t since it white-box #includes ../src/plugins/limit.c
|
||||
* itself, so this is the same table entry limit_get_usage_instances()
|
||||
* exposes, populated the same way production code populates it.
|
||||
*
|
||||
* `matched_limit` must be configured the way add_configured_limit() shapes
|
||||
* it (absolute value, i.e. percent=0, on "mem") so is_host_based_limit()
|
||||
* returns 0 and instantiate_limit()'s internal `shared` local stays 1 --
|
||||
* add_vres() then flags the resulting vres->vres XCE_RESF_SHARED|
|
||||
* XCE_RESF_GLOBAL, the exact case task 3.1's own scope covers (a per-host
|
||||
* vres is task 3.2's job, deliberately not exercised here).
|
||||
*
|
||||
* user/queue/project are passed as empty strings, never NULL --
|
||||
* instantiate_limit()/build_vres_name() both reject NULL outright (see
|
||||
* xcelimit.c). Since this fixture's limit has no per-user/queue/project
|
||||
* scoping configured (per_consumer=0 for all three, add_configured_limit()'s
|
||||
* own shape), build_vres_name() never actually reads these values into the
|
||||
* vresname it builds anyway -- only "limit=<name>" is ever produced for
|
||||
* this fixture family.
|
||||
*/
|
||||
static xce_vres_t* instantiate_and_register_global_vres(
|
||||
xce_sched_sim_driver_t* sim, xce_sched_plugin_t* plugin,
|
||||
xce_limit_t* matched_limit)
|
||||
{
|
||||
limit_plugin_data_t* privdata = (limit_plugin_data_t*)plugin->data;
|
||||
xce_vres_t* vres = NULL;
|
||||
int rc;
|
||||
|
||||
rc = instantiate_limit(&vres, sim->core.resmgr, matched_limit, "", "",
|
||||
"");
|
||||
/* instantiate_limit() returns its own final key_find()'s positive
|
||||
* result on success (matching match_job_global_limit_()'s own
|
||||
* `if (rc < 0) return rc;` success convention, plugins/limit.c), not
|
||||
* literally 0 -- do not assert rc == 0 here. */
|
||||
TEST_ASSERT_TRUE(rc >= 0);
|
||||
TEST_ASSERT_NOT_NULL(vres);
|
||||
TEST_ASSERT_NOT_NULL(vres->vres);
|
||||
TEST_ASSERT_TRUE(vres->vres->idx >= 0);
|
||||
TEST_ASSERT_TRUE_MESSAGE(
|
||||
(vres->vres->flags & (XCE_RESF_GLOBAL | XCE_RESF_SHARED)) != 0,
|
||||
"fixture sanity: this helper only ever produces a GLOBAL/SHARED "
|
||||
"vres -- a per-host vres would mean the fixture's own limit shape "
|
||||
"drifted from add_configured_limit()'s absolute-value contract");
|
||||
|
||||
rc = key_add(privdata->instances, (void*)vres->vres->name, (void*)vres);
|
||||
TEST_ASSERT_TRUE(rc >= 0);
|
||||
|
||||
return vres;
|
||||
}
|
||||
|
||||
/*
|
||||
* put_shared_fvalue - manually populates sim->core.sharedInst or
|
||||
* core.sharedInUse at `idx` with a Value_u_fvalue xce_val_t -- the exact
|
||||
* shape put_resvals() (resource.c, deliberately NOT linked into this TU,
|
||||
* see this file's own header comment) would build. Constructed by hand
|
||||
* here rather than driving the real put_resvals()/update_limit_res_value()
|
||||
* so this fixture does not need to link resource.c for real -- a
|
||||
* deliberate, simpler-and-equally-valid choice this task's own brief calls
|
||||
* out ("directly construct an xce_vres_t ... and manually populate
|
||||
* core.sharedInst/core.sharedInUse via zarray_put() ... matching how
|
||||
* update_limit_res_value()/put_resvals() do it").
|
||||
*/
|
||||
static void put_shared_fvalue(zarray_t* array, int idx, double val)
|
||||
{
|
||||
xce_val_t* v = (xce_val_t*)calloc(1, sizeof(*v));
|
||||
int rc;
|
||||
|
||||
TEST_ASSERT_NOT_NULL(v);
|
||||
v->which = Value_u_fvalue;
|
||||
v->u.fvalue = val;
|
||||
|
||||
rc = zarray_put(array, idx, (void*)v);
|
||||
TEST_ASSERT_TRUE(rc >= 0);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Tests */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
|
@ -961,6 +1046,160 @@ test_info_limit_named_does_not_duplicate_host_scope_note_when_per_host_explicitl
|
|||
"per_host_explicitly_configured: PASS\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Task 3.1's own central Observable #1 (Requirement 2.1, 1.4, 3.3): a
|
||||
* GLOBAL/SHARED limit instance that has genuinely been matched AND charged
|
||||
* shows an accounted/capacity pair reflecting that real state -- not a
|
||||
* placeholder or the no-instance message.
|
||||
*/
|
||||
static void
|
||||
test_info_limit_named_shows_accounted_and_capacity_for_matched_and_charged_global_instance(
|
||||
void)
|
||||
{
|
||||
xce_sched_sim_driver_t sim;
|
||||
xce_sched_plugin_t plugin;
|
||||
xce_scheduler_t group_sched;
|
||||
xce_limit_t* matched = NULL;
|
||||
var limits_handle;
|
||||
xce_vres_t* vres;
|
||||
char* captured;
|
||||
int rc;
|
||||
|
||||
memset(&sim, 0, sizeof(sim));
|
||||
rc = xce_sched_core_init(&(sim.core));
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
register_per_host_resource(&sim, "mem");
|
||||
register_limit_plugin(&sim, &plugin, &group_sched);
|
||||
add_configured_limit(&plugin, "charged_limit");
|
||||
|
||||
limits_handle = limit_get_configured_limits(&(sim.core));
|
||||
rc = key_find(limits_handle, (void*)"charged_limit", (void**)&matched);
|
||||
TEST_ASSERT_TRUE(rc > 0);
|
||||
TEST_ASSERT_NOT_NULL(matched);
|
||||
|
||||
vres = instantiate_and_register_global_vres(&sim, &plugin, matched);
|
||||
put_shared_fvalue(&(sim.core.sharedInst), vres->vres->idx, 10.0);
|
||||
put_shared_fvalue(&(sim.core.sharedInUse), vres->vres->idx, 4.0);
|
||||
|
||||
captured = capture_info_limit(&sim, "charged_limit");
|
||||
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||
strstr(captured, "accounted=4.00"),
|
||||
"Requirement 2.1: a global limit matched and charged must show its "
|
||||
"accounted usage");
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||
strstr(captured, "capacity=10.00"),
|
||||
"Requirement 2.1: ... alongside its configured capacity");
|
||||
TEST_ASSERT_NULL_MESSAGE(
|
||||
strstr(captured, "no active usage instance yet"),
|
||||
"a genuinely matched+charged instance must not report 'no active "
|
||||
"usage instance yet'");
|
||||
|
||||
free(captured);
|
||||
|
||||
printf("test_info_limit_named_shows_accounted_and_capacity_for_matched_"
|
||||
"and_charged_global_instance: PASS\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Task 3.1's own central Observable #2 (Requirement 2.2): a configured
|
||||
* limit that has NEVER been evaluated against any job (no xce_vres_t at
|
||||
* all in privdata->instances for it) must report the explicit no-instance
|
||||
* message, never a fabricated zero-usage line.
|
||||
*/
|
||||
static void
|
||||
test_info_limit_named_shows_no_active_usage_instance_for_never_matched_limit(
|
||||
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, "never_matched_limit");
|
||||
/* Deliberately no instantiate_and_register_global_vres() call -- this
|
||||
* limit has never been evaluated against any job, so
|
||||
* privdata->instances has no xce_vres_t at all for it. */
|
||||
|
||||
captured = capture_info_limit(&sim, "never_matched_limit");
|
||||
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||
strstr(captured, "no active usage instance yet"),
|
||||
"Requirement 2.2: a configured-but-never-evaluated limit must "
|
||||
"report no active usage instance, not a fabricated zero");
|
||||
|
||||
free(captured);
|
||||
|
||||
printf("test_info_limit_named_shows_no_active_usage_instance_for_never_"
|
||||
"matched_limit: PASS\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Task 3.1's own central Observable #3 -- THE existence-vs-zero-usage
|
||||
* distinction (Requirement 2.2's own most important correctness property,
|
||||
* per design.md's explicit callout): a vres that genuinely exists AND has
|
||||
* a real capacity entry, but has never been charged (no core.sharedInUse
|
||||
* entry), must display a genuine accounted=0 -- NOT the no-instance
|
||||
* message, since the instance itself (and its capacity) both genuinely
|
||||
* exist.
|
||||
*/
|
||||
static void
|
||||
test_info_limit_named_shows_accounted_zero_for_matched_but_never_charged_instance(
|
||||
void)
|
||||
{
|
||||
xce_sched_sim_driver_t sim;
|
||||
xce_sched_plugin_t plugin;
|
||||
xce_scheduler_t group_sched;
|
||||
xce_limit_t* matched = NULL;
|
||||
var limits_handle;
|
||||
xce_vres_t* vres;
|
||||
char* captured;
|
||||
int rc;
|
||||
|
||||
memset(&sim, 0, sizeof(sim));
|
||||
rc = xce_sched_core_init(&(sim.core));
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
register_per_host_resource(&sim, "mem");
|
||||
register_limit_plugin(&sim, &plugin, &group_sched);
|
||||
add_configured_limit(&plugin, "uncharged_limit");
|
||||
|
||||
limits_handle = limit_get_configured_limits(&(sim.core));
|
||||
rc = key_find(limits_handle, (void*)"uncharged_limit", (void**)&matched);
|
||||
TEST_ASSERT_TRUE(rc > 0);
|
||||
TEST_ASSERT_NOT_NULL(matched);
|
||||
|
||||
vres = instantiate_and_register_global_vres(&sim, &plugin, matched);
|
||||
put_shared_fvalue(&(sim.core.sharedInst), vres->vres->idx, 10.0);
|
||||
/* Deliberately no put_shared_fvalue() into core.sharedInUse -- a
|
||||
* genuinely matched vres with a real capacity entry, but nothing
|
||||
* charged against it yet. */
|
||||
|
||||
captured = capture_info_limit(&sim, "uncharged_limit");
|
||||
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(
|
||||
strstr(captured, "accounted=0.00"),
|
||||
"the existence-vs-zero-usage distinction: a matched vres with a "
|
||||
"real capacity entry but an absent in-use entry must display a "
|
||||
"genuine accounted=0, not the no-instance message");
|
||||
TEST_ASSERT_NULL_MESSAGE(
|
||||
strstr(captured, "no active usage instance yet"),
|
||||
"must NOT report 'no active usage instance yet' -- the vres and "
|
||||
"its capacity both genuinely exist");
|
||||
|
||||
free(captured);
|
||||
|
||||
printf("test_info_limit_named_shows_accounted_zero_for_matched_but_"
|
||||
"never_charged_instance: PASS\n");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
|
|
@ -985,5 +1224,11 @@ int main(void)
|
|||
test_info_limit_named_does_not_show_host_scope_note_for_absolute_value_limit);
|
||||
RUN_TEST(
|
||||
test_info_limit_named_does_not_duplicate_host_scope_note_when_per_host_explicitly_configured);
|
||||
RUN_TEST(
|
||||
test_info_limit_named_shows_accounted_and_capacity_for_matched_and_charged_global_instance);
|
||||
RUN_TEST(
|
||||
test_info_limit_named_shows_no_active_usage_instance_for_never_matched_limit);
|
||||
RUN_TEST(
|
||||
test_info_limit_named_shows_accounted_zero_for_matched_but_never_charged_instance);
|
||||
return UNITY_END();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue