When a supervisor restarts a child one too many times and the whole tree comes down, the hardest part of the postmortem usually isn’t finding the crash — it’s reconstructing what the tree actually looked like at the moment of failure. Logs tell you *that* something restarted; they rarely tell you the shape of the tree, which children were linked to which supervisor, or what state each process held right before it died.
The problem gets worse when the diagram in the incident report is drawn from memory an hour after the fact. Two engineers looking at the same crash will draw two different trees, and neither is verifiable against what actually ran.
The constraint worth designing around: any diagram used as incident evidence should be regenerable from the same inputs every time, by anyone, without redrawing by hand.
A deterministic capture step
Before anything gets visualized, capture the tree structure as data, not as a picture. supervisor:which_children/1 and supervisor:count_children/1 give you the child list and restart counts at a point in time. Wrapping a snapshot function around these — walking from the root supervisor down, recording pid, child id, restart type, and current status — gives you a plain Erlang term you can write to disk as-is.
erlang
snapshot(Sup) ->
Children = supervisor:which_children(Sup),
[ {Id, Pid, Type, snapshot_if_sup(Pid)} ||
{Id, Pid, Type, _Modules} <- Children ].
This term is your source of truth. Store it alongside the crash report, timestamped, and treat it the same way you’d treat a core dump: immutable, checked into the incident record, never edited by hand.
From data to diagram, reproducibly
Once you have the tree as data, generating a diagram is a pure function of that data — which is the point. A small script that walks the snapshot term and emits Graphviz dot syntax will produce the same diagram every time given the same input, which means two people re-running it against the same snapshot file get identical output. That reproducibility is the whole value: the diagram becomes checkable, not just illustrative.
A rough checklist for this step:
- Normalize child ids and pids into stable labels before rendering (raw pids change across runs and are useless for comparing incidents over time).
- Mark which node actually crashed and which were killed as a side effect of the restart strategy — one-for-one versus one-for-all trees look very different once children start cascading.
- Keep the restart intensity and period annotated on the supervisor node itself; it’s often the missing context in postmortems.
- Version the rendering script with the codebase, not with the incident tooling, so old snapshots still render correctly against old topology.
The tradeoff here is upfront effort: writing and maintaining a snapshot-to-dot pipeline takes longer than sketching a box diagram in five minutes. But the sketch has to be redrawn and re-argued every time someone questions it, while the generated diagram just gets re-run.
Where a quick visual draft still has a place
Graphviz output is accurate but not always readable at a glance, especially for a written report going to people outside the team who need a simplified, cleaned-up version of the failure — a one-page visual summary rather than the full technical graph. For that narrower need, some teams sketch a simplified version by hand or with a general image tool, purely as a communication aid layered on top of the verified diagram, never as a replacement for it.
If you want a fast way to rough out that kind of simplified illustration — an annotated box-and-arrow summary for a report or a slide — a lightweight AI image generator like Nano Banana 2 Lite can be useful for quickly drafting that visual layer from a text description or a reference image. It’s a third-party, independent tool unaffiliated with Google or DeepMind, and it’s not a substitute for the deterministic dot-graph step above — it’s only worth reaching for once the actual evidence diagram already exists and you need a simplified derivative of it for a non-technical audience.
Limitations
This workflow only captures what which_children and count_children expose at the moment you call them; it says nothing about message queues, monitor references, or state held inside gen_server loops unless you extend the snapshot to pull that separately. It also assumes the supervisor itself is still alive and responsive when you snapshot — if the whole tree is unresponsive, you’re back to whatever the SASL or logger reports captured before the crash. Treat this as a floor for incident evidence, not a complete picture, and extend the snapshot function as your specific failure modes demand.