A flame graph is a visualization of hierarchical profiling data, typically sampled stack traces. Each box represents a function in a stack; the y-axis shows stack depth and the width of a box shows how often that function appeared in the samples. The x-axis is not time: frames are sorted alphabetically so identical stacks merge.
What a flame graph is
A flame graph turns thousands of stack traces into one picture that shows where a program spends its time. Brendan Gregg created the visualization in 2011 while analyzing MySQL performance, because reading the raw text output of profilers was too slow. Today flame graphs are the standard view in CPU profilers, continuous profiling tools, browser developer tools and observability platforms.
The input is usually a sampling profile. A profiler such as Linux perf, async-profiler for the JVM, Go's pprof or an eBPF-based profiler interrupts the program at a fixed frequency, often 99 or 100 times per second, and records the current call stack. Identical stacks are counted, and the flame graph draws each unique stack as a column of boxes whose width is proportional to its count.
How to read a flame graph
- Each box is a stack frame, usually a function. The box directly above it is a function it called.
- The y-axis is stack depth. In the classic layout the root (for example the thread entry point) is at the bottom and the leaf functions are at the top. The top edge therefore shows what was actually running on the CPU when the samples were taken.
- The x-axis is not time. Frames on each level are sorted alphabetically to maximize merging. Left-to-right position carries no meaning; a box on the left did not run earlier than one on the right.
- Width is what matters. A box's width shows the share of samples in which that function was on the stack, including everything it called (inclusive time). The part of a box with nothing above it is the function's own time (self time).
- Colors are usually not meaningful. The original tool uses random warm colors just to separate neighboring boxes. Some tools use color to mark code types, for example kernel versus user space or JIT-compiled versus native frames, or to highlight search matches.
When looking for optimization targets, scan the top edge for wide "plateaus": a wide box with nothing on top is a function that burns CPU itself. Wide boxes lower in the graph show which code paths lead there.
Variants: icicle graphs, flame charts and more
| Variant | Difference | Typical use |
|---|---|---|
| Icicle graph | Same data, inverted: root at the top | Default layout in many profilers such as pprof's web UI and several continuous profilers |
| Flame chart | x-axis is time; stacks are not merged | Browser DevTools, analyzing one request or frame over time |
| Differential flame graph | Colors show change between two profiles, commonly red for growth and blue for reduction | Comparing before and after a release |
| Off-CPU flame graph | Width is time spent blocked (I/O, locks, sleep) instead of on-CPU | Latency problems that do not consume CPU |
| Memory / allocation flame graph | Width is bytes allocated or retained | Allocation hotspots, GC pressure |
Example: generating one on Linux
The classic workflow with Brendan Gregg's FlameGraph scripts samples all CPUs at 99 Hz for 30 seconds and renders an interactive SVG:
perf record -F 99 -a -g -- sleep 30
perf script | ./stackcollapse-perf.pl | ./flamegraph.pl > cpu.svg
A frequency of 99 instead of 100 Hz avoids sampling in lockstep with periodic timers. For managed runtimes, use a profiler that understands the runtime's stacks, such as async-profiler for Java or the built-in pprof endpoints for Go; otherwise JIT-compiled frames may appear as unresolved addresses.
Common pitfalls
- Reading the x-axis as time. This is the most frequent misinterpretation. Use a flame chart or a trace if order matters.
- Missing frames. Code compiled without frame pointers, inlining and missing debug symbols produce truncated or "unknown" stacks.
- Too few samples. Thirty seconds on an idle service may contain only a handful of samples, which is not representative.
- Ignoring off-CPU time. A slow request that waits on a database barely appears in a CPU flame graph. Combine profiles with spans to see waiting time.
Why it matters for observability
Metrics show that CPU or latency went up, and traces show which service and operation became slower. Flame graphs answer the next question: which code inside that process is responsible. With continuous profiling in production, a flame graph from the exact time window of an incident, or a differential flame graph between two releases, often points straight at a regex, a serialization routine or a hot lock.
How Obseria helps
Obseria continuous profiling collects CPU, allocation and lock profiles from all hosts with under 1% overhead and displays them as flame graphs filtered by service, version and time range. From a slow span in distributed tracing you can jump to the profile of the same process and period.