Ein Flame Graph ist eine Visualisierung hierarchischer Profiling-Daten, meist gesampelter Stacktraces. Jeder Kasten steht für eine Funktion im Stack; die y-Achse zeigt die Stacktiefe, die Breite eines Kastens, wie häufig die Funktion in den Samples vorkam. Die x-Achse ist keine Zeitachse: Frames werden alphabetisch sortiert, damit identische Stacks zusammenfallen.
Was ein Flame Graph ist
Ein Flame Graph verdichtet Tausende Stacktraces zu einem Bild, das zeigt, wo ein Programm seine Zeit verbringt. Brendan Gregg entwickelte die Darstellung 2011 bei der Performance-Analyse von MySQL, weil das Lesen der Rohausgabe von Profilern zu lange dauerte. Heute sind Flame Graphs die Standardansicht in CPU-Profilern, Continuous-Profiling-Werkzeugen, Browser-Entwicklertools und Observability-Plattformen.
Grundlage ist meist ein Sampling-Profil. Ein Profiler wie Linux perf, async-profiler für die JVM, pprof für Go oder ein eBPF-basierter Profiler unterbricht das Programm in fester Frequenz, häufig 99- oder 100-mal pro Sekunde, und zeichnet den aktuellen Call Stack auf. Identische Stacks werden gezählt; der Flame Graph zeichnet jeden eindeutigen Stack als Säule aus Kästen, deren Breite proportional zur Anzahl ist.
So lesen Sie einen Flame Graph
- Jeder Kasten ist ein Stack Frame, meist eine Funktion. Der Kasten direkt darüber ist eine Funktion, die sie aufgerufen hat.
- Die y-Achse ist die Stacktiefe. Im klassischen Layout liegt die Wurzel (etwa der Einstiegspunkt des Threads) unten, die Blattfunktionen oben. Die Oberkante zeigt also, was beim Sampling tatsächlich auf der CPU lief.
- Die x-Achse ist keine Zeit. Frames werden pro Ebene alphabetisch sortiert, damit möglichst viele zusammenfallen. Die Position von links nach rechts hat keine Bedeutung; ein Kasten links lief nicht früher als einer rechts.
- Entscheidend ist die Breite. Sie zeigt den Anteil der Samples, in denen die Funktion auf dem Stack lag, einschließlich allem, was sie aufrief (inklusive Zeit). Der Teil eines Kastens, über dem nichts mehr liegt, ist die eigene Zeit der Funktion (Self Time).
- Farben haben meist keine Bedeutung. Das Originalwerkzeug wählt zufällige warme Farben, nur um benachbarte Kästen zu unterscheiden. Manche Tools kodieren damit Codearten, etwa Kernel gegenüber User Space oder JIT-kompilierte gegenüber nativen Frames, oder heben Suchtreffer hervor.
Auf der Suche nach Optimierungszielen lohnt der Blick auf breite „Plateaus“ an der Oberkante: Ein breiter Kasten ohne etwas darüber ist eine Funktion, die selbst CPU verbraucht. Breite Kästen weiter unten zeigen, welche Codepfade dorthin führen.
Varianten: Icicle Graphs, Flame Charts und mehr
| Variante | Unterschied | Typischer Einsatz |
|---|---|---|
| Icicle Graph | Gleiche Daten, umgedreht: Wurzel oben | Standardlayout vieler Profiler, etwa der Weboberfläche von pprof und mehrerer Continuous Profiler |
| Flame Chart | Die x-Achse ist die Zeit, Stacks werden nicht zusammengefasst | Browser-DevTools, Analyse eines einzelnen Requests oder Frames im Zeitverlauf |
| Differential Flame Graph | Farben zeigen Veränderungen zwischen zwei Profilen, üblich: Rot für Zunahme, Blau für Abnahme | Vergleich vor und nach einem Release |
| Off-CPU Flame Graph | Breite ist Wartezeit (I/O, Locks, Sleep) statt CPU-Zeit | Latenzprobleme, die keine CPU verbrauchen |
| Speicher-/Allokations-Flame-Graph | Breite ist allozierter oder gehaltener Speicher in Bytes | Allokations-Hotspots, GC-Druck |
Beispiel: Erzeugung unter Linux
Der klassische Ablauf mit Brendan Greggs FlameGraph-Skripten sampelt alle CPUs 30 Sekunden lang mit 99 Hz und erzeugt ein interaktives SVG:
perf record -F 99 -a -g -- sleep 30
perf script | ./stackcollapse-perf.pl | ./flamegraph.pl > cpu.svg
99 statt 100 Hz verhindert, dass im Gleichtakt mit periodischen Timern gesampelt wird. Für Managed Runtimes empfiehlt sich ein Profiler, der deren Stacks versteht, etwa async-profiler für Java oder die eingebauten pprof-Endpunkte in Go. Sonst erscheinen JIT-kompilierte Frames womöglich nur als unaufgelöste Adressen.
Typische Fallstricke
- Die x-Achse als Zeit lesen. Das ist die häufigste Fehlinterpretation. Wenn die Reihenfolge zählt, nutzen Sie einen Flame Chart oder einen Trace.
- Fehlende Frames. Code ohne Frame Pointer, Inlining und fehlende Debug-Symbole führen zu abgeschnittenen oder „unknown“-Stacks.
- Zu wenige Samples. 30 Sekunden auf einem fast untätigen Service liefern womöglich nur eine Handvoll Samples, die nicht repräsentativ sind.
- Off-CPU-Zeit ignorieren. Ein langsamer Request, der auf die Datenbank wartet, taucht in einem CPU-Flame-Graph kaum auf. Kombinieren Sie Profile mit Spans, um Wartezeiten zu sehen.
Warum das für Observability wichtig ist
Metriken zeigen, dass CPU oder Latenz gestiegen sind, Traces zeigen, welcher Service und welche Operation langsamer wurden. Flame Graphs beantworten die nächste Frage: welcher Code in diesem Prozess verantwortlich ist. Mit Continuous Profiling in Produktion weist ein Flame Graph aus genau dem Zeitfenster eines Incidents oder ein Differential Flame Graph zwischen zwei Releases oft direkt auf einen regulären Ausdruck, eine Serialisierungsroutine oder einen umkämpften Lock.
Wie Obseria unterstützt
Das Continuous Profiling von Obseria erfasst CPU-, Allokations- und Lock-Profile aller Hosts mit unter 1 % Overhead und zeigt sie als Flame Graphs, gefiltert nach Service, Version und Zeitraum. Von einem langsamen Span im Distributed Tracing springen Sie direkt zum Profil desselben Prozesses im selben Zeitraum.