2 minutes
Finding Bottlenecks in Go: Profiling with PPROF
When a Go service or function is slower than expected, scattering time.Since(start) logs across the codebase rarely tells the full story. And while wiring net/http/pprof into a live server is useful for production debugging, profiling an isolated Benchmark locally is much faster and free of noise from concurrent traffic.
1. Write a Benchmark for the Target Path
Create a benchmark in my_package_test.go covering the code you want to inspect:
package mypackage
import "testing"
func heavyComputation() {
sum := 0
for i := 0; i < 100000000; i++ {
sum += i
}
_ = sum
}
func BenchmarkHeavyComputation(b *testing.B) {
for i := 0; i < b.N; i++ {
heavyComputation()
}
}
2. Generate the CPU Profile
Run go test with -run NONE (to skip regular unit tests), -bench ., and -cpuprofile:
go test -run NONE -bench . -cpuprofile=cpu.pprof
Example output
goos: darwin
goarch: arm64
pkg: github.com/albertmoreno/my_package
BenchmarkHeavyComputation-8 10 107871140 ns/op
PASS
ok github.com/albertmoreno/my_package 1.229s
3. Inspect the Flame Graph in the Browser
The CLI view (go tool pprof cpu.pprof with top and list) works well for shallow functions, but deep call stacks are much easier to read visually. Pass -http to launch pprof’s built-in web UI:
go tool pprof -http=:8080 cpu.pprof
(Note: rendering the graph views requires Graphviz installed locally, e.g., brew install graphviz.)
In the web UI, switch to View -> Flame Graph:
- Each bar represents a function in the call stack.
- The width of the bar shows the percentage of total CPU time spent in that function and its callees.
- Clicking any bar zooms into that subtree so you can spot which call path is dominating CPU time.