Task 6.3: the contention and extra replays run with tracing off, all jobs at summary and full level, and a job subset, in step and continue mode; decisions, full pend reasons, --sim-output recordings, snapshots, --sim-verify results and per-step accounting dumps are identical. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
283 lines
8.9 KiB
C
283 lines
8.9 KiB
C
/*
|
|
* test_trace_equivalence_cmp.c - the comparisons of the decision-trace
|
|
* equivalence test (test_trace_equivalence.c, which documents what is
|
|
* compared and why; scheduler-sim-decision-trace task 6.3): texts,
|
|
* .replay files apart from the header's wall-clock field, and the decoded
|
|
* recorded sessions (decisions, allocations, pend reasons). Each returns
|
|
* NULL when equal, else a malloc'd description of the first difference.
|
|
* Kept apart from the scenarios for the 800-line file cap.
|
|
*/
|
|
#include <limits.h>
|
|
#include <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <unity/unity.h>
|
|
|
|
#include "controller.capnp.h"
|
|
#include "preason.h"
|
|
#include "recording.h"
|
|
#include "scheduler.h"
|
|
|
|
#include "test_repl_trace_e2e.h"
|
|
#include "test_trace_equivalence.h"
|
|
|
|
extern int msg_root_has_shape(struct capn* c, capn_ptr* out,
|
|
size_t want_words, size_t want_ptrs);
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* Comparisons: each returns NULL when equal, else a description */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
static char* describe(const char* fmt, ...)
|
|
__attribute__((format(printf, 1, 2)));
|
|
|
|
static char* describe(const char* fmt, ...)
|
|
{
|
|
char* out = (char*)malloc(1024);
|
|
va_list ap;
|
|
|
|
TEST_ASSERT_NOT_NULL(out);
|
|
va_start(ap, fmt);
|
|
vsnprintf(out, 1024, fmt, ap);
|
|
va_end(ap);
|
|
return out;
|
|
}
|
|
|
|
/* First differing line of two texts. */
|
|
char* text_diff(const char* a, const char* b)
|
|
{
|
|
long i = 0;
|
|
long line = 1;
|
|
long bol = 0;
|
|
|
|
while ((a[i] != '\0') && (a[i] == b[i])) {
|
|
if (a[i] == '\n') {
|
|
line++;
|
|
bol = i + 1;
|
|
}
|
|
i++;
|
|
}
|
|
if (a[i] == b[i]) {
|
|
return NULL;
|
|
}
|
|
return describe("line %ld:\n off: %.*s\n traced: %.*s", line,
|
|
(int)strcspn(a + bol, "\n"), a + bol,
|
|
(int)strcspn(b + bol, "\n"), b + bol);
|
|
}
|
|
|
|
/* A recording (or snapshot) header without recordingStartWall, and the
|
|
* offset just past it. */
|
|
static void read_header(const char* path, replay_header_t* hdr, long* end)
|
|
{
|
|
FILE* f = fopen(path, "rb");
|
|
struct capn c;
|
|
struct ReplayHeader wire;
|
|
ReplayHeader_ptr p;
|
|
|
|
TEST_ASSERT_NOT_NULL_MESSAGE(f, path);
|
|
TEST_ASSERT_EQUAL_INT(0, capn_init_fp(&c, f, 0));
|
|
p.p = capn_getp(capn_root(&c), 0, 1);
|
|
read_ReplayHeader(&wire, p);
|
|
memset(hdr, 0, sizeof(*hdr));
|
|
decode_ReplayHeader(hdr, &wire);
|
|
capn_free(&c);
|
|
*end = ftell(f);
|
|
fclose(f);
|
|
}
|
|
|
|
static bool same_str(const char* a, const char* b)
|
|
{
|
|
return (a == b) || ((a != NULL) && (b != NULL) && (strcmp(a, b) == 0));
|
|
}
|
|
|
|
/* Byte comparison of two .replay files apart from recordingStartWall. */
|
|
char* recording_diff(const char* a_path, const char* b_path)
|
|
{
|
|
replay_header_t ha;
|
|
replay_header_t hb;
|
|
long ea;
|
|
long eb;
|
|
long la;
|
|
long lb;
|
|
char* a = slurp(a_path, &la);
|
|
char* b = slurp(b_path, &lb);
|
|
char* why = NULL;
|
|
long i;
|
|
|
|
TEST_ASSERT_NOT_NULL_MESSAGE(a, a_path);
|
|
TEST_ASSERT_NOT_NULL_MESSAGE(b, b_path);
|
|
read_header(a_path, &ha, &ea);
|
|
read_header(b_path, &hb, &eb);
|
|
if (!same_str(ha.recorder_version, hb.recorder_version)
|
|
|| (ha.recording_start_ts != hb.recording_start_ts)
|
|
|| !same_str(ha.sched_policy, hb.sched_policy)
|
|
|| (ha.cluster != NULL) || (hb.cluster != NULL)) {
|
|
why = describe("header fields differ");
|
|
} else if ((la - ea) != (lb - eb)) {
|
|
why = describe("length after the header: %ld != %ld", la - ea,
|
|
lb - eb);
|
|
} else {
|
|
for (i = 0; (i < la - ea) && (a[ea + i] == b[eb + i]); i++) {
|
|
}
|
|
if (i < la - ea) {
|
|
why = describe("byte %ld after the header differs", i);
|
|
}
|
|
}
|
|
free_ReplayHeader(&ha);
|
|
free_ReplayHeader(&hb);
|
|
free(a);
|
|
free(b);
|
|
return why;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* Decoded decisions */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
void sessions_read(const char* path, sessions_t* out)
|
|
{
|
|
FILE* f = fopen(path, "rb");
|
|
struct capn c;
|
|
int msg = 0;
|
|
|
|
memset(out, 0, sizeof(*out));
|
|
TEST_ASSERT_NOT_NULL_MESSAGE(f, path);
|
|
while (capn_init_fp(&c, f, 0) == 0) {
|
|
capn_ptr root;
|
|
|
|
if ((msg++ > 0)
|
|
&& msg_root_has_shape(&c, &root, ReplaySession_word_count,
|
|
ReplaySession_pointer_count)) {
|
|
ReplaySession_ptr sp;
|
|
replay_session_t* s = NULL;
|
|
int i;
|
|
|
|
sp.p = root;
|
|
decode_ReplaySession_ptr(&s, sp);
|
|
TEST_ASSERT_NOT_NULL(s);
|
|
out->s = (replay_session_t**)realloc(
|
|
out->s, sizeof(*out->s) * (size_t)(out->n + 1));
|
|
TEST_ASSERT_NOT_NULL(out->s);
|
|
out->s[out->n++] = s;
|
|
for (i = 0; i < s->n_decisions; i++) {
|
|
replay_decision_t* d = s->decisions[i];
|
|
|
|
if (d->body_which == ReplayDecision_body_dispatch) {
|
|
out->ndispatch++;
|
|
} else if (d->body_which == ReplayDecision_body_pend) {
|
|
xce_job_preason_t* pr = d->body.pend->reason;
|
|
|
|
out->npend++;
|
|
if ((pr != NULL)
|
|
&& ((pr->nhost_reasons + pr->nres_reasons) > 0)) {
|
|
out->npend_detail++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
capn_free(&c);
|
|
}
|
|
fclose(f);
|
|
}
|
|
|
|
void sessions_free(sessions_t* s)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < s->n; i++) {
|
|
free_ReplaySession_ptr(&(s->s[i]));
|
|
}
|
|
free(s->s);
|
|
memset(s, 0, sizeof(*s));
|
|
}
|
|
|
|
static char* alloc_diff(const xce_job_alloc_t* a, const xce_job_alloc_t* b)
|
|
{
|
|
int h;
|
|
|
|
if ((a == NULL) || (b == NULL)) {
|
|
return (a == b) ? NULL : describe("alloc NULL on one side");
|
|
}
|
|
if ((a->jobid != b->jobid) || (a->nhosts != b->nhosts)
|
|
|| (a->nres_shared != b->nres_shared)) {
|
|
return describe("dispatch %llu: nhosts %d/%d, nres_shared %d/%d",
|
|
(unsigned long long)a->jobid, a->nhosts, b->nhosts,
|
|
a->nres_shared, b->nres_shared);
|
|
}
|
|
for (h = 0; h < a->nhosts; h++) {
|
|
if (!same_str(a->allocs[h]->host, b->allocs[h]->host)
|
|
|| (a->allocs[h]->nallocs != b->allocs[h]->nallocs)) {
|
|
return describe("dispatch %llu: host %d differs",
|
|
(unsigned long long)a->jobid, h);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static char* decision_diff(const replay_decision_t* a,
|
|
const replay_decision_t* b)
|
|
{
|
|
char* why = NULL;
|
|
|
|
if ((a->ts != b->ts) || (a->body_which != b->body_which)) {
|
|
return describe("ts %llu/%llu, op %d/%d", (unsigned long long)a->ts,
|
|
(unsigned long long)b->ts, a->body_which,
|
|
b->body_which);
|
|
}
|
|
if (a->body_which == ReplayDecision_body_dispatch) {
|
|
return alloc_diff(a->body.dispatch->alloc, b->body.dispatch->alloc);
|
|
}
|
|
if (a->body_which == ReplayDecision_body_pend) {
|
|
if (a->body.pend->jobid != b->body.pend->jobid) {
|
|
return describe("pend jobid %llu/%llu",
|
|
(unsigned long long)a->body.pend->jobid,
|
|
(unsigned long long)b->body.pend->jobid);
|
|
}
|
|
if (!job_preason_eq(a->body.pend->reason, b->body.pend->reason,
|
|
&why)) {
|
|
char* out = describe("pend %llu reason: %s",
|
|
(unsigned long long)a->body.pend->jobid,
|
|
(why != NULL) ? why : "?");
|
|
|
|
free(why);
|
|
return out;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
char* sessions_diff(const sessions_t* a, const sessions_t* b)
|
|
{
|
|
int i;
|
|
int j;
|
|
|
|
if (a->n != b->n) {
|
|
return describe("session count %d != %d", a->n, b->n);
|
|
}
|
|
for (i = 0; i < a->n; i++) {
|
|
if ((a->s[i]->begin_round != b->s[i]->begin_round)
|
|
|| (a->s[i]->n_decisions != b->s[i]->n_decisions)) {
|
|
return describe("session %d: round %llu/%llu, %d/%d decisions",
|
|
i, (unsigned long long)a->s[i]->begin_round,
|
|
(unsigned long long)b->s[i]->begin_round,
|
|
a->s[i]->n_decisions, b->s[i]->n_decisions);
|
|
}
|
|
for (j = 0; j < a->s[i]->n_decisions; j++) {
|
|
char* why = decision_diff(a->s[i]->decisions[j],
|
|
b->s[i]->decisions[j]);
|
|
|
|
if (why != NULL) {
|
|
char* out = describe("session %d decision %d: %s", i, j, why);
|
|
|
|
free(why);
|
|
return out;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|