Per-Run Step Timeline (waterfall + bottleneck steps)
The action profiler aggregates timings by step name across many runs — great
for “which action is slow on average”, useless for “why was this run slow”. A
single run is an ordered timeline: step A ran, then B, then C, and one of them
dominated. step_timeline turns one run’s steps into a waterfall (each step’s
offset from the start, its duration and its share of the total) and ranks the
bottleneck steps, so you can read a single slow run instead of an average.
build_timeline()— the waterfall + total / busy / bottleneck / parallelism,critical_steps()— the steps that dominate the run, longest first.
A step is any dict with a name (default "name") and a duration; an
optional start places it on an absolute timeline (overlapping / parallel
steps), else steps are laid out back-to-back. Pure standard library; no device,
no PySide6.
Headless API
from je_auto_control import build_timeline, critical_steps
steps = [{"name": "login", "duration": 1.0},
{"name": "load_dashboard", "duration": 4.0},
{"name": "submit", "duration": 1.0}]
build_timeline(steps)
# {"steps": [{"name": "login", "offset": 0.0, "duration": 1.0, "pct": 16.7},
# {"name": "load_dashboard", "offset": 1.0, ..., "pct": 66.7}, ...],
# "total": 6.0, "busy": 6.0,
# "bottleneck": {"name": "load_dashboard", "duration": 4.0},
# "parallelism": 1.0}
critical_steps(steps, top=2)
# [{"name": "load_dashboard", "duration": 4.0, "pct": 66.7},
# {"name": "login", "duration": 1.0, "pct": 16.7}]
total is the wall-clock span, busy the summed step time; parallelism =
busy / total is 1.0 for a purely sequential run and > 1 when steps overlap
(supply start times). pct is each step’s share of the total time.
Executor commands
AC_build_timeline (steps) and AC_critical_steps (steps / top).
They are exposed as read-only ac_* MCP tools and as Script Builder commands
under Testing.