In `go tool pprof`, what do the `list` and `peek` commands show that `top` does not?
answer
- one zooms in, one looks sideways
- per-line weights beside your own source
- who calls this, and what does it call
- needs the source files to be findable
- callers explain a hot shared leaf
basics
~20 slist <regexp> prints a matching function's source annotated with per-line flat and cum weight, so you see which statement costs. peek <regexp> prints that function's callers and callees with the share each edge carries. top only ranks whole functions.
solid answer
~50 s`top` tells you which function is hot; those two commands tell you *where inside it* and *who is driving it*. `list renderField` prints the function's source with two weight columns beside every line, so a single expression can be identified as the cost. It needs the source files to be readable at the paths recorded in the profile, which is why it often fails on a profile captured on another machine — `-source_path` and `-trim_path` fix that mapping. `peek renderField` prints the call edges instead: each caller with the fraction of the function's cumulative weight it contributed, and each callee with its share. That is how you tell a function that is slow from a function that is merely called too often. `weblist` renders the same annotated source in a browser, and `-lines` changes granularity so nodes are lines rather than functions.
code
text · 9 lines(pprof) list renderField
Total: 2s
ROUTINE ======================== main.(*generator).renderField in /src/gen/render.go
300ms 1.46s (flat, cum) 73.00% of Total
. . 58:func (g *generator) renderField(f field) string {
20ms 20ms 59: name := f.Name
280ms 1.10s 60: if bytes.Equal(g.scratch, []byte(name)) {
. 340ms 61: return g.slowPath(f)
. . 62: }go deeper
Know that after top you type list <function> to see the hot lines, and that pprof needs the matching source checked out to show them.
Explain the two columns beside each source line, why peek answers a question top cannot, and what makes list print nothing.
Demonstrate the loop: rank, attribute callers, read lines, change one thing, recapture — and recognise inlining and missing source paths before they send you down a wrong path.
Own the reproducibility angle: profiles are only actionable later if the build that produced them is identifiable, so keep the commit and build metadata beside the artifact.
## Three levels of zoom `go tool pprof` gives you a ranking (`top`), a source view (`list`), and a local call-graph view (`peek`). Reading a profile is normally a walk through all three: rank, then look at the callers to understand why the function runs, then look at the source to see what inside it costs. ## `list <regexp>` `list` takes a regular expression matched against function names and prints the *source* of every matching function, with two columns to the left of each line: the flat weight attributed to that line and the cumulative weight of everything that line reaches. A header row gives the function's own flat and cum totals and its share of the profile. This is where a profile stops being statistical and becomes actionable, because a line with 280ms next to it is a specific expression you can change. Practical caveats: - **The source must be findable.** pprof resolves the file paths recorded when the profile was produced. If the profile came from a build machine, or from a colleague's checkout last week, those paths do not exist locally and `list` prints no source. `-source_path` points pprof at your checkout root and `-trim_path` strips the recorded prefix; getting these right is usually the whole fix. - **Attribution is per program counter, not per statement you wrote.** The compiler reorders and merges; a loop's cost can land on the loop line, the condition, or the body. - **Inlining moves cost to the call site.** If a small callee was inlined into this function, its samples are attributed to the line that called it, and you will see a line that appears to do nothing carrying real time. Raising granularity with `-lines`, or reading `peek` and `list` together, resolves the confusion. - **`weblist <regexp>`** opens the same annotated source in a browser, with the disassembly available per line if you supplied the binary. ## `peek <regexp>` `peek` prints, for each matching function, the immediate callers above it and the immediate callees below it, each with the portion of the function's cumulative weight that flows through that edge. `top` cannot express this: it flattens the graph into one row per function, summed over every caller. That distinction matters because two very different problems produce identical `top` rows. If `renderField` shows 1.46s cum, `peek` might show that 1.40s of it arrives from one caller in a loop — the fix is at the caller, calling it less. Or it might show the weight spread over eight callers with one expensive callee underneath — the fix is inside, or below. Same row, opposite conclusions. `peek` is also the fastest way to attribute a shared leaf. A byte comparison or an allocation shows up as one fat row with no context; `peek` on it names the handful of call sites responsible, which is usually enough to stop reading and start editing. ## Putting them together A realistic pass over a CPU profile captured from a code generator that runs as a build step: 1. `top` — the leaves are a byte comparison and the allocator. 2. `peek` on the byte comparison — nearly all of its weight arrives from one field-rendering function. 3. `list` on that function — one line, a comparison inside a loop that re-converts a string to bytes on every iteration, carries the weight. 4. Fix that line; recapture; compare. Without `peek` you would not know which of several callers to look at. Without `list` you would be guessing at which of forty lines to change. And note what each command needs: `top` and `peek` need only the profile, while `list` also needs the sources. That asymmetry is why a profile handed to you by someone else is still useful even when `list` refuses to print anything. ## Filters that apply to all of them `focus=<regexp>` keeps only samples whose stack matches; `ignore=<regexp>` drops those that do; `hide` and `show` prune nodes from the display without changing the totals. Applying `focus` before `top` is how you ask what one subsystem costs, and the filter stays in effect for the `list` and `peek` you run next.
- You run `list` on a profile a colleague captured last week and pprof prints the function header but no source lines. What happened?The source paths baked into the profile do not resolve on your machine — a different checkout root, a container build path, or a version you do not have. pprof needs the files themselves; nothing in the profile contains them. Point it at your tree with `-source_path`, strip the recorded prefix with `-trim_path`, and check out the commit the profile came from so the line numbers still mean something.
- `list` shows real time on a line that looks like it does almost nothing. What is the usual explanation?Inlining. A small callee compiled into the caller has no frame of its own, so its samples are attributed to the call-site line. The same effect makes a hot inlined helper vanish from `top`. Reading `peek` alongside, or switching granularity to lines, usually recovers what is really running there.
- When would `peek` change your fix even though `top` already named the hot function?When the function is fine and the call pattern is not. If `peek` shows 95% of the weight arriving from a single caller inside a loop, the cheapest fix is hoisting or caching at that caller, not micro-optimising the callee. `top` sums over all callers and hides exactly that.
saying these in an interview costs you the question
- Thinks list works without the source files present
- Believes peek shows the whole call graph rather than one hop each way
- Ignores inlining when a trivial line shows weight
- Optimises a shared leaf without checking which callers drive it
- Assumes the profile embeds the source code