profiling.step_profiler¶
Deep per-step runtime profiler for raccoon missions.
The mission step machinery wraps every user step in Step.run_step(), which
does more than just run the step: it reads/writes the SQLite timing database,
acquires hardware-resource locks, generates signatures and logs. When a robot
“stands still” between two steps, the standstill is this overhead — not the
step body — and until now there was no way to see where it actually goes.
StepProfiler answers that by instrumenting the machinery at runtime (no edits
to the core). For every step it records a phase breakdown:
total = execute + db_read + db_write + acquire + (unattributed overhead)
and it runs an event-loop-lag monitor in parallel, which reveals whether something blocks the asyncio loop (a synchronous C++/HAL call, an fsync that doesn’t really yield, GC pauses, …). That distinguishes “Python overhead between steps” from “a blocking call stalls everything”.
Usage (wrap the mission run):
from raccoon.profiling import StepProfiler
async def main():
async with StepProfiler(trace_path="profile.json") as prof:
await mission.run(robot)
# report prints automatically on exit; prof holds the raw samples
Then open profile.json at chrome://tracing (or edge://tracing /
https://ui.perfetto.dev) to see the gaps between steps on a timeline.
It also works as a plain with block if you are not inside async code yet;
the loop-lag monitor then starts lazily on the first step.
Classes¶
A timed sub-region of a step (execute / db_read / db_write / acquire). |
|
One execution of one step, with its phase breakdown. |
|
Runtime profiler that decomposes per-step wall time and event-loop lag. |
Module Contents¶
- class profiling.step_profiler.Phase¶
A timed sub-region of a step (execute / db_read / db_write / acquire).
- class profiling.step_profiler.StepSample¶
One execution of one step, with its phase breakdown.
- property body_compute: float¶
Step-body time that is neither a deliberate sleep nor a HAL call.
This is the “stands still but profiling shows no wait” budget: pure Python compute plus any blocking that escaped attribution.
halis a subset ofexecute(for motion steps it happens insideon_update), so it is subtracted here only to isolate the remainder.
- class profiling.step_profiler.StepProfiler(trace_path: str | None = None, *, loop_lag: bool = True, loop_lag_interval: float = 0.005, lag_spike_ms: float = 5.0, print_report: bool = True, top: int = 25, hal: bool = True, sleep_track: bool = True, hal_span_ms: float = 1.0, defer_trace: bool = True)¶
Runtime profiler that decomposes per-step wall time and event-loop lag.
- Parameters:
trace_path – If set, write a Chrome-tracing (
chrome://tracing) JSON file here on exit. Open it there or at https://ui.perfetto.dev.loop_lag – Run the event-loop-lag monitor (default True).
loop_lag_interval – Sampling period of the lag monitor, seconds. Smaller = finer resolution but more samples. Default 0.005 (5 ms).
lag_spike_ms – Lag above this (ms) is recorded as a spike and shown in the report / trace. Default 5 ms.
print_report – Print the text report on exit (default True).
top – How many step signatures to show in the per-signature table.
defer_trace – Buffer the Chrome-trace write in RAM instead of writing it to disk when the profiled block exits, and flush all buffered traces in one go via
flush_traces()after the run. Default True — a per-mission profiler otherwise json.dumps a multi-MB trace to the Pi’s SD card between missions, standing the robot still (~1.8 s for the setup trace) and perturbing the very run it measures.
- trace_path = None¶
- loop_lag = True¶
- loop_lag_interval = 0.005¶
- lag_spike_ms = 5.0¶
- print_report = True¶
- top = 25¶
- hal = True¶
- sleep_track = True¶
- hal_span_ms = 1.0¶
- defer_trace = True¶
- samples: list[StepSample] = []¶
- classmethod from_env(env: dict[str, str] | None = None) StepProfiler | None¶
Build a profiler from environment variables, or
Noneif disabled.Profiling is off unless
RACCOON_PROFILEis set to a truthy value. Every knob ofStepProfileris exposed so a run can be tuned without touching any code:Environment variable
Effect
RACCOON_PROFILEMaster switch. A path (contains
/or ends.json) is used as the Chrome-trace path.1/true/on/yesenables with the default trace path (raccoon-profile.json).0/false/off/""disables.RACCOON_PROFILE_TRACEExplicit trace path (overrides the path inferred from
RACCOON_PROFILE; set to empty to skip the trace file).RACCOON_PROFILE_REPORT0/offto suppress the text report (default on).RACCOON_PROFILE_LOOP_LAG0/offto disable the event-loop-lag monitor (default on).RACCOON_PROFILE_LAG_INTERVAL_MSLag sampling period in ms (default 5).
RACCOON_PROFILE_LAG_SPIKE_MSSpike threshold in ms (default 5).
RACCOON_PROFILE_TOPRows in the per-signature table (default 25).
RACCOON_PROFILE_HAL0/offto disable timing of synchronous HAL/transport calls (set_position,set_velocity, …) made inside a step body (default on).RACCOON_PROFILE_SLEEP0/offto disable splitting deliberateasyncio.sleepwaits out of the step body (default on).RACCOON_PROFILE_HAL_SPAN_MSOnly HAL calls at least this long (ms) get an individual trace span (the per-method totals always accumulate). Default 1 ms.
RACCOON_PROFILE_DEFER0/offto write each trace inline when its block exits instead of buffering it for a single post-run flush (default on — keeps the multi-MB json.dump off the between-missions hot path).Example:
RACCOON_PROFILE=1 RACCOON_PROFILE_LAG_SPIKE_MS=2 uv run raccoon run
- classmethod flush_traces() list[str]¶
Write every buffered Chrome trace to disk; return the paths written.
Call once after the run (robot idle) to drain the traces buffered by deferred per-mission profilers. Failures on one trace never block the others. Idempotent: a second call finds an empty queue.