docs: add py-spy profiling recipe to CLAUDE.md
Captures how we found the hot Params writes in carstate.py so the same technique can be repeated. Includes the awk aggregators for per-callsite and per-file-line breakdowns. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
37
CLAUDE.md
37
CLAUDE.md
@@ -195,6 +195,43 @@ The UI process runs a ZMQ REP server at `ipc:///tmp/clearpilot_ui_rpc`. Send `"d
|
|||||||
- **`showDriverView` overriding transitions (fixed)**: was forcing `slayout` to onroad/home every frame at 20Hz, overriding park/drive logic. Fixed to only act when not in started state.
|
- **`showDriverView` overriding transitions (fixed)**: was forcing `slayout` to onroad/home every frame at 20Hz, overriding park/drive logic. Fixed to only act when not in started state.
|
||||||
- **Sidebar appearing during onroad transition (fixed)**: `MainWindow::closeSettings()` was re-enabling the sidebar. Fixed by not calling `closeSettings` during `offroadTransition`.
|
- **Sidebar appearing during onroad transition (fixed)**: `MainWindow::closeSettings()` was re-enabling the sidebar. Fixed by not calling `closeSettings` during `offroadTransition`.
|
||||||
|
|
||||||
|
## Performance Profiling
|
||||||
|
|
||||||
|
Use `py-spy` to find CPU hotspots in any Python process. It's installed at `/home/comma/.local/bin/py-spy`. (If missing: `su - comma -c "/usr/local/pyenv/versions/3.11.4/bin/pip install py-spy"`.)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Find the target pid
|
||||||
|
ps -eo pid,cmd | grep -E "selfdrive.controls.controlsd" | grep -v grep
|
||||||
|
|
||||||
|
# Record 10s of stacks at 200Hz, raw (folded) format
|
||||||
|
/home/comma/.local/bin/py-spy record -o /tmp/ctrl.txt --pid <PID> --duration 10 --rate 200 --format raw
|
||||||
|
|
||||||
|
# Aggregate: which line of step() is consuming the most samples
|
||||||
|
awk -F';' '{
|
||||||
|
for(i=1;i<=NF;i++) if ($i ~ /step \(selfdrive\/controls\/controlsd.py/) step_line=i;
|
||||||
|
if (step_line && step_line < NF) {
|
||||||
|
n=split($NF, parts, " "); count=parts[n];
|
||||||
|
caller = $(step_line+1);
|
||||||
|
sum[caller] += count;
|
||||||
|
}
|
||||||
|
step_line=0;
|
||||||
|
} END { for (c in sum) printf "%6d %s\n", sum[c], c }' /tmp/ctrl.txt | sort -rn | head -15
|
||||||
|
|
||||||
|
# Aggregate by a source file — shows hottest lines in that file
|
||||||
|
awk -F';' '{
|
||||||
|
for(i=1;i<=NF;i++) if ($i ~ /carstate\.py:/) {
|
||||||
|
match($i, /:[0-9]+/); ln = substr($i, RSTART+1, RLENGTH-1);
|
||||||
|
n=split($NF, parts, " "); count=parts[n];
|
||||||
|
sum[ln] += count;
|
||||||
|
}
|
||||||
|
} END { for (l in sum) printf "%5d line %s\n", sum[l], l }' /tmp/ctrl.txt | sort -rn | head -15
|
||||||
|
|
||||||
|
# Quick stack dump (single sample, no recording)
|
||||||
|
/home/comma/.local/bin/py-spy dump --pid <PID>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Known performance trap — hot `Params` writes**: `Params.put()` does `mkstemp` + `fsync` + `flock` + `rename` + `fsync_dir`. At 100Hz even on tmpfs the `flock` contention is ruinous. Cache the last-written value and skip writes when unchanged. Found this pattern in `carstate.py` and `controlsd.py` — controlsd went from 69% → 28% CPU after gating writes.
|
||||||
|
|
||||||
## Session Logging
|
## Session Logging
|
||||||
|
|
||||||
Per-process stderr and an aggregate event log are captured in `/data/log2/current/`.
|
Per-process stderr and an aggregate event log are captured in `/data/log2/current/`.
|
||||||
|
|||||||
Reference in New Issue
Block a user