skip to content

What does `go tool pprof -http=:8080 cpu.pprof` open, and how do you read its flame graph?

level: middleimportance: should knowfreq 47%

answer

  1. same profile, browser instead of prompt
  2. boxes stack by call depth
  3. width is cumulative share, not duration
  4. left to right carries no meaning
  5. look for a wide box with no wide child

basics

~20 s

It starts a local web server and opens pprof's browser UI over that profile: Top, Graph, Flame Graph and Peek views. In the flame graph each box is a stack frame, and its width is that call path's share of samples.

solid answer

~50 s

`-http=:8080` serves pprof's interactive UI on localhost instead of using the text prompt, and it is the same profile with the same filters — a View menu switches between Top, Graph, Flame Graph, Peek, Source and Disassemble. The flame graph stacks frames vertically by call depth, and the width of a box is the cumulative share of samples on that path; clicking a box zooms into that subtree, and the search box applies a focus regexp. What the horizontal axis is *not* is time: boxes are ordered for stable layout, not chronologically, so left-to-right position means nothing. Because width is cumulative, the widest box near the top of the graph is usually `main` and is not the fix; you look for the widest box that has no wide child, which is where the work is. Note the dot-based graph view needs Graphviz installed, as do the text `web` and `svg` commands.

go deeper

for a junior

Know the invocation and that it opens the same profile in a browser, and that a wider box means a larger share of the samples.

for a middle

Explain that width is cumulative, that left-to-right order is not time, and which views the UI offers over the same data.

for a senior

Show that you use the graphical view for cost split by call path, fall back to the text ranking when the profile is diffuse, and keep the endpoint on localhost.

for a principal

Decide how profiles get shared: an artifact plus a documented way to open it beats screenshots, and it keeps the diagnostic surface off production interfaces.

## What the flag does `go tool pprof -http=:8080 cpu.pprof` skips the interactive text prompt, starts an HTTP server bound to that address on the local machine, and opens a browser at it. The profile is the same one the text commands read; only the presentation differs, and every text command has a counterpart in the UI. The View menu offers: - **Top** — the same ranking as the `top` command, sortable by clicking a column. - **Graph** — the call graph as a dot-rendered diagram, node size and edge thickness proportional to weight. This rendering requires Graphviz to be installed; so do the `web` and `svg` commands at the text prompt, which is why they fail with a `dot` execution error on a machine without it. - **Flame Graph** — the stacked call-path view described below. - **Peek** — callers and callees, as the `peek` command prints them. - **Source** — annotated source, as `list` prints it, subject to the same requirement that the source files be findable locally. - **Disassemble** — per-instruction weights, which needs the matching binary. The search box at the top applies a focus regexp to every view at once, and the Refine menu offers focus, ignore, hide and show. Filters persist as you move between views, which is the main workflow advantage over typing commands one at a time. ## Reading a flame graph correctly Each box is a frame in a call stack. Boxes stack in the direction of call depth, so a child box sits directly against its caller. **The width of a box is the share of samples in which that frame appeared on the stack at that position** — cumulative weight, the same quantity the cum column reports. Three consequences follow, and each of them is a common misreading: 1. **The horizontal axis is not time.** Siblings are laid out in a stable order so the picture is comparable between runs; nothing about left or right means earlier or later. A flame graph cannot tell you that one phase ran before another. (The tool that shows events on a real timeline is a different one entirely.) 2. **Width is not self time.** Every ancestor of a hot leaf is exactly as wide as the leaf beneath it, plus its siblings. The root frames span nearly the whole picture and are never the answer. 3. **The thing you are looking for is a wide box with no wide child** — a plateau. That is a frame whose width is its own work rather than its callees'. Visually it is a wide, flat top; that is where the self time is, and it corresponds to a high flat value in `top`. Clicking a box zooms so that box becomes the full width, which is how you explore a subtree that is only 5% of the profile but is the part you own. There is a reset control to zoom back out. ## When the graphical view earns its keep The text `top` collapses a function into one row summed over every caller. A flame graph keeps the call paths separate, so a helper invoked from three subsystems appears as three boxes, and you can immediately see that only one of the paths is expensive. That is the single strongest reason to open the browser UI: cost that is *split by context* is hard to read in a flat ranking and obvious in the graph. The converse is also true. A profile whose cost is spread across hundreds of narrow towers is easier to judge from `top` and its sum% column than from a picture, and a picture invites the eye toward the biggest shape, which is exactly the root frame you must ignore. ## Practical notes Bind it to localhost, not a public interface — the UI serves whatever is in the profile, including function and file names, and it is a diagnostic surface, not a service. `-http=:0` picks a free port. The saved profile artifact is all the UI needs; you can browse a profile captured on another host weeks earlier, though the Source view will stay empty unless the matching sources resolve locally. Go 1.26 changed the UI's default landing view to the flame graph; before that it opened on the graph view. Both views have been available for many releases, so the change is which one you see first, not what is available.

  • Why is the widest box in a flame graph usually the wrong thing to optimise?
    Because width is cumulative. The frames near the root — `main` and the top-level driver — contain every sample by definition, so they are always widest and contain none of the work themselves. Descend until the width stops being inherited from a single child; the plateau with a flat top is the frame spending its own instructions.
  • The `web` command fails with an error about executing dot. What is missing?
    Graphviz. pprof shells out to `dot` to render the call graph for `web`, `svg` and the browser UI's Graph view. Install Graphviz or stay in the views that do not need it — Top, Flame Graph, Peek and Source all render without it.
  • Can a flame graph tell you the order in which two subsystems ran?
    No. Sibling boxes are ordered for a stable, comparable layout, not chronologically, and the profile itself is an aggregate of samples with no timeline attached. Any question about sequencing, latency over time, or when something started belongs to an execution trace rather than to a profile.

It is a cross-section through a stack of sediment: each layer's thickness is how much of the total it accounts for, and the layers above tell you what deposited it — but the horizontal direction is not a timeline.

saying these in an interview costs you the question

  • Reads the horizontal axis of a flame graph as time
  • Treats box width as the frame's own self time
  • Picks the widest box near the root as the target
  • Expects the Graph view to render without Graphviz
  • Exposes the pprof UI on a public interface