How to read a flamegraph

You’ve run a profiling session, say with async-profiler, and now you’re staring at a flamegraph. It’s one of the best ways to see where a program’s time goes, as long as you know the reading grid. It comes down to a few rules.

What each axis means

A flamegraph aggregates thousands of stack samples. Each rectangle is a frame, meaning a method.

The classic trap: the horizontal axis isn’t time

This is the mistake people make most. Left to right is not a timeline. Frames at the same level are simply sorted alphabetically to merge identical stacks. A method on the far right doesn’t run “after” one on the left. A flamegraph answers “where does the time go?”, not “in what order?”. For chronology you need a different tool, a trace or a timeline.

In practice: hunt for plateaus

The approach that works:

  1. Start at the top and find the wide frames. The top of a stack is the code that was actually running when the sample was taken (its “self time”). A wide plateau up there is time burnt right there. First suspect.
  2. Go back down to follow the path. Tracing a wide frame downward shows you who led to it. Often the culprit isn’t the leaf itself but the fact that it gets called far too often, from higher up.
  3. Ignore thin, isolated towers. A narrow, very tall stack costs little: lots of nested calls, little total time. Not a priority.

The rule to keep in mind: width tells you how much it costs, position at the top tells you where it’s actually spent.

A few patterns you learn to spot

Match your reading to the profiled event

The same graph reads differently depending on what was measured. On a CPU flamegraph, a wide frame is computation to optimise. On a wall-clock flamegraph, a wide frame can be a wait (I/O, lock, park), and the goal there isn’t to speed the code up but to remove the blocking. On an allocation flamegraph, width is bytes allocated, not time. Always keep in mind what you asked async-profiler to measure.

In short

Look for the wide, start at the top, don’t read the horizontal as a clock, and remember what the profiled event means. With that, a flamegraph that looked unreadable turns into a fairly direct map of what to fix first.

If you haven’t generated yours yet: Profiling the JVM with async-profiler.