Challenge ·Easy

Meet the Go Profiler

Get hands-on with Go's built-in profiler: explore the /debug/pprof/ endpoints of a running service, capture a real CPU profile, and read the hot functions with go tool pprof.

You just joined the platform team. The hello-api service is running fine on port 8080, and (because good Go services do this) it exposes the pprof endpoints under /debug/pprof/. Nothing is broken today. Your job is to get comfortable with the profiler anyway, so that when something does break, reading a profile is muscle memory instead of a frantic search through docs.

The app has three endpoints you can poke at:

  • GET / says hello
  • GET /work burns CPU for a bit (great for generating load)
  • GET /allocate retains 1 MiB per call (matters later, when we talk about heap profiles)

Task 1: Find the profiler

Every profile the Go runtime can produce is listed on the profiler's index page. Fetch http://localhost:8080/debug/pprof/ and confirm the built-ins are all there: profile (CPU), heap, goroutine, allocs, block, mutex, threadcreate, and trace.

Hint 1

Plain old curl http://localhost:8080/debug/pprof/ does it.

Hint 2

The index is plain HTML. Look for href='profile?debug=1'-style links and note they're relative paths under /debug/pprof/.

Task 2: Capture a CPU profile

The CPU profiler samples which function is running, over and over, until you stop. Collect a 5-second profile and save the raw bytes to /tmp/cpu.prof:

curl -o /tmp/cpu.prof 'http://localhost:8080/debug/pprof/profile?seconds=5'

The command blocks for the whole 5 seconds. That's not a hang; it's the sampling window. What lands in the file is a gzip-compressed protobuf in the pprof format.

Hint 3

-o writes the response body exactly as received, which is what you want. Don't pipe it through anything that might reformat it.

Task 3: Read the profile

A profile file is just compressed protobuf until go tool pprof reads it:

go tool pprof -top /tmp/cpu.prof

The -top report lists the hottest functions. Somewhere in there you should find main.busyLoop, the function behind the /work endpoint... provided the profile actually caught the app doing work. Find it to finish the challenge.

Hint 4

If the report says Total samples = 0, the app was idle while you profiled. CPU profiling samples running code, not sleeping code, so an idle profile is an empty profile. Generate load during the capture:

for i in $(seq 1 50); do curl -s -o /dev/null http://localhost:8080/work & done
curl -o /tmp/cpu.prof 'http://localhost:8080/debug/pprof/profile?seconds=5'
Hint 5

Expect main.busyLoop at the very top, holding nearly all the samples. The two percentage columns: flat% is time in the function itself, cum% also counts everything it calls.

Where to go next

  • Capture /debug/pprof/heap after hammering /allocate a few times, then look for main.allocate in the report.
  • Get a plain-text goroutine dump: curl 'http://localhost:8080/debug/pprof/goroutine?debug=1'.
  • When you're comfortable, take on the follow-up challenge, The Goroutine Leak (coming soon). Same service family, but this time something actually is broken.