refactor(scheduler-sim-decision-trace): split trace sources under the 800-line cap
Task 2.4: outcome capture moves to sched_trace_outcome.c and the position/host/summary/rollback half of the model to sched_trace_scan.c; shared helpers go behind sched_trace_internal.h (sti_ prefix). Code is moved unchanged; every target that compiles the trace sources gets all four, and the core isolation scan covers the new files. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
eacb1fa599
commit
144a01982e
40 changed files with 1355 additions and 1094 deletions
|
|
@ -27,7 +27,7 @@
|
|||
## Boundary Commitments
|
||||
|
||||
### This Spec Owns
|
||||
- The trace record model, its arena storage, entry budget and counters (`sched_trace.c`), and the allocator-side capture helpers that read scheduler state (`sched_trace_capture.c`).
|
||||
- The trace record model, its arena storage, entry budget and counters (`sched_trace.c`, `sched_trace_scan.c`), and the allocator-side capture helpers that read scheduler state (`sched_trace_capture.c`, `sched_trace_outcome.c`), with their shared internal header `sched_trace_internal.h`.
|
||||
- Two gate fields on `xce_sched_core_t`: `trace_ctl` (session settings snapshot) and `trace_rec` (record under construction). It also owns the `SCHED_TRACE` hook macro.
|
||||
- The `trace` field on `xce_decision_t`, the `XCE_DECISION_TRACE` op and `make_trace_decision()`.
|
||||
- The hook call sites in `sched_election.c`, `scheduler.c`, `alloc_runner.c`, `host_iter.c` and `resource.c`. Each is a single gated statement that never changes control flow or return values.
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
### Allowed Dependencies
|
||||
- Core side (`sched_trace*.c`): libxce containers and types (`xvec`, `xlist`, `zarray`, `xce/types.h`), `include/sched/alloc_iter.h` (for `xce_alloc_plan_t`), `src/include/utils.h` (only `array_jobid`/`array_idx` for selector matching), `include/sched/types.h`, `include/core.h`, and `include/preason.h`. These files perform no stdio, file or network I/O and do not include REPL or sim headers.
|
||||
- REPL side (`repl_trace*.c`): `include/sched/trace.h` (read-only use of records), `repl_internal.h`, `repl_fmt.c` helpers, libxce `htab`, and `parse_jobsel`/`array_jobid`/`fmt_jobid` (`utils.h`). It must not call scheduler mutation APIs.
|
||||
- Dependency direction: `trace.h` types → `sched_trace.c` (model) → `sched_trace_capture.c` (allocator-side readers) → hook sites. Separately, `trace.h` → `repl_trace_render.c` → `repl_trace_store.c` → `repl_trace.c` → `repl.c`/`repl_info.c`/`repl_params.c`/`repl_rollup.c`/`sim_loop.c` call sites. Core-side files never include REPL-side headers.
|
||||
- Dependency direction: `trace.h` types → `sched_trace.c`/`sched_trace_scan.c` (model) → `sched_trace_capture.c`/`sched_trace_outcome.c` (allocator-side readers) → hook sites. Separately, `trace.h` → `repl_trace_render.c` → `repl_trace_store.c` → `repl_trace.c` → `repl.c`/`repl_info.c`/`repl_params.c`/`repl_rollup.c`/`sim_loop.c` call sites. Core-side files never include REPL-side headers.
|
||||
|
||||
### Revalidation Triggers
|
||||
- Any change to `xce_decision_t` or `xce_decision_op_t` (shared with `scheduler-preemption`).
|
||||
|
|
@ -128,11 +128,18 @@ src/scheduler/
|
|||
├── include/sched/
|
||||
│ └── trace.h # Record, entry, settings types; SCHED_TRACE macro; model + capture API
|
||||
├── src/
|
||||
│ ├── sched_trace.c # Record model: arena, entry append, budget, counters, verdict tallies,
|
||||
│ │ # jobsel predicate, record free/size. No I/O, no scheduler-state reads.
|
||||
│ ├── sched_trace.c # Record model: arena, entry append, budget, requirement/plan/check
|
||||
│ │ # entries, jobsel predicate, record free/size. No I/O, no
|
||||
│ │ # scheduler-state reads.
|
||||
│ ├── sched_trace_scan.c # Record model, part 2: positions, host verdicts and rows, counters,
|
||||
│ │ # verdict tallies, rollback, commit, search end. No I/O.
|
||||
│ ├── sched_trace_capture.c # Allocator-side capture helpers that read scheduler structures
|
||||
│ │ # (job/queue keys, glimits, resource flags, vres lookup, alloc/pend
|
||||
│ │ # outcome copy with 50-host cap). No I/O.
|
||||
│ │ # (job/queue keys, glimits, resource flags, vres lookup, check
|
||||
│ │ # rows, attempt reasons). No I/O.
|
||||
│ ├── sched_trace_outcome.c # Capture, part 2: alloc/pend outcome copy with 50-host cap,
|
||||
│ │ # reconstructed-dispatch record. No I/O.
|
||||
│ ├── sched_trace_internal.h # Helpers shared by the sched_trace*.c files (task 2.4 split to
|
||||
│ │ # stay under the 800-line cap); not installed.
|
||||
│ ├── repl_trace.c # `trace on|off|status` parsing (raw line), settings lifecycle
|
||||
│ │ # (next/cur references), session begin/end hooks, file open/close,
|
||||
│ │ # dev/inode conflict checks, inline emission, shutdown
|
||||
|
|
@ -141,7 +148,7 @@ src/scheduler/
|
|||
│ └── repl_trace_render.c # Record -> lines on one or two FILE* sinks; units mode; jobid fmt;
|
||||
│ # hierarchy/indent; summary vs full host section; outcome section
|
||||
└── testsuite/
|
||||
├── test_trace_model.c # sched_trace.c unit tests
|
||||
├── test_trace_model.c # sched_trace.c + sched_trace_scan.c unit tests
|
||||
├── test_trace_capture.c # capture helpers against fixture core/schedobj/workers
|
||||
├── test_trace_hooks_gate.c # SCHED_TRACE no-evaluation proof; live/non-interactive never trace
|
||||
├── test_trace_render.c # renderer output per section, units modes, truncation, outcome cap
|
||||
|
|
@ -192,9 +199,9 @@ src/scheduler/
|
|||
- `src/scheduler/include/sim.h`, `src/sim.c`, `src/main.c`: `log_path` plumbing. `sim.h` also declares `sim_print_decision`, which `sim_loop.c` makes non-static (format unchanged) so continue-mode trace emission reuses the exact echo line.
|
||||
- `src/scheduler/meson.build`:
|
||||
- add the new sources to the scheduler sources;
|
||||
- add `sched_trace.c` and `sched_trace_capture.c` to `test_core_isolation.c`'s source-text scan list (forbidden headers and symbols, plus a new no-stdio check for these two files only). They are not added to `sched_core_isolation_lib`, whose `nm` check covers the four core state-machine files; the capture file references scheduler symbols outside that archive's scope;
|
||||
- add the trace sources (`sched_trace.c`, `sched_trace_scan.c`, `sched_trace_capture.c`, `sched_trace_outcome.c` and `sched_trace_internal.h`) to `test_core_isolation.c`'s source-text scan list (forbidden headers and symbols, plus a new no-stdio check for these files only). They are not added to `sched_core_isolation_lib`, whose `nm` check covers the four core state-machine files; the capture file references scheduler symbols outside that archive's scope;
|
||||
- add `repl_trace*.c` to `sched_sim_isolation_lib`.
|
||||
- `src/scheduler/testsuite/meson.build`, `test_core_isolation.c`, `test_sim_isolation.c`, plus every white-box test that `#include`s a hooked or calling source: register the new tests, scan the new files, and add `#include` lines for `sched_trace*.c` / `repl_trace*.c`. `SCHED_TRACE` bodies are compiled even when they never run, so a test that includes a hooked file must also include (or link) `sched_trace.c` and `sched_trace_capture.c`.
|
||||
- `src/scheduler/testsuite/meson.build`, `test_core_isolation.c`, `test_sim_isolation.c`, plus every white-box test that `#include`s a hooked or calling source: register the new tests, scan the new files, and add `#include` lines for `sched_trace*.c` / `repl_trace*.c`. `SCHED_TRACE` bodies are compiled even when they never run, so a test that includes a hooked file must also include (or link) `sched_trace.c`, `sched_trace_scan.c`, `sched_trace_capture.c` and `sched_trace_outcome.c`.
|
||||
- **Hooked sources**: `resource.c`, `host_iter.c`, `alloc_runner.c`, `scheduler.c`, `sched_election.c` and `driver.c`. These are included by `test_alloc_runner*.c`, `test_host_iter.c`, `test_alloc_equivalence_*.c`, `test_parjob_combinatorial.c`, `test_core_events.c`, `test.c` and others (about 20).
|
||||
- **Calling sources**: `repl.c` and `sim_loop.c`, included by about 26 tests.
|
||||
- The exact list is produced by `grep -l '#include "../src/\(resource\|host_iter\|alloc_runner\|scheduler\|sched_election\|driver\|repl\|sim_loop\).c"' testsuite/*.c` and updated in one dedicated task right after the new files exist.
|
||||
|
|
@ -325,7 +332,7 @@ A settings object (selection, level, bound, open file) is referenced by `rs->tra
|
|||
|
||||
### Core Side
|
||||
|
||||
#### TraceModel (`include/sched/trace.h`, `src/sched_trace.c`)
|
||||
#### TraceModel (`include/sched/trace.h`, `src/sched_trace.c`, `src/sched_trace_scan.c`)
|
||||
|
||||
**Responsibilities & Constraints**
|
||||
- Owns `xce_sched_trace_rec_t`: an append-only sequence of typed entries in a per-record arena, plus fixed-size header, selection, counters and outcome blocks. A record is built by exactly one thread at a time and is immutable once attached to a decision.
|
||||
|
|
@ -464,7 +471,7 @@ void sched_trace_finalize_failed(xce_sched_trace_rec_t*, int err);
|
|||
- Validation: `test_trace_model.c` covers budget edges (bound-1, bound, bound+1), summary tallies with multi-kind hosts, counters under truncation, and `ctl_wants` for plain, element and array selectors.
|
||||
- Risk: arena growth uses `malloc` on the allocator thread. This happens only for traced jobs, never in live.
|
||||
|
||||
#### TraceCapture (`src/sched_trace_capture.c`)
|
||||
#### TraceCapture (`src/sched_trace_capture.c`, `src/sched_trace_outcome.c`)
|
||||
|
||||
**Responsibilities & Constraints**
|
||||
- Reads scheduler state on the thread that owns it at the moment of capture, and writes entries through TraceModel:
|
||||
|
|
@ -489,7 +496,9 @@ void sched_trace_finalize_failed(xce_sched_trace_rec_t*, int err);
|
|||
**Contracts**: Service [x]
|
||||
|
||||
```c
|
||||
/* declared in include/sched/trace.h, implemented in sched_trace_capture.c */
|
||||
/* declared in include/sched/trace.h; implemented in sched_trace_capture.c
|
||||
* (selection, cluster-wide limits, check rows, attempt reasons) and
|
||||
* sched_trace_outcome.c (dispatch, pend, reconstructed record) */
|
||||
void sched_trace_capture_selection(xce_sched_trace_rec_t*, const xce_sched_core_t* core,
|
||||
const xce_sched_job_t* job);
|
||||
void sched_trace_capture_glimits(xce_sched_trace_rec_t*, const xce_sched_obj_t* obj);
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@
|
|||
- a full allocation with tracing never enabled creates no record.
|
||||
- _Requirements: 11.1, 11.2, 11.3_
|
||||
|
||||
- [ ] 2. Allocator-side capture of scheduler state
|
||||
- [x] 2. Allocator-side capture of scheduler state
|
||||
- [x] 2.1 Capture job selection facts, cluster-wide limits and attempt failure reasons, test first
|
||||
- Selection capture: policy name, the job's queue and queue priority, the job's priority and its queue time.
|
||||
- Cluster-wide limit capture: each matched limit's name and scope instance, read from the job's matched cluster-wide limits.
|
||||
|
|
@ -114,7 +114,7 @@
|
|||
- The full scheduler test suite, including the 28 tests updated in 1.6, must still build and pass under ASAN with the completed capture source. Missing scheduler sources are added to those tests, or stubbed.
|
||||
- _Requirements: 2.5, 6.1, 6.2, 6.3, 6.4, 9.4_
|
||||
|
||||
- [ ] 2.4 Split the trace sources under the 800-line file cap, with no behaviour change
|
||||
- [x] 2.4 Split the trace sources under the 800-line file cap, with no behaviour change
|
||||
- Added during implementation (2026-10-08, user decision). After 2.3, `sched_trace.c` is 1130 lines and `sched_trace_capture.c` is 1065, both over the scheduler's 800-line cap (src/scheduler/CLAUDE.md).
|
||||
- Move outcome capture (dispatch, pend, reconstructed record) into a new `src/sched_trace_outcome.c`. Split `sched_trace.c` along a clear seam, e.g. entries and line budget versus counters, summaries and rollback, so every trace source is under 800 lines. Shared static helpers go behind an internal header (e.g. `src/sched_trace_internal.h`); the public `include/sched/trace.h` interface is unchanged.
|
||||
- Register every new source in the scheduler's main source list, in the core isolation test's scan and no-stdio check, and in each white-box test that includes the trace sources (the 27 from 1.6, plus the trace model, capture and gate tests).
|
||||
|
|
@ -359,3 +359,4 @@
|
|||
- 2.2: rows go through `sched_trace_row()`, a model addition not named in the design, as CHECK_ROW (cluster-wide check, all rows) or HOST_ROW (host scope, unmet rows only). Host rows are held until the host verdict is stored and then stored one level deeper, each counting against the budget; they are dropped at summary level, where the verdict bits and limit tally are still set. Rows arriving outside an open check (`rec->check_open`) or a host scope are dropped, so 3.3/3.4 must open a host scope before host_iter's pre-filter calls `sched_check_host_satisfy` and must call `sched_trace_check_end` on every exit path of the cluster-wide check. 4.1 must render a NULL row name by its index, `limited_res == NULL` as a raw amount, and HOST_ROW at host depth + 1. Capture now calls librtti `vget()`, which every test already links.
|
||||
- 2.2 OPEN for 6.2: if `limit_row()` runs out of memory, `sched_trace_check_rows` returns early, so that row's LIMIT bit and later rows' SHORTFALL bits are lost. The verdict derived from rows then becomes SELECT and the Req 9.3 counts are off. This needs fault injection to test, so fix it in 6.2: keep setting the verdict bits and continue.
|
||||
- 2.3: the outcome capture functions are `sched_trace_capture_dispatch`, `sched_trace_capture_pend` and `sched_trace_rec_reconstructed`, declared in trace.h; 3.x and 5.3 call them. Limit charges are classified by the limit flag or a `limit=` prefix and resolved via glimits (shared) or the worker's hlimits (host, worker looked up by host name). An unresolved charge has `limited_res == NULL`, which is the raw-number fallback. Shared pend entries carry host "" and are treated as host-less. For 4.1: limit totals over omitted hosts have a scope without the host, and a NULL host name yields the scope "h=". Follow-up (low): capture's `key_find`/`vget` on resmgr/workers run without the RTTI read lock that `sched_get_worker` takes. That is safe while the sim runs in lock-step; the 2.4 split may wrap them.
|
||||
- 2.4: the trace sources are now sched_trace.c (record, arena, entries, budget, requirement/plan/check), sched_trace_scan.c (positions, host verdicts/rows, tallies, rollback, commit, search end), sched_trace_capture.c, sched_trace_outcome.c (dispatch, pend, reconstructed) and sched_trace_internal.h (12 shared `sti_` helpers). Every target that compiles one trace .c file needs all four. Later tasks that extend the trace must keep each file under 800 lines and add new files to the core isolation scan.
|
||||
|
|
|
|||
|
|
@ -17,9 +17,10 @@
|
|||
* Only the simulator REPL ever publishes a control block (`trace_ctl`), so
|
||||
* the live scheduler never builds a record (Requirement 11.2).
|
||||
*
|
||||
* The model (src/sched_trace.c) and the capture helpers
|
||||
* (src/sched_trace_capture.c) do no I/O and no logging; test_core_isolation.c
|
||||
* scans both files for stdio and file I/O.
|
||||
* The model (src/sched_trace.c, src/sched_trace_scan.c) and the capture
|
||||
* helpers (src/sched_trace_capture.c, src/sched_trace_outcome.c) do no I/O
|
||||
* and no logging; test_core_isolation.c scans these files and their shared
|
||||
* src/sched_trace_internal.h for stdio and file I/O.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
|
|
@ -697,7 +698,8 @@ extern void sched_trace_finalize_failed(xce_sched_trace_rec_t* rec, int err);
|
|||
extern void sched_trace_search_end(xce_sched_trace_rec_t* rec, int rc);
|
||||
|
||||
/*
|
||||
* TraceCapture (src/sched_trace_capture.c, design.md "TraceCapture"). Each
|
||||
* TraceCapture (src/sched_trace_capture.c and, for the outcomes,
|
||||
* src/sched_trace_outcome.c; design.md "TraceCapture"). Each
|
||||
* reads scheduler state on the thread that owns it, read-only, and copies
|
||||
* what it reports into `rec` (never NULL: the SCHED_TRACE gate).
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -36,7 +36,9 @@ scheduler_srcs = [
|
|||
'src/host_iter.c',
|
||||
'src/alloc_runner.c',
|
||||
'src/sched_trace.c',
|
||||
'src/sched_trace_scan.c',
|
||||
'src/sched_trace_capture.c',
|
||||
'src/sched_trace_outcome.c',
|
||||
'src/plugins/fcfs.c',
|
||||
'src/plugins/builtin_fcfs.c',
|
||||
'src/plugins/seqjob_allocator.c',
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@
|
|||
* level host verdicts are not stored: they are tallied per position, and
|
||||
* the position's end becomes one position-summary entry (5.9).
|
||||
*
|
||||
* This file holds the record, the entry sequence and the requirement, plan
|
||||
* and check entries; positions, hosts, tallies, rollback and the search
|
||||
* end are in sched_trace_scan.c (sched_trace_internal.h, task 2.4).
|
||||
*
|
||||
* No I/O and no logging: test_core_isolation.c scans this file for stdio
|
||||
* and file I/O.
|
||||
*/
|
||||
|
|
@ -33,6 +37,7 @@
|
|||
|
||||
#include "sched/alloc_iter.h"
|
||||
#include "sched/trace.h"
|
||||
#include "sched_trace_internal.h"
|
||||
#include "utils.h"
|
||||
|
||||
/* Smallest chunk the arena allocates; a larger request gets a chunk of its
|
||||
|
|
@ -57,17 +62,8 @@ static unsigned char* chunk_data(sched_trace_chunk_t* c)
|
|||
return ((unsigned char*)c) + chunk_hdr_size();
|
||||
}
|
||||
|
||||
/* Summary level: one limit hit by hosts of the current position. Builder
|
||||
* memory, owned by the record until the position ends. */
|
||||
struct sched_trace_tally_build_s {
|
||||
char* limit;
|
||||
char* scope;
|
||||
int count; /* hosts with a limit-exceeded verdict */
|
||||
unsigned last_host; /* host_seq of the last host that hit it */
|
||||
};
|
||||
|
||||
/* Drops the current position's limit tallies (keeps the array). */
|
||||
static void pos_limits_clear(xce_sched_trace_rec_t* rec)
|
||||
void sti_pos_limits_clear(xce_sched_trace_rec_t* rec)
|
||||
{
|
||||
int k;
|
||||
|
||||
|
|
@ -79,7 +75,7 @@ static void pos_limits_clear(xce_sched_trace_rec_t* rec)
|
|||
}
|
||||
|
||||
/* Drops the current host's held rows (keeps the array). */
|
||||
static void host_rows_clear(xce_sched_trace_rec_t* rec)
|
||||
void sti_host_rows_clear(xce_sched_trace_rec_t* rec)
|
||||
{
|
||||
int k;
|
||||
|
||||
|
|
@ -92,13 +88,6 @@ static void host_rows_clear(xce_sched_trace_rec_t* rec)
|
|||
rec->host_nrows = 0;
|
||||
}
|
||||
|
||||
/* Starts the current position's tallies over. */
|
||||
static void pos_tally_reset(xce_sched_trace_rec_t* rec)
|
||||
{
|
||||
memset(rec->pos_counts, 0, sizeof(rec->pos_counts));
|
||||
pos_limits_clear(rec);
|
||||
}
|
||||
|
||||
bool sched_trace_ctl_wants(const xce_sched_trace_ctl_t* ctl, uint64_t jobid)
|
||||
{
|
||||
int i;
|
||||
|
|
@ -246,9 +235,9 @@ void sched_trace_rec_free(xce_sched_trace_rec_t* rec)
|
|||
return;
|
||||
}
|
||||
|
||||
pos_limits_clear(rec);
|
||||
sti_pos_limits_clear(rec);
|
||||
free(rec->pos_limits);
|
||||
host_rows_clear(rec);
|
||||
sti_host_rows_clear(rec);
|
||||
free(rec->host_rows);
|
||||
|
||||
c = rec->arena;
|
||||
|
|
@ -301,8 +290,8 @@ size_t sched_trace_rec_bytes(const xce_sched_trace_rec_t* rec)
|
|||
* budget (counted as one omitted line), or after an arena failure (the
|
||||
* record is already marked incomplete and appending has stopped).
|
||||
*/
|
||||
static sched_trace_entry_t* entry_add(xce_sched_trace_rec_t* rec,
|
||||
sched_trace_entry_type_t type)
|
||||
sched_trace_entry_t* sti_entry_add(xce_sched_trace_rec_t* rec,
|
||||
sched_trace_entry_type_t type)
|
||||
{
|
||||
sched_trace_entry_t* e;
|
||||
|
||||
|
|
@ -338,30 +327,30 @@ static sched_trace_entry_t* entry_add(xce_sched_trace_rec_t* rec,
|
|||
}
|
||||
|
||||
/* A call that stores no entry still ends a run of released hosts. */
|
||||
static void rollback_run_end(xce_sched_trace_rec_t* rec)
|
||||
void sti_rollback_run_end(xce_sched_trace_rec_t* rec)
|
||||
{
|
||||
rec->rollback_open = false;
|
||||
rec->rollback_e = NULL;
|
||||
}
|
||||
|
||||
/* A scope begin is stored at the current level and opens the next one. */
|
||||
static sched_trace_entry_t* scope_begin(xce_sched_trace_rec_t* rec,
|
||||
sched_trace_entry_type_t type)
|
||||
sched_trace_entry_t* sti_scope_begin(xce_sched_trace_rec_t* rec,
|
||||
sched_trace_entry_type_t type)
|
||||
{
|
||||
sched_trace_entry_t* e = entry_add(rec, type);
|
||||
sched_trace_entry_t* e = sti_entry_add(rec, type);
|
||||
|
||||
rec->depth++;
|
||||
return e;
|
||||
}
|
||||
|
||||
/* A scope end closes the level and is stored at its begin's level. */
|
||||
static sched_trace_entry_t* scope_end(xce_sched_trace_rec_t* rec,
|
||||
sched_trace_entry_type_t type)
|
||||
sched_trace_entry_t* sti_scope_end(xce_sched_trace_rec_t* rec,
|
||||
sched_trace_entry_type_t type)
|
||||
{
|
||||
if (rec->depth > 0) {
|
||||
rec->depth--;
|
||||
}
|
||||
return entry_add(rec, type);
|
||||
return sti_entry_add(rec, type);
|
||||
}
|
||||
|
||||
/* Arena copy of `n` ints; NULL for none. */
|
||||
|
|
@ -398,7 +387,7 @@ void sched_trace_path_push(xce_sched_trace_rec_t* rec,
|
|||
{
|
||||
sched_trace_path_t* p = &rec->path;
|
||||
|
||||
rollback_run_end(rec);
|
||||
sti_rollback_run_end(rec);
|
||||
if (p->depth >= 2) {
|
||||
/* deeper than container > member: not kept, but balanced */
|
||||
rec->path_overflow++;
|
||||
|
|
@ -412,7 +401,7 @@ void sched_trace_path_push(xce_sched_trace_rec_t* rec,
|
|||
|
||||
void sched_trace_path_pop(xce_sched_trace_rec_t* rec)
|
||||
{
|
||||
rollback_run_end(rec);
|
||||
sti_rollback_run_end(rec);
|
||||
if (rec->path_overflow > 0) {
|
||||
rec->path_overflow--;
|
||||
return;
|
||||
|
|
@ -438,7 +427,7 @@ void sched_trace_resreq_begin(xce_sched_trace_rec_t* rec,
|
|||
rec->scope_not_tried = 0;
|
||||
rec->scope_plan_err = false;
|
||||
|
||||
e = scope_begin(rec, SCHED_TRACE_E_RESREQ_BEGIN);
|
||||
e = sti_scope_begin(rec, SCHED_TRACE_E_RESREQ_BEGIN);
|
||||
if (e == NULL) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -486,7 +475,7 @@ void sched_trace_resreq_end(xce_sched_trace_rec_t* rec, int rc,
|
|||
rec->resreq_open--;
|
||||
}
|
||||
|
||||
e = scope_end(rec, SCHED_TRACE_E_RESREQ_END);
|
||||
e = sti_scope_end(rec, SCHED_TRACE_E_RESREQ_END);
|
||||
if (e == NULL) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -497,7 +486,7 @@ void sched_trace_resreq_end(xce_sched_trace_rec_t* rec, int rc,
|
|||
void sched_trace_member_failed(xce_sched_trace_rec_t* rec, int j, int m,
|
||||
int err)
|
||||
{
|
||||
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_MEMBER_FAILED);
|
||||
sched_trace_entry_t* e = sti_entry_add(rec, SCHED_TRACE_E_MEMBER_FAILED);
|
||||
|
||||
if (e == NULL) {
|
||||
return;
|
||||
|
|
@ -509,7 +498,7 @@ void sched_trace_member_failed(xce_sched_trace_rec_t* rec, int j, int m,
|
|||
|
||||
void sched_trace_alt_result(xce_sched_trace_rec_t* rec, int chosen_i, int n)
|
||||
{
|
||||
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_ALT_RESULT);
|
||||
sched_trace_entry_t* e = sti_entry_add(rec, SCHED_TRACE_E_ALT_RESULT);
|
||||
|
||||
if (e == NULL) {
|
||||
return;
|
||||
|
|
@ -520,7 +509,7 @@ void sched_trace_alt_result(xce_sched_trace_rec_t* rec, int chosen_i, int n)
|
|||
|
||||
void sched_trace_alt_skipped(xce_sched_trace_rec_t* rec, int i, int n)
|
||||
{
|
||||
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_ALT_SKIPPED);
|
||||
sched_trace_entry_t* e = sti_entry_add(rec, SCHED_TRACE_E_ALT_SKIPPED);
|
||||
|
||||
if (e == NULL) {
|
||||
return;
|
||||
|
|
@ -534,7 +523,7 @@ void sched_trace_attempt_reasons(xce_sched_trace_rec_t* rec,
|
|||
int ncodes, const char* const* hosts,
|
||||
int nhosts, const char* const* res, int nres)
|
||||
{
|
||||
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_ATTEMPT_REASONS);
|
||||
sched_trace_entry_t* e = sti_entry_add(rec, SCHED_TRACE_E_ATTEMPT_REASONS);
|
||||
|
||||
if (e == NULL) {
|
||||
return;
|
||||
|
|
@ -560,7 +549,7 @@ void sched_trace_attempt_reasons(xce_sched_trace_rec_t* rec,
|
|||
void sched_trace_check_begin(xce_sched_trace_rec_t* rec,
|
||||
const xce_sched_obj_t* obj)
|
||||
{
|
||||
rollback_run_end(rec);
|
||||
sti_rollback_run_end(rec);
|
||||
rec->scope_obj = obj;
|
||||
rec->scope_w = NULL;
|
||||
rec->check_open = true;
|
||||
|
|
@ -574,7 +563,7 @@ void sched_trace_check_end(xce_sched_trace_rec_t* rec, int rc,
|
|||
rec->scope_obj = NULL;
|
||||
rec->check_open = false;
|
||||
|
||||
e = entry_add(rec, SCHED_TRACE_E_CHECK_END);
|
||||
e = sti_entry_add(rec, SCHED_TRACE_E_CHECK_END);
|
||||
if (e == NULL) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -585,7 +574,7 @@ void sched_trace_check_end(xce_sched_trace_rec_t* rec, int rc,
|
|||
void sched_trace_no_plan(xce_sched_trace_rec_t* rec,
|
||||
xce_sched_trace_noplan_t why)
|
||||
{
|
||||
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_NO_PLAN);
|
||||
sched_trace_entry_t* e = sti_entry_add(rec, SCHED_TRACE_E_NO_PLAN);
|
||||
|
||||
if (e == NULL) {
|
||||
return;
|
||||
|
|
@ -614,7 +603,7 @@ void sched_trace_plan_begin(xce_sched_trace_rec_t* rec,
|
|||
rec->cur_plan_no = ++rec->plan_no;
|
||||
rec->scope_tried++;
|
||||
rec->counters.plans_tried++;
|
||||
e = scope_begin(rec, SCHED_TRACE_E_PLAN_BEGIN);
|
||||
e = sti_scope_begin(rec, SCHED_TRACE_E_PLAN_BEGIN);
|
||||
if (e != NULL) {
|
||||
plan_fill(rec, e, plan, rec->cur_plan_no, 0);
|
||||
}
|
||||
|
|
@ -628,7 +617,7 @@ void sched_trace_plan_discarded(xce_sched_trace_rec_t* rec,
|
|||
rec->plan_no++;
|
||||
rec->scope_discarded++;
|
||||
rec->counters.plans_discarded++;
|
||||
e = entry_add(rec, SCHED_TRACE_E_PLAN_DISCARDED);
|
||||
e = sti_entry_add(rec, SCHED_TRACE_E_PLAN_DISCARDED);
|
||||
if (e != NULL) {
|
||||
plan_fill(rec, e, plan, rec->plan_no, 0);
|
||||
}
|
||||
|
|
@ -641,7 +630,7 @@ void sched_trace_plan_not_tried(xce_sched_trace_rec_t* rec,
|
|||
|
||||
rec->plan_no++;
|
||||
rec->scope_not_tried++;
|
||||
e = entry_add(rec, SCHED_TRACE_E_PLAN_NOT_TRIED);
|
||||
e = sti_entry_add(rec, SCHED_TRACE_E_PLAN_NOT_TRIED);
|
||||
if (e != NULL) {
|
||||
plan_fill(rec, e, plan, rec->plan_no, err);
|
||||
}
|
||||
|
|
@ -655,7 +644,7 @@ void sched_trace_plan_end(xce_sched_trace_rec_t* rec, int rc, int failed_pos)
|
|||
rec->scope_plan_err = true;
|
||||
}
|
||||
|
||||
e = scope_end(rec, SCHED_TRACE_E_PLAN_END);
|
||||
e = sti_scope_end(rec, SCHED_TRACE_E_PLAN_END);
|
||||
if (e == NULL) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -663,468 +652,3 @@ void sched_trace_plan_end(xce_sched_trace_rec_t* rec, int rc, int failed_pos)
|
|||
e->u.plan_end.rc = rc;
|
||||
e->u.plan_end.failed_pos = failed_pos;
|
||||
}
|
||||
|
||||
void sched_trace_pos_begin(xce_sched_trace_rec_t* rec, int pos, int slots)
|
||||
{
|
||||
sched_trace_entry_t* e;
|
||||
|
||||
rec->cur_pos = pos;
|
||||
rec->scan_err = 0;
|
||||
rec->scan_err_set = false;
|
||||
pos_tally_reset(rec);
|
||||
|
||||
e = scope_begin(rec, SCHED_TRACE_E_POS_BEGIN);
|
||||
if (e == NULL) {
|
||||
return;
|
||||
}
|
||||
e->u.pos_begin.pos = pos;
|
||||
e->u.pos_begin.slots = slots;
|
||||
}
|
||||
|
||||
void sched_trace_pos_scan_error(xce_sched_trace_rec_t* rec, int err)
|
||||
{
|
||||
rollback_run_end(rec);
|
||||
rec->scan_err = err;
|
||||
rec->scan_err_set = true;
|
||||
}
|
||||
|
||||
/* Fills a POS_SUMMARY entry from the current position's tallies; only
|
||||
* limits some host was rejected for are kept. */
|
||||
static void pos_summary_fill(xce_sched_trace_rec_t* rec, sched_trace_entry_t* e,
|
||||
xce_sched_trace_pos_end_t how, const char* host,
|
||||
int err)
|
||||
{
|
||||
int n = 0;
|
||||
int k;
|
||||
|
||||
e->u.pos_summary.pos = rec->cur_pos;
|
||||
e->u.pos_summary.how = how;
|
||||
e->u.pos_summary.err = err;
|
||||
e->u.pos_summary.host = sched_trace_arena_strdup(rec, host);
|
||||
memcpy(e->u.pos_summary.counts, rec->pos_counts,
|
||||
sizeof(e->u.pos_summary.counts));
|
||||
|
||||
for (k = 0; k < rec->pos_nlimits; k++) {
|
||||
if (rec->pos_limits[k].count > 0) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
if (n == 0) {
|
||||
return;
|
||||
}
|
||||
e->u.pos_summary.limits = (sched_trace_limit_tally_t*)
|
||||
sched_trace_arena_alloc(rec, sizeof(sched_trace_limit_tally_t)
|
||||
* (size_t)n);
|
||||
if (e->u.pos_summary.limits == NULL) {
|
||||
return;
|
||||
}
|
||||
n = 0;
|
||||
for (k = 0; k < rec->pos_nlimits; k++) {
|
||||
const struct sched_trace_tally_build_s* b = &rec->pos_limits[k];
|
||||
sched_trace_limit_tally_t* t;
|
||||
|
||||
if (b->count <= 0) {
|
||||
continue;
|
||||
}
|
||||
t = &e->u.pos_summary.limits[n++];
|
||||
t->limit = sched_trace_arena_strdup(rec, b->limit);
|
||||
t->scope = sched_trace_arena_strdup(rec, b->scope);
|
||||
t->count = b->count;
|
||||
}
|
||||
e->u.pos_summary.nlimits = n;
|
||||
}
|
||||
|
||||
void sched_trace_pos_end(xce_sched_trace_rec_t* rec,
|
||||
xce_sched_trace_pos_end_t how, const char* host)
|
||||
{
|
||||
sched_trace_entry_t* e;
|
||||
int err = 0;
|
||||
|
||||
if (rec->scan_err_set) {
|
||||
/* the host scan stopped: the runner's UNFILLED is a scan error */
|
||||
err = rec->scan_err;
|
||||
if (how == XCE_TRACE_POS_UNFILLED) {
|
||||
how = XCE_TRACE_POS_SCAN_ERROR;
|
||||
}
|
||||
}
|
||||
rec->scan_err = 0;
|
||||
rec->scan_err_set = false;
|
||||
|
||||
if (rec->level == XCE_TRACE_LEVEL_SUMMARY) {
|
||||
/* the position's end carries its host tallies (5.9) */
|
||||
e = scope_end(rec, SCHED_TRACE_E_POS_SUMMARY);
|
||||
if (e != NULL) {
|
||||
pos_summary_fill(rec, e, how, host, err);
|
||||
}
|
||||
pos_tally_reset(rec);
|
||||
return;
|
||||
}
|
||||
|
||||
e = scope_end(rec, SCHED_TRACE_E_POS_END);
|
||||
if (e == NULL) {
|
||||
return;
|
||||
}
|
||||
e->u.pos_end.pos = rec->cur_pos;
|
||||
e->u.pos_end.how = how;
|
||||
e->u.pos_end.err = err;
|
||||
e->u.pos_end.host = sched_trace_arena_strdup(rec, host);
|
||||
}
|
||||
|
||||
void sched_trace_host_begin(xce_sched_trace_rec_t* rec,
|
||||
const xce_sched_obj_t* obj,
|
||||
const xce_sched_worker_t* w)
|
||||
{
|
||||
rollback_run_end(rec);
|
||||
rec->host_seq++;
|
||||
rec->scope_obj = obj;
|
||||
rec->scope_w = w;
|
||||
rec->host_row_verdicts = 0;
|
||||
host_rows_clear(rec); /* a scope that ended without its verdict */
|
||||
}
|
||||
|
||||
/* Counts one host evaluation: decision-wide per verdict kind, and at
|
||||
* summary level per position, with each limit the host hit counted once
|
||||
* when its verdict is limit exceeded (5.9, 9.3). */
|
||||
static void host_count(xce_sched_trace_rec_t* rec, unsigned verdicts)
|
||||
{
|
||||
int k;
|
||||
|
||||
for (k = 0; k < XCE_TRACE_V_NKINDS; k++) {
|
||||
if ((verdicts & (1u << k)) != 0) {
|
||||
rec->counters.host_evals[k]++;
|
||||
rec->pos_counts[k]++;
|
||||
}
|
||||
}
|
||||
if ((verdicts & XCE_TRACE_V_LIMIT) == 0) {
|
||||
return;
|
||||
}
|
||||
for (k = 0; k < rec->pos_nlimits; k++) {
|
||||
if (rec->pos_limits[k].last_host == rec->host_seq) {
|
||||
rec->pos_limits[k].count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Copies a row's strings into the arena (NULL stays NULL). */
|
||||
static void row_to_arena(xce_sched_trace_rec_t* rec, sched_trace_row_t* dst,
|
||||
const sched_trace_row_t* src)
|
||||
{
|
||||
*dst = *src;
|
||||
dst->res = sched_trace_arena_strdup(rec, src->res);
|
||||
dst->limit = sched_trace_arena_strdup(rec, src->limit);
|
||||
dst->scope = sched_trace_arena_strdup(rec, src->scope);
|
||||
dst->limited_res = sched_trace_arena_strdup(rec, src->limited_res);
|
||||
}
|
||||
|
||||
/* Stores the current host's held rows after its verdict line, one level
|
||||
* below it, then drops them. Each counts against the budget. */
|
||||
static void host_rows_flush(xce_sched_trace_rec_t* rec)
|
||||
{
|
||||
int k;
|
||||
|
||||
for (k = 0; k < rec->host_nrows; k++) {
|
||||
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_HOST_ROW);
|
||||
|
||||
if (e == NULL) {
|
||||
continue;
|
||||
}
|
||||
e->depth++;
|
||||
row_to_arena(rec, &e->u.row, &rec->host_rows[k]);
|
||||
}
|
||||
host_rows_clear(rec);
|
||||
}
|
||||
|
||||
/* NULL-preserving heap copy for held rows; sets *failed on failure. */
|
||||
static const char* row_strdup(const char* s, bool* failed)
|
||||
{
|
||||
char* d;
|
||||
|
||||
if (s == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
d = strdup(s);
|
||||
if (d == NULL) {
|
||||
*failed = true;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
void sched_trace_row(xce_sched_trace_rec_t* rec, const sched_trace_row_t* row)
|
||||
{
|
||||
sched_trace_entry_t* e;
|
||||
sched_trace_row_t* h;
|
||||
bool failed = false;
|
||||
|
||||
rollback_run_end(rec);
|
||||
if (row == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (rec->scope_w == NULL) {
|
||||
/* the cluster-wide check: stored now, before its check-end line */
|
||||
e = entry_add(rec, SCHED_TRACE_E_CHECK_ROW);
|
||||
if (e != NULL) {
|
||||
row_to_arena(rec, &e->u.row, row);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* a host scope: no host lines at summary level; at full level the row
|
||||
* waits for the host's verdict line, which it belongs under */
|
||||
if ((rec->level == XCE_TRACE_LEVEL_SUMMARY) || rec->incomplete) {
|
||||
return;
|
||||
}
|
||||
if (rec->host_nrows == rec->host_rows_cap) {
|
||||
int cap = (rec->host_rows_cap > 0) ? rec->host_rows_cap * 2 : 4;
|
||||
void* p = realloc(rec->host_rows, sizeof(*h) * (size_t)cap);
|
||||
|
||||
if (p == NULL) {
|
||||
rec->incomplete = true; /* design "Error Strategy" */
|
||||
return;
|
||||
}
|
||||
rec->host_rows = (sched_trace_row_t*)p;
|
||||
rec->host_rows_cap = cap;
|
||||
}
|
||||
h = &rec->host_rows[rec->host_nrows];
|
||||
*h = *row;
|
||||
h->res = row_strdup(row->res, &failed);
|
||||
h->limit = row_strdup(row->limit, &failed);
|
||||
h->scope = row_strdup(row->scope, &failed);
|
||||
h->limited_res = row_strdup(row->limited_res, &failed);
|
||||
if (failed) {
|
||||
free((void*)h->res);
|
||||
free((void*)h->limit);
|
||||
free((void*)h->scope);
|
||||
free((void*)h->limited_res);
|
||||
rec->incomplete = true;
|
||||
return;
|
||||
}
|
||||
rec->host_nrows++;
|
||||
}
|
||||
|
||||
void sched_trace_host_verdict(xce_sched_trace_rec_t* rec, unsigned verdicts,
|
||||
int err)
|
||||
{
|
||||
sched_trace_entry_t* e;
|
||||
sched_trace_phase_t phase = SCHED_TRACE_PHASE_NONE;
|
||||
const char* name
|
||||
= (rec->scope_w != NULL) ? rec->scope_w->name : NULL;
|
||||
|
||||
if (verdicts == 0) {
|
||||
/* from the rows the per-host check recorded; none means the
|
||||
* check's own selection branch rejected the host */
|
||||
verdicts = rec->host_row_verdicts & (XCE_TRACE_V_SHORTFALL
|
||||
| XCE_TRACE_V_LIMIT);
|
||||
if (verdicts == 0) {
|
||||
verdicts = XCE_TRACE_V_SELECT;
|
||||
}
|
||||
phase = SCHED_TRACE_PHASE_PERHOST;
|
||||
} else if ((verdicts & XCE_TRACE_V_SELECT) != 0) {
|
||||
phase = SCHED_TRACE_PHASE_PREFILTER;
|
||||
} else if ((verdicts & (XCE_TRACE_V_SHORTFALL | XCE_TRACE_V_LIMIT)) != 0) {
|
||||
phase = SCHED_TRACE_PHASE_PERHOST;
|
||||
}
|
||||
|
||||
/* one host evaluation, counted once under each kind it matched (9.3) */
|
||||
host_count(rec, verdicts);
|
||||
|
||||
/* the host scope ends here */
|
||||
rec->scope_obj = NULL;
|
||||
rec->scope_w = NULL;
|
||||
rec->host_row_verdicts = 0;
|
||||
rec->host_seq++; /* later hits belong to no host until the next one */
|
||||
rollback_run_end(rec);
|
||||
|
||||
if (rec->level == XCE_TRACE_LEVEL_SUMMARY) {
|
||||
host_rows_clear(rec);
|
||||
return; /* tallied for the position summary, no host line */
|
||||
}
|
||||
|
||||
e = entry_add(rec, SCHED_TRACE_E_HOST);
|
||||
if (e != NULL) {
|
||||
e->u.host.host = sched_trace_arena_strdup(rec, name);
|
||||
e->u.host.verdicts = verdicts;
|
||||
e->u.host.phase = phase;
|
||||
e->u.host.err = err;
|
||||
}
|
||||
/* the host's unmet rows, under its verdict line (5.4, 5.5) */
|
||||
host_rows_flush(rec);
|
||||
}
|
||||
|
||||
void sched_trace_commit(xce_sched_trace_rec_t* rec)
|
||||
{
|
||||
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_COMMIT);
|
||||
|
||||
if (e == NULL) {
|
||||
return;
|
||||
}
|
||||
e->u.commit.plan_no = rec->cur_plan_no;
|
||||
}
|
||||
|
||||
void sched_trace_finalize_failed(xce_sched_trace_rec_t* rec, int err)
|
||||
{
|
||||
sched_trace_entry_t* e = entry_add(rec, SCHED_TRACE_E_FINALIZE_FAILED);
|
||||
|
||||
if (e == NULL) {
|
||||
return;
|
||||
}
|
||||
e->u.commit.plan_no = rec->cur_plan_no;
|
||||
e->u.commit.err = err;
|
||||
}
|
||||
|
||||
|
||||
static bool tally_str_eq(const char* a, const char* b)
|
||||
{
|
||||
if ((a == NULL) || (b == NULL)) {
|
||||
return a == b;
|
||||
}
|
||||
return strcmp(a, b) == 0;
|
||||
}
|
||||
|
||||
/* NULL-preserving heap copy for the builder's tallies; sets *failed when a
|
||||
* non-NULL string could not be copied. */
|
||||
static char* tally_strdup(const char* s, bool* failed)
|
||||
{
|
||||
char* d;
|
||||
|
||||
if (s == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
d = strdup(s);
|
||||
if (d == NULL) {
|
||||
*failed = true;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
void sched_trace_host_limit_hit(xce_sched_trace_rec_t* rec, const char* limit,
|
||||
const char* scope)
|
||||
{
|
||||
struct sched_trace_tally_build_s* b;
|
||||
bool failed = false;
|
||||
int k;
|
||||
|
||||
rollback_run_end(rec);
|
||||
if (rec->scope_w == NULL) {
|
||||
return; /* not a host scope: the cluster-wide check's rows */
|
||||
}
|
||||
rec->host_row_verdicts |= XCE_TRACE_V_LIMIT;
|
||||
if ((rec->level != XCE_TRACE_LEVEL_SUMMARY) || rec->incomplete) {
|
||||
return; /* full level shows the rows themselves (2.2) */
|
||||
}
|
||||
|
||||
for (k = 0; k < rec->pos_nlimits; k++) {
|
||||
b = &rec->pos_limits[k];
|
||||
if (tally_str_eq(b->limit, limit) && tally_str_eq(b->scope, scope)) {
|
||||
b->last_host = rec->host_seq; /* once per host, at its verdict */
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (rec->pos_nlimits == rec->pos_limits_cap) {
|
||||
int cap = (rec->pos_limits_cap > 0) ? rec->pos_limits_cap * 2 : 4;
|
||||
void* p = realloc(rec->pos_limits, sizeof(*b) * (size_t)cap);
|
||||
|
||||
if (p == NULL) {
|
||||
rec->incomplete = true;
|
||||
return;
|
||||
}
|
||||
rec->pos_limits = (struct sched_trace_tally_build_s*)p;
|
||||
rec->pos_limits_cap = cap;
|
||||
}
|
||||
b = &rec->pos_limits[rec->pos_nlimits];
|
||||
b->limit = tally_strdup(limit, &failed);
|
||||
b->scope = tally_strdup(scope, &failed);
|
||||
b->count = 0;
|
||||
b->last_host = rec->host_seq;
|
||||
if (failed) {
|
||||
free(b->limit);
|
||||
free(b->scope);
|
||||
rec->incomplete = true;
|
||||
return;
|
||||
}
|
||||
rec->pos_nlimits++;
|
||||
}
|
||||
|
||||
/* Appends one host to a ROLLBACK entry, growing its array in the arena. */
|
||||
static void rollback_append(xce_sched_trace_rec_t* rec, sched_trace_entry_t* e,
|
||||
const char* host)
|
||||
{
|
||||
if (e->u.rollback.nhosts == e->u.rollback.cap) {
|
||||
int cap = (e->u.rollback.cap > 0) ? e->u.rollback.cap * 2 : 4;
|
||||
const char** h = (const char**)sched_trace_arena_alloc(
|
||||
rec, sizeof(char*) * (size_t)cap);
|
||||
|
||||
if (h == NULL) {
|
||||
return;
|
||||
}
|
||||
if (e->u.rollback.nhosts > 0) {
|
||||
memcpy(h, e->u.rollback.hosts,
|
||||
sizeof(char*) * (size_t)e->u.rollback.nhosts);
|
||||
}
|
||||
e->u.rollback.hosts = h;
|
||||
e->u.rollback.cap = cap;
|
||||
}
|
||||
e->u.rollback.hosts[e->u.rollback.nhosts++]
|
||||
= sched_trace_arena_strdup(rec, host);
|
||||
}
|
||||
|
||||
void sched_trace_rollback_host(xce_sched_trace_rec_t* rec, const char* host)
|
||||
{
|
||||
sched_trace_entry_t* e;
|
||||
|
||||
if (rec->rollback_open) {
|
||||
/* the same run of released hosts: the same line */
|
||||
if ((rec->rollback_e != NULL) && !rec->incomplete) {
|
||||
rollback_append(rec, rec->rollback_e, host);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
e = entry_add(rec, SCHED_TRACE_E_ROLLBACK);
|
||||
rec->rollback_open = true; /* also when omitted: one line either way */
|
||||
rec->rollback_e = e;
|
||||
if (e != NULL) {
|
||||
rollback_append(rec, e, host);
|
||||
}
|
||||
}
|
||||
|
||||
void sched_trace_search_end(xce_sched_trace_rec_t* rec, int rc)
|
||||
{
|
||||
sched_trace_entry_t* e;
|
||||
sched_trace_search_stmt_t stmt = SCHED_TRACE_SEARCH_NO_STMT;
|
||||
int t = rec->scope_tried;
|
||||
int d = rec->scope_discarded;
|
||||
int x = rec->scope_not_tried;
|
||||
int g = t + d + x;
|
||||
|
||||
if (rc == 0) {
|
||||
if (g == 0) {
|
||||
stmt = SCHED_TRACE_SEARCH_NONE_GENERATED;
|
||||
rec->counters.reqs_none_generated++;
|
||||
} else if (d == g) {
|
||||
stmt = SCHED_TRACE_SEARCH_ALL_DISCARDED;
|
||||
rec->counters.reqs_all_discarded++;
|
||||
} else if ((t > 0) && !rec->scope_plan_err) {
|
||||
stmt = SCHED_TRACE_SEARCH_NONE_PLACED;
|
||||
rec->counters.reqs_none_placed++;
|
||||
}
|
||||
}
|
||||
|
||||
/* the search is over: a later search in this requirement counts anew */
|
||||
rec->scope_tried = 0;
|
||||
rec->scope_discarded = 0;
|
||||
rec->scope_not_tried = 0;
|
||||
rec->scope_plan_err = false;
|
||||
|
||||
e = entry_add(rec, SCHED_TRACE_E_SEARCH_END);
|
||||
if (e == NULL) {
|
||||
return;
|
||||
}
|
||||
e->u.search_end.generated = g;
|
||||
e->u.search_end.tried = t;
|
||||
e->u.search_end.discarded = d;
|
||||
e->u.search_end.not_tried = x;
|
||||
e->u.search_end.rc = rc;
|
||||
e->u.search_end.stmt = stmt;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,18 +22,20 @@
|
|||
* librtti's vget()/key_find(), which every scheduler target links), so
|
||||
* every white-box test that #includes this file links without extra
|
||||
* sources.
|
||||
*
|
||||
* The outcome capture (task 2.3) is in sched_trace_outcome.c; the helpers
|
||||
* it shares with this file are declared in sched_trace_internal.h (task
|
||||
* 2.4).
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "sched/trace.h"
|
||||
#include "sched_trace_internal.h"
|
||||
|
||||
/* ---- limit name and scope instance ------------------------------------ */
|
||||
|
||||
#define VRES_PREFIX "limit="
|
||||
#define VRES_PREFIX_LEN (sizeof(VRES_PREFIX) - 1)
|
||||
|
||||
/*
|
||||
* Splits a limit vres name, "limit=<name>[/u=..][/q=..][/p=..]" (lib
|
||||
* xcelimit.c build_vres_name()), into the limit name and the scope part.
|
||||
|
|
@ -74,8 +76,8 @@ static bool split_vres_name(const char* vname, const char* known,
|
|||
/* Arena copy of a vres scope part, "u=a/q=b" -> "u=a,q=b", with
|
||||
* ",h=<host>" ("h=<host>" when the scope is empty) appended for a non-NULL
|
||||
* host: host-limit vres names do not encode the host. */
|
||||
static char* copy_scope(xce_sched_trace_rec_t* rec, const char* scope,
|
||||
const char* host)
|
||||
char* sti_copy_scope(xce_sched_trace_rec_t* rec, const char* scope,
|
||||
const char* host)
|
||||
{
|
||||
size_t n = strlen(scope);
|
||||
size_t hn = (host != NULL) ? strlen(host) : 0;
|
||||
|
|
@ -103,7 +105,7 @@ static char* copy_scope(xce_sched_trace_rec_t* rec, const char* scope,
|
|||
}
|
||||
|
||||
/* Arena copy of the first `n` bytes of `s`, NUL-terminated. */
|
||||
static char* copy_prefix(xce_sched_trace_rec_t* rec, const char* s, size_t n)
|
||||
char* sti_copy_prefix(xce_sched_trace_rec_t* rec, const char* s, size_t n)
|
||||
{
|
||||
char* out = (char*)sched_trace_arena_alloc(rec, n + 1);
|
||||
|
||||
|
|
@ -148,9 +150,9 @@ static bool limit_ref(xce_sched_trace_rec_t* rec, const xce_vres_t* vres,
|
|||
if (known != NULL) {
|
||||
out->limit = sched_trace_arena_strdup(rec, known);
|
||||
} else {
|
||||
out->limit = copy_prefix(rec, name, name_len);
|
||||
out->limit = sti_copy_prefix(rec, name, name_len);
|
||||
}
|
||||
out->scope = copy_scope(rec, scope, NULL);
|
||||
out->scope = sti_copy_scope(rec, scope, NULL);
|
||||
return (out->limit != NULL) && (out->scope != NULL);
|
||||
}
|
||||
|
||||
|
|
@ -234,7 +236,7 @@ static float row_uncomit_at(const xvec_t* uncomitted, int idx)
|
|||
/* Worker `idx`'s matched host limits, obj->hlimits[idx] (sparse: NULL
|
||||
* when the worker has none). Read directly: zarray_get() takes a
|
||||
* non-const array. */
|
||||
static const xlist_t* worker_limits(const xce_sched_obj_t* obj, int idx)
|
||||
const xlist_t* sti_worker_limits(const xce_sched_obj_t* obj, int idx)
|
||||
{
|
||||
const zarray_t* hl = &obj->hlimits;
|
||||
|
||||
|
|
@ -254,13 +256,13 @@ static const xlist_t* scope_limits(const xce_sched_trace_rec_t* rec)
|
|||
if (rec->scope_w == NULL) {
|
||||
return &rec->scope_obj->glimits;
|
||||
}
|
||||
return worker_limits(rec->scope_obj, rec->scope_w->idx);
|
||||
return sti_worker_limits(rec->scope_obj, rec->scope_w->idx);
|
||||
}
|
||||
|
||||
/* The matched limit in `list` whose vres resource is `r` or is named
|
||||
* `name`, or NULL. Read directly: xlist_get() takes a non-const list. */
|
||||
static const xce_vres_t* list_vres(const xlist_t* list,
|
||||
const xce_resource_t* r, const char* name)
|
||||
const xce_vres_t* sti_list_vres(const xlist_t* list, const xce_resource_t* r,
|
||||
const char* name)
|
||||
{
|
||||
int i;
|
||||
|
||||
|
|
@ -282,21 +284,12 @@ static const xce_vres_t* list_vres(const xlist_t* list,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/* A limit resource named `resname`, resolved: the limit name (not
|
||||
* NUL-terminated), the vres-form scope part and the limited resource. */
|
||||
typedef struct {
|
||||
const char* name;
|
||||
size_t name_len;
|
||||
const char* scope;
|
||||
const char* limited_res; /* NULL: no matched limit names it */
|
||||
} charge_ref_t;
|
||||
|
||||
/* Resolves `resname` through its matched limit `v` (NULL: none, so the
|
||||
* limit name and scope are parsed from the name, and the limited resource
|
||||
* is unknown). A name without the "limit=" prefix names the limit
|
||||
* itself. */
|
||||
static void charge_resolve(const xce_vres_t* v, const char* resname,
|
||||
charge_ref_t* c)
|
||||
void sti_charge_resolve(const xce_vres_t* v, const char* resname,
|
||||
charge_ref_t* c)
|
||||
{
|
||||
const char* known = ((v != NULL) && (v->limit != NULL)) ? v->limit->name
|
||||
: NULL;
|
||||
|
|
@ -328,7 +321,8 @@ static bool limit_row(const xce_sched_trace_rec_t* rec, const xce_resource_t* r,
|
|||
char* p;
|
||||
size_t k;
|
||||
|
||||
charge_resolve(list_vres(scope_limits(rec), r, r->name), r->name, &c);
|
||||
sti_charge_resolve(sti_list_vres(scope_limits(rec), r, r->name), r->name,
|
||||
&c);
|
||||
if (rec->scope_w != NULL) {
|
||||
/* host-limit vres names do not encode the host */
|
||||
host = (rec->scope_w->name != NULL) ? rec->scope_w->name : "";
|
||||
|
|
@ -430,8 +424,7 @@ void sched_trace_check_rows(xce_sched_trace_rec_t* rec, xce_sched_core_t* core,
|
|||
|
||||
/* ---- attempt failure reasons (3.5) ------------------------------------ */
|
||||
|
||||
static void count_code(sched_trace_reason_count_t* codes, int* ncodes,
|
||||
int code)
|
||||
void sti_count_code(sched_trace_reason_count_t* codes, int* ncodes, int code)
|
||||
{
|
||||
int i;
|
||||
|
||||
|
|
@ -510,7 +503,7 @@ void sched_trace_capture_attempt_reasons(xce_sched_trace_rec_t* rec,
|
|||
}
|
||||
|
||||
if (r->main_reason != 0) {
|
||||
count_code(codes, &ncodes, r->main_reason);
|
||||
sti_count_code(codes, &ncodes, r->main_reason);
|
||||
}
|
||||
for (i = 0; i < nh; i++) {
|
||||
const xce_host_reason_t* h = r->host_based_reasons[i];
|
||||
|
|
@ -518,7 +511,7 @@ void sched_trace_capture_attempt_reasons(xce_sched_trace_rec_t* rec,
|
|||
if (h == NULL) {
|
||||
continue;
|
||||
}
|
||||
count_code(codes, &ncodes, h->reason);
|
||||
sti_count_code(codes, &ncodes, h->reason);
|
||||
add_distinct(hosts, &nhosts, h->host);
|
||||
}
|
||||
for (i = 0; i < nr; i++) {
|
||||
|
|
@ -527,7 +520,7 @@ void sched_trace_capture_attempt_reasons(xce_sched_trace_rec_t* rec,
|
|||
if (x == NULL) {
|
||||
continue;
|
||||
}
|
||||
count_code(codes, &ncodes, x->reason);
|
||||
sti_count_code(codes, &ncodes, x->reason);
|
||||
add_distinct(hosts, &nhosts, x->host);
|
||||
add_distinct(res, &nres, x->resource);
|
||||
}
|
||||
|
|
@ -538,528 +531,3 @@ void sched_trace_capture_attempt_reasons(xce_sched_trace_rec_t* rec,
|
|||
free((void*)hosts);
|
||||
free((void*)res);
|
||||
}
|
||||
|
||||
/* ---- dispatch outcome (6.1-6.3, 9.4) ---------------------------------- */
|
||||
|
||||
/* True iff the allocated resource `resname` is a limit charge: its
|
||||
* registered resource has XCE_RESF_LIMIT, or, when the name is not
|
||||
* registered in this replay, it has the "limit=" prefix. */
|
||||
static bool is_limit_charge(xce_sched_core_t* core, const char* resname)
|
||||
{
|
||||
xce_resource_t* r = NULL;
|
||||
|
||||
if ((core != NULL) && (core->resmgr != NULL)
|
||||
&& (key_find(core->resmgr, (void*)resname, (void**)&r) >= 0)
|
||||
&& (r != NULL)) {
|
||||
return (r->flags & XCE_RESF_LIMIT) != 0;
|
||||
}
|
||||
return strncmp(resname, VRES_PREFIX, VRES_PREFIX_LEN) == 0;
|
||||
}
|
||||
|
||||
/* The host limits matched to the job on the worker named `host`, or NULL
|
||||
* (no schedobj, unknown worker, none matched). */
|
||||
static const xlist_t* host_limits(xce_sched_core_t* core,
|
||||
const xce_sched_obj_t* obj, const char* host)
|
||||
{
|
||||
xce_sched_worker_t* w = NULL;
|
||||
|
||||
if ((obj == NULL) || (core == NULL) || (core->workers == NULL)
|
||||
|| (host == NULL)
|
||||
|| (key_find(core->workers, (void*)host, (void**)&w) < 0)
|
||||
|| (w == NULL)) {
|
||||
return NULL;
|
||||
}
|
||||
return worker_limits(obj, w->idx);
|
||||
}
|
||||
|
||||
/* Stores allocated amount `a` as a limit charge, resolved through `limits`;
|
||||
* a host charge (`host` non-NULL) gets the host appended to its scope. */
|
||||
static bool charge_row(xce_sched_trace_rec_t* rec, const xlist_t* limits,
|
||||
const xce_alloc_t* a, const char* host,
|
||||
sched_trace_alloc_row_t* out)
|
||||
{
|
||||
charge_ref_t c;
|
||||
|
||||
charge_resolve(list_vres(limits, NULL, a->resname), a->resname, &c);
|
||||
out->res = sched_trace_arena_strdup(rec, a->resname);
|
||||
out->value = a->value;
|
||||
out->limit = copy_prefix(rec, c.name, c.name_len);
|
||||
out->scope = copy_scope(rec, c.scope, host);
|
||||
out->limited_res = sched_trace_arena_strdup(rec, c.limited_res);
|
||||
return (out->res != NULL) && (out->limit != NULL) && (out->scope != NULL)
|
||||
&& ((c.limited_res == NULL) || (out->limited_res != NULL));
|
||||
}
|
||||
|
||||
/* One allocation list (a host's, or the shared one) as resource rows and
|
||||
* limit charges, each in allocation order. False on an arena failure. */
|
||||
static bool alloc_rows(xce_sched_trace_rec_t* rec, xce_sched_core_t* core,
|
||||
const xlist_t* limits, int n, xce_alloc_t** allocs,
|
||||
const char* host, int* nres,
|
||||
sched_trace_alloc_row_t** res, int* ncharges,
|
||||
sched_trace_alloc_row_t** charges)
|
||||
{
|
||||
size_t sz = sizeof(**res) * (size_t)n;
|
||||
int i;
|
||||
|
||||
if ((n <= 0) || (allocs == NULL)) {
|
||||
return true;
|
||||
}
|
||||
*res = (sched_trace_alloc_row_t*)sched_trace_arena_alloc(rec, sz);
|
||||
*charges = (sched_trace_alloc_row_t*)sched_trace_arena_alloc(rec, sz);
|
||||
if ((*res == NULL) || (*charges == NULL)) {
|
||||
return false;
|
||||
}
|
||||
for (i = 0; i < n; i++) {
|
||||
const xce_alloc_t* a = allocs[i];
|
||||
sched_trace_alloc_row_t* row;
|
||||
|
||||
if ((a == NULL) || (a->resname == NULL)) {
|
||||
continue;
|
||||
}
|
||||
if (is_limit_charge(core, a->resname)) {
|
||||
if (!charge_row(rec, limits, a, host, &(*charges)[(*ncharges)++])) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
row = &(*res)[(*nres)++];
|
||||
row->res = sched_trace_arena_strdup(rec, a->resname);
|
||||
row->value = a->value;
|
||||
if (row->res == NULL) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* A total over the omitted hosts, keyed by resource name or, for a charge,
|
||||
* by limit name and scope. Scratch memory; the strings are the sources'. */
|
||||
typedef struct {
|
||||
const char* res; /* resource total; NULL: a limit total */
|
||||
charge_ref_t c; /* limit total */
|
||||
double value;
|
||||
} agg_t;
|
||||
|
||||
static void agg_add(agg_t* agg, int* n, const char* res,
|
||||
const charge_ref_t* c, double value)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < *n; i++) {
|
||||
if ((res != NULL)
|
||||
? (strcmp(agg[i].res, res) == 0)
|
||||
: ((agg[i].c.name_len == c->name_len)
|
||||
&& (strncmp(agg[i].c.name, c->name, c->name_len) == 0)
|
||||
&& (strcmp(agg[i].c.scope, c->scope) == 0))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == *n) {
|
||||
memset(&agg[i], 0, sizeof(agg[i]));
|
||||
agg[i].res = res;
|
||||
if (c != NULL) {
|
||||
agg[i].c = *c;
|
||||
}
|
||||
(*n)++;
|
||||
}
|
||||
agg[i].value += value;
|
||||
if ((c != NULL) && (agg[i].c.limited_res == NULL)) {
|
||||
agg[i].c.limited_res = c->limited_res;
|
||||
}
|
||||
}
|
||||
|
||||
/* Copies `n` totals into the arena as rows (a charge's scope has no
|
||||
* host part: it totals hosts). */
|
||||
static bool agg_store(xce_sched_trace_rec_t* rec, const agg_t* agg, int n,
|
||||
int* nout, sched_trace_alloc_row_t** out)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (n == 0) {
|
||||
return true;
|
||||
}
|
||||
*out = (sched_trace_alloc_row_t*)sched_trace_arena_alloc(
|
||||
rec, sizeof(**out) * (size_t)n);
|
||||
if (*out == NULL) {
|
||||
return false;
|
||||
}
|
||||
for (i = 0; i < n; i++) {
|
||||
sched_trace_alloc_row_t* row = &(*out)[i];
|
||||
|
||||
row->value = agg[i].value;
|
||||
if (agg[i].res != NULL) {
|
||||
row->res = sched_trace_arena_strdup(rec, agg[i].res);
|
||||
if (row->res == NULL) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
row->limit = copy_prefix(rec, agg[i].c.name, agg[i].c.name_len);
|
||||
row->scope = copy_scope(rec, agg[i].c.scope, NULL);
|
||||
row->limited_res = sched_trace_arena_strdup(rec, agg[i].c.limited_res);
|
||||
if ((row->limit == NULL) || (row->scope == NULL)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*nout = n;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Totals of the hosts from index `from` on (9.4): per resource without the
|
||||
* limit charges, and per limit. False on an allocation failure. */
|
||||
static bool aggregate_hosts(xce_sched_trace_rec_t* rec, xce_sched_core_t* core,
|
||||
const xce_sched_obj_t* obj,
|
||||
const xce_job_alloc_t* alloc, int from)
|
||||
{
|
||||
sched_trace_dispatch_out_t* d = &rec->dispatch;
|
||||
size_t total = 0;
|
||||
agg_t* res;
|
||||
agg_t* chg;
|
||||
int nres = 0;
|
||||
int nchg = 0;
|
||||
bool ok;
|
||||
int i;
|
||||
int k;
|
||||
|
||||
for (i = from; i < alloc->nhosts; i++) {
|
||||
if ((alloc->allocs[i] != NULL) && (alloc->allocs[i]->allocs != NULL)
|
||||
&& (alloc->allocs[i]->nallocs > 0)) {
|
||||
total += (size_t)alloc->allocs[i]->nallocs;
|
||||
}
|
||||
}
|
||||
res = (agg_t*)malloc(sizeof(*res) * (total + 1));
|
||||
chg = (agg_t*)malloc(sizeof(*chg) * (total + 1));
|
||||
ok = (res != NULL) && (chg != NULL);
|
||||
|
||||
for (i = from; ok && (i < alloc->nhosts); i++) {
|
||||
const xce_host_alloc_t* h = alloc->allocs[i];
|
||||
const xlist_t* limits;
|
||||
|
||||
if ((h == NULL) || (h->allocs == NULL)) {
|
||||
continue;
|
||||
}
|
||||
limits = host_limits(core, obj, h->host);
|
||||
for (k = 0; k < h->nallocs; k++) {
|
||||
const xce_alloc_t* a = h->allocs[k];
|
||||
charge_ref_t c;
|
||||
|
||||
if ((a == NULL) || (a->resname == NULL)) {
|
||||
continue;
|
||||
}
|
||||
if (!is_limit_charge(core, a->resname)) {
|
||||
agg_add(res, &nres, a->resname, NULL, a->value);
|
||||
continue;
|
||||
}
|
||||
charge_resolve(list_vres(limits, NULL, a->resname), a->resname,
|
||||
&c);
|
||||
agg_add(chg, &nchg, NULL, &c, a->value);
|
||||
}
|
||||
}
|
||||
ok = ok && agg_store(rec, res, nres, &d->nagg_res, &d->agg_res)
|
||||
&& agg_store(rec, chg, nchg, &d->nagg_charges, &d->agg_charges);
|
||||
free(res);
|
||||
free(chg);
|
||||
return ok;
|
||||
}
|
||||
|
||||
void sched_trace_capture_dispatch(xce_sched_trace_rec_t* rec,
|
||||
xce_sched_core_t* core,
|
||||
const xce_sched_obj_t* obj,
|
||||
const xce_job_alloc_t* alloc)
|
||||
{
|
||||
sched_trace_dispatch_out_t* d = &rec->dispatch;
|
||||
int from = -1;
|
||||
int nh;
|
||||
int i;
|
||||
|
||||
memset(d, 0, sizeof(*d));
|
||||
rec->outcome = SCHED_TRACE_OUTCOME_DISPATCH;
|
||||
if (alloc == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!alloc_rows(rec, core, (obj != NULL) ? &obj->glimits : NULL,
|
||||
alloc->nres_shared, alloc->allocs_shared, NULL,
|
||||
&d->nshared, &d->shared, &d->nshared_charges,
|
||||
&d->shared_charges)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
nh = (alloc->allocs != NULL) ? alloc->nhosts : 0;
|
||||
if (nh > 0) {
|
||||
int keep = (nh < XCE_TRACE_OUTCOME_HOSTS) ? nh : XCE_TRACE_OUTCOME_HOSTS;
|
||||
|
||||
d->hosts = (sched_trace_host_out_t*)sched_trace_arena_alloc(
|
||||
rec, sizeof(*d->hosts) * (size_t)keep);
|
||||
if (d->hosts == NULL) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < nh; i++) {
|
||||
const xce_host_alloc_t* h = alloc->allocs[i];
|
||||
sched_trace_host_out_t* ho;
|
||||
|
||||
if (h == NULL) {
|
||||
continue;
|
||||
}
|
||||
if (d->nhosts == XCE_TRACE_OUTCOME_HOSTS) {
|
||||
if (from < 0) {
|
||||
from = i;
|
||||
}
|
||||
d->omitted_hosts++;
|
||||
continue;
|
||||
}
|
||||
ho = &d->hosts[d->nhosts++];
|
||||
ho->host = sched_trace_arena_strdup(rec, h->host);
|
||||
if (((h->host != NULL) && (ho->host == NULL))
|
||||
|| !alloc_rows(rec, core, host_limits(core, obj, h->host),
|
||||
h->nallocs, h->allocs,
|
||||
(h->host != NULL) ? h->host : "", &ho->nres,
|
||||
&ho->res, &ho->ncharges, &ho->charges)) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
if ((from >= 0) && !aggregate_hosts(rec, core, obj, alloc, from)) {
|
||||
goto fail;
|
||||
}
|
||||
return;
|
||||
|
||||
fail:
|
||||
rec->incomplete = true; /* design "Error Strategy" */
|
||||
}
|
||||
|
||||
xce_sched_trace_rec_t*
|
||||
sched_trace_rec_reconstructed(const xce_sched_trace_ctl_t* ctl,
|
||||
xce_sched_core_t* core, uint64_t jobid,
|
||||
const xce_sched_obj_t* obj,
|
||||
const xce_job_alloc_t* allocs)
|
||||
{
|
||||
xce_sched_trace_rec_t* rec;
|
||||
|
||||
rec = sched_trace_rec_new(ctl, jobid, XCE_TRACE_REC_RECONSTRUCTED);
|
||||
if (rec != NULL) {
|
||||
sched_trace_capture_dispatch(rec, core, obj, allocs);
|
||||
}
|
||||
return rec;
|
||||
}
|
||||
|
||||
/* ---- pend outcome (6.4, 9.4) ------------------------------------------ */
|
||||
|
||||
/* A set of host names, open addressing over a power-of-two table. Scratch
|
||||
* memory; the names are the source's. */
|
||||
typedef struct {
|
||||
const char** slot;
|
||||
size_t mask;
|
||||
int n;
|
||||
} host_set_t;
|
||||
|
||||
static bool host_set_init(host_set_t* s, int nmax)
|
||||
{
|
||||
size_t cap = 16;
|
||||
|
||||
while (cap < (size_t)nmax * 2 + 2) {
|
||||
cap <<= 1;
|
||||
}
|
||||
s->slot = (const char**)calloc(cap, sizeof(*s->slot));
|
||||
s->mask = cap - 1;
|
||||
s->n = 0;
|
||||
return s->slot != NULL;
|
||||
}
|
||||
|
||||
/* True iff `h` is in the set; when absent and `insert`, adds it. */
|
||||
static bool host_set_has(host_set_t* s, const char* h, bool insert)
|
||||
{
|
||||
size_t i = 2166136261u; /* FNV-1a */
|
||||
const unsigned char* p;
|
||||
|
||||
for (p = (const unsigned char*)h; *p != '\0'; p++) {
|
||||
i = (i ^ *p) * 16777619u;
|
||||
}
|
||||
for (i &= s->mask; s->slot[i] != NULL; i = (i + 1) & s->mask) {
|
||||
if (strcmp(s->slot[i], h) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (insert) {
|
||||
s->slot[i] = h;
|
||||
s->n++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
host_set_t kept; /* the first 50 distinct hosts */
|
||||
host_set_t omitted; /* the hosts after them */
|
||||
sched_trace_reason_count_t* codes; /* omitted entries per code */
|
||||
int ncodes;
|
||||
} pend_build_t;
|
||||
|
||||
/* True iff an entry naming `host` is kept: it names no host, or one of the
|
||||
* first 50 distinct hosts. Otherwise it is counted under its code. */
|
||||
static bool pend_keep(pend_build_t* b, const char* host, int code)
|
||||
{
|
||||
if ((host == NULL) || (host[0] == '\0')
|
||||
|| host_set_has(&b->kept, host, false)) {
|
||||
return true;
|
||||
}
|
||||
if (b->kept.n < XCE_TRACE_OUTCOME_HOSTS) {
|
||||
(void)host_set_has(&b->kept, host, true);
|
||||
return true;
|
||||
}
|
||||
(void)host_set_has(&b->omitted, host, true);
|
||||
count_code(b->codes, &b->ncodes, code);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Arena copies of the kept host entries (NULL entries stay NULL). */
|
||||
static bool copy_host_reasons(xce_sched_trace_rec_t* rec,
|
||||
const xce_job_preason_t* r, const bool* keep,
|
||||
int nh, xce_job_preason_t* c)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nh; i++) {
|
||||
c->nhost_reasons += keep[i] ? 1 : 0;
|
||||
}
|
||||
if (c->nhost_reasons == 0) {
|
||||
return true;
|
||||
}
|
||||
c->host_based_reasons = (xce_host_reason_t**)sched_trace_arena_alloc(
|
||||
rec, sizeof(*c->host_based_reasons) * (size_t)c->nhost_reasons);
|
||||
if (c->host_based_reasons == NULL) {
|
||||
return false;
|
||||
}
|
||||
c->nhost_reasons = 0;
|
||||
for (i = 0; i < nh; i++) {
|
||||
const xce_host_reason_t* src = r->host_based_reasons[i];
|
||||
xce_host_reason_t* dst = NULL;
|
||||
|
||||
if (!keep[i]) {
|
||||
continue;
|
||||
}
|
||||
if (src != NULL) {
|
||||
dst = (xce_host_reason_t*)sched_trace_arena_alloc(rec, sizeof(*dst));
|
||||
if (dst == NULL) {
|
||||
return false;
|
||||
}
|
||||
dst->reason = src->reason;
|
||||
dst->host = sched_trace_arena_strdup(rec, src->host);
|
||||
if ((src->host != NULL) && (dst->host == NULL)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
c->host_based_reasons[c->nhost_reasons++] = dst;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Arena copies of the kept resource entries (NULL entries stay NULL). */
|
||||
static bool copy_res_reasons(xce_sched_trace_rec_t* rec,
|
||||
const xce_job_preason_t* r, const bool* keep,
|
||||
int nr, xce_job_preason_t* c)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nr; i++) {
|
||||
c->nres_reasons += keep[i] ? 1 : 0;
|
||||
}
|
||||
if (c->nres_reasons == 0) {
|
||||
return true;
|
||||
}
|
||||
c->res_based_reasons = (xce_res_reason_t**)sched_trace_arena_alloc(
|
||||
rec, sizeof(*c->res_based_reasons) * (size_t)c->nres_reasons);
|
||||
if (c->res_based_reasons == NULL) {
|
||||
return false;
|
||||
}
|
||||
c->nres_reasons = 0;
|
||||
for (i = 0; i < nr; i++) {
|
||||
const xce_res_reason_t* src = r->res_based_reasons[i];
|
||||
xce_res_reason_t* dst = NULL;
|
||||
|
||||
if (!keep[i]) {
|
||||
continue;
|
||||
}
|
||||
if (src != NULL) {
|
||||
dst = (xce_res_reason_t*)sched_trace_arena_alloc(rec, sizeof(*dst));
|
||||
if (dst == NULL) {
|
||||
return false;
|
||||
}
|
||||
dst->reason = src->reason;
|
||||
dst->host = sched_trace_arena_strdup(rec, src->host);
|
||||
dst->resource = sched_trace_arena_strdup(rec, src->resource);
|
||||
if (((src->host != NULL) && (dst->host == NULL))
|
||||
|| ((src->resource != NULL) && (dst->resource == NULL))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
c->res_based_reasons[c->nres_reasons++] = dst;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void sched_trace_capture_pend(xce_sched_trace_rec_t* rec,
|
||||
const xce_pendreason_t* reason)
|
||||
{
|
||||
sched_trace_pend_out_t* p = &rec->pend;
|
||||
const xce_job_preason_t* r = (reason != NULL) ? reason->reasons : NULL;
|
||||
pend_build_t b;
|
||||
bool* keep;
|
||||
int nh;
|
||||
int nr;
|
||||
int i;
|
||||
bool ok;
|
||||
|
||||
memset(p, 0, sizeof(*p));
|
||||
rec->outcome = SCHED_TRACE_OUTCOME_PEND;
|
||||
if (r == NULL) {
|
||||
return;
|
||||
}
|
||||
nh = ((r->host_based_reasons != NULL) && (r->nhost_reasons > 0))
|
||||
? r->nhost_reasons : 0;
|
||||
nr = ((r->res_based_reasons != NULL) && (r->nres_reasons > 0))
|
||||
? r->nres_reasons : 0;
|
||||
|
||||
/* which entries are kept, in one pass over the source order */
|
||||
memset(&b, 0, sizeof(b));
|
||||
keep = (bool*)malloc(sizeof(*keep) * (size_t)(nh + nr + 1));
|
||||
b.codes = (sched_trace_reason_count_t*)malloc(sizeof(*b.codes)
|
||||
* (size_t)(nh + nr + 1));
|
||||
ok = (keep != NULL) && (b.codes != NULL)
|
||||
&& host_set_init(&b.kept, XCE_TRACE_OUTCOME_HOSTS)
|
||||
&& host_set_init(&b.omitted, nh + nr);
|
||||
for (i = 0; ok && (i < nh); i++) {
|
||||
const xce_host_reason_t* e = r->host_based_reasons[i];
|
||||
|
||||
keep[i] = (e == NULL) || pend_keep(&b, e->host, e->reason);
|
||||
}
|
||||
for (i = 0; ok && (i < nr); i++) {
|
||||
const xce_res_reason_t* e = r->res_based_reasons[i];
|
||||
|
||||
keep[nh + i] = (e == NULL) || pend_keep(&b, e->host, e->reason);
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
p->reasons = (xce_job_preason_t*)sched_trace_arena_alloc(
|
||||
rec, sizeof(*p->reasons));
|
||||
ok = (p->reasons != NULL);
|
||||
}
|
||||
if (ok) {
|
||||
p->reasons->main_reason = r->main_reason;
|
||||
ok = copy_host_reasons(rec, r, keep, nh, p->reasons)
|
||||
&& copy_res_reasons(rec, r, keep + nh, nr, p->reasons);
|
||||
}
|
||||
if (ok && (b.ncodes > 0)) {
|
||||
p->omitted_codes = (sched_trace_reason_count_t*)sched_trace_arena_memdup(
|
||||
rec, b.codes, sizeof(*b.codes) * (size_t)b.ncodes);
|
||||
ok = (p->omitted_codes != NULL);
|
||||
p->ncodes = ok ? b.ncodes : 0;
|
||||
}
|
||||
p->omitted_hosts = b.omitted.n;
|
||||
if (!ok) {
|
||||
rec->incomplete = true; /* design "Error Strategy" */
|
||||
}
|
||||
free(keep);
|
||||
free(b.codes);
|
||||
free((void*)b.kept.slot);
|
||||
free((void*)b.omitted.slot);
|
||||
}
|
||||
|
|
|
|||
83
src/scheduler/src/sched_trace_internal.h
Normal file
83
src/scheduler/src/sched_trace_internal.h
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
#if !defined(_SCHED_TRACE_INTERNAL_H_)
|
||||
|
||||
#define _SCHED_TRACE_INTERNAL_H_ 1
|
||||
|
||||
/*
|
||||
* sched_trace_internal.h - declarations shared by the decision trace
|
||||
* sources (scheduler-sim-decision-trace spec, task 2.4). NOT installed and
|
||||
* NOT included outside src/scheduler/src/sched_trace*.c; the public
|
||||
* interface stays include/sched/trace.h.
|
||||
*
|
||||
* The sources are split to keep each under the scheduler's 800-line cap:
|
||||
*
|
||||
* - sched_trace.c: the record (arena, header, lifecycle), the entry
|
||||
* sequence and line budget, and the requirement, plan and check
|
||||
* entries (TraceModel).
|
||||
* - sched_trace_scan.c: positions, host verdicts and rows, the
|
||||
* summary-level tallies, rollback, commit and the search-end counters
|
||||
* (TraceModel).
|
||||
* - sched_trace_capture.c: limit names and scopes, selection,
|
||||
* cluster-wide limits, check rows and attempt reasons (TraceCapture).
|
||||
* - sched_trace_outcome.c: the dispatch and pend outcomes and the
|
||||
* reconstructed-dispatch record (TraceCapture).
|
||||
*
|
||||
* The helpers below were file statics before the split. They have
|
||||
* external linkage now, so they carry the sti_ prefix; nothing outside
|
||||
* the trace sources calls them. The same no-I/O rule applies to this
|
||||
* header (test_core_isolation.c).
|
||||
*/
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "sched/trace.h"
|
||||
|
||||
/* ---- model (sched_trace.c), used by sched_trace_scan.c ---------------- */
|
||||
|
||||
/* Summary level: one limit hit by hosts of the current position. Builder
|
||||
* memory, owned by the record until the position ends. */
|
||||
struct sched_trace_tally_build_s {
|
||||
char* limit;
|
||||
char* scope;
|
||||
int count; /* hosts with a limit-exceeded verdict */
|
||||
unsigned last_host; /* host_seq of the last host that hit it */
|
||||
};
|
||||
|
||||
extern void sti_pos_limits_clear(xce_sched_trace_rec_t* rec);
|
||||
extern void sti_host_rows_clear(xce_sched_trace_rec_t* rec);
|
||||
extern sched_trace_entry_t* sti_entry_add(xce_sched_trace_rec_t* rec,
|
||||
sched_trace_entry_type_t type);
|
||||
extern void sti_rollback_run_end(xce_sched_trace_rec_t* rec);
|
||||
extern sched_trace_entry_t* sti_scope_begin(xce_sched_trace_rec_t* rec,
|
||||
sched_trace_entry_type_t type);
|
||||
extern sched_trace_entry_t* sti_scope_end(xce_sched_trace_rec_t* rec,
|
||||
sched_trace_entry_type_t type);
|
||||
|
||||
/* ---- capture (sched_trace_capture.c), used by sched_trace_outcome.c --- */
|
||||
|
||||
#define VRES_PREFIX "limit="
|
||||
#define VRES_PREFIX_LEN (sizeof(VRES_PREFIX) - 1)
|
||||
|
||||
/* A limit resource named `resname`, resolved: the limit name (not
|
||||
* NUL-terminated), the vres-form scope part and the limited resource. */
|
||||
typedef struct {
|
||||
const char* name;
|
||||
size_t name_len;
|
||||
const char* scope;
|
||||
const char* limited_res; /* NULL: no matched limit names it */
|
||||
} charge_ref_t;
|
||||
|
||||
extern char* sti_copy_scope(xce_sched_trace_rec_t* rec, const char* scope,
|
||||
const char* host);
|
||||
extern char* sti_copy_prefix(xce_sched_trace_rec_t* rec, const char* s,
|
||||
size_t n);
|
||||
extern const xlist_t* sti_worker_limits(const xce_sched_obj_t* obj, int idx);
|
||||
extern const xce_vres_t* sti_list_vres(const xlist_t* list,
|
||||
const xce_resource_t* r,
|
||||
const char* name);
|
||||
extern void sti_charge_resolve(const xce_vres_t* v, const char* resname,
|
||||
charge_ref_t* c);
|
||||
extern void sti_count_code(sched_trace_reason_count_t* codes, int* ncodes,
|
||||
int code);
|
||||
|
||||
#endif /* _SCHED_TRACE_INTERNAL_H_ */
|
||||
548
src/scheduler/src/sched_trace_outcome.c
Normal file
548
src/scheduler/src/sched_trace_outcome.c
Normal file
|
|
@ -0,0 +1,548 @@
|
|||
/*
|
||||
* sched_trace_outcome.c - allocator-side trace capture, part 2
|
||||
* (scheduler-sim-decision-trace spec, design.md "TraceCapture").
|
||||
*
|
||||
* The decision's outcome: the dispatch (6.1-6.3) with its per-host
|
||||
* resource rows and limit charges, totals over the hosts past the first
|
||||
* XCE_TRACE_OUTCOME_HOSTS (9.4), the pend reasons (6.4, 9.4), and the
|
||||
* reconstructed-dispatch record (2.5). Split from sched_trace_capture.c
|
||||
* (task 2.4), whose limit-resolution helpers it shares through
|
||||
* sched_trace_internal.h.
|
||||
*
|
||||
* Same rules as sched_trace_capture.c: inputs are read-only, struct fields
|
||||
* and librtti's key_find() only, no scheduler API that mutates state
|
||||
* (Requirement 10), and no I/O and no logging (test_core_isolation.c scans
|
||||
* this file).
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "core.h"
|
||||
#include "sched/trace.h"
|
||||
#include "sched_trace_internal.h"
|
||||
|
||||
/* ---- dispatch outcome (6.1-6.3, 9.4) ---------------------------------- */
|
||||
|
||||
/* True iff the allocated resource `resname` is a limit charge: its
|
||||
* registered resource has XCE_RESF_LIMIT, or, when the name is not
|
||||
* registered in this replay, it has the "limit=" prefix. */
|
||||
static bool is_limit_charge(xce_sched_core_t* core, const char* resname)
|
||||
{
|
||||
xce_resource_t* r = NULL;
|
||||
|
||||
if ((core != NULL) && (core->resmgr != NULL)
|
||||
&& (key_find(core->resmgr, (void*)resname, (void**)&r) >= 0)
|
||||
&& (r != NULL)) {
|
||||
return (r->flags & XCE_RESF_LIMIT) != 0;
|
||||
}
|
||||
return strncmp(resname, VRES_PREFIX, VRES_PREFIX_LEN) == 0;
|
||||
}
|
||||
|
||||
/* The host limits matched to the job on the worker named `host`, or NULL
|
||||
* (no schedobj, unknown worker, none matched). */
|
||||
static const xlist_t* host_limits(xce_sched_core_t* core,
|
||||
const xce_sched_obj_t* obj, const char* host)
|
||||
{
|
||||
xce_sched_worker_t* w = NULL;
|
||||
|
||||
if ((obj == NULL) || (core == NULL) || (core->workers == NULL)
|
||||
|| (host == NULL)
|
||||
|| (key_find(core->workers, (void*)host, (void**)&w) < 0)
|
||||
|| (w == NULL)) {
|
||||
return NULL;
|
||||
}
|
||||
return sti_worker_limits(obj, w->idx);
|
||||
}
|
||||
|
||||
/* Stores allocated amount `a` as a limit charge, resolved through `limits`;
|
||||
* a host charge (`host` non-NULL) gets the host appended to its scope. */
|
||||
static bool charge_row(xce_sched_trace_rec_t* rec, const xlist_t* limits,
|
||||
const xce_alloc_t* a, const char* host,
|
||||
sched_trace_alloc_row_t* out)
|
||||
{
|
||||
charge_ref_t c;
|
||||
|
||||
sti_charge_resolve(sti_list_vres(limits, NULL, a->resname), a->resname, &c);
|
||||
out->res = sched_trace_arena_strdup(rec, a->resname);
|
||||
out->value = a->value;
|
||||
out->limit = sti_copy_prefix(rec, c.name, c.name_len);
|
||||
out->scope = sti_copy_scope(rec, c.scope, host);
|
||||
out->limited_res = sched_trace_arena_strdup(rec, c.limited_res);
|
||||
return (out->res != NULL) && (out->limit != NULL) && (out->scope != NULL)
|
||||
&& ((c.limited_res == NULL) || (out->limited_res != NULL));
|
||||
}
|
||||
|
||||
/* One allocation list (a host's, or the shared one) as resource rows and
|
||||
* limit charges, each in allocation order. False on an arena failure. */
|
||||
static bool alloc_rows(xce_sched_trace_rec_t* rec, xce_sched_core_t* core,
|
||||
const xlist_t* limits, int n, xce_alloc_t** allocs,
|
||||
const char* host, int* nres,
|
||||
sched_trace_alloc_row_t** res, int* ncharges,
|
||||
sched_trace_alloc_row_t** charges)
|
||||
{
|
||||
size_t sz = sizeof(**res) * (size_t)n;
|
||||
int i;
|
||||
|
||||
if ((n <= 0) || (allocs == NULL)) {
|
||||
return true;
|
||||
}
|
||||
*res = (sched_trace_alloc_row_t*)sched_trace_arena_alloc(rec, sz);
|
||||
*charges = (sched_trace_alloc_row_t*)sched_trace_arena_alloc(rec, sz);
|
||||
if ((*res == NULL) || (*charges == NULL)) {
|
||||
return false;
|
||||
}
|
||||
for (i = 0; i < n; i++) {
|
||||
const xce_alloc_t* a = allocs[i];
|
||||
sched_trace_alloc_row_t* row;
|
||||
|
||||
if ((a == NULL) || (a->resname == NULL)) {
|
||||
continue;
|
||||
}
|
||||
if (is_limit_charge(core, a->resname)) {
|
||||
if (!charge_row(rec, limits, a, host, &(*charges)[(*ncharges)++])) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
row = &(*res)[(*nres)++];
|
||||
row->res = sched_trace_arena_strdup(rec, a->resname);
|
||||
row->value = a->value;
|
||||
if (row->res == NULL) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* A total over the omitted hosts, keyed by resource name or, for a charge,
|
||||
* by limit name and scope. Scratch memory; the strings are the sources'. */
|
||||
typedef struct {
|
||||
const char* res; /* resource total; NULL: a limit total */
|
||||
charge_ref_t c; /* limit total */
|
||||
double value;
|
||||
} agg_t;
|
||||
|
||||
static void agg_add(agg_t* agg, int* n, const char* res,
|
||||
const charge_ref_t* c, double value)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < *n; i++) {
|
||||
if ((res != NULL)
|
||||
? (strcmp(agg[i].res, res) == 0)
|
||||
: ((agg[i].c.name_len == c->name_len)
|
||||
&& (strncmp(agg[i].c.name, c->name, c->name_len) == 0)
|
||||
&& (strcmp(agg[i].c.scope, c->scope) == 0))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == *n) {
|
||||
memset(&agg[i], 0, sizeof(agg[i]));
|
||||
agg[i].res = res;
|
||||
if (c != NULL) {
|
||||
agg[i].c = *c;
|
||||
}
|
||||
(*n)++;
|
||||
}
|
||||
agg[i].value += value;
|
||||
if ((c != NULL) && (agg[i].c.limited_res == NULL)) {
|
||||
agg[i].c.limited_res = c->limited_res;
|
||||
}
|
||||
}
|
||||
|
||||
/* Copies `n` totals into the arena as rows (a charge's scope has no
|
||||
* host part: it totals hosts). */
|
||||
static bool agg_store(xce_sched_trace_rec_t* rec, const agg_t* agg, int n,
|
||||
int* nout, sched_trace_alloc_row_t** out)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (n == 0) {
|
||||
return true;
|
||||
}
|
||||
*out = (sched_trace_alloc_row_t*)sched_trace_arena_alloc(
|
||||
rec, sizeof(**out) * (size_t)n);
|
||||
if (*out == NULL) {
|
||||
return false;
|
||||
}
|
||||
for (i = 0; i < n; i++) {
|
||||
sched_trace_alloc_row_t* row = &(*out)[i];
|
||||
|
||||
row->value = agg[i].value;
|
||||
if (agg[i].res != NULL) {
|
||||
row->res = sched_trace_arena_strdup(rec, agg[i].res);
|
||||
if (row->res == NULL) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
row->limit
|
||||
= sti_copy_prefix(rec, agg[i].c.name, agg[i].c.name_len);
|
||||
row->scope = sti_copy_scope(rec, agg[i].c.scope, NULL);
|
||||
row->limited_res = sched_trace_arena_strdup(rec, agg[i].c.limited_res);
|
||||
if ((row->limit == NULL) || (row->scope == NULL)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*nout = n;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Totals of the hosts from index `from` on (9.4): per resource without the
|
||||
* limit charges, and per limit. False on an allocation failure. */
|
||||
static bool aggregate_hosts(xce_sched_trace_rec_t* rec, xce_sched_core_t* core,
|
||||
const xce_sched_obj_t* obj,
|
||||
const xce_job_alloc_t* alloc, int from)
|
||||
{
|
||||
sched_trace_dispatch_out_t* d = &rec->dispatch;
|
||||
size_t total = 0;
|
||||
agg_t* res;
|
||||
agg_t* chg;
|
||||
int nres = 0;
|
||||
int nchg = 0;
|
||||
bool ok;
|
||||
int i;
|
||||
int k;
|
||||
|
||||
for (i = from; i < alloc->nhosts; i++) {
|
||||
if ((alloc->allocs[i] != NULL) && (alloc->allocs[i]->allocs != NULL)
|
||||
&& (alloc->allocs[i]->nallocs > 0)) {
|
||||
total += (size_t)alloc->allocs[i]->nallocs;
|
||||
}
|
||||
}
|
||||
res = (agg_t*)malloc(sizeof(*res) * (total + 1));
|
||||
chg = (agg_t*)malloc(sizeof(*chg) * (total + 1));
|
||||
ok = (res != NULL) && (chg != NULL);
|
||||
|
||||
for (i = from; ok && (i < alloc->nhosts); i++) {
|
||||
const xce_host_alloc_t* h = alloc->allocs[i];
|
||||
const xlist_t* limits;
|
||||
|
||||
if ((h == NULL) || (h->allocs == NULL)) {
|
||||
continue;
|
||||
}
|
||||
limits = host_limits(core, obj, h->host);
|
||||
for (k = 0; k < h->nallocs; k++) {
|
||||
const xce_alloc_t* a = h->allocs[k];
|
||||
charge_ref_t c;
|
||||
|
||||
if ((a == NULL) || (a->resname == NULL)) {
|
||||
continue;
|
||||
}
|
||||
if (!is_limit_charge(core, a->resname)) {
|
||||
agg_add(res, &nres, a->resname, NULL, a->value);
|
||||
continue;
|
||||
}
|
||||
sti_charge_resolve(sti_list_vres(limits, NULL, a->resname),
|
||||
a->resname, &c);
|
||||
agg_add(chg, &nchg, NULL, &c, a->value);
|
||||
}
|
||||
}
|
||||
ok = ok && agg_store(rec, res, nres, &d->nagg_res, &d->agg_res)
|
||||
&& agg_store(rec, chg, nchg, &d->nagg_charges, &d->agg_charges);
|
||||
free(res);
|
||||
free(chg);
|
||||
return ok;
|
||||
}
|
||||
|
||||
void sched_trace_capture_dispatch(xce_sched_trace_rec_t* rec,
|
||||
xce_sched_core_t* core,
|
||||
const xce_sched_obj_t* obj,
|
||||
const xce_job_alloc_t* alloc)
|
||||
{
|
||||
sched_trace_dispatch_out_t* d = &rec->dispatch;
|
||||
int from = -1;
|
||||
int nh;
|
||||
int i;
|
||||
|
||||
memset(d, 0, sizeof(*d));
|
||||
rec->outcome = SCHED_TRACE_OUTCOME_DISPATCH;
|
||||
if (alloc == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!alloc_rows(rec, core, (obj != NULL) ? &obj->glimits : NULL,
|
||||
alloc->nres_shared, alloc->allocs_shared, NULL,
|
||||
&d->nshared, &d->shared, &d->nshared_charges,
|
||||
&d->shared_charges)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
nh = (alloc->allocs != NULL) ? alloc->nhosts : 0;
|
||||
if (nh > 0) {
|
||||
int keep = (nh < XCE_TRACE_OUTCOME_HOSTS) ? nh : XCE_TRACE_OUTCOME_HOSTS;
|
||||
|
||||
d->hosts = (sched_trace_host_out_t*)sched_trace_arena_alloc(
|
||||
rec, sizeof(*d->hosts) * (size_t)keep);
|
||||
if (d->hosts == NULL) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < nh; i++) {
|
||||
const xce_host_alloc_t* h = alloc->allocs[i];
|
||||
sched_trace_host_out_t* ho;
|
||||
|
||||
if (h == NULL) {
|
||||
continue;
|
||||
}
|
||||
if (d->nhosts == XCE_TRACE_OUTCOME_HOSTS) {
|
||||
if (from < 0) {
|
||||
from = i;
|
||||
}
|
||||
d->omitted_hosts++;
|
||||
continue;
|
||||
}
|
||||
ho = &d->hosts[d->nhosts++];
|
||||
ho->host = sched_trace_arena_strdup(rec, h->host);
|
||||
if (((h->host != NULL) && (ho->host == NULL))
|
||||
|| !alloc_rows(rec, core, host_limits(core, obj, h->host),
|
||||
h->nallocs, h->allocs,
|
||||
(h->host != NULL) ? h->host : "", &ho->nres,
|
||||
&ho->res, &ho->ncharges, &ho->charges)) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
if ((from >= 0) && !aggregate_hosts(rec, core, obj, alloc, from)) {
|
||||
goto fail;
|
||||
}
|
||||
return;
|
||||
|
||||
fail:
|
||||
rec->incomplete = true; /* design "Error Strategy" */
|
||||
}
|
||||
|
||||
xce_sched_trace_rec_t*
|
||||
sched_trace_rec_reconstructed(const xce_sched_trace_ctl_t* ctl,
|
||||
xce_sched_core_t* core, uint64_t jobid,
|
||||
const xce_sched_obj_t* obj,
|
||||
const xce_job_alloc_t* allocs)
|
||||
{
|
||||
xce_sched_trace_rec_t* rec;
|
||||
|
||||
rec = sched_trace_rec_new(ctl, jobid, XCE_TRACE_REC_RECONSTRUCTED);
|
||||
if (rec != NULL) {
|
||||
sched_trace_capture_dispatch(rec, core, obj, allocs);
|
||||
}
|
||||
return rec;
|
||||
}
|
||||
|
||||
/* ---- pend outcome (6.4, 9.4) ------------------------------------------ */
|
||||
|
||||
/* A set of host names, open addressing over a power-of-two table. Scratch
|
||||
* memory; the names are the source's. */
|
||||
typedef struct {
|
||||
const char** slot;
|
||||
size_t mask;
|
||||
int n;
|
||||
} host_set_t;
|
||||
|
||||
static bool host_set_init(host_set_t* s, int nmax)
|
||||
{
|
||||
size_t cap = 16;
|
||||
|
||||
while (cap < (size_t)nmax * 2 + 2) {
|
||||
cap <<= 1;
|
||||
}
|
||||
s->slot = (const char**)calloc(cap, sizeof(*s->slot));
|
||||
s->mask = cap - 1;
|
||||
s->n = 0;
|
||||
return s->slot != NULL;
|
||||
}
|
||||
|
||||
/* True iff `h` is in the set; when absent and `insert`, adds it. */
|
||||
static bool host_set_has(host_set_t* s, const char* h, bool insert)
|
||||
{
|
||||
size_t i = 2166136261u; /* FNV-1a */
|
||||
const unsigned char* p;
|
||||
|
||||
for (p = (const unsigned char*)h; *p != '\0'; p++) {
|
||||
i = (i ^ *p) * 16777619u;
|
||||
}
|
||||
for (i &= s->mask; s->slot[i] != NULL; i = (i + 1) & s->mask) {
|
||||
if (strcmp(s->slot[i], h) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (insert) {
|
||||
s->slot[i] = h;
|
||||
s->n++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
host_set_t kept; /* the first 50 distinct hosts */
|
||||
host_set_t omitted; /* the hosts after them */
|
||||
sched_trace_reason_count_t* codes; /* omitted entries per code */
|
||||
int ncodes;
|
||||
} pend_build_t;
|
||||
|
||||
/* True iff an entry naming `host` is kept: it names no host, or one of the
|
||||
* first 50 distinct hosts. Otherwise it is counted under its code. */
|
||||
static bool pend_keep(pend_build_t* b, const char* host, int code)
|
||||
{
|
||||
if ((host == NULL) || (host[0] == '\0')
|
||||
|| host_set_has(&b->kept, host, false)) {
|
||||
return true;
|
||||
}
|
||||
if (b->kept.n < XCE_TRACE_OUTCOME_HOSTS) {
|
||||
(void)host_set_has(&b->kept, host, true);
|
||||
return true;
|
||||
}
|
||||
(void)host_set_has(&b->omitted, host, true);
|
||||
sti_count_code(b->codes, &b->ncodes, code);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Arena copies of the kept host entries (NULL entries stay NULL). */
|
||||
static bool copy_host_reasons(xce_sched_trace_rec_t* rec,
|
||||
const xce_job_preason_t* r, const bool* keep,
|
||||
int nh, xce_job_preason_t* c)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nh; i++) {
|
||||
c->nhost_reasons += keep[i] ? 1 : 0;
|
||||
}
|
||||
if (c->nhost_reasons == 0) {
|
||||
return true;
|
||||
}
|
||||
c->host_based_reasons = (xce_host_reason_t**)sched_trace_arena_alloc(
|
||||
rec, sizeof(*c->host_based_reasons) * (size_t)c->nhost_reasons);
|
||||
if (c->host_based_reasons == NULL) {
|
||||
return false;
|
||||
}
|
||||
c->nhost_reasons = 0;
|
||||
for (i = 0; i < nh; i++) {
|
||||
const xce_host_reason_t* src = r->host_based_reasons[i];
|
||||
xce_host_reason_t* dst = NULL;
|
||||
|
||||
if (!keep[i]) {
|
||||
continue;
|
||||
}
|
||||
if (src != NULL) {
|
||||
dst = (xce_host_reason_t*)sched_trace_arena_alloc(rec, sizeof(*dst));
|
||||
if (dst == NULL) {
|
||||
return false;
|
||||
}
|
||||
dst->reason = src->reason;
|
||||
dst->host = sched_trace_arena_strdup(rec, src->host);
|
||||
if ((src->host != NULL) && (dst->host == NULL)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
c->host_based_reasons[c->nhost_reasons++] = dst;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Arena copies of the kept resource entries (NULL entries stay NULL). */
|
||||
static bool copy_res_reasons(xce_sched_trace_rec_t* rec,
|
||||
const xce_job_preason_t* r, const bool* keep,
|
||||
int nr, xce_job_preason_t* c)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nr; i++) {
|
||||
c->nres_reasons += keep[i] ? 1 : 0;
|
||||
}
|
||||
if (c->nres_reasons == 0) {
|
||||
return true;
|
||||
}
|
||||
c->res_based_reasons = (xce_res_reason_t**)sched_trace_arena_alloc(
|
||||
rec, sizeof(*c->res_based_reasons) * (size_t)c->nres_reasons);
|
||||
if (c->res_based_reasons == NULL) {
|
||||
return false;
|
||||
}
|
||||
c->nres_reasons = 0;
|
||||
for (i = 0; i < nr; i++) {
|
||||
const xce_res_reason_t* src = r->res_based_reasons[i];
|
||||
xce_res_reason_t* dst = NULL;
|
||||
|
||||
if (!keep[i]) {
|
||||
continue;
|
||||
}
|
||||
if (src != NULL) {
|
||||
dst = (xce_res_reason_t*)sched_trace_arena_alloc(rec, sizeof(*dst));
|
||||
if (dst == NULL) {
|
||||
return false;
|
||||
}
|
||||
dst->reason = src->reason;
|
||||
dst->host = sched_trace_arena_strdup(rec, src->host);
|
||||
dst->resource = sched_trace_arena_strdup(rec, src->resource);
|
||||
if (((src->host != NULL) && (dst->host == NULL))
|
||||
|| ((src->resource != NULL) && (dst->resource == NULL))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
c->res_based_reasons[c->nres_reasons++] = dst;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void sched_trace_capture_pend(xce_sched_trace_rec_t* rec,
|
||||
const xce_pendreason_t* reason)
|
||||
{
|
||||
sched_trace_pend_out_t* p = &rec->pend;
|
||||
const xce_job_preason_t* r = (reason != NULL) ? reason->reasons : NULL;
|
||||
pend_build_t b;
|
||||
bool* keep;
|
||||
int nh;
|
||||
int nr;
|
||||
int i;
|
||||
bool ok;
|
||||
|
||||
memset(p, 0, sizeof(*p));
|
||||
rec->outcome = SCHED_TRACE_OUTCOME_PEND;
|
||||
if (r == NULL) {
|
||||
return;
|
||||
}
|
||||
nh = ((r->host_based_reasons != NULL) && (r->nhost_reasons > 0))
|
||||
? r->nhost_reasons : 0;
|
||||
nr = ((r->res_based_reasons != NULL) && (r->nres_reasons > 0))
|
||||
? r->nres_reasons : 0;
|
||||
|
||||
/* which entries are kept, in one pass over the source order */
|
||||
memset(&b, 0, sizeof(b));
|
||||
keep = (bool*)malloc(sizeof(*keep) * (size_t)(nh + nr + 1));
|
||||
b.codes = (sched_trace_reason_count_t*)malloc(sizeof(*b.codes)
|
||||
* (size_t)(nh + nr + 1));
|
||||
ok = (keep != NULL) && (b.codes != NULL)
|
||||
&& host_set_init(&b.kept, XCE_TRACE_OUTCOME_HOSTS)
|
||||
&& host_set_init(&b.omitted, nh + nr);
|
||||
for (i = 0; ok && (i < nh); i++) {
|
||||
const xce_host_reason_t* e = r->host_based_reasons[i];
|
||||
|
||||
keep[i] = (e == NULL) || pend_keep(&b, e->host, e->reason);
|
||||
}
|
||||
for (i = 0; ok && (i < nr); i++) {
|
||||
const xce_res_reason_t* e = r->res_based_reasons[i];
|
||||
|
||||
keep[nh + i] = (e == NULL) || pend_keep(&b, e->host, e->reason);
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
p->reasons = (xce_job_preason_t*)sched_trace_arena_alloc(
|
||||
rec, sizeof(*p->reasons));
|
||||
ok = (p->reasons != NULL);
|
||||
}
|
||||
if (ok) {
|
||||
p->reasons->main_reason = r->main_reason;
|
||||
ok = copy_host_reasons(rec, r, keep, nh, p->reasons)
|
||||
&& copy_res_reasons(rec, r, keep + nh, nr, p->reasons);
|
||||
}
|
||||
if (ok && (b.ncodes > 0)) {
|
||||
p->omitted_codes = (sched_trace_reason_count_t*)sched_trace_arena_memdup(
|
||||
rec, b.codes, sizeof(*b.codes) * (size_t)b.ncodes);
|
||||
ok = (p->omitted_codes != NULL);
|
||||
p->ncodes = ok ? b.ncodes : 0;
|
||||
}
|
||||
p->omitted_hosts = b.omitted.n;
|
||||
if (!ok) {
|
||||
rec->incomplete = true; /* design "Error Strategy" */
|
||||
}
|
||||
free(keep);
|
||||
free(b.codes);
|
||||
free((void*)b.kept.slot);
|
||||
free((void*)b.omitted.slot);
|
||||
}
|
||||
491
src/scheduler/src/sched_trace_scan.c
Normal file
491
src/scheduler/src/sched_trace_scan.c
Normal file
|
|
@ -0,0 +1,491 @@
|
|||
/*
|
||||
* sched_trace_scan.c - decision trace record model, part 2
|
||||
* (scheduler-sim-decision-trace spec, design.md "TraceModel").
|
||||
*
|
||||
* The host scan's entries and counters: positions, host verdicts and the
|
||||
* rows held under them, the summary-level per-position tallies (5.9),
|
||||
* rollback runs, commit and finalize failures, and the per-requirement
|
||||
* search-end statement with its decision-wide counters (9.3). Split from
|
||||
* sched_trace.c (task 2.4), which keeps the record, its arena and the entry
|
||||
* sequence these functions append to (sched_trace_internal.h).
|
||||
*
|
||||
* Same rules as sched_trace.c: one building thread, no locking, and no
|
||||
* I/O and no logging (test_core_isolation.c scans this file).
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "sched/trace.h"
|
||||
#include "sched_trace_internal.h"
|
||||
|
||||
/* Starts the current position's tallies over. */
|
||||
static void pos_tally_reset(xce_sched_trace_rec_t* rec)
|
||||
{
|
||||
memset(rec->pos_counts, 0, sizeof(rec->pos_counts));
|
||||
sti_pos_limits_clear(rec);
|
||||
}
|
||||
|
||||
void sched_trace_pos_begin(xce_sched_trace_rec_t* rec, int pos, int slots)
|
||||
{
|
||||
sched_trace_entry_t* e;
|
||||
|
||||
rec->cur_pos = pos;
|
||||
rec->scan_err = 0;
|
||||
rec->scan_err_set = false;
|
||||
pos_tally_reset(rec);
|
||||
|
||||
e = sti_scope_begin(rec, SCHED_TRACE_E_POS_BEGIN);
|
||||
if (e == NULL) {
|
||||
return;
|
||||
}
|
||||
e->u.pos_begin.pos = pos;
|
||||
e->u.pos_begin.slots = slots;
|
||||
}
|
||||
|
||||
void sched_trace_pos_scan_error(xce_sched_trace_rec_t* rec, int err)
|
||||
{
|
||||
sti_rollback_run_end(rec);
|
||||
rec->scan_err = err;
|
||||
rec->scan_err_set = true;
|
||||
}
|
||||
|
||||
/* Fills a POS_SUMMARY entry from the current position's tallies; only
|
||||
* limits some host was rejected for are kept. */
|
||||
static void pos_summary_fill(xce_sched_trace_rec_t* rec, sched_trace_entry_t* e,
|
||||
xce_sched_trace_pos_end_t how, const char* host,
|
||||
int err)
|
||||
{
|
||||
int n = 0;
|
||||
int k;
|
||||
|
||||
e->u.pos_summary.pos = rec->cur_pos;
|
||||
e->u.pos_summary.how = how;
|
||||
e->u.pos_summary.err = err;
|
||||
e->u.pos_summary.host = sched_trace_arena_strdup(rec, host);
|
||||
memcpy(e->u.pos_summary.counts, rec->pos_counts,
|
||||
sizeof(e->u.pos_summary.counts));
|
||||
|
||||
for (k = 0; k < rec->pos_nlimits; k++) {
|
||||
if (rec->pos_limits[k].count > 0) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
if (n == 0) {
|
||||
return;
|
||||
}
|
||||
e->u.pos_summary.limits = (sched_trace_limit_tally_t*)
|
||||
sched_trace_arena_alloc(rec, sizeof(sched_trace_limit_tally_t)
|
||||
* (size_t)n);
|
||||
if (e->u.pos_summary.limits == NULL) {
|
||||
return;
|
||||
}
|
||||
n = 0;
|
||||
for (k = 0; k < rec->pos_nlimits; k++) {
|
||||
const struct sched_trace_tally_build_s* b = &rec->pos_limits[k];
|
||||
sched_trace_limit_tally_t* t;
|
||||
|
||||
if (b->count <= 0) {
|
||||
continue;
|
||||
}
|
||||
t = &e->u.pos_summary.limits[n++];
|
||||
t->limit = sched_trace_arena_strdup(rec, b->limit);
|
||||
t->scope = sched_trace_arena_strdup(rec, b->scope);
|
||||
t->count = b->count;
|
||||
}
|
||||
e->u.pos_summary.nlimits = n;
|
||||
}
|
||||
|
||||
void sched_trace_pos_end(xce_sched_trace_rec_t* rec,
|
||||
xce_sched_trace_pos_end_t how, const char* host)
|
||||
{
|
||||
sched_trace_entry_t* e;
|
||||
int err = 0;
|
||||
|
||||
if (rec->scan_err_set) {
|
||||
/* the host scan stopped: the runner's UNFILLED is a scan error */
|
||||
err = rec->scan_err;
|
||||
if (how == XCE_TRACE_POS_UNFILLED) {
|
||||
how = XCE_TRACE_POS_SCAN_ERROR;
|
||||
}
|
||||
}
|
||||
rec->scan_err = 0;
|
||||
rec->scan_err_set = false;
|
||||
|
||||
if (rec->level == XCE_TRACE_LEVEL_SUMMARY) {
|
||||
/* the position's end carries its host tallies (5.9) */
|
||||
e = sti_scope_end(rec, SCHED_TRACE_E_POS_SUMMARY);
|
||||
if (e != NULL) {
|
||||
pos_summary_fill(rec, e, how, host, err);
|
||||
}
|
||||
pos_tally_reset(rec);
|
||||
return;
|
||||
}
|
||||
|
||||
e = sti_scope_end(rec, SCHED_TRACE_E_POS_END);
|
||||
if (e == NULL) {
|
||||
return;
|
||||
}
|
||||
e->u.pos_end.pos = rec->cur_pos;
|
||||
e->u.pos_end.how = how;
|
||||
e->u.pos_end.err = err;
|
||||
e->u.pos_end.host = sched_trace_arena_strdup(rec, host);
|
||||
}
|
||||
|
||||
void sched_trace_host_begin(xce_sched_trace_rec_t* rec,
|
||||
const xce_sched_obj_t* obj,
|
||||
const xce_sched_worker_t* w)
|
||||
{
|
||||
sti_rollback_run_end(rec);
|
||||
rec->host_seq++;
|
||||
rec->scope_obj = obj;
|
||||
rec->scope_w = w;
|
||||
rec->host_row_verdicts = 0;
|
||||
sti_host_rows_clear(rec); /* a scope that ended without its verdict */
|
||||
}
|
||||
|
||||
/* Counts one host evaluation: decision-wide per verdict kind, and at
|
||||
* summary level per position, with each limit the host hit counted once
|
||||
* when its verdict is limit exceeded (5.9, 9.3). */
|
||||
static void host_count(xce_sched_trace_rec_t* rec, unsigned verdicts)
|
||||
{
|
||||
int k;
|
||||
|
||||
for (k = 0; k < XCE_TRACE_V_NKINDS; k++) {
|
||||
if ((verdicts & (1u << k)) != 0) {
|
||||
rec->counters.host_evals[k]++;
|
||||
rec->pos_counts[k]++;
|
||||
}
|
||||
}
|
||||
if ((verdicts & XCE_TRACE_V_LIMIT) == 0) {
|
||||
return;
|
||||
}
|
||||
for (k = 0; k < rec->pos_nlimits; k++) {
|
||||
if (rec->pos_limits[k].last_host == rec->host_seq) {
|
||||
rec->pos_limits[k].count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Copies a row's strings into the arena (NULL stays NULL). */
|
||||
static void row_to_arena(xce_sched_trace_rec_t* rec, sched_trace_row_t* dst,
|
||||
const sched_trace_row_t* src)
|
||||
{
|
||||
*dst = *src;
|
||||
dst->res = sched_trace_arena_strdup(rec, src->res);
|
||||
dst->limit = sched_trace_arena_strdup(rec, src->limit);
|
||||
dst->scope = sched_trace_arena_strdup(rec, src->scope);
|
||||
dst->limited_res = sched_trace_arena_strdup(rec, src->limited_res);
|
||||
}
|
||||
|
||||
/* Stores the current host's held rows after its verdict line, one level
|
||||
* below it, then drops them. Each counts against the budget. */
|
||||
static void host_rows_flush(xce_sched_trace_rec_t* rec)
|
||||
{
|
||||
int k;
|
||||
|
||||
for (k = 0; k < rec->host_nrows; k++) {
|
||||
sched_trace_entry_t* e = sti_entry_add(rec, SCHED_TRACE_E_HOST_ROW);
|
||||
|
||||
if (e == NULL) {
|
||||
continue;
|
||||
}
|
||||
e->depth++;
|
||||
row_to_arena(rec, &e->u.row, &rec->host_rows[k]);
|
||||
}
|
||||
sti_host_rows_clear(rec);
|
||||
}
|
||||
|
||||
/* NULL-preserving heap copy for held rows; sets *failed on failure. */
|
||||
static const char* row_strdup(const char* s, bool* failed)
|
||||
{
|
||||
char* d;
|
||||
|
||||
if (s == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
d = strdup(s);
|
||||
if (d == NULL) {
|
||||
*failed = true;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
void sched_trace_row(xce_sched_trace_rec_t* rec, const sched_trace_row_t* row)
|
||||
{
|
||||
sched_trace_entry_t* e;
|
||||
sched_trace_row_t* h;
|
||||
bool failed = false;
|
||||
|
||||
sti_rollback_run_end(rec);
|
||||
if (row == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (rec->scope_w == NULL) {
|
||||
/* the cluster-wide check: stored now, before its check-end line */
|
||||
e = sti_entry_add(rec, SCHED_TRACE_E_CHECK_ROW);
|
||||
if (e != NULL) {
|
||||
row_to_arena(rec, &e->u.row, row);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* a host scope: no host lines at summary level; at full level the row
|
||||
* waits for the host's verdict line, which it belongs under */
|
||||
if ((rec->level == XCE_TRACE_LEVEL_SUMMARY) || rec->incomplete) {
|
||||
return;
|
||||
}
|
||||
if (rec->host_nrows == rec->host_rows_cap) {
|
||||
int cap = (rec->host_rows_cap > 0) ? rec->host_rows_cap * 2 : 4;
|
||||
void* p = realloc(rec->host_rows, sizeof(*h) * (size_t)cap);
|
||||
|
||||
if (p == NULL) {
|
||||
rec->incomplete = true; /* design "Error Strategy" */
|
||||
return;
|
||||
}
|
||||
rec->host_rows = (sched_trace_row_t*)p;
|
||||
rec->host_rows_cap = cap;
|
||||
}
|
||||
h = &rec->host_rows[rec->host_nrows];
|
||||
*h = *row;
|
||||
h->res = row_strdup(row->res, &failed);
|
||||
h->limit = row_strdup(row->limit, &failed);
|
||||
h->scope = row_strdup(row->scope, &failed);
|
||||
h->limited_res = row_strdup(row->limited_res, &failed);
|
||||
if (failed) {
|
||||
free((void*)h->res);
|
||||
free((void*)h->limit);
|
||||
free((void*)h->scope);
|
||||
free((void*)h->limited_res);
|
||||
rec->incomplete = true;
|
||||
return;
|
||||
}
|
||||
rec->host_nrows++;
|
||||
}
|
||||
|
||||
void sched_trace_host_verdict(xce_sched_trace_rec_t* rec, unsigned verdicts,
|
||||
int err)
|
||||
{
|
||||
sched_trace_entry_t* e;
|
||||
sched_trace_phase_t phase = SCHED_TRACE_PHASE_NONE;
|
||||
const char* name
|
||||
= (rec->scope_w != NULL) ? rec->scope_w->name : NULL;
|
||||
|
||||
if (verdicts == 0) {
|
||||
/* from the rows the per-host check recorded; none means the
|
||||
* check's own selection branch rejected the host */
|
||||
verdicts = rec->host_row_verdicts & (XCE_TRACE_V_SHORTFALL
|
||||
| XCE_TRACE_V_LIMIT);
|
||||
if (verdicts == 0) {
|
||||
verdicts = XCE_TRACE_V_SELECT;
|
||||
}
|
||||
phase = SCHED_TRACE_PHASE_PERHOST;
|
||||
} else if ((verdicts & XCE_TRACE_V_SELECT) != 0) {
|
||||
phase = SCHED_TRACE_PHASE_PREFILTER;
|
||||
} else if ((verdicts & (XCE_TRACE_V_SHORTFALL | XCE_TRACE_V_LIMIT)) != 0) {
|
||||
phase = SCHED_TRACE_PHASE_PERHOST;
|
||||
}
|
||||
|
||||
/* one host evaluation, counted once under each kind it matched (9.3) */
|
||||
host_count(rec, verdicts);
|
||||
|
||||
/* the host scope ends here */
|
||||
rec->scope_obj = NULL;
|
||||
rec->scope_w = NULL;
|
||||
rec->host_row_verdicts = 0;
|
||||
rec->host_seq++; /* later hits belong to no host until the next one */
|
||||
sti_rollback_run_end(rec);
|
||||
|
||||
if (rec->level == XCE_TRACE_LEVEL_SUMMARY) {
|
||||
sti_host_rows_clear(rec);
|
||||
return; /* tallied for the position summary, no host line */
|
||||
}
|
||||
|
||||
e = sti_entry_add(rec, SCHED_TRACE_E_HOST);
|
||||
if (e != NULL) {
|
||||
e->u.host.host = sched_trace_arena_strdup(rec, name);
|
||||
e->u.host.verdicts = verdicts;
|
||||
e->u.host.phase = phase;
|
||||
e->u.host.err = err;
|
||||
}
|
||||
/* the host's unmet rows, under its verdict line (5.4, 5.5) */
|
||||
host_rows_flush(rec);
|
||||
}
|
||||
|
||||
void sched_trace_commit(xce_sched_trace_rec_t* rec)
|
||||
{
|
||||
sched_trace_entry_t* e = sti_entry_add(rec, SCHED_TRACE_E_COMMIT);
|
||||
|
||||
if (e == NULL) {
|
||||
return;
|
||||
}
|
||||
e->u.commit.plan_no = rec->cur_plan_no;
|
||||
}
|
||||
|
||||
void sched_trace_finalize_failed(xce_sched_trace_rec_t* rec, int err)
|
||||
{
|
||||
sched_trace_entry_t* e = sti_entry_add(rec, SCHED_TRACE_E_FINALIZE_FAILED);
|
||||
|
||||
if (e == NULL) {
|
||||
return;
|
||||
}
|
||||
e->u.commit.plan_no = rec->cur_plan_no;
|
||||
e->u.commit.err = err;
|
||||
}
|
||||
|
||||
|
||||
static bool tally_str_eq(const char* a, const char* b)
|
||||
{
|
||||
if ((a == NULL) || (b == NULL)) {
|
||||
return a == b;
|
||||
}
|
||||
return strcmp(a, b) == 0;
|
||||
}
|
||||
|
||||
/* NULL-preserving heap copy for the builder's tallies; sets *failed when a
|
||||
* non-NULL string could not be copied. */
|
||||
static char* tally_strdup(const char* s, bool* failed)
|
||||
{
|
||||
char* d;
|
||||
|
||||
if (s == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
d = strdup(s);
|
||||
if (d == NULL) {
|
||||
*failed = true;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
void sched_trace_host_limit_hit(xce_sched_trace_rec_t* rec, const char* limit,
|
||||
const char* scope)
|
||||
{
|
||||
struct sched_trace_tally_build_s* b;
|
||||
bool failed = false;
|
||||
int k;
|
||||
|
||||
sti_rollback_run_end(rec);
|
||||
if (rec->scope_w == NULL) {
|
||||
return; /* not a host scope: the cluster-wide check's rows */
|
||||
}
|
||||
rec->host_row_verdicts |= XCE_TRACE_V_LIMIT;
|
||||
if ((rec->level != XCE_TRACE_LEVEL_SUMMARY) || rec->incomplete) {
|
||||
return; /* full level shows the rows themselves (2.2) */
|
||||
}
|
||||
|
||||
for (k = 0; k < rec->pos_nlimits; k++) {
|
||||
b = &rec->pos_limits[k];
|
||||
if (tally_str_eq(b->limit, limit) && tally_str_eq(b->scope, scope)) {
|
||||
b->last_host = rec->host_seq; /* once per host, at its verdict */
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (rec->pos_nlimits == rec->pos_limits_cap) {
|
||||
int cap = (rec->pos_limits_cap > 0) ? rec->pos_limits_cap * 2 : 4;
|
||||
void* p = realloc(rec->pos_limits, sizeof(*b) * (size_t)cap);
|
||||
|
||||
if (p == NULL) {
|
||||
rec->incomplete = true;
|
||||
return;
|
||||
}
|
||||
rec->pos_limits = (struct sched_trace_tally_build_s*)p;
|
||||
rec->pos_limits_cap = cap;
|
||||
}
|
||||
b = &rec->pos_limits[rec->pos_nlimits];
|
||||
b->limit = tally_strdup(limit, &failed);
|
||||
b->scope = tally_strdup(scope, &failed);
|
||||
b->count = 0;
|
||||
b->last_host = rec->host_seq;
|
||||
if (failed) {
|
||||
free(b->limit);
|
||||
free(b->scope);
|
||||
rec->incomplete = true;
|
||||
return;
|
||||
}
|
||||
rec->pos_nlimits++;
|
||||
}
|
||||
|
||||
/* Appends one host to a ROLLBACK entry, growing its array in the arena. */
|
||||
static void rollback_append(xce_sched_trace_rec_t* rec, sched_trace_entry_t* e,
|
||||
const char* host)
|
||||
{
|
||||
if (e->u.rollback.nhosts == e->u.rollback.cap) {
|
||||
int cap = (e->u.rollback.cap > 0) ? e->u.rollback.cap * 2 : 4;
|
||||
const char** h = (const char**)sched_trace_arena_alloc(
|
||||
rec, sizeof(char*) * (size_t)cap);
|
||||
|
||||
if (h == NULL) {
|
||||
return;
|
||||
}
|
||||
if (e->u.rollback.nhosts > 0) {
|
||||
memcpy(h, e->u.rollback.hosts,
|
||||
sizeof(char*) * (size_t)e->u.rollback.nhosts);
|
||||
}
|
||||
e->u.rollback.hosts = h;
|
||||
e->u.rollback.cap = cap;
|
||||
}
|
||||
e->u.rollback.hosts[e->u.rollback.nhosts++]
|
||||
= sched_trace_arena_strdup(rec, host);
|
||||
}
|
||||
|
||||
void sched_trace_rollback_host(xce_sched_trace_rec_t* rec, const char* host)
|
||||
{
|
||||
sched_trace_entry_t* e;
|
||||
|
||||
if (rec->rollback_open) {
|
||||
/* the same run of released hosts: the same line */
|
||||
if ((rec->rollback_e != NULL) && !rec->incomplete) {
|
||||
rollback_append(rec, rec->rollback_e, host);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
e = sti_entry_add(rec, SCHED_TRACE_E_ROLLBACK);
|
||||
rec->rollback_open = true; /* also when omitted: one line either way */
|
||||
rec->rollback_e = e;
|
||||
if (e != NULL) {
|
||||
rollback_append(rec, e, host);
|
||||
}
|
||||
}
|
||||
|
||||
void sched_trace_search_end(xce_sched_trace_rec_t* rec, int rc)
|
||||
{
|
||||
sched_trace_entry_t* e;
|
||||
sched_trace_search_stmt_t stmt = SCHED_TRACE_SEARCH_NO_STMT;
|
||||
int t = rec->scope_tried;
|
||||
int d = rec->scope_discarded;
|
||||
int x = rec->scope_not_tried;
|
||||
int g = t + d + x;
|
||||
|
||||
if (rc == 0) {
|
||||
if (g == 0) {
|
||||
stmt = SCHED_TRACE_SEARCH_NONE_GENERATED;
|
||||
rec->counters.reqs_none_generated++;
|
||||
} else if (d == g) {
|
||||
stmt = SCHED_TRACE_SEARCH_ALL_DISCARDED;
|
||||
rec->counters.reqs_all_discarded++;
|
||||
} else if ((t > 0) && !rec->scope_plan_err) {
|
||||
stmt = SCHED_TRACE_SEARCH_NONE_PLACED;
|
||||
rec->counters.reqs_none_placed++;
|
||||
}
|
||||
}
|
||||
|
||||
/* the search is over: a later search in this requirement counts anew */
|
||||
rec->scope_tried = 0;
|
||||
rec->scope_discarded = 0;
|
||||
rec->scope_not_tried = 0;
|
||||
rec->scope_plan_err = false;
|
||||
|
||||
e = sti_entry_add(rec, SCHED_TRACE_E_SEARCH_END);
|
||||
if (e == NULL) {
|
||||
return;
|
||||
}
|
||||
e->u.search_end.generated = g;
|
||||
e->u.search_end.tried = t;
|
||||
e->u.search_end.discarded = d;
|
||||
e->u.search_end.not_tried = x;
|
||||
e->u.search_end.rc = rc;
|
||||
e->u.search_end.stmt = stmt;
|
||||
}
|
||||
|
|
@ -241,6 +241,18 @@ sched_core_isolation_test = executable(
|
|||
+ (meson.current_source_dir() / '..' / 'src'
|
||||
/ 'sched_trace_capture.c')
|
||||
+ '"',
|
||||
'-DXCE_SCHED_TRACE_SCAN_C_PATH="'
|
||||
+ (meson.current_source_dir() / '..' / 'src'
|
||||
/ 'sched_trace_scan.c')
|
||||
+ '"',
|
||||
'-DXCE_SCHED_TRACE_OUTCOME_C_PATH="'
|
||||
+ (meson.current_source_dir() / '..' / 'src'
|
||||
/ 'sched_trace_outcome.c')
|
||||
+ '"',
|
||||
'-DXCE_SCHED_TRACE_INTERNAL_H_PATH="'
|
||||
+ (meson.current_source_dir() / '..' / 'src'
|
||||
/ 'sched_trace_internal.h')
|
||||
+ '"',
|
||||
'-DXCE_CORE_LIB_PATH="'
|
||||
+ sched_core_isolation_lib.full_path()
|
||||
+ '"'],
|
||||
|
|
@ -2520,10 +2532,12 @@ test('scheduler repl trace baseline test',
|
|||
# scheduler-sim-decision-trace task 1.3: decision trace record model
|
||||
# (src/sched_trace.c) unit tests -- selection matching, record header and
|
||||
# per-record arena copying and size, record finishing. Links only the model
|
||||
# source; libxce supplies the jobid helpers.
|
||||
# sources (sched_trace.c and, since task 2.4, sched_trace_scan.c); libxce
|
||||
# supplies the jobid helpers.
|
||||
sched_trace_model_test = executable(
|
||||
'sched_trace_model_test',
|
||||
['test_trace_model.c', '../src/sched_trace.c'],
|
||||
['test_trace_model.c', '../src/sched_trace.c',
|
||||
'../src/sched_trace_scan.c'],
|
||||
include_directories:[incdir],
|
||||
dependencies: [scheduler_test_deps],
|
||||
install: false)
|
||||
|
|
@ -2537,7 +2551,8 @@ test('scheduler trace model test',
|
|||
# src/decision.c; links the record model for its constructor and free.
|
||||
sched_decision_trace_test = executable(
|
||||
'sched_decision_trace_test',
|
||||
['test_decision_trace.c', '../src/sched_trace.c'],
|
||||
['test_decision_trace.c', '../src/sched_trace.c',
|
||||
'../src/sched_trace_scan.c'],
|
||||
include_directories:[incdir],
|
||||
dependencies: [scheduler_test_deps],
|
||||
install: false)
|
||||
|
|
@ -2549,11 +2564,13 @@ test('scheduler decision trace test',
|
|||
# scheduler-sim-decision-trace task 2.1: capture helpers (design.md
|
||||
# "TraceCapture") over a fixture core and job -- selection facts,
|
||||
# cluster-wide limits and attempt failure reasons. Links the record model
|
||||
# and the capture source; libxce supplies the pend-reason helpers.
|
||||
# and the capture sources (split in task 2.4); libxce supplies the
|
||||
# pend-reason helpers.
|
||||
sched_trace_capture_test = executable(
|
||||
'sched_trace_capture_test',
|
||||
['test_trace_capture.c', '../src/sched_trace.c',
|
||||
'../src/sched_trace_capture.c'],
|
||||
'../src/sched_trace_scan.c', '../src/sched_trace_capture.c',
|
||||
'../src/sched_trace_outcome.c'],
|
||||
include_directories:[incdir],
|
||||
dependencies: [scheduler_test_deps],
|
||||
install: false)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@
|
|||
#include "../src/resource.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
|
||||
/* scheduler-sim-live-allocation spec, task 1.3: sim.h only depends on
|
||||
* core.h (+ stdbool/stdint/stdio, per its own header comment), so it is
|
||||
|
|
|
|||
|
|
@ -111,7 +111,9 @@
|
|||
#include "../src/driver.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
#include "../src/recording.c"
|
||||
#include "../src/recording_events.c"
|
||||
#include "../src/snapshot.c"
|
||||
|
|
|
|||
|
|
@ -25,7 +25,9 @@
|
|||
#include "../src/alloc_runner.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
#include "../src/plugins/parjob_allocator.c"
|
||||
|
||||
/* Link-satisfying stubs (same convention as test_seqjob_allocator.c). */
|
||||
|
|
|
|||
|
|
@ -40,7 +40,9 @@
|
|||
#include "../src/alloc_runner.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
#include "../src/plugins/seqjob_allocator.c"
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
|
|
|||
|
|
@ -38,7 +38,9 @@
|
|||
#include "../src/alloc_runner.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
#include "../src/plugins/seqjob_allocator.c"
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
|
|
|||
|
|
@ -31,7 +31,9 @@
|
|||
#include "../src/resource.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
#include "../src/schedobj.c"
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
|
|
|||
|
|
@ -40,7 +40,9 @@ static void* test_calloc(size_t n, size_t sz);
|
|||
#include "../src/resource.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
#include "../src/schedobj.c"
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
|
|
|||
|
|
@ -36,7 +36,9 @@ static void* test_realloc(void* p, size_t n);
|
|||
#include "../src/resource.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
#include "../src/schedobj.c"
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
|
|
|||
|
|
@ -72,7 +72,9 @@
|
|||
#include "../src/resource.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
|
||||
void kill_sched_job(void* arg) { (void)arg; }
|
||||
void kill_sched_queue(void* arg) { (void)arg; }
|
||||
|
|
|
|||
|
|
@ -64,11 +64,13 @@
|
|||
* sidesteps that entirely by checking *usage*, not *visibility*).
|
||||
*
|
||||
* Decision trace sources (scheduler-sim-decision-trace spec, task 1.3,
|
||||
* design.md "Invariant Cross-Check" dimension 2): sched_trace.c (record
|
||||
* model) and sched_trace_capture.c (capture helpers) run on the allocator
|
||||
* design.md "Invariant Cross-Check" dimension 2): sched_trace.c and
|
||||
* sched_trace_scan.c (record model), sched_trace_capture.c and
|
||||
* sched_trace_outcome.c (capture helpers) and their shared
|
||||
* sched_trace_internal.h (split in task 2.4) run on the allocator
|
||||
* thread inside the core's allocation chain, so they join layer 1's
|
||||
* forbidden-headers and forbidden-symbols scans, plus a check that applies
|
||||
* to these two files only: no stdio, no file I/O and no logging (a trace
|
||||
* to these trace files only: no stdio, no file I/O and no logging (a trace
|
||||
* record is pure data; only the simulator REPL renders or writes it). They
|
||||
* are NOT part of layer 2: sched_core_isolation_lib covers the four core
|
||||
* state-machine files, and the capture file references scheduler symbols
|
||||
|
|
@ -117,6 +119,18 @@
|
|||
#error \
|
||||
"XCE_SCHED_TRACE_CAPTURE_C_PATH must be defined by meson.build to sched_trace_capture.c's absolute path"
|
||||
#endif
|
||||
#ifndef XCE_SCHED_TRACE_SCAN_C_PATH
|
||||
#error \
|
||||
"XCE_SCHED_TRACE_SCAN_C_PATH must be defined by meson.build to sched_trace_scan.c's absolute path"
|
||||
#endif
|
||||
#ifndef XCE_SCHED_TRACE_OUTCOME_C_PATH
|
||||
#error \
|
||||
"XCE_SCHED_TRACE_OUTCOME_C_PATH must be defined by meson.build to sched_trace_outcome.c's absolute path"
|
||||
#endif
|
||||
#ifndef XCE_SCHED_TRACE_INTERNAL_H_PATH
|
||||
#error \
|
||||
"XCE_SCHED_TRACE_INTERNAL_H_PATH must be defined by meson.build to sched_trace_internal.h's absolute path"
|
||||
#endif
|
||||
#ifndef XCE_CORE_LIB_PATH
|
||||
#error \
|
||||
"XCE_CORE_LIB_PATH must be defined by meson.build to the absolute path of core.c's isolated static_library() archive"
|
||||
|
|
@ -217,6 +231,9 @@ static void test_core_source_paths_are_readable(void)
|
|||
char* cc = read_whole_file(XCE_CORE_CLOCK_C_PATH);
|
||||
char* st = read_whole_file(XCE_SCHED_TRACE_C_PATH);
|
||||
char* sc = read_whole_file(XCE_SCHED_TRACE_CAPTURE_C_PATH);
|
||||
char* ss = read_whole_file(XCE_SCHED_TRACE_SCAN_C_PATH);
|
||||
char* so = read_whole_file(XCE_SCHED_TRACE_OUTCOME_C_PATH);
|
||||
char* si = read_whole_file(XCE_SCHED_TRACE_INTERNAL_H_PATH);
|
||||
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(h, XCE_CORE_H_PATH);
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(c, XCE_CORE_C_PATH);
|
||||
|
|
@ -225,6 +242,9 @@ static void test_core_source_paths_are_readable(void)
|
|||
TEST_ASSERT_NOT_NULL_MESSAGE(cc, XCE_CORE_CLOCK_C_PATH);
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(st, XCE_SCHED_TRACE_C_PATH);
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(sc, XCE_SCHED_TRACE_CAPTURE_C_PATH);
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(ss, XCE_SCHED_TRACE_SCAN_C_PATH);
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(so, XCE_SCHED_TRACE_OUTCOME_C_PATH);
|
||||
TEST_ASSERT_NOT_NULL_MESSAGE(si, XCE_SCHED_TRACE_INTERNAL_H_PATH);
|
||||
|
||||
free(h);
|
||||
free(c);
|
||||
|
|
@ -233,6 +253,9 @@ static void test_core_source_paths_are_readable(void)
|
|||
free(cc);
|
||||
free(st);
|
||||
free(sc);
|
||||
free(ss);
|
||||
free(so);
|
||||
free(si);
|
||||
}
|
||||
|
||||
/* Scans one file's text for every FORBIDDEN_INCLUDES entry, tagging each
|
||||
|
|
@ -261,6 +284,9 @@ static void test_core_never_includes_forbidden_headers(void)
|
|||
char* cc = read_whole_file(XCE_CORE_CLOCK_C_PATH);
|
||||
char* st = read_whole_file(XCE_SCHED_TRACE_C_PATH);
|
||||
char* sc = read_whole_file(XCE_SCHED_TRACE_CAPTURE_C_PATH);
|
||||
char* ss = read_whole_file(XCE_SCHED_TRACE_SCAN_C_PATH);
|
||||
char* so = read_whole_file(XCE_SCHED_TRACE_OUTCOME_C_PATH);
|
||||
char* si = read_whole_file(XCE_SCHED_TRACE_INTERNAL_H_PATH);
|
||||
|
||||
TEST_ASSERT_NOT_NULL(h);
|
||||
TEST_ASSERT_NOT_NULL(c);
|
||||
|
|
@ -269,6 +295,9 @@ static void test_core_never_includes_forbidden_headers(void)
|
|||
TEST_ASSERT_NOT_NULL(cc);
|
||||
TEST_ASSERT_NOT_NULL(st);
|
||||
TEST_ASSERT_NOT_NULL(sc);
|
||||
TEST_ASSERT_NOT_NULL(ss);
|
||||
TEST_ASSERT_NOT_NULL(so);
|
||||
TEST_ASSERT_NOT_NULL(si);
|
||||
|
||||
assert_no_forbidden_includes(h, "core.h");
|
||||
assert_no_forbidden_includes(c, "core.c");
|
||||
|
|
@ -277,6 +306,9 @@ static void test_core_never_includes_forbidden_headers(void)
|
|||
assert_no_forbidden_includes(cc, "core_clock.c");
|
||||
assert_no_forbidden_includes(st, "sched_trace.c");
|
||||
assert_no_forbidden_includes(sc, "sched_trace_capture.c");
|
||||
assert_no_forbidden_includes(ss, "sched_trace_scan.c");
|
||||
assert_no_forbidden_includes(so, "sched_trace_outcome.c");
|
||||
assert_no_forbidden_includes(si, "sched_trace_internal.h");
|
||||
|
||||
free(h);
|
||||
free(c);
|
||||
|
|
@ -285,6 +317,9 @@ static void test_core_never_includes_forbidden_headers(void)
|
|||
free(cc);
|
||||
free(st);
|
||||
free(sc);
|
||||
free(ss);
|
||||
free(so);
|
||||
free(si);
|
||||
}
|
||||
|
||||
/* Shared by test_core_never_calls_forbidden_symbols() -- see
|
||||
|
|
@ -313,6 +348,9 @@ static void test_core_never_calls_forbidden_symbols(void)
|
|||
char* cc = read_whole_file(XCE_CORE_CLOCK_C_PATH);
|
||||
char* st = read_whole_file(XCE_SCHED_TRACE_C_PATH);
|
||||
char* sc = read_whole_file(XCE_SCHED_TRACE_CAPTURE_C_PATH);
|
||||
char* ss = read_whole_file(XCE_SCHED_TRACE_SCAN_C_PATH);
|
||||
char* so = read_whole_file(XCE_SCHED_TRACE_OUTCOME_C_PATH);
|
||||
char* si = read_whole_file(XCE_SCHED_TRACE_INTERNAL_H_PATH);
|
||||
|
||||
TEST_ASSERT_NOT_NULL(h);
|
||||
TEST_ASSERT_NOT_NULL(c);
|
||||
|
|
@ -321,6 +359,9 @@ static void test_core_never_calls_forbidden_symbols(void)
|
|||
TEST_ASSERT_NOT_NULL(cc);
|
||||
TEST_ASSERT_NOT_NULL(st);
|
||||
TEST_ASSERT_NOT_NULL(sc);
|
||||
TEST_ASSERT_NOT_NULL(ss);
|
||||
TEST_ASSERT_NOT_NULL(so);
|
||||
TEST_ASSERT_NOT_NULL(si);
|
||||
|
||||
assert_no_forbidden_symbols(h, "core.h");
|
||||
assert_no_forbidden_symbols(c, "core.c");
|
||||
|
|
@ -329,6 +370,9 @@ static void test_core_never_calls_forbidden_symbols(void)
|
|||
assert_no_forbidden_symbols(cc, "core_clock.c");
|
||||
assert_no_forbidden_symbols(st, "sched_trace.c");
|
||||
assert_no_forbidden_symbols(sc, "sched_trace_capture.c");
|
||||
assert_no_forbidden_symbols(ss, "sched_trace_scan.c");
|
||||
assert_no_forbidden_symbols(so, "sched_trace_outcome.c");
|
||||
assert_no_forbidden_symbols(si, "sched_trace_internal.h");
|
||||
|
||||
free(h);
|
||||
free(c);
|
||||
|
|
@ -337,6 +381,9 @@ static void test_core_never_calls_forbidden_symbols(void)
|
|||
free(cc);
|
||||
free(st);
|
||||
free(sc);
|
||||
free(ss);
|
||||
free(so);
|
||||
free(si);
|
||||
}
|
||||
|
||||
/* core_clock.c is deliberately NOT scanned here -- it is the one
|
||||
|
|
@ -386,8 +433,9 @@ static void test_core_never_calls_wallclock_functions(void)
|
|||
/*
|
||||
* No stdio, no file I/O, no logging in the decision trace sources
|
||||
* (scheduler-sim-decision-trace design.md, TraceModel / TraceCapture: "No
|
||||
* I/O and no tlog"). Applies to sched_trace.c and sched_trace_capture.c
|
||||
* only.
|
||||
* I/O and no tlog"). Applies to the trace sources only: sched_trace.c,
|
||||
* sched_trace_scan.c, sched_trace_capture.c, sched_trace_outcome.c and
|
||||
* sched_trace_internal.h.
|
||||
*
|
||||
* Comments, string literals and character literals are blanked before the
|
||||
* scan, so prose such as "never calls fopen" in a comment is not a hit.
|
||||
|
|
@ -650,15 +698,27 @@ static void test_trace_sources_do_no_stdio_or_file_io(void)
|
|||
{
|
||||
char* st = read_whole_file(XCE_SCHED_TRACE_C_PATH);
|
||||
char* sc = read_whole_file(XCE_SCHED_TRACE_CAPTURE_C_PATH);
|
||||
char* ss = read_whole_file(XCE_SCHED_TRACE_SCAN_C_PATH);
|
||||
char* so = read_whole_file(XCE_SCHED_TRACE_OUTCOME_C_PATH);
|
||||
char* si = read_whole_file(XCE_SCHED_TRACE_INTERNAL_H_PATH);
|
||||
|
||||
TEST_ASSERT_NOT_NULL(st);
|
||||
TEST_ASSERT_NOT_NULL(sc);
|
||||
TEST_ASSERT_NOT_NULL(ss);
|
||||
TEST_ASSERT_NOT_NULL(so);
|
||||
TEST_ASSERT_NOT_NULL(si);
|
||||
|
||||
assert_trace_source_has_no_io(st, "sched_trace.c");
|
||||
assert_trace_source_has_no_io(sc, "sched_trace_capture.c");
|
||||
assert_trace_source_has_no_io(ss, "sched_trace_scan.c");
|
||||
assert_trace_source_has_no_io(so, "sched_trace_outcome.c");
|
||||
assert_trace_source_has_no_io(si, "sched_trace_internal.h");
|
||||
|
||||
free(st);
|
||||
free(sc);
|
||||
free(ss);
|
||||
free(so);
|
||||
free(si);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -62,7 +62,9 @@
|
|||
#include "../src/driver.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
|
||||
void kill_sched_job(void* arg) { (void)arg; }
|
||||
void kill_sched_queue(void* arg) { (void)arg; }
|
||||
|
|
|
|||
|
|
@ -165,7 +165,9 @@
|
|||
#include "../src/driver.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
#include "../src/plugin.c"
|
||||
|
||||
void kill_sched_job(void* arg) { (void)arg; }
|
||||
|
|
|
|||
|
|
@ -60,7 +60,9 @@
|
|||
#include "../src/driver.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
|
||||
void kill_sched_job(void* arg) { (void)arg; }
|
||||
void kill_sched_queue(void* arg) { (void)arg; }
|
||||
|
|
|
|||
|
|
@ -70,7 +70,9 @@
|
|||
#include "../src/host_iter.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
|
||||
/* Link-satisfying stubs -- identical set to test_resource_accounting.c's
|
||||
* own (see that file's header comment for the full rationale). None of
|
||||
|
|
|
|||
|
|
@ -96,7 +96,9 @@
|
|||
#include "../src/resource.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
#include "../src/schedobj.c"
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
|
|
|||
|
|
@ -97,7 +97,9 @@
|
|||
#include "../src/driver.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
#include "../src/plugin.c"
|
||||
#include "../src/recording.c"
|
||||
#include "../src/recording_events.c"
|
||||
|
|
|
|||
|
|
@ -92,7 +92,9 @@
|
|||
#include "../src/resource.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
#include "../src/schedobj.c"
|
||||
|
||||
void plugin_unregister(void* arg) { (void)arg; }
|
||||
|
|
|
|||
|
|
@ -87,7 +87,9 @@
|
|||
#include "../src/driver.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
|
||||
void kill_sched_job(void* arg) { (void)arg; }
|
||||
void kill_sched_queue(void* arg) { (void)arg; }
|
||||
|
|
|
|||
|
|
@ -52,7 +52,9 @@
|
|||
#include "../src/alloc_runner.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
#include "../src/plugins/seqjob_allocator.c"
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
|
|
|||
|
|
@ -95,7 +95,9 @@
|
|||
#include "../src/alloc_runner.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
#include "../src/plugins/seqjob_allocator.c"
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
|
|
|||
|
|
@ -208,7 +208,9 @@
|
|||
#include "../src/resource.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
#include "../src/sim.c"
|
||||
#include "../src/sim_loop.c"
|
||||
#include "../src/sim_picker.c" /* bug-1567: sched_sim_driver_run() calls sim_bootstrap_picker() (sim_loop.c-cap sibling) as an extern -- undefined reference at link time otherwise */
|
||||
|
|
|
|||
|
|
@ -202,7 +202,9 @@
|
|||
#include "../src/resource.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
#include "../src/sim.c"
|
||||
#include "../src/sim_loop.c"
|
||||
#include "../src/sim_picker.c" /* bug-1567: sched_sim_driver_run() calls sim_bootstrap_picker() (sim_loop.c-cap sibling) as an extern -- undefined reference at link time otherwise */
|
||||
|
|
|
|||
|
|
@ -191,7 +191,9 @@
|
|||
#include "../src/driver.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
#include "../src/plugin.c"
|
||||
#include "../src/plugins/builtin_fcfs.c"
|
||||
#include "../src/recording.c"
|
||||
|
|
|
|||
|
|
@ -139,7 +139,9 @@
|
|||
#include "../src/resource.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
#include "../src/sim.c"
|
||||
#include "../src/sim_loop.c"
|
||||
#include "../src/sim_picker.c" /* bug-1567: sim_loop.c calls sim_bootstrap_picker() (sim_loop.c-cap sibling) as an extern -- undefined reference at link time otherwise */
|
||||
|
|
|
|||
|
|
@ -146,7 +146,9 @@
|
|||
#include "../src/resource.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
#include "../src/sim.c"
|
||||
#include "../src/sim_loop.c"
|
||||
#include "../src/sim_picker.c" /* bug-1567: sched_sim_driver_run() now calls sim_bootstrap_picker() (sim_loop.c-cap sibling) as an extern -- undefined reference at link time otherwise */
|
||||
|
|
|
|||
|
|
@ -114,7 +114,9 @@
|
|||
#include "../src/resource.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
#include "../src/sim.c"
|
||||
#include "../src/sim_loop.c"
|
||||
#include "../src/sim_picker.c" /* bug-1567: sched_sim_driver_run() calls sim_bootstrap_picker() (sim_loop.c-cap sibling) as an extern -- undefined reference at link time otherwise */
|
||||
|
|
|
|||
|
|
@ -136,7 +136,9 @@
|
|||
#include "../src/driver.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
#include "../src/plugins/builtin_fcfs.c"
|
||||
#include "../src/recording.c"
|
||||
#include "../src/recording_events.c"
|
||||
|
|
|
|||
|
|
@ -101,7 +101,9 @@
|
|||
#include "../src/resource.c"
|
||||
/* Hooked sources compile SCHED_TRACE bodies (scheduler-sim-decision-trace). */
|
||||
#include "../src/sched_trace.c"
|
||||
#include "../src/sched_trace_scan.c"
|
||||
#include "../src/sched_trace_capture.c"
|
||||
#include "../src/sched_trace_outcome.c"
|
||||
#include "../src/snapshot.c"
|
||||
#include "../src/snapshot_load.c"
|
||||
#include "../src/snapshot_wire.c"
|
||||
|
|
|
|||
|
|
@ -40,8 +40,9 @@
|
|||
* - with 50 or fewer hosts the pend copy equals its source under
|
||||
* job_preason_eq(), the equality libxce defines for pend reasons.
|
||||
*
|
||||
* Links src/sched_trace.c and src/sched_trace_capture.c, plus libxce for
|
||||
* the pend-reason, resmgr and container helpers.
|
||||
* Links the trace model (src/sched_trace.c, sched_trace_scan.c) and the
|
||||
* capture sources (src/sched_trace_capture.c, sched_trace_outcome.c), plus
|
||||
* libxce for the pend-reason, resmgr and container helpers.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@
|
|||
* tallying hits per limit (5.9, 5.10);
|
||||
* - consecutive released-host calls merge into one rollback entry (5.8).
|
||||
*
|
||||
* Links only src/sched_trace.c (plus libxce for the jobid helpers).
|
||||
* Links only the model, src/sched_trace.c and src/sched_trace_scan.c (plus
|
||||
* libxce for the jobid helpers).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue