/* * 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 #include #include #include #include #include #include #include #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; }