feat(scheduler-sim-decision-parity): relocate resource accounting to core-level (task 1.1)
Relocates sched_account_jobs()/sched_charge_account()/sched_uncharge_account()/ sched_update_account()/sched_update_res_accounts()/sched_commit_job_alloc() from xce_scheduler_t* to xce_sched_core_t*, mirroring the sched_resolve_job_picker() precedent, so the simulation driver can call the same accounting service the normal-mode driver already uses. Pure signature relocation -- every body reference was already sched->core.*, no logic change. Updates the 3 real call sites (driver.c's normal-mode round, scheduler.c's sched_begin_session and sched_commit_decision) and 7 existing tests' link-satisfying stub signatures. Adds test_resource_accounting.c proving Requirement 1.4: the relocated accounting produces identical resInUse/sharedInUse output whether called against a core embedded in xce_scheduler_t or xce_sched_sim_driver_t. 43/44 scheduler tests pass; the 1 failure is pre-existing bug-1560 (unrelated recording.c sandbox-permission ordering issue). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
0b2c856d8b
commit
3106c82ed0
15 changed files with 498 additions and 75 deletions
359
src/scheduler/testsuite/test_resource_accounting.c
Normal file
359
src/scheduler/testsuite/test_resource_accounting.c
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
/*
|
||||
* test_resource_accounting.c - Requirement 1.4 proof for the Core-Level
|
||||
* Resource Accounting component (scheduler-sim-decision-parity spec, task
|
||||
* 1.1; design.md's "Core-Level Resource Accounting" Unit Tests bullet):
|
||||
* "produces identical resInUse/sharedInUse output whether called with a
|
||||
* core obtained from xce_scheduler_t or from xce_sched_sim_driver_t."
|
||||
*
|
||||
* Task 1.1 itself is a pure signature relocation (xce_scheduler_t* ->
|
||||
* xce_sched_core_t*) of sched_account_jobs()/sched_charge_account()/
|
||||
* sched_uncharge_account()/sched_update_account()/
|
||||
* sched_update_res_accounts()/sched_commit_job_alloc() -- body unchanged,
|
||||
* every reference already `sched->core.*` before the move (confirmed via
|
||||
* `grep "sched->" resource.c | grep -v "sched->core"` returning zero
|
||||
* matches, design.md's Existing Architecture Analysis). Requirement 1.4 is
|
||||
* nonetheless a genuinely NEW observable property worth its own test (not
|
||||
* just "nothing broke"): now that these functions take xce_sched_core_t*
|
||||
* directly, they are usable identically regardless of which driver-level
|
||||
* container struct (xce_scheduler_t, the normal-mode driver's; or
|
||||
* xce_sched_sim_driver_t, the simulation driver's, sim.h) embeds the core.
|
||||
* This file drives the REAL sched_charge_account()/sched_uncharge_account()/
|
||||
* sched_commit_job_alloc() (resource.c) against two independently
|
||||
* initialized xce_sched_core_t instances -- one embedded in an
|
||||
* xce_scheduler_t, one embedded in an xce_sched_sim_driver_t -- with
|
||||
* identical worker/account state, and asserts byte-identical
|
||||
* resInUse/sharedInUse results.
|
||||
*
|
||||
* sched_account_jobs() itself (scheduler.c) is NOT white-box #included here
|
||||
* -- unlike this file's own sched_charge_account()/sched_commit_job_alloc()
|
||||
* targets (resource.c, a lightweight, already-proven-includable file per
|
||||
* test.c's own #include "../src/resource.c"), scheduler.c is a ~1400-line
|
||||
* file with a much larger link-time dependency surface (controller-join,
|
||||
* capnp decision publishing, HA candidate handling, picker/allocator thread
|
||||
* lifecycle) that no existing test in this directory white-box #includes for
|
||||
* exactly that reason (confirmed via grep across testsuite/ before writing
|
||||
* this file). sched_account_jobs() itself is a thin, ~20-line wrapper
|
||||
* (iterate core->jobs, call sched_charge_account() for each RUN-state job
|
||||
* with a known allocation) with no accounting logic of its own -- its
|
||||
* driver-ordering discipline (called at the right point in the round
|
||||
* boundary, on both drivers) is already proven by
|
||||
* test_driver_session_ordering.c (a spy, by design -- see that file's own
|
||||
* header comment) and this feature's later tasks (2.x/3.x, sim_loop.c's own
|
||||
* round-boundary integration). This file instead proves the actual
|
||||
* accounting *body* -- the part design.md's Testing Strategy bullet is
|
||||
* about -- via its real callees, exercised directly.
|
||||
*
|
||||
* White-box #include of ../src/core.c (for the real
|
||||
* xce_sched_core_init()/sched_add_worker()/sched_get_worker() the fixture
|
||||
* needs) and ../src/resource.c (the functions under test). Neither file
|
||||
* needs core_clock.c/core_events.c/core_session.c/decision.c/plugin.c --
|
||||
* this test never calls xce_core_push_events()/xce_core_begin_session()/
|
||||
* anything decision- or plugin-registration-related, only the plain
|
||||
* add-worker/charge-account entry points core.c/resource.c themselves
|
||||
* define. Stubs below (kill_sched_* family, plugin_unregister) are the
|
||||
* standard link-satisfying no-ops xce_sched_core_init()'s kvmgr datakillers
|
||||
* reference (never actually invoked -- this fixture is never torn down,
|
||||
* same convention as test_driver_session_ordering.c/
|
||||
* test_snapshot_save_load.c's identical stub set).
|
||||
* sched_worker_release_host_ctx() is the one real (non-stub) symbol this
|
||||
* pulls from resource.c itself, not from core_session.c -- unused here
|
||||
* (this test never calls it), listed only because resource.c defines it.
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <unity/unity.h>
|
||||
|
||||
#include "scheduler.h"
|
||||
#include "sim.h"
|
||||
|
||||
/* Pull in the real xce_sched_core_init()/sched_add_worker()/
|
||||
* sched_get_worker() (core.c) and sched_charge_account()/
|
||||
* sched_uncharge_account()/sched_commit_job_alloc()/get_resvals() (resource.c,
|
||||
* the functions under test) into this TU (white-box #include, same
|
||||
* convention as every other file in this directory). */
|
||||
#include "../src/core.c"
|
||||
#include "../src/resource.c"
|
||||
|
||||
void kill_sched_job(void* arg) { (void)arg; }
|
||||
void kill_sched_queue(void* arg) { (void)arg; }
|
||||
void kill_sched_worker(void* arg) { (void)arg; }
|
||||
void kill_sched_user(void* arg) { (void)arg; }
|
||||
void kill_sched_project(void* arg) { (void)arg; }
|
||||
void kill_sched_resval(xce_val_t* val) { (void)val; }
|
||||
void plugin_unregister(void* arg) { (void)arg; }
|
||||
|
||||
/* Link-satisfying stubs for resource.c's sched_try_alloc_on_host_() (its
|
||||
* OWN callees, plugin_prepare_allocation()/sched_ctx_get_uncomit_host(),
|
||||
* live in plugin.c/schedobj.c -- neither white-box #included here). Never
|
||||
* actually invoked: this file never calls sched_try_alloc_on_hosts()/
|
||||
* sched_try_alloc_on_host_(), only sched_charge_account()/
|
||||
* sched_uncharge_account()/sched_commit_job_alloc(). Same two stubs
|
||||
* test.c's own #include "../src/resource.c" already establishes needing. */
|
||||
int plugin_prepare_allocation(void* s, xce_sched_ctx_t* c)
|
||||
{
|
||||
(void)s;
|
||||
(void)c;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sched_ctx_get_uncomit_host(xvec_t** v, xce_sched_ctx_t* c, int i)
|
||||
{
|
||||
(void)v;
|
||||
(void)c;
|
||||
(void)i;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void setUp(void) { return; }
|
||||
void tearDown(void) { return; }
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Fixture helpers */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/*
|
||||
* setup_core_with_worker - real xce_sched_core_init() (core.c) plus one
|
||||
* real worker (sched_add_worker(), core.c), its resvals/resInUse zarrays
|
||||
* freshly zarray_init()'d exactly the way a real worker join would leave
|
||||
* them (sched_add_worker() itself does not do this -- callers own it, same
|
||||
* as every other white-box fixture in this directory that adds a worker
|
||||
* directly rather than through the JOBS_PUSH/WORKERS_PUSH event path).
|
||||
*/
|
||||
static void setup_core_with_worker(xce_sched_core_t* core,
|
||||
const char* worker_name)
|
||||
{
|
||||
xce_sched_worker_t* w;
|
||||
int rc;
|
||||
|
||||
rc = xce_sched_core_init(core);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
w = (xce_sched_worker_t*)calloc(1, sizeof(*w));
|
||||
TEST_ASSERT_NOT_NULL(w);
|
||||
|
||||
w->name = strdup(worker_name);
|
||||
TEST_ASSERT_NOT_NULL(w->name);
|
||||
|
||||
rc = zarray_init(&(w->resvals), 8, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
rc = zarray_init(&(w->resInUse), 8, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
rc = sched_add_worker(core, w);
|
||||
TEST_ASSERT_TRUE(rc >= 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* make_test_account - one shared-resource charge (residx `shared_residx`)
|
||||
* plus one per-host charge (residx `host_residx`, against `worker_name`) --
|
||||
* enough to exercise both of sched_update_account()'s two branches
|
||||
* (core->sharedInUse and a specific worker's resInUse). residx is set
|
||||
* non-negative directly (bypassing sched_update_res_accounts()'s
|
||||
* resmgr-lookup branch entirely) -- this test targets the charge/uncharge
|
||||
* arithmetic itself, not resource-name resolution, which is unrelated,
|
||||
* already-existing logic this task's relocation does not touch.
|
||||
*/
|
||||
static xce_job_account_t*
|
||||
make_test_account(const char* worker_name, int shared_residx,
|
||||
double shared_used, double shared_reserved, int host_residx,
|
||||
double host_used, double host_reserved)
|
||||
{
|
||||
xce_job_account_t* acct
|
||||
= (xce_job_account_t*)calloc(1, sizeof(*acct));
|
||||
TEST_ASSERT_NOT_NULL(acct);
|
||||
|
||||
acct->nres_shared = 1;
|
||||
acct->accounts_shared = (xce_res_account_t**)calloc(
|
||||
1, sizeof(*acct->accounts_shared));
|
||||
TEST_ASSERT_NOT_NULL(acct->accounts_shared);
|
||||
acct->accounts_shared[0]
|
||||
= (xce_res_account_t*)calloc(1, sizeof(xce_res_account_t));
|
||||
TEST_ASSERT_NOT_NULL(acct->accounts_shared[0]);
|
||||
acct->accounts_shared[0]->residx = shared_residx;
|
||||
acct->accounts_shared[0]->used = shared_used;
|
||||
acct->accounts_shared[0]->reserved = shared_reserved;
|
||||
|
||||
acct->nhosts = 1;
|
||||
acct->accounts = (xce_host_account_t**)calloc(1, sizeof(*acct->accounts));
|
||||
TEST_ASSERT_NOT_NULL(acct->accounts);
|
||||
acct->accounts[0]
|
||||
= (xce_host_account_t*)calloc(1, sizeof(xce_host_account_t));
|
||||
TEST_ASSERT_NOT_NULL(acct->accounts[0]);
|
||||
acct->accounts[0]->host = strdup(worker_name);
|
||||
acct->accounts[0]->naccount = 1;
|
||||
acct->accounts[0]->accounts
|
||||
= (xce_res_account_t**)calloc(1, sizeof(*acct->accounts[0]->accounts));
|
||||
TEST_ASSERT_NOT_NULL(acct->accounts[0]->accounts);
|
||||
acct->accounts[0]->accounts[0]
|
||||
= (xce_res_account_t*)calloc(1, sizeof(xce_res_account_t));
|
||||
TEST_ASSERT_NOT_NULL(acct->accounts[0]->accounts[0]);
|
||||
acct->accounts[0]->accounts[0]->residx = host_residx;
|
||||
acct->accounts[0]->accounts[0]->used = host_used;
|
||||
acct->accounts[0]->accounts[0]->reserved = host_reserved;
|
||||
|
||||
return acct;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* Tests */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
#define WORKER_NAME "worker-a"
|
||||
#define SHARED_RESIDX 0
|
||||
#define HOST_RESIDX 1
|
||||
#define SHARED_USED 3.0
|
||||
#define SHARED_RESERVED 1.5
|
||||
#define HOST_USED 2.0
|
||||
#define HOST_RESERVED 0.5
|
||||
/* SHARED_USED + SHARED_RESERVED, HOST_USED + HOST_RESERVED */
|
||||
#define EXPECTED_SHARED_CHARGE 4.5f
|
||||
#define EXPECTED_HOST_CHARGE 2.5f
|
||||
|
||||
/*
|
||||
* test_charge_account_identical_across_core_containers - Requirement 1.4:
|
||||
* sched_charge_account()/sched_uncharge_account() (resource.c) produce
|
||||
* byte-identical resInUse/sharedInUse results whether `core` came from an
|
||||
* xce_scheduler_t (sched.core, the normal-mode driver's container) or an
|
||||
* xce_sched_sim_driver_t (sim.core, the simulation driver's container) --
|
||||
* the exact property that makes the simulation driver's build now able to
|
||||
* call the same accounting service the normal-mode driver already used.
|
||||
*/
|
||||
static void test_charge_account_identical_across_core_containers(void)
|
||||
{
|
||||
xce_scheduler_t sched;
|
||||
xce_sched_sim_driver_t sim;
|
||||
xce_job_account_t* acct;
|
||||
xce_sched_worker_t* w_sched;
|
||||
xce_sched_worker_t* w_sim;
|
||||
float shared_sched[1];
|
||||
float shared_sim[1];
|
||||
float host_sched[1];
|
||||
float host_sim[1];
|
||||
int shared_idx[1] = { SHARED_RESIDX };
|
||||
int host_idx[1] = { HOST_RESIDX };
|
||||
int rc;
|
||||
|
||||
memset(&sched, 0, sizeof(sched));
|
||||
memset(&sim, 0, sizeof(sim));
|
||||
|
||||
setup_core_with_worker(&(sched.core), WORKER_NAME);
|
||||
setup_core_with_worker(&(sim.core), WORKER_NAME);
|
||||
|
||||
/* Same account struct fed to both cores -- sched_charge_account() never
|
||||
* mutates it when residx is already non-negative (see
|
||||
* make_test_account()'s own comment), so reuse is safe and keeps the
|
||||
* two calls genuinely identical, not merely equal-by-construction. */
|
||||
acct = make_test_account(WORKER_NAME, SHARED_RESIDX, SHARED_USED,
|
||||
SHARED_RESERVED, HOST_RESIDX, HOST_USED,
|
||||
HOST_RESERVED);
|
||||
|
||||
rc = sched_charge_account(&(sched.core), acct);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
rc = sched_charge_account(&(sim.core), acct);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
/* --- sharedInUse: identical across both containers, and correct --- */
|
||||
get_resvals(&(sched.core.sharedInUse), 1, shared_idx, shared_sched);
|
||||
get_resvals(&(sim.core.sharedInUse), 1, shared_idx, shared_sim);
|
||||
TEST_ASSERT_EQUAL_FLOAT(EXPECTED_SHARED_CHARGE, shared_sched[0]);
|
||||
TEST_ASSERT_EQUAL_FLOAT(shared_sched[0], shared_sim[0]);
|
||||
|
||||
/* --- per-worker resInUse: identical across both containers --- */
|
||||
w_sched = sched_get_worker(&(sched.core), WORKER_NAME);
|
||||
w_sim = sched_get_worker(&(sim.core), WORKER_NAME);
|
||||
TEST_ASSERT_NOT_NULL(w_sched);
|
||||
TEST_ASSERT_NOT_NULL(w_sim);
|
||||
|
||||
get_resvals(&(w_sched->resInUse), 1, host_idx, host_sched);
|
||||
get_resvals(&(w_sim->resInUse), 1, host_idx, host_sim);
|
||||
TEST_ASSERT_EQUAL_FLOAT(EXPECTED_HOST_CHARGE, host_sched[0]);
|
||||
TEST_ASSERT_EQUAL_FLOAT(host_sched[0], host_sim[0]);
|
||||
|
||||
/* --- sched_uncharge_account() mirrors this identically too --- */
|
||||
rc = sched_uncharge_account(&(sched.core), acct);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
rc = sched_uncharge_account(&(sim.core), acct);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
get_resvals(&(sched.core.sharedInUse), 1, shared_idx, shared_sched);
|
||||
get_resvals(&(sim.core.sharedInUse), 1, shared_idx, shared_sim);
|
||||
TEST_ASSERT_EQUAL_FLOAT(0.0f, shared_sched[0]);
|
||||
TEST_ASSERT_EQUAL_FLOAT(shared_sched[0], shared_sim[0]);
|
||||
|
||||
get_resvals(&(w_sched->resInUse), 1, host_idx, host_sched);
|
||||
get_resvals(&(w_sim->resInUse), 1, host_idx, host_sim);
|
||||
TEST_ASSERT_EQUAL_FLOAT(0.0f, host_sched[0]);
|
||||
TEST_ASSERT_EQUAL_FLOAT(host_sched[0], host_sim[0]);
|
||||
}
|
||||
|
||||
/*
|
||||
* test_commit_job_alloc_identical_across_core_containers - Requirement 1.4
|
||||
* for sched_commit_job_alloc() specifically (the third relocated group
|
||||
* member scheduler.c's sched_commit_decision() calls, design.md's own File
|
||||
* Structure Plan entry for scheduler.c): both its no-op (job->accounts ==
|
||||
* NULL) and its charging path behave identically regardless of which
|
||||
* driver-level struct the core came from.
|
||||
*/
|
||||
static void test_commit_job_alloc_identical_across_core_containers(void)
|
||||
{
|
||||
xce_scheduler_t sched;
|
||||
xce_sched_sim_driver_t sim;
|
||||
xce_sched_job_t job_sched;
|
||||
xce_sched_job_t job_sim;
|
||||
xce_job_account_t* acct;
|
||||
float shared_sched[1];
|
||||
float shared_sim[1];
|
||||
int shared_idx[1] = { SHARED_RESIDX };
|
||||
int rc;
|
||||
|
||||
memset(&sched, 0, sizeof(sched));
|
||||
memset(&sim, 0, sizeof(sim));
|
||||
memset(&job_sched, 0, sizeof(job_sched));
|
||||
memset(&job_sim, 0, sizeof(job_sim));
|
||||
|
||||
setup_core_with_worker(&(sched.core), WORKER_NAME);
|
||||
setup_core_with_worker(&(sim.core), WORKER_NAME);
|
||||
|
||||
/* --- job->accounts == NULL: documented no-op, identical on both --- */
|
||||
job_sched.accounts = NULL;
|
||||
job_sim.accounts = NULL;
|
||||
|
||||
rc = sched_commit_job_alloc(&(sched.core), &job_sched);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
rc = sched_commit_job_alloc(&(sim.core), &job_sim);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
get_resvals(&(sched.core.sharedInUse), 1, shared_idx, shared_sched);
|
||||
get_resvals(&(sim.core.sharedInUse), 1, shared_idx, shared_sim);
|
||||
TEST_ASSERT_EQUAL_FLOAT(0.0f, shared_sched[0]);
|
||||
TEST_ASSERT_EQUAL_FLOAT(shared_sched[0], shared_sim[0]);
|
||||
|
||||
/* --- job->accounts set: charges through sched_charge_account(),
|
||||
* identical on both --- */
|
||||
acct = make_test_account(WORKER_NAME, SHARED_RESIDX,
|
||||
SHARED_USED, SHARED_RESERVED,
|
||||
HOST_RESIDX, HOST_USED,
|
||||
HOST_RESERVED);
|
||||
job_sched.accounts = acct;
|
||||
job_sim.accounts = acct;
|
||||
|
||||
rc = sched_commit_job_alloc(&(sched.core), &job_sched);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
rc = sched_commit_job_alloc(&(sim.core), &job_sim);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
get_resvals(&(sched.core.sharedInUse), 1, shared_idx, shared_sched);
|
||||
get_resvals(&(sim.core.sharedInUse), 1, shared_idx, shared_sim);
|
||||
TEST_ASSERT_EQUAL_FLOAT(EXPECTED_SHARED_CHARGE, shared_sched[0]);
|
||||
TEST_ASSERT_EQUAL_FLOAT(shared_sched[0], shared_sim[0]);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_charge_account_identical_across_core_containers);
|
||||
RUN_TEST(test_commit_job_alloc_identical_across_core_containers);
|
||||
return UNITY_END();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue