loop_monitor.hpp¶
LoopMonitor — loop-overrun / tick-timing detection.
This header declares 2 types (6 members).
Extracted from include/shulib/diag/loop_monitor.hpp — this page is that header's documentation, reformatted, so it cannot disagree with the code. Prose about how to think about the API lives in the user guide; worked recipes live in the cookbook; this page is the complete, mechanical list of what exists.
Contents¶
struct LoopMonitorConfig¶
LoopMonitor's one tuning knob, taken BY VALUE at construction — editing the struct afterwards has no effect on a live monitor. The 15 ms default leaves 5 ms of margin on the nominal 10 ms control loop; see budget for why it must not simply equal the tick period.
struct, declared at include/shulib/diag/loop_monitor.hpp:42.
LoopMonitorConfig::budget¶
The dt at which a tick counts as an overrun (INCLUSIVE — see header). Must be > 0 and strictly greater than the nominal tick period.
field, declared at include/shulib/diag/loop_monitor.hpp:45.
class LoopMonitor¶
Loop-overrun detection: it measures the real dt between consecutive tick() calls on the INJECTED clock and raises FaultCode::LoopOverrun through the latch when a tick reaches its budget. It exists because a blown control tick silently corrupts every dt-dependent computation downstream — PID derivative and integral, profile sampling, the odometry twist — which is how a promised sub-degree heading quietly decays into drift nobody can explain. Single-task by contract, like the rest of the diagnostics layer.
class, declared at include/shulib/diag/loop_monitor.hpp:54.
LoopMonitor::LoopMonitor¶
clock and faults are held BY REFERENCE and must outlive the monitor; config is copied. config.budget must be > 0 (precondition) and, to be usable at all, strictly greater than the nominal tick period — a tick exactly AT the budget is an overrun, so a 10 ms budget on a 10 ms loop faults on every tick.
function, declared at include/shulib/diag/loop_monitor.hpp:60.
LoopMonitor::tick¶
Call exactly once per loop iteration. Returns this tick's measured dt (0 on the baseline tick). Raises LOOP_OVERRUN via the latch when dt >= budget.
function, declared at include/shulib/diag/loop_monitor.hpp:67.
LoopMonitor::worstDt¶
Largest dt observed since construction (the §18.3 "worst loop dt" summary quantity, consumed at C5). Time{0} until two ticks have happened.
function, declared at include/shulib/diag/loop_monitor.hpp:89.
LoopMonitor::overrunCount¶
How many ticks have reached the budget since construction. It counts TICKS, not episodes — a loop that stays slow increments (and raises, and logs) once per tick — and reset() does not clear it, so this is a whole-run total. Baseline ticks never count.
function, declared at include/shulib/diag/loop_monitor.hpp:93.
LoopMonitor::reset¶
Re-baseline after a DELIBERATE gap (run boundary, pause): the next tick() only baselines, so the gap is not misreported as an overrun. Keeps worstDt/counts.
function, declared at include/shulib/diag/loop_monitor.hpp:97.
Design commentary, from the header¶
The header opens with the reasoning behind these shapes. It is reproduced here in full because a reference that only lists signatures teaches nobody why.
The header’s own reasoning — 26 lines
LoopMonitor — loop-overrun / tick-timing detection (master plan §18.4; WS13, chunk A1).
Why this exists: a blown control tick silently corrupts every dt-dependent computation
downstream — PID derivative/integral, profile sampling, the odometry twist — which is
exactly how the promised < 1° heading quietly degrades. The monitor measures the real
dt between consecutive tick() calls on the injected clock and raises LOOP_OVERRUN
through the FaultLatch when a tick reaches its budget, so a timing pathology becomes a
visible fault instead of a mystery drift.
BOUNDARY SEMANTICS (pinned by test, mutation-checked): a tick whose dt satisfies
dt >= budget — INCLUSIVE at the budget —
is an overrun. The budget is a hard deadline in the deadline-scheduling sense: a loop
that consumes its entire budget has zero margin left and the next tick already starts
late, so "exactly at budget" is a miss, not a pass. Consequence for configuration: the
budget must be strictly GREATER than the nominal tick period (a healthy 10ms loop has
dt == 10ms every tick; a 10ms budget would fault permanently — use e.g. 15ms).
The first tick() after construction or reset() only baselines the clock (there is no
previous tick to difference against) — it can never fault, returns dt = 0, and does
not count toward worstDt(). reset() exists for deliberate pauses (e.g. between runs)
so a legitimate gap is not reported as an overrun.
Single-task by contract, like the rest of diag/ (see fault.hpp's concurrency note).
Last updated 2026-08-15 — published from the main release branch, commit fa545ff. This site tracks releases, so between them it can lag the repository — that lag is deliberate, and it is what keeps the development log off the public site. For the current state of the code, see the repository.