Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/api/system_timing.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ the window — the measurement is the system's work, not the
scheduler's. The sample is handed to the system's rolling window and
the budget check in schedule order.

A run shorter than the platform's `steady_clock` tick measures as
exactly `0.0` ms (the start and end reads land on the same tick —
e.g. a sub-tick run on a platform whose clock tick is tens of
nanoseconds or more). `0.0` is a legitimate reading, not a failure
state: it records in the window like any sample, and the budget check
simply sees a run far under budget. Resolution is platform-sensitive
(ARCH-009), so consumers must not treat a `0.0` measurement as
"not measured".

## The rolling window

One `Histogram` per system (M0-CORE-08): fixed capacity
Expand Down
8 changes: 4 additions & 4 deletions laige-api.json
Original file line number Diff line number Diff line change
Expand Up @@ -520,9 +520,9 @@
{"name": "laige::SystemInfo::declaresWrite", "kind": "method", "header": "src/laige-sim/include/laige/sim/system.h", "line": 514, "signature": "[[nodiscard]] bool declaresWrite(ComponentTypeId componentId) const noexcept", "summary": "True when the system declares `componentId` for writing.", "budget": "O(1); no allocation.", "experimental": false},
{"name": "laige::SystemTimingStats", "kind": "struct", "header": "src/laige-sim/include/laige/sim/system.h", "line": 536, "signature": "struct SystemTimingStats", "summary": "One system's measured-run scalars (M1-SYS-03; PRD §9.3 G-R5): the cheap per-frame snapshot the profiler (M1-PROF-01) and the frame graph (M1-PROF-02) pull through World::systemTimingStats(id) — O(1), no allocation, no side effects. The window SAMPLES are not here (the rolling histogram is read cold through World::systemTimingWindow(id) — its stats() is O(n log n)). All counters are since-construction; the window rolls across ticks (kSystemTimingWindowSamples, not per-frame).", "budget": null, "experimental": false},
{"name": "laige::SystemTimingStats::runs", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 538, "signature": "std::uint64_t runs{}", "summary": "Measured runs of the system since world construction.", "budget": null, "experimental": false},
{"name": "laige::SystemTimingStats::lastMs", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 541, "signature": "double lastMs{}", "summary": "The measured time (ms) of the most recent run (0 before the first run).", "budget": null, "experimental": false},
{"name": "laige::SystemTimingStats::warns", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 543, "signature": "std::uint32_t warns{}", "summary": "The system/budget_overrun warns issued since construction.", "budget": null, "experimental": false},
{"name": "laige::SystemTimingStats::errors", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 546, "signature": "std::uint32_t errors{}", "summary": "The system/budget_critical error events issued since construction.", "budget": null, "experimental": false},
{"name": "LAIGE_SYSTEM", "kind": "macro", "header": "src/laige-sim/include/laige/sim/system.h", "line": 570, "signature": "#define LAIGE_SYSTEM(Name, budget_ms, ...)", "summary": "Declare a system (FR-1.3): at namespace scope, directly above the plain system function's definition. `Name` is both the C++ function name and the system's registration name (stringified); `budget_ms` is the declared per-tick time budget in milliseconds (a numeric literal, e.g. 1 or 0.5 — converted to the exact fpx16_16 once, at program start); the optional trailing `Dep...` names are the depends_on spec (M1-SYS-02): the registration names of the systems `Name` must run after, stringified verbatim into the def's `dependsOn` field (comma-separated, as written). Expands to the function declaration plus", "budget": null, "experimental": false}
{"name": "laige::SystemTimingStats::lastMs", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 544, "signature": "double lastMs{}", "summary": "The measured time (ms) of the most recent run (0 before the first run). A run shorter than the platform's steady_clock tick measures as exactly 0.0 ms — a legitimate sub-resolution reading (wall-clock resolution is platform-sensitive; ARCH-009), not a failure state.", "budget": null, "experimental": false},
{"name": "laige::SystemTimingStats::warns", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 546, "signature": "std::uint32_t warns{}", "summary": "The system/budget_overrun warns issued since construction.", "budget": null, "experimental": false},
{"name": "laige::SystemTimingStats::errors", "kind": "variable", "header": "src/laige-sim/include/laige/sim/system.h", "line": 549, "signature": "std::uint32_t errors{}", "summary": "The system/budget_critical error events issued since construction.", "budget": null, "experimental": false},
{"name": "LAIGE_SYSTEM", "kind": "macro", "header": "src/laige-sim/include/laige/sim/system.h", "line": 573, "signature": "#define LAIGE_SYSTEM(Name, budget_ms, ...)", "summary": "Declare a system (FR-1.3): at namespace scope, directly above the plain system function's definition. `Name` is both the C++ function name and the system's registration name (stringified); `budget_ms` is the declared per-tick time budget in milliseconds (a numeric literal, e.g. 1 or 0.5 — converted to the exact fpx16_16 once, at program start); the optional trailing `Dep...` names are the depends_on spec (M1-SYS-02): the registration names of the systems `Name` must run after, stringified verbatim into the def's `dependsOn` field (comma-separated, as written). Expands to the function declaration plus", "budget": null, "experimental": false}
]
}
8 changes: 6 additions & 2 deletions src/laige-sim/include/laige/sim/system.h
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,10 @@ struct SystemTimingStats {
// Measured runs of the system since world construction.
std::uint64_t runs{};
// The measured time (ms) of the most recent run (0 before the first
// run).
// run). A run shorter than the platform's steady_clock tick
// measures as exactly 0.0 ms — a legitimate sub-resolution reading
// (wall-clock resolution is platform-sensitive; ARCH-009), not a
// failure state.
double lastMs{};
// The system/budget_overrun warns issued since construction.
std::uint32_t warns{};
Expand Down Expand Up @@ -597,7 +600,8 @@ struct SystemTimingRecord {
// constructor, so the record holds it as a unique_ptr — the one
// level of indirection is bounded by kMaxSystems).
std::unique_ptr<Histogram> window;
double lastMs{}; // most recent measured run (0 before first)
double lastMs{}; // most recent measured run (0 before first; a run
// shorter than the steady_clock tick reads 0.0)
std::uint64_t runs{}; // measured runs since construction
std::uint32_t warns{}; // budget_overrun warns issued
std::uint32_t errors{}; // budget_critical errors issued
Expand Down
14 changes: 11 additions & 3 deletions tests/laige-sim/system_timing_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ void burnMs(double ms) {
const auto start = std::chrono::steady_clock::now();
while (std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(
std::chrono::steady_clock::now() - start).count() < ms) {
gBurnSink += 1;
// Ordinary assignment: compound assignment to a volatile is
// deprecated in C++20 (P1152R2) and a hard error under
// -Werror,-Wdeprecated-volatile (AppleClang on the macos-14 job).
gBurnSink = gBurnSink + 1;
}
}

Expand Down Expand Up @@ -306,8 +309,13 @@ TEST(SystemTiming, HealthyTicksLogNothingAndTrackStats) {
EXPECT_EQ(st.value().warns, 0u);
EXPECT_EQ(st.value().errors, 0u);
// A noop system on an empty world runs in microseconds — well
// under its 1 ms budget, with margin for a slow machine.
EXPECT_GT(st.value().lastMs, 0.0);
// under its 1 ms budget, with margin for a slow machine. The lower
// bound is >= 0.0, not > 0.0: a run shorter than the platform's
// steady_clock tick measures as exactly 0.0 ms (the start and end
// reads land on the same tick), and that sub-resolution reading is
// a legitimate value, not a failure (the tracked-state check is
// the runs counter above; lastMs is a diagnostic).
EXPECT_GE(st.value().lastMs, 0.0);
EXPECT_LT(st.value().lastMs, 1.0);

const laige::Histogram* win = w.systemTimingWindow(SystemId{1});
Expand Down
Loading