package main

import (
	// omitted
	_ "net/http/pprof" // auto-register handlers to the HTTP server for profiling via HTTP
	// omitted
)

func main() {
	// omitted

	runtime.GOMAXPROCS(1) // limit CPU usage to avoid overload
	runtime.SetMutexProfileFraction(1) // enable lock contention profiling
	runtime.SetBlockProfileRate(1) // enable blocking operation profiling

	go func() {
		// start an HTTP server; pprof handlers have already been registered
    // /debug/pprof/
		if err := http.ListenAndServe(":6060", nil); err != nil {
			log.Fatal(err)
		}
		os.Exit(0)
	}()

	// omitted
}
  • http://localhost:6060/debug/pprof/
TypeDescriptionNotes
allocsSamples of memory allocationsCan open in a browser, but readability is low
blocksSamples of blocking operationsCan open in a browser, but readability is low
cmdlineShow program startup command/argsCan open in a browser; shows ./go-pprof-practice
goroutineStacks of all current goroutinesCan open in a browser, but readability is low
heapSamples of heap memory usageCan open in a browser, but readability is low
mutexSamples of lock contentionCan open in a browser, but readability is low
profileSamples of CPU usageOpening in a browser downloads a file
threadcreateSamples of system thread creationCan open in a browser, but readability is low
traceProgram execution tracesOpening in a browser downloads a file; not covered here, see Go trace

Investigate high CPU usage

  1. You can see CPU usage is quite high. Run go tool pprof http://localhost:6060/debug/pprof/profile to enter an interactive terminal.
  2. Enter top to view calls with high CPU usage.
  3. Clearly, high CPU usage is caused by github.com/wolfogre/go-pprof-practice/animal/felidae/tiger.(*Tiger).Eat.
  4. Enter list Eat to see the exact code location.
  5. You can see that line 24 runs a 10-billion-iteration empty loop, consuming lots of CPU time.

Next is an extended step: visualize the call stack. It is cool, but you need to install graphviz first. It is easy to install on most systems.

brew install graphviz # for macos
apt install graphviz # for ubuntu
yum install graphviz # for centos
  1. In the interactive terminal, enter web (although the command name is “web”, it actually generates an .svg file and opens it with your default .svg viewer).
  2. Enter exit to leave the pprof interactive terminal.

Investigate high memory usage

  1. Memory usage is still high: go tool pprof http://localhost:6060/debug/pprof/heap
  2. Use top and list again to locate the problematic code.
  3. This time, the issue is in github.com/wolfogre/go-pprof-practice/animal/muridae/mouse.(*Mouse).Steal.
  4. Use web to view the visualization and confirm the problem is indeed here.

Investigate frequent GC

GODEBUG=gctrace=1 ./go-pprof-practice | grep gc

When using pprof, what we care about is not just where memory is consumed, but where memory is being allocated repeatedly. These are different.

  1. go tool pprof http://localhost:6060/debug/pprof/allocs
  2. You can see github.com/wolfogre/go-pprof-practice/animal/canidae/dog.(*Dog).Run performs meaningless allocations.

Investigate goroutine leaks

In the browser, you can see the number of goroutines has reached 106: 106 goroutine.

  1. go tool pprof http://localhost:6060/debug/pprof/goroutine
  2. The issue is a bit subtle this time, but you can still see that github.com/wolfogre/go-pprof-practice/animal/canidae/wolf.(*Wolf).Drink keeps creating useless goroutines.

Investigate lock contention

  1. go tool pprof http://localhost:6060/debug/pprof/mutex
  2. The problem is at github.com/wolfogre/go-pprof-practice/animal/canidae/wolf.(*Wolf).Howl. Note that using locks is not inherently wrong; not all locks are problematic. Let’s see why this one triggers.

Investigate blocking operations

  1. go tool pprof http://localhost:6060/debug/pprof/block
  2. The blocking operation is in github.com/wolfogre/go-pprof-practice/animal/felidae/cat.(*Cat).Pee