feat(scheduler-sim-decision-trace): hook resource-requirement branching
Task 3.2: single requirements, top-level compounds, alternatives and members push their path level and record one begin and one end; member failures record the members not tried, merge failures and skipped alternatives are separate entries, the chosen alternative or all-failed is recorded, and each compound attempt's reasons are captured before the job's stored reason is replaced. Scheduling results are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ed7b889bb7
commit
542c6e602a
4 changed files with 680 additions and 3 deletions
|
|
@ -20,6 +20,7 @@
|
|||
#include "resobj.h"
|
||||
#include "resreq.h"
|
||||
#include "sched/alloc_cache.h"
|
||||
#include "sched/trace.h"
|
||||
#include "scheduler.h"
|
||||
#include "utils.h"
|
||||
#include "xcealloc.h"
|
||||
|
|
@ -343,16 +344,29 @@ static int sched_make_compound_alloc_(xce_sched_core_t* core,
|
|||
int i;
|
||||
xce_sched_ctx_t ctx;
|
||||
xce_job_alloc_t* merged = NULL;
|
||||
int m = resreq->primaries.count;
|
||||
int rc = 0;
|
||||
|
||||
sched_ctx_init(&ctx, core, obj);
|
||||
|
||||
for (i = 0; i < resreq->primaries.count; i++) {
|
||||
for (i = 0; i < m; i++) {
|
||||
xce_primary_resreq_t* primary
|
||||
= (xce_primary_resreq_t*)xlist_get(&(resreq->primaries), i);
|
||||
|
||||
sched_ctx_set_primary(&ctx, primary);
|
||||
/* Decision trace (3.1-3.3): member i+1 of m under the container the
|
||||
* caller pushed; a failing member lists the ones after it as not
|
||||
* tried. */
|
||||
SCHED_TRACE(core,
|
||||
sched_trace_path_push(core->trace_rec,
|
||||
XCE_TRACE_PATH_MEMBER, i + 1, m));
|
||||
SCHED_TRACE(core,
|
||||
sched_trace_resreq_begin(core->trace_rec, primary, 0));
|
||||
rc = sched_make_primary_alloc_(core, &ctx);
|
||||
SCHED_TRACE(core,
|
||||
sched_trace_resreq_end(core->trace_rec, rc,
|
||||
(rc <= 0) ? (m - (i + 1)) : 0));
|
||||
SCHED_TRACE(core, sched_trace_path_pop(core->trace_rec));
|
||||
if (rc <= 0) {
|
||||
break;
|
||||
}
|
||||
|
|
@ -368,6 +382,10 @@ static int sched_make_compound_alloc_(xce_sched_core_t* core,
|
|||
|
||||
free_JobAlloc_ptr(&(ctx.alloc));
|
||||
if (mrc < 0) {
|
||||
/* member i+1 was satisfied; the merge failure is a
|
||||
* separate entry, not a second end (3.3) */
|
||||
SCHED_TRACE(core, sched_trace_member_failed(core->trace_rec,
|
||||
i + 1, m, mrc));
|
||||
rc = mrc;
|
||||
break;
|
||||
}
|
||||
|
|
@ -388,6 +406,13 @@ static int sched_make_compound_alloc_(xce_sched_core_t* core,
|
|||
}
|
||||
}
|
||||
|
||||
/* Decision trace (3.5): a failed attempt's reasons, before obj->reason
|
||||
* is replaced by them (or by a later alternative's). */
|
||||
SCHED_TRACE(
|
||||
core, if (rc <= 0) {
|
||||
sched_trace_capture_attempt_reasons(core->trace_rec, ctx.reason);
|
||||
});
|
||||
|
||||
if (ctx.reason != NULL) {
|
||||
/* bug-1884: the previous attempt's reason (its decision has
|
||||
* been published) or an earlier alternative's */
|
||||
|
|
@ -410,8 +435,9 @@ static int sched_make_alter_allocation(xce_sched_core_t* core,
|
|||
xce_sched_obj_t* obj)
|
||||
{
|
||||
int i;
|
||||
int n = obj->u.alters.count;
|
||||
|
||||
for (i = 0; i < obj->u.alters.count; i++) {
|
||||
for (i = 0; i < n; i++) {
|
||||
/* bug-1769: u.alters holds xce_compound_schedobj_t* (every builder:
|
||||
* core_events.c, snapshot_load.c, schedobj.c), not xce_sched_obj_t*
|
||||
* -- reading them as the latter overran the 24-byte allocation.
|
||||
|
|
@ -421,15 +447,31 @@ static int sched_make_alter_allocation(xce_sched_core_t* core,
|
|||
int rc;
|
||||
|
||||
if ((alt == NULL) || (alt->resreq == NULL)) {
|
||||
/* Decision trace (3.6): skipped in its position */
|
||||
SCHED_TRACE(core,
|
||||
sched_trace_alt_skipped(core->trace_rec, i + 1, n));
|
||||
continue;
|
||||
}
|
||||
|
||||
SCHED_TRACE(core, sched_trace_path_push(core->trace_rec,
|
||||
XCE_TRACE_PATH_ALT, i + 1, n));
|
||||
SCHED_TRACE(core,
|
||||
sched_trace_resreq_begin(core->trace_rec, NULL,
|
||||
alt->resreq->primaries.count));
|
||||
rc = sched_make_compound_alloc_(core, obj, alt->resreq);
|
||||
SCHED_TRACE(core, sched_trace_resreq_end(core->trace_rec, rc, 0));
|
||||
SCHED_TRACE(core, sched_trace_path_pop(core->trace_rec));
|
||||
if (rc > 0) {
|
||||
/* Decision trace (3.4): chosen; the rest are not tried */
|
||||
SCHED_TRACE(core,
|
||||
sched_trace_alt_result(core->trace_rec, i + 1, n));
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
/* Decision trace (3.5): every alternative failed */
|
||||
SCHED_TRACE(core, sched_trace_alt_result(core->trace_rec, 0, n));
|
||||
|
||||
/* FIXME: merge pend reason from all children */
|
||||
|
||||
return 0;
|
||||
|
|
@ -446,12 +488,28 @@ int sched_make_allocation(xce_sched_core_t* core, xce_sched_obj_t* obj)
|
|||
|
||||
switch (obj->tag) {
|
||||
case XCE_RESREQ_PRIMARY:
|
||||
/* Decision trace (3.1, 3.2): the single requirement */
|
||||
SCHED_TRACE(core, sched_trace_path_push(core->trace_rec,
|
||||
XCE_TRACE_PATH_SINGLE, 1, 1));
|
||||
SCHED_TRACE(core, sched_trace_resreq_begin(core->trace_rec,
|
||||
obj->u.primary->resreq, 0));
|
||||
rc = sched_make_primary_allocation(core, obj);
|
||||
SCHED_TRACE(core, sched_trace_resreq_end(core->trace_rec, rc, 0));
|
||||
SCHED_TRACE(core, sched_trace_path_pop(core->trace_rec));
|
||||
break;
|
||||
case XCE_RESREQ_COMPOUND:
|
||||
/* Decision trace (3.1, 3.2): the top-level compound container */
|
||||
SCHED_TRACE(core, sched_trace_path_push(core->trace_rec,
|
||||
XCE_TRACE_PATH_COMPOUND, 1, 1));
|
||||
SCHED_TRACE(core, sched_trace_resreq_begin(
|
||||
core->trace_rec, NULL,
|
||||
obj->u.compound->resreq->primaries.count));
|
||||
rc = sched_make_compound_allocation(core, obj);
|
||||
SCHED_TRACE(core, sched_trace_resreq_end(core->trace_rec, rc, 0));
|
||||
SCHED_TRACE(core, sched_trace_path_pop(core->trace_rec));
|
||||
break;
|
||||
case XCE_RESREQ_ALTER:
|
||||
/* Decision trace (3.4-3.6): per-alternative hooks in the loop */
|
||||
rc = sched_make_alter_allocation(core, obj);
|
||||
break;
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -2654,3 +2654,29 @@ sched_trace_hooks_alloc_loop_test = executable(
|
|||
test('scheduler trace hooks alloc loop test',
|
||||
sched_trace_hooks_alloc_loop_test, suite: 'scheduler',
|
||||
env: ['ASAN_OPTIONS=detect_leaks=0'])
|
||||
|
||||
# scheduler-sim-decision-trace task 3.2: the resource-requirement branching
|
||||
# trace hooks (design.md "TraceHooks", PRIMARY / COMPOUND / ALTER / member
|
||||
# rows; Requirements 3.1-3.6) on the real sched_make_allocation() chain over
|
||||
# a bare simulation core. Real-scheduler_srcs link technique; libxce's
|
||||
# merge_job_alloc() is wrapped for the merge failure.
|
||||
sched_trace_hooks_resreq_test_srcs = ['test_trace_hooks_resreq.c']
|
||||
foreach f : scheduler_srcs
|
||||
if f != 'src/main.c'
|
||||
sched_trace_hooks_resreq_test_srcs += ['../' + f]
|
||||
endif
|
||||
endforeach
|
||||
|
||||
sched_trace_hooks_resreq_test = executable(
|
||||
'sched_trace_hooks_resreq_test',
|
||||
sched_trace_hooks_resreq_test_srcs,
|
||||
include_directories:[incdir],
|
||||
dependencies: [scheduler_deps, unity_dep],
|
||||
link_args: ['-Wl,--wrap=xcecli_start',
|
||||
'-Wl,--wrap=xcecli_join',
|
||||
'-Wl,--wrap=merge_job_alloc'],
|
||||
install: false)
|
||||
|
||||
test('scheduler trace hooks resreq test',
|
||||
sched_trace_hooks_resreq_test, suite: 'scheduler',
|
||||
env: ['ASAN_OPTIONS=detect_leaks=0'])
|
||||
|
|
|
|||
592
src/scheduler/testsuite/test_trace_hooks_resreq.c
Normal file
592
src/scheduler/testsuite/test_trace_hooks_resreq.c
Normal file
|
|
@ -0,0 +1,592 @@
|
|||
/*
|
||||
* test_trace_hooks_resreq.c - the resource-requirement branching trace hooks
|
||||
* (scheduler-sim-decision-trace spec, task 3.2; design.md "TraceHooks",
|
||||
* PRIMARY / top-level COMPOUND / ALTER loop / compound member rows;
|
||||
* Requirements 3.1-3.6).
|
||||
*
|
||||
* Drives the real sched_make_allocation() chain and the real seqjob/parjob
|
||||
* allocator plugins on a bare simulation core (same fixture as
|
||||
* test_sched_alloc_from_stamp.c's simulation-mode test: no threads, the
|
||||
* allocators bootstrapped with the sim wrappers), with a record set on
|
||||
* core->trace_rec for the call, and checks the requirement-branch entries
|
||||
* the record holds:
|
||||
*
|
||||
* - a single requirement: SINGLE 1/1, one begin with its task counts and
|
||||
* requested resources, one end (3.1, 3.2);
|
||||
* - a compound whose second member cannot be satisfied: each member's
|
||||
* begin/end under COMPOUND 1/1, the failing member listing the members
|
||||
* after it as not tried, then the attempt's failure reasons (3.2, 3.3);
|
||||
* - a compound whose merge fails after a satisfied member: the member's
|
||||
* end stays satisfied and a separate merge-failure entry follows (3.3);
|
||||
* - an alternative job with a skipped, a failed and a chosen alternative,
|
||||
* and one after it: the skip in its position, the failed alternative's
|
||||
* reasons, the chosen one and nothing for the rest (3.4, 3.6);
|
||||
* - an alternative job where every alternative fails: each alternative's
|
||||
* own failure reasons, then "all failed" (3.5).
|
||||
*
|
||||
* Every scenario first runs without a record and then with one, and the
|
||||
* return code, allocation and pend reason left on the scheduling object
|
||||
* must be identical (Requirement 10: tracing never changes scheduling).
|
||||
*
|
||||
* Only the requirement-branch entry types are inspected, so entries other
|
||||
* hooks add inside the allocator (plans, positions, hosts) do not matter.
|
||||
* merge_job_alloc() (libxce) is wrapped at link time for the merge failure.
|
||||
*/
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <unity/unity.h>
|
||||
|
||||
#include "client.h"
|
||||
#include "controller.capnp.h"
|
||||
#include "core.h"
|
||||
#include "decision.h"
|
||||
#include "plugin.h"
|
||||
#include "preason.h"
|
||||
#include "resobj.h"
|
||||
#include "resreq.h"
|
||||
#include "sched/trace.h"
|
||||
#include "sched/types.h"
|
||||
#include "scheduler.h"
|
||||
#include "sim.h"
|
||||
#include "xcealloc.h"
|
||||
#include "xstring.h"
|
||||
|
||||
extern void sim_bootstrap_seqjob_allocator(xce_sched_sim_driver_t* sim);
|
||||
extern void sim_bootstrap_parjob_allocator(xce_sched_sim_driver_t* sim);
|
||||
|
||||
#define WORKER_A_NAME "resreq-host-a"
|
||||
#define WORKER_B_NAME "resreq-host-b"
|
||||
#define WORKER_SLOTS 2.0 /* 4 slots in the cluster */
|
||||
#define QUEUE_NAME "resreq_queue"
|
||||
|
||||
/* ── link seams ─────────────────────────────────────────────────────── */
|
||||
|
||||
int __wrap_xcecli_start(xcecli_ctx_t c)
|
||||
{
|
||||
(void)c;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __wrap_xcecli_join(xcecli_ctx_t c, int role, hostattrs_t* attrs)
|
||||
{
|
||||
(void)c;
|
||||
(void)role;
|
||||
(void)attrs;
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern int __real_merge_job_alloc(xce_job_alloc_t*, const xce_job_alloc_t*);
|
||||
|
||||
/* > 0: that many merges succeed, then the next one fails with -ENOMEM and
|
||||
* the counter is disarmed. 0: every merge is real. */
|
||||
static int g_merge_fail_after;
|
||||
|
||||
int __wrap_merge_job_alloc(xce_job_alloc_t* dst, const xce_job_alloc_t* src)
|
||||
{
|
||||
if (g_merge_fail_after > 0) {
|
||||
if (--g_merge_fail_after == 0) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
return __real_merge_job_alloc(dst, src);
|
||||
}
|
||||
|
||||
void setUp(void) { return; }
|
||||
void tearDown(void) { return; }
|
||||
|
||||
/* ── fixture ────────────────────────────────────────────────────────── */
|
||||
|
||||
static xce_sched_sim_driver_t g_sim;
|
||||
static xce_scheduler_t g_job_wrapper; /* make_sched_job() only */
|
||||
static xce_sched_trace_ctl_t g_ctl;
|
||||
|
||||
static void add_sim_worker(xce_sched_core_t* core, const char* name,
|
||||
double slots)
|
||||
{
|
||||
xce_sched_worker_t* worker
|
||||
= (xce_sched_worker_t*)calloc(1, sizeof(*worker));
|
||||
xce_resource_t* r = NULL;
|
||||
xce_val_t* val = NULL;
|
||||
|
||||
TEST_ASSERT_NOT_NULL(worker);
|
||||
worker->name = strdup(name);
|
||||
worker->status = 0;
|
||||
worker->idx = -1;
|
||||
zarray_init(&(worker->resvals), 128,
|
||||
(zarray_item_killer_t)kill_sched_resval);
|
||||
zarray_init(&(worker->resInUse), 128,
|
||||
(zarray_item_killer_t)kill_sched_resval);
|
||||
|
||||
TEST_ASSERT_TRUE(key_find(core->resmgr, (void*)"slots", (void**)&r) >= 0);
|
||||
TEST_ASSERT_NOT_NULL(r);
|
||||
|
||||
val = (xce_val_t*)calloc(1, sizeof(*val));
|
||||
TEST_ASSERT_NOT_NULL(val);
|
||||
val->which = Value_u_fvalue;
|
||||
val->u.fvalue = slots;
|
||||
zarray_put(&(worker->resvals), r->idx, val);
|
||||
|
||||
TEST_ASSERT_TRUE(sched_add_worker(core, worker) >= 0);
|
||||
}
|
||||
|
||||
static void start_sim(void)
|
||||
{
|
||||
xce_resource_t res;
|
||||
xce_sched_queue_t* queue;
|
||||
|
||||
memset(&g_sim, 0, sizeof(g_sim));
|
||||
TEST_ASSERT_EQUAL_INT(0, xce_sched_core_init(&(g_sim.core)));
|
||||
|
||||
memset(&res, 0, sizeof(res));
|
||||
res.name = strdup("slots");
|
||||
TEST_ASSERT_NOT_NULL(makeResource(g_sim.core.resmgr, &res));
|
||||
|
||||
sim_bootstrap_seqjob_allocator(&g_sim);
|
||||
sim_bootstrap_parjob_allocator(&g_sim);
|
||||
TEST_ASSERT_NOT_NULL(g_sim.core.seqjob_allocator);
|
||||
TEST_ASSERT_NOT_NULL(g_sim.core.parjob_allocator);
|
||||
|
||||
queue = (xce_sched_queue_t*)calloc(1, sizeof(*queue));
|
||||
TEST_ASSERT_NOT_NULL(queue);
|
||||
queue->name = makeString(QUEUE_NAME);
|
||||
TEST_ASSERT_TRUE(sched_add_queue(&(g_sim.core), queue) >= 0);
|
||||
|
||||
add_sim_worker(&(g_sim.core), WORKER_A_NAME, WORKER_SLOTS);
|
||||
add_sim_worker(&(g_sim.core), WORKER_B_NAME, WORKER_SLOTS);
|
||||
|
||||
memset(&g_job_wrapper, 0, sizeof(g_job_wrapper));
|
||||
TEST_ASSERT_EQUAL_INT(0, xce_sched_core_init(&(g_job_wrapper.core)));
|
||||
g_job_wrapper.core.resmgr = g_sim.core.resmgr;
|
||||
g_job_wrapper.core.workers = g_sim.core.workers;
|
||||
g_job_wrapper.core.queues = g_sim.core.queues;
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, xce_core_begin_session(&(g_sim.core)));
|
||||
|
||||
memset(&g_ctl, 0, sizeof(g_ctl));
|
||||
g_ctl.all = true;
|
||||
g_ctl.level = XCE_TRACE_LEVEL_FULL;
|
||||
g_ctl.line_bound = XCE_TRACE_LINE_BOUND_DEFAULT;
|
||||
g_ctl.session_no = 1;
|
||||
g_ctl.policy_name = "builtin_fcfs";
|
||||
}
|
||||
|
||||
static xce_sched_job_t* make_job(uint64_t jobid, const char* resreq,
|
||||
xce_resreq_tag_t want_tag)
|
||||
{
|
||||
xce_job_t* j = (xce_job_t*)calloc(1, sizeof(*j));
|
||||
job_submit_data_t* submit = (job_submit_data_t*)calloc(1, sizeof(*submit));
|
||||
xce_sched_job_t* job;
|
||||
|
||||
TEST_ASSERT_NOT_NULL(j);
|
||||
TEST_ASSERT_NOT_NULL(submit);
|
||||
submit->queue = (char*)QUEUE_NAME;
|
||||
submit->user = (char*)"resreq_user";
|
||||
submit->project_name = (char*)"resreq_proj";
|
||||
submit->resreq = (char*)resreq;
|
||||
submit->mintasks = 1;
|
||||
submit->maxtasks = 1;
|
||||
j->jobid = jobid;
|
||||
j->state = JOB_STAT_PEND;
|
||||
j->submit = submit;
|
||||
|
||||
job = make_sched_job(&g_job_wrapper, j);
|
||||
TEST_ASSERT_NOT_NULL(job);
|
||||
TEST_ASSERT_NOT_NULL(job->schedobj);
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(want_tag, job->schedobj->tag,
|
||||
"fixture: unexpected resreq kind");
|
||||
if (want_tag == XCE_RESREQ_PRIMARY) {
|
||||
job->schedobj->u.primary->resreq->minTasks = 1;
|
||||
job->schedobj->u.primary->resreq->maxTasks = 1;
|
||||
}
|
||||
TEST_ASSERT_TRUE(sched_add_job(&(g_sim.core), job) >= 0);
|
||||
return job;
|
||||
}
|
||||
|
||||
/* What one sched_make_allocation() call left on the scheduling object. */
|
||||
typedef struct {
|
||||
int rc;
|
||||
xce_job_alloc_t* alloc;
|
||||
xce_pendreason_t* reason;
|
||||
} run_result_t;
|
||||
|
||||
/* Runs the real allocation chain on obj, with a record on the core when
|
||||
* `rec` is non-NULL, and takes the allocation and reason off obj. */
|
||||
static run_result_t run_alloc(xce_sched_obj_t* obj, xce_sched_trace_rec_t* rec,
|
||||
int merge_fail_after)
|
||||
{
|
||||
run_result_t r;
|
||||
|
||||
obj->alloc = NULL;
|
||||
obj->reason = NULL;
|
||||
|
||||
g_merge_fail_after = merge_fail_after;
|
||||
g_sim.core.trace_rec = rec;
|
||||
r.rc = sched_make_allocation(&(g_sim.core), obj);
|
||||
g_sim.core.trace_rec = NULL;
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(0, g_merge_fail_after,
|
||||
"fixture: the merge failure was not hit");
|
||||
|
||||
r.alloc = obj->alloc;
|
||||
r.reason = obj->reason;
|
||||
obj->alloc = NULL;
|
||||
obj->reason = NULL;
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Runs obj without and then with a record, asserts the scheduling results
|
||||
* are identical, and returns the record. */
|
||||
static xce_sched_trace_rec_t* run_traced(xce_sched_job_t* job,
|
||||
int merge_fail_after, int* rc_out)
|
||||
{
|
||||
xce_sched_trace_rec_t* rec;
|
||||
run_result_t off;
|
||||
run_result_t on;
|
||||
char* why = NULL;
|
||||
|
||||
off = run_alloc(job->schedobj, NULL, merge_fail_after);
|
||||
|
||||
rec = sched_trace_rec_new(&g_ctl, job->jobid, XCE_TRACE_REC_DECISION);
|
||||
TEST_ASSERT_NOT_NULL(rec);
|
||||
on = run_alloc(job->schedobj, rec, merge_fail_after);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(off.rc, on.rc,
|
||||
"tracing changed the allocation result");
|
||||
TEST_ASSERT_TRUE_MESSAGE(job_alloc_eq(off.alloc, on.alloc, &why),
|
||||
why != NULL ? why : "allocation differs");
|
||||
TEST_ASSERT_TRUE_MESSAGE((off.reason == NULL) == (on.reason == NULL),
|
||||
"tracing changed whether a reason is left");
|
||||
if (off.reason != NULL) {
|
||||
TEST_ASSERT_TRUE_MESSAGE(
|
||||
job_preason_eq(off.reason->reasons, on.reason->reasons, &why),
|
||||
why != NULL ? why : "pend reason differs");
|
||||
}
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rec->resreq_open,
|
||||
"a requirement begin has no end");
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(0, rec->path.depth, "a path push has no pop");
|
||||
|
||||
free_JobAlloc_ptr(&(off.alloc));
|
||||
free_JobAlloc_ptr(&(on.alloc));
|
||||
free_PendReason_ptr(&(off.reason));
|
||||
free_PendReason_ptr(&(on.reason));
|
||||
*rc_out = on.rc;
|
||||
return rec;
|
||||
}
|
||||
|
||||
/* ── entry checks ───────────────────────────────────────────────────── */
|
||||
|
||||
#define MAX_BRANCH 32
|
||||
|
||||
static bool is_branch_entry(sched_trace_entry_type_t t)
|
||||
{
|
||||
return (t == SCHED_TRACE_E_RESREQ_BEGIN) || (t == SCHED_TRACE_E_RESREQ_END)
|
||||
|| (t == SCHED_TRACE_E_ALT_SKIPPED) || (t == SCHED_TRACE_E_ALT_RESULT)
|
||||
|| (t == SCHED_TRACE_E_MEMBER_FAILED)
|
||||
|| (t == SCHED_TRACE_E_ATTEMPT_REASONS);
|
||||
}
|
||||
|
||||
/* The requirement-branch entries of rec, in the order made. */
|
||||
static int branch_entries(const xce_sched_trace_rec_t* rec,
|
||||
const sched_trace_entry_t** out)
|
||||
{
|
||||
const sched_trace_entry_t* e;
|
||||
int n = 0;
|
||||
|
||||
for (e = rec->entries; e != NULL; e = e->next) {
|
||||
if (is_branch_entry(e->type)) {
|
||||
TEST_ASSERT_TRUE(n < MAX_BRANCH);
|
||||
out[n++] = e;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
#define K_NONE (-1)
|
||||
|
||||
static void assert_path(const sched_trace_entry_t* e, int k0, int i0, int n0,
|
||||
int k1, int i1, int n1)
|
||||
{
|
||||
int depth = (k0 == K_NONE) ? 0 : ((k1 == K_NONE) ? 1 : 2);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(depth, e->path.depth, "path depth");
|
||||
if (depth >= 1) {
|
||||
TEST_ASSERT_EQUAL_INT(k0, e->path.lvl[0].kind);
|
||||
TEST_ASSERT_EQUAL_INT(i0, e->path.lvl[0].i);
|
||||
TEST_ASSERT_EQUAL_INT(n0, e->path.lvl[0].n);
|
||||
}
|
||||
if (depth == 2) {
|
||||
TEST_ASSERT_EQUAL_INT(k1, e->path.lvl[1].kind);
|
||||
TEST_ASSERT_EQUAL_INT(i1, e->path.lvl[1].i);
|
||||
TEST_ASSERT_EQUAL_INT(n1, e->path.lvl[1].n);
|
||||
}
|
||||
}
|
||||
|
||||
static void assert_type(const sched_trace_entry_t* e,
|
||||
sched_trace_entry_type_t t)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(t, e->type, "unexpected entry type");
|
||||
}
|
||||
|
||||
/* A member/single begin: task counts and one requested resource `res`. */
|
||||
static void assert_primary_begin(const sched_trace_entry_t* e, int min_tasks,
|
||||
int max_tasks, const char* res, float value)
|
||||
{
|
||||
int k;
|
||||
|
||||
assert_type(e, SCHED_TRACE_E_RESREQ_BEGIN);
|
||||
TEST_ASSERT_FALSE(e->u.resreq_begin.container);
|
||||
TEST_ASSERT_EQUAL_INT(min_tasks, e->u.resreq_begin.min_tasks);
|
||||
TEST_ASSERT_EQUAL_INT(max_tasks, e->u.resreq_begin.max_tasks);
|
||||
TEST_ASSERT_TRUE(e->u.resreq_begin.nreq > 0);
|
||||
for (k = 0; k < e->u.resreq_begin.nreq; k++) {
|
||||
const sched_trace_req_t* r = &e->u.resreq_begin.req[k];
|
||||
|
||||
if ((r->name != NULL) && (strcmp(r->name, res) == 0)) {
|
||||
TEST_ASSERT_EQUAL_FLOAT(value, r->value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
TEST_FAIL_MESSAGE("the requested resource is not in the begin entry");
|
||||
}
|
||||
|
||||
static void assert_container_begin(const sched_trace_entry_t* e, int nmembers)
|
||||
{
|
||||
assert_type(e, SCHED_TRACE_E_RESREQ_BEGIN);
|
||||
TEST_ASSERT_TRUE(e->u.resreq_begin.container);
|
||||
TEST_ASSERT_EQUAL_INT(nmembers, e->u.resreq_begin.nmembers);
|
||||
}
|
||||
|
||||
/* rc_sign: 1 satisfied, 0 not satisfied, -1 internal error */
|
||||
static void assert_end(const sched_trace_entry_t* e, int rc_sign,
|
||||
int not_tried_after)
|
||||
{
|
||||
assert_type(e, SCHED_TRACE_E_RESREQ_END);
|
||||
if (rc_sign > 0) {
|
||||
TEST_ASSERT_TRUE_MESSAGE(e->u.resreq_end.rc > 0, "expected satisfied");
|
||||
} else if (rc_sign == 0) {
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(0, e->u.resreq_end.rc,
|
||||
"expected not satisfied");
|
||||
} else {
|
||||
TEST_ASSERT_TRUE_MESSAGE(e->u.resreq_end.rc < 0, "expected an error");
|
||||
}
|
||||
TEST_ASSERT_EQUAL_INT(not_tried_after, e->u.resreq_end.not_tried_after);
|
||||
}
|
||||
|
||||
/* ── tests ──────────────────────────────────────────────────────────── */
|
||||
|
||||
static void test_single_requirement(void)
|
||||
{
|
||||
xce_sched_job_t* job
|
||||
= make_job(632001, "rusage[mem=1]", XCE_RESREQ_PRIMARY);
|
||||
const sched_trace_entry_t* b[MAX_BRANCH];
|
||||
xce_sched_trace_rec_t* rec;
|
||||
int rc;
|
||||
int n;
|
||||
|
||||
rec = run_traced(job, 0, &rc);
|
||||
TEST_ASSERT_TRUE(rc > 0);
|
||||
n = branch_entries(rec, b);
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(2, n, "a single requirement: begin + end");
|
||||
|
||||
assert_primary_begin(b[0], 1, 1, "mem", 1.0f);
|
||||
assert_path(b[0], XCE_TRACE_PATH_SINGLE, 1, 1, K_NONE, 0, 0);
|
||||
assert_end(b[1], 1, 0);
|
||||
assert_path(b[1], XCE_TRACE_PATH_SINGLE, 1, 1, K_NONE, 0, 0);
|
||||
|
||||
sched_trace_rec_free(rec);
|
||||
}
|
||||
|
||||
static void test_compound_failing_member(void)
|
||||
{
|
||||
xce_sched_job_t* job = make_job(
|
||||
632002, "1*{rusage[mem=1]} + 9*{rusage[mem=2]} + 1*{rusage[mem=3]}",
|
||||
XCE_RESREQ_COMPOUND);
|
||||
const sched_trace_entry_t* b[MAX_BRANCH];
|
||||
xce_sched_trace_rec_t* rec;
|
||||
int rc;
|
||||
int n;
|
||||
|
||||
rec = run_traced(job, 0, &rc);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
n = branch_entries(rec, b);
|
||||
TEST_ASSERT_EQUAL_INT(7, n);
|
||||
|
||||
assert_container_begin(b[0], 3);
|
||||
assert_path(b[0], XCE_TRACE_PATH_COMPOUND, 1, 1, K_NONE, 0, 0);
|
||||
|
||||
assert_primary_begin(b[1], 1, 1, "mem", 1.0f);
|
||||
assert_path(b[1], XCE_TRACE_PATH_COMPOUND, 1, 1, XCE_TRACE_PATH_MEMBER, 1,
|
||||
3);
|
||||
assert_end(b[2], 1, 0);
|
||||
|
||||
assert_primary_begin(b[3], 9, 9, "mem", 2.0f);
|
||||
assert_path(b[3], XCE_TRACE_PATH_COMPOUND, 1, 1, XCE_TRACE_PATH_MEMBER, 2,
|
||||
3);
|
||||
assert_end(b[4], 0, 1); /* point of failure: member 3/3 not tried */
|
||||
assert_path(b[4], XCE_TRACE_PATH_COMPOUND, 1, 1, XCE_TRACE_PATH_MEMBER, 2,
|
||||
3);
|
||||
|
||||
assert_type(b[5], SCHED_TRACE_E_ATTEMPT_REASONS);
|
||||
TEST_ASSERT_TRUE_MESSAGE(b[5]->u.reasons.ncodes > 0,
|
||||
"the failed attempt's reasons are not captured");
|
||||
assert_path(b[5], XCE_TRACE_PATH_COMPOUND, 1, 1, K_NONE, 0, 0);
|
||||
|
||||
assert_end(b[6], 0, 0);
|
||||
assert_path(b[6], XCE_TRACE_PATH_COMPOUND, 1, 1, K_NONE, 0, 0);
|
||||
|
||||
sched_trace_rec_free(rec);
|
||||
}
|
||||
|
||||
static void test_compound_merge_failure(void)
|
||||
{
|
||||
xce_sched_job_t* job = make_job(
|
||||
632003, "1*{rusage[mem=1]} + 1*{rusage[mem=2]} + 1*{rusage[mem=3]}",
|
||||
XCE_RESREQ_COMPOUND);
|
||||
const sched_trace_entry_t* b[MAX_BRANCH];
|
||||
xce_sched_trace_rec_t* rec;
|
||||
int rc;
|
||||
int n;
|
||||
|
||||
/* the first merge (after member 2) fails */
|
||||
rec = run_traced(job, 1, &rc);
|
||||
TEST_ASSERT_EQUAL_INT(-ENOMEM, rc);
|
||||
n = branch_entries(rec, b);
|
||||
TEST_ASSERT_EQUAL_INT(8, n);
|
||||
|
||||
assert_container_begin(b[0], 3);
|
||||
assert_primary_begin(b[1], 1, 1, "mem", 1.0f);
|
||||
assert_end(b[2], 1, 0);
|
||||
assert_primary_begin(b[3], 1, 1, "mem", 2.0f);
|
||||
assert_path(b[3], XCE_TRACE_PATH_COMPOUND, 1, 1, XCE_TRACE_PATH_MEMBER, 2,
|
||||
3);
|
||||
assert_end(b[4], 1, 0); /* the member itself was satisfied */
|
||||
|
||||
assert_type(b[5], SCHED_TRACE_E_MEMBER_FAILED);
|
||||
TEST_ASSERT_EQUAL_INT(2, b[5]->u.member_failed.j);
|
||||
TEST_ASSERT_EQUAL_INT(3, b[5]->u.member_failed.m);
|
||||
TEST_ASSERT_EQUAL_INT(-ENOMEM, b[5]->u.member_failed.err);
|
||||
|
||||
assert_type(b[6], SCHED_TRACE_E_ATTEMPT_REASONS);
|
||||
assert_end(b[7], -1, 0);
|
||||
TEST_ASSERT_EQUAL_INT(-ENOMEM, b[7]->u.resreq_end.rc);
|
||||
|
||||
sched_trace_rec_free(rec);
|
||||
}
|
||||
|
||||
static void test_alternatives_skipped_failed_chosen(void)
|
||||
{
|
||||
xce_sched_job_t* job = make_job(
|
||||
632004,
|
||||
"{1*{rusage[mem=1]}} || {9*{rusage[mem=2]}} || "
|
||||
"{1*{rusage[mem=3]} + 1*{rusage[mem=4]}} || {1*{rusage[mem=5]}}",
|
||||
XCE_RESREQ_ALTER);
|
||||
xce_compound_schedobj_t* alt1;
|
||||
xce_compound_resreq_t* saved;
|
||||
const sched_trace_entry_t* b[MAX_BRANCH];
|
||||
xce_sched_trace_rec_t* rec;
|
||||
int rc;
|
||||
int n;
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(4, job->schedobj->u.alters.count);
|
||||
/* alternative 1 has no requirement content: skipped */
|
||||
alt1 = (xce_compound_schedobj_t*)xlist_get(&(job->schedobj->u.alters), 0);
|
||||
TEST_ASSERT_NOT_NULL(alt1);
|
||||
saved = alt1->resreq;
|
||||
alt1->resreq = NULL;
|
||||
rec = run_traced(job, 0, &rc);
|
||||
alt1->resreq = saved;
|
||||
|
||||
TEST_ASSERT_TRUE(rc > 0);
|
||||
n = branch_entries(rec, b);
|
||||
TEST_ASSERT_EQUAL_INT(13, n);
|
||||
|
||||
assert_type(b[0], SCHED_TRACE_E_ALT_SKIPPED);
|
||||
TEST_ASSERT_EQUAL_INT(1, b[0]->u.alt.i);
|
||||
TEST_ASSERT_EQUAL_INT(4, b[0]->u.alt.n);
|
||||
assert_path(b[0], K_NONE, 0, 0, K_NONE, 0, 0);
|
||||
|
||||
/* alternative 2/4: not satisfied, with its reasons */
|
||||
assert_container_begin(b[1], 1);
|
||||
assert_path(b[1], XCE_TRACE_PATH_ALT, 2, 4, K_NONE, 0, 0);
|
||||
assert_primary_begin(b[2], 9, 9, "mem", 2.0f);
|
||||
assert_path(b[2], XCE_TRACE_PATH_ALT, 2, 4, XCE_TRACE_PATH_MEMBER, 1, 1);
|
||||
assert_end(b[3], 0, 0);
|
||||
assert_type(b[4], SCHED_TRACE_E_ATTEMPT_REASONS);
|
||||
TEST_ASSERT_TRUE(b[4]->u.reasons.ncodes > 0);
|
||||
assert_path(b[4], XCE_TRACE_PATH_ALT, 2, 4, K_NONE, 0, 0);
|
||||
assert_end(b[5], 0, 0);
|
||||
assert_path(b[5], XCE_TRACE_PATH_ALT, 2, 4, K_NONE, 0, 0);
|
||||
|
||||
/* alternative 3/4: two members, satisfied */
|
||||
assert_container_begin(b[6], 2);
|
||||
assert_path(b[6], XCE_TRACE_PATH_ALT, 3, 4, K_NONE, 0, 0);
|
||||
assert_primary_begin(b[7], 1, 1, "mem", 3.0f);
|
||||
assert_path(b[7], XCE_TRACE_PATH_ALT, 3, 4, XCE_TRACE_PATH_MEMBER, 1, 2);
|
||||
assert_end(b[8], 1, 0);
|
||||
assert_primary_begin(b[9], 1, 1, "mem", 4.0f);
|
||||
assert_path(b[9], XCE_TRACE_PATH_ALT, 3, 4, XCE_TRACE_PATH_MEMBER, 2, 2);
|
||||
assert_end(b[10], 1, 0);
|
||||
assert_end(b[11], 1, 0);
|
||||
assert_path(b[11], XCE_TRACE_PATH_ALT, 3, 4, K_NONE, 0, 0);
|
||||
|
||||
/* chosen: 3/4, alternative 4 not tried (nothing recorded for it) */
|
||||
assert_type(b[12], SCHED_TRACE_E_ALT_RESULT);
|
||||
TEST_ASSERT_EQUAL_INT(3, b[12]->u.alt.i);
|
||||
TEST_ASSERT_EQUAL_INT(4, b[12]->u.alt.n);
|
||||
assert_path(b[12], K_NONE, 0, 0, K_NONE, 0, 0);
|
||||
|
||||
sched_trace_rec_free(rec);
|
||||
}
|
||||
|
||||
static void test_all_alternatives_fail_each_has_reasons(void)
|
||||
{
|
||||
xce_sched_job_t* job = make_job(
|
||||
632005, "{9*{rusage[mem=1]}} || {7*{rusage[mem=2]}}", XCE_RESREQ_ALTER);
|
||||
const sched_trace_entry_t* b[MAX_BRANCH];
|
||||
xce_sched_trace_rec_t* rec;
|
||||
int rc;
|
||||
int n;
|
||||
int a;
|
||||
|
||||
rec = run_traced(job, 0, &rc);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
n = branch_entries(rec, b);
|
||||
TEST_ASSERT_EQUAL_INT(11, n);
|
||||
|
||||
for (a = 0; a < 2; a++) {
|
||||
const sched_trace_entry_t** e = &b[a * 5];
|
||||
|
||||
assert_container_begin(e[0], 1);
|
||||
assert_path(e[0], XCE_TRACE_PATH_ALT, a + 1, 2, K_NONE, 0, 0);
|
||||
assert_primary_begin(e[1], (a == 0) ? 9 : 7, (a == 0) ? 9 : 7, "mem",
|
||||
(a == 0) ? 1.0f : 2.0f);
|
||||
assert_end(e[2], 0, 0);
|
||||
assert_type(e[3], SCHED_TRACE_E_ATTEMPT_REASONS);
|
||||
TEST_ASSERT_TRUE_MESSAGE(e[3]->u.reasons.ncodes > 0,
|
||||
"an alternative's own reasons are missing");
|
||||
assert_path(e[3], XCE_TRACE_PATH_ALT, a + 1, 2, K_NONE, 0, 0);
|
||||
assert_end(e[4], 0, 0);
|
||||
}
|
||||
|
||||
assert_type(b[10], SCHED_TRACE_E_ALT_RESULT);
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(0, b[10]->u.alt.i, "expected all failed");
|
||||
TEST_ASSERT_EQUAL_INT(2, b[10]->u.alt.n);
|
||||
|
||||
sched_trace_rec_free(rec);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
start_sim();
|
||||
RUN_TEST(test_single_requirement);
|
||||
RUN_TEST(test_compound_failing_member);
|
||||
RUN_TEST(test_compound_merge_failure);
|
||||
RUN_TEST(test_alternatives_skipped_failed_chosen);
|
||||
RUN_TEST(test_all_alternatives_fail_each_has_reasons);
|
||||
return UNITY_END();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue