Go cookbook · Advanced

Profile a running service with pprof

CPU or memory usage climbs in production and you need to see where.

Create a free account to save this page, take notes and track your progress. Sign in

Approach

Register the pprof handlers on a separate, internal-only server (not the public mux), then capture CPU, heap and goroutine profiles with go tool pprof.

Solution

package main

import (
	"log"
	"net/http"
	"net/http/pprof"
	"time"
)

func startDebugServer() {
	mux := http.NewServeMux()
	mux.HandleFunc("/debug/pprof/", pprof.Index)
	mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
	mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
	mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
	srv := &http.Server{Addr: "127.0.0.1:6060", Handler: mux, ReadHeaderTimeout: 5 * time.Second}
	go func() { log.Println(srv.ListenAndServe()) }()
}

func main() {
	startDebugServer()
	// ... start the public server on its own mux ...
	select {}
}
# 30-second CPU profile, opened in a browser UI
go tool pprof -http=:8081 http://127.0.0.1:6060/debug/pprof/profile?seconds=30
# Heap in use, and goroutine dump to spot leaks
go tool pprof -http=:8082 http://127.0.0.1:6060/debug/pprof/heap
curl -s 'http://127.0.0.1:6060/debug/pprof/goroutine?debug=2' | head -50

Pitfalls

  • Importing _ "net/http/pprof", which registers handlers on http.DefaultServeMux, then serving that mux publicly.
  • Profiling for a few seconds during idle periods; capture under real load.
  • Ignoring goroutine counts; steadily growing counts usually mean a leak.

Variations

  • Continuous profiling services (Pyroscope, Grafana) collect profiles over time.
  • Use the execution tracer (/debug/pprof/trace) for latency and scheduling issues.

Further reading: net/http/pprof

Reference: https://pkg.go.dev/net/http/pprof

More Go recipes

Was this page helpful?