Skip to content

Day 155 — pprof CPU & heap profiling

Month 6 · Week 3 · ⬅ Day 154 · Day 156 ➡ · Journal index

🎯 Learning Objective

Capture CPU and heap profiles from a Go program two ways — programmatically with runtime/pprof and live over HTTP with net/http/pprof — and read them with go tool pprof.

📚 Topics

  • CPU profiling (sampling) vs heap profiling (snapshot of live allocations)
  • runtime/pprof (StartCPUProfile/StopCPUProfile, WriteHeapProfile)
  • net/http/pprof blank-import endpoint; go tool pprof top/list/web

📖 Reading / Sources

📝 Notes

  • A CPU profile is statistical: the runtime interrupts ~100×/sec (runtime.SetCPUProfileRate) and records the running stack. A function only shows up if it actually consumes CPU long enough to be sampled — an idle program yields an empty profile → [[pprof]].
  • A heap profile is a snapshot of allocations alive right now, not a time-series. Call runtime.GC() before WriteHeapProfile so the snapshot reflects truly-live memory (drops uncollected garbage).
  • Only one CPU profile can run at a time; StartCPUProfile errors if one is already active. Always pair it with a defer pprof.StopCPUProfile() (or stop explicitly) — the file is only valid after Stop flushes.
  • The heap profile has four views: inuse_space/inuse_objects (live, the default) and alloc_space/alloc_objects (cumulative since start). Use alloc_* to hunt allocation churn; inuse_* to hunt leaks.
  • net/http/pprof: import _ "net/http/pprof" registers /debug/pprof/* on http.DefaultServeMux as a side effect. Serve it on an admin-only port (e.g. localhost:6060) — never on the public API; the endpoints leak internals and let anyone trigger expensive dumps → [[pprof-security]].
  • Read profiles with go tool pprof <file|url>: top (hottest functions), list Func (annotated source), web (SVG call graph, needs graphviz), peek. Add ?seconds=30 to the HTTP CPU endpoint to set the sampling window.
  • Other built-in profiles: goroutine (dump + counts — find leaks/deadlocks), block, mutex (both need explicit rate-enabling), threadcreate, plus the execution tracer at /debug/pprof/trace (read with go tool trace).
  • Profile labels (pprof.Do / pprof.Labels) tag samples (e.g. by request route) so you can slice a CPU profile by tag.

💻 Code Examples

// Programmatic CPU profile: valid only after StopCPUProfile flushes.
f, _ := os.Create("cpu.pprof")
if err := pprof.StartCPUProfile(f); err != nil { log.Fatal(err) }
doWork()
pprof.StopCPUProfile()
f.Close()

// Heap snapshot: GC first so it reflects live memory.
runtime.GC()
h, _ := os.Create("heap.pprof")
pprof.WriteHeapProfile(h)
h.Close()

Full code: examples/month-06/pprof/main.go · Run: go run ./examples/month-06/pprof

🏋️ Exercises / Practice

Exercise Status Link
Write & read CPU + heap profiles examples/month-06/pprof
Kill allocations after profiling (append pattern) exercises/month-06/week-3/allocfree

🐛 Mistakes Made

  • Read the CPU profile file before calling StopCPUProfile → it was empty/garbage. Stop flushes; read after.
  • Forgot runtime.GC() before the heap snapshot, so dead-but-uncollected objects inflated inuse_space.

❓ Open Questions

  • When is the execution tracer (go tool trace) the right tool over pprof? (Latency/scheduling/blocking questions, not "where's the CPU going".)

🧠 Active Recall (answer without looking)

  1. Q: Why can a CPU-bound function be missing from a CPU profile?
    A

CPU profiling samples the stack ~100×/sec. If the function ran but the sampler never landed on it (too fast, or the program ran only briefly), it won't appear. Profiles are statistical, not exhaustive — run longer or under more load to get signal. 2. Q: Difference between the heap profile's inuse_space and alloc_space views?

A

inuse_space is memory live at the snapshot instant (good for finding leaks / what's retained). alloc_space is cumulative bytes allocated since the program started, including freed memory (good for finding allocation churn / GC pressure).

🪶 Feynman Reflection

A CPU profile is like a time-lapse camera that snaps the call stack a hundred times a second — whoever's on stage most often is your hot path. A heap profile is a single still photo of everything currently holding memory. One answers "where is time spent," the other "what is holding RAM."

🕳️ Knowledge Gaps

  • Reading flame graphs / go tool pprof -http UI fluently — practice on a real profile.
  • Block & mutex profiles: when to enable their rates and what the output means.

✅ Summary

I can capture CPU and heap profiles programmatically and via the HTTP endpoint, and navigate them with top/list, knowing CPU is sampled and heap is a live snapshot.

⏭️ Next Steps / Prep for Tomorrow

  • Day 156: escape analysis — why those heap allocations happen and how to read -gcflags=-m.

Time spent Difficulty Confidence
90 min 🟦🟦⬜⬜⬜ 🟦🟦🟦⬜⬜

Suggested commit: docs(journal): pprof cpu & heap profiling (day 155)