summaryrefslogtreecommitdiff
path: root/docs/superpowers/specs/2026-07-25-sysview-v2-postmortem-design.md
blob: c36f6fd7cd744ef6242d45989163831d25969edb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# sysview skill v2 — post-mortem autopsy + instrumentation quick wins — Design

Date: 2026-07-25
Branch: claude/add-systemview-debug (builds on the sysview skill, f198be682)

## Goal

Close the gaps between the sysview skill and the SystemView feature set
(UM08027) that matter for USB debugging: crash-context capture (post-mortem
mode) and three small instrumentation recipes (markers, event filtering,
PrintfHost), plus the reporter columns to make them useful without the GUI.

## Context

The sysview skill (`.claude/skills/sysview/`) covers continuous J-Link
recording, FreeRTOS task events, manual ISR wrap, contexts/CPU-load analysis,
and CSV export — hardware-validated on same54_xplained + J-Trace. A gap
analysis against UM08027 found the highest-value uncovered features to be
post-mortem mode (§3.15.3) and the DisableEvents/marker/Printf APIs. The
`--export-terminal` flag exists but has never been exercised with real
`PrintfHost` output.

## Design

### 1. Post-mortem capture path

- **Build**: `sysview.cmake` gains `-DSYSVIEW_POST_MORTEM=1`, defining
  `SEGGER_SYSVIEW_POST_MORTEM_MODE=1` (UM08027 §4.5.2.4).
  `SEGGER_SYSVIEW_SYNC_PERIOD_SHIFT` stays at its target-source default
  (8 = sync every 256 events); add an override only if validation shows
  resync gaps in decoded dumps. The target then
  writes the SysView RTT ring with overwrite — no host reader needed; the
  buffer always holds the most recent scheduling history (~last seconds,
  buffer-size dependent). The existing config's target-side DWT CYCCNT enable
  already satisfies the manual's ENABLE_DWT_CYCCNT requirement (no debugger
  attached while recording).
- **Dump**: new script `scripts/sysview_dump.py` — a separate unit from the
  live recorder (nothing in common with Xvfb/GUI choreography). Flow: resolve
  probe (shared helper pattern), attach **without reset or reflash** (evidence
  preservation, same rule as target-debug's fault autopsy), halt, read the
  `_SEGGER_RTT.aUp[1]` descriptor (pBuffer/SizeOfBuffer/WrOff/RdOff) via
  JLinkExe, dump the buffer memory, and linearize oldest→newest. **Split
  point to be determined on hardware**: ring logic says `[WrOff..end) +
  [0..WrOff)`, but the manual's literal text (§3.15.3) ends the second chunk
  at `RdOff - 1` — with no host reader these may or may not coincide.
  Implementation decodes both candidates; the one yielding a sane timeline
  wins and gets recorded in the SKILL. Write `capture.SVDat` into `--out`.
- **Decode**: unchanged — `sysview_record.py --from-raw <capture.SVDat>`
  already parses a raw stream via SystemView `-load` and exports CSV.
- **Routing**: one cross-ref line in target-debug's vector-catch/fault-autopsy
  section: a post-mortem sysview build answers "what was the system doing
  right before the fault/wedge".
- **Caveats documented in SKILL.md**: post-mortem and live recording are
  mutually exclusive build modes (the live GUI recorder cannot drain an
  overwrite-mode ring); halting for the dump kills USB service (host URB
  timeouts), which is acceptable post-crash.

### 2. Instrumentation quick wins (SKILL.md optional edits 3–5)

- **Markers**: `SEGGER_SYSVIEW_MarkStart(id)/MarkStop(id)` bracketing one code
  path (e.g. a transfer, an enumeration phase), with `NameMarker` for the
  label.
- **Event filtering**: `SEGGER_SYSVIEW_DisableEvents(<mask>)` after `Conf()`
  as the documented overflow fix — keeps task/ISR events, drops the highest-
  rate classes. The exact mask combination is determined empirically during
  implementation on hardware and recorded in the SKILL (not guessed here).
- **PrintfHost**: `SEGGER_SYSVIEW_PrintfHost()` for log lines correlated with
  the timeline; exercised in validation so `--export-terminal` is proven.

### 3. Reporter additions (`sysview_report.py`)

- Surface `Total Blocked Time` per context (column already present in
  contexts.csv, currently dropped).
- Marker-pair durations from events.txt: per marker id, n/p50/p99/max — same
  table shape as the existing ISR/ready→run tables.

## Error handling

- `sysview_dump.py`: refuses to run if the RTT magic ("SEGGER RTT") is absent
  at the given/ELF-derived address (wrong ELF or corrupted RAM); reports
  whether the buffer had wrapped (WrOff vs sync coverage); resumes the core
  only if it halted it and `--resume` is passed (default: leave halted — the
  user is mid-autopsy).
- Decode of a wrapped ring starting mid-packet is expected to produce leading
  garbage until the first sync — the SYNC_PERIOD_SHIFT packets exist for
  this; the SKILL documents "leading events before the first sync are
  unreliable".

## Validation (same54_xplained + jtrace, dogfood pattern)

1. Post-mortem: flash `-DSYSVIEW_POST_MORTEM=1` build, run CDC bulk traffic,
   then run `sysview_dump.py` mid-load — its halt IS the simulated crash —
   and decode via `--from-raw`. Expect plausible contexts and the traffic
   window's tail present in the timeline.
2. Quick wins, deterministic overflow A/B at `-DSYSVIEW_BUFFER_SIZE=4096`
   (the size that reliably overflowed in v1 under CDC bulk load with API
   tracing): baseline run shows nonzero overflow; same run with the
   validated DisableEvents mask shows overflow 0. Same instrumented build
   carries a marker pair + PrintfHost — expect the marker table populated
   and terminal.csv containing the Printf lines.
3. Both scripts pass `python3 -m py_compile`; pre-commit clean; instrumentation
   edits reverted and pristine firmware reflashed afterward (skill's own
   rule).

## Scope revision (2026-07-25, user-directed — supersedes parts of the above)

- **SystemView becomes a first-class optional dependency**:
  `tools/get_deps.py` entry `lib/SystemView` →
  github.com/SEGGERMicro/SystemView pinned at the V4.12.0 tag commit
  (92ca7a810c5765ba64911919acd511c61b6b083f). `sysview.cmake` consumes it;
  the ~/.cache ad-hoc clone goes away.
- **Leveled instrumentation** (2026-07-25 refinement): not a boolean —
  `CFG_TUD_SYSVIEW` and `CFG_TUH_SYSVIEW` are levels 0–4 (0 = off), like
  `CFG_TUSB_DEBUG`. Site macros `TUD_SYSVIEW_CALL/RET(level, id)` (and
  `TUH_`) expand to nothing when the configured level is below the site's
  level, via the same token-paste dispatch as `TU_LOG(n, …)`. Category
  levels are macro-configurable with defaults: **USB ISR = 1, usbd/usbh
  functions = 2, dcd/hcd API = 3, class-driver API = 4**. dcd/hcd
  instrumentation wraps the call sites in usbd.c/usbh.c (the port boundary),
  never the portable drivers themselves. Build: `-DSYSVIEW=<level>` sets
  both sides (ON = 4).
- **Instrumentation moves in-tree as first-class analysis**:
  `SEGGER_SYSVIEW_Config_TinyUSB.c` becomes `src/common/tusb_sysview.c/.h`
  behind a `CFG_TUSB_SYSVIEW` option (default 0; empty macros, zero code/size
  when off — proven with a code-size compare). Adds `TU_SYSVIEW_*`
  enter/exit macros placed in usbd/usbh/dcd/hcd/class-driver hot paths
  (SEGGER module + RecordVoid/RecordEndCall convention) → **per-function
  timing of the USB stack with no trace hardware** — the easy alternative to
  etm-trace for hot-function hunting.
- **Metrics focus** (all machine-readable): USB task + ISR timing (have),
  per-function stack-path timing (new), FreeRTOS **task stack** high-water
  (INCLUDE_* defines + shim include via guarded blocks in family
  FreeRTOSConfig.h — in-tree now, the zero-edit constraint no longer
  applies; validation families first, sweep later), **heap** events via a
  traceMALLOC/traceFREE → SEGGER_SYSVIEW_HeapAlloc/HeapFree mapping (the
  V4.12 shim lacks one — verified). Heap events exist only when
  `configSUPPORT_DYNAMIC_ALLOCATION=1`; validation families are fully
  static, so the reporter handles zero-heap gracefully and the mapping is
  validated with a temporary dynamic-alloc build.
- **Reportable everything**: `sysview_report.py --json` emitting
  contexts/ISR/latency/functions/stack/heap/overflow — the future hook for
  posting a PR comment from HIL runs (the posting itself is NOT wired now).
- **OpenOCD / non-SEGGER probes** (question resolved): the SystemView GUI's
  live recorder is J-Link-only (SEGGER-official; community TCP bridges are
  experimental). Supported OpenOCD routes here: (a) raw channel-1 capture
  via OpenOCD `rtt server` → file → `--from-raw` decode — works because the
  target self-starts recording in `Conf()`; (b) the post-mortem dump —
  probe-agnostic memory reads. OpenOCD RTT is polled (drop risk under
  burst) — the overflow gate detects loss. Validation task on
  stm32h743nucleo (rig, ST-Link/OpenOCD).

## Out of scope

Each waits for a real pull, per the harness promotion-criteria philosophy:
UART recorder (non-SEGGER-probe boards), multicore (rp2350), data plot
(RegisterData/SampleData), single-shot recording, GUI trigger modes,
HIL/PR-comment posting automation.

## Rejected alternatives

- Folding the dump into `sysview_record.py` as a `--post-mortem` flag: one
  entry point, but it grows an already-long script and tangles two unrelated
  workflows (live capture vs halted autopsy).
- Automating the GUI's Target → Read Recorded Data: more headless dialog
  choreography — the most fragile part of v1.