CoursesAdvanced Linux internals & toolingAdvanced tooling & observability

Flame graphs & CPU profiling

perf + FlameGraph: see the hot path at a glance.

Advanced14 min · lesson 13 of 17

A flame graph is the single most famous performance-visualization in the Linux world — it turns thousands of CPU samples into one picture that shows, at a glance, where a program spends its time. perf tells you the hot functions as a list; a flame graph shows the whole call stack as stacked bars, so you see not just "function X is hot" but the entire path of calls that led there and how the time is distributed across it. Brendan Gregg’s FlameGraph tools made this the standard way to read a profile.

How to read a flame graph
the axes
width = time
wider box = more CPU samples (the hot code)
height = stack depth
each box calls the one above it
NOT time order
x-axis is sorted, not chronological
Scan for the widest boxes at any level — that is where the CPU time actually goes. Wide plateaus at the top are the leaf functions doing the work.

Generating one

The recipe is three steps: record with perf, collapse the stacks into one line each, and render to an interactive SVG. You profile the workload (system-wide or a specific process), fold the samples, and pipe through flamegraph.pl. The result is an SVG you open in a browser and click to zoom into any subtree — the practical way to hand a profile to someone else, or to compare before-and-after a fix.

terminal
# 1) record CPU stacks for 30s (system-wide, with call graphs)
$ sudo perf record -F 99 -a -g -- sleep 30
# 2) fold the stacks, 3) render to an interactive SVG
$ sudo perf script | ./stackcollapse-perf.pl | ./flamegraph.pl > flame.svg
$ xdg-open flame.svg # click any box to zoom; search to highlight a function

What flame graphs reveal

The pattern-reading is the skill. A wide plateau at the top means a leaf function burning CPU directly — optimize that function. A wide tower means a deep call chain where the cost is spread across layers — often a sign of an expensive abstraction or too many calls. Two flame graphs side by side (a "differential") show exactly what a change made faster or slower. There are also off-CPU flame graphs (built from scheduler traces instead of CPU samples) that show where a program spends time blocked and waiting, not running — the other half of the latency story.

A flame graph needs stack traces to be readable
If a flame graph is a mess of unknown/hex addresses, the profiled program was compiled without frame pointers or debug symbols, so perf could not walk the stacks. The fixes: build (or install debug symbols) with frame pointers, or use perf record --call-graph dwarf for DWARF-based unwinding (heavier but works on optimized binaries). For interpreted/JIT runtimes (Java, Node, Python) you need the runtime’s symbol support. A flame graph is only as good as the stacks feeding it — get symbols first.