How to profile in Go.
This discusses a few go profiling mechanisms and when and where to use each. Which tool you use depends heavily on if the code is local or on a deployed system. Also, it depends on when you need access to the profiles that are generated.
Go profiling basics
Go provides some very useful diagnostic documentation for profiling, tracing, and debugging go code. There are many links off of this documentation that describes how to use the pprof tool.
Here is a great gophercon video from Dave Cheney on profiling that I highly recommend watching. Also, here is a workshop from Dave Cheney that is also excellent.
Another useful link is from DataDog, just know they point out limitations is the hope of selling something.
Lastly, another good link here
Types of profiles
-
CPU Profiler
-
Memory Profiler
-
Block Profiler
-
Mutex Profiler
-
Goroutine Profiler
-
Trace Profiler
Go profiling from unit tests
It is sometimes useful to profile using unit tests. The easiest way to do this is to create a benchmark test. The function should have the signature func BenchmarkXXXXX(b *testing.B)
. Here are some examples of usage.
There are several testing flags that can help generate the correct profiles needed to pass to the pprof tool.
You can also run like so:
go test -cpuprofile cpu.pprof
go test -memprofile mem.pprof
go test -blockprofile block.pprof
go test -mutexprofile mutex.pprof
go test -trace trace.out
go test -race
Profiling in a standalone program
As mentioned in the pprof documentation, you can embed the creating of profiles into your code. An example can be seen here.
Exposed profiles
Here is a list of profiles in pprof
profiles.m = map[string]*Profile{
"goroutine": goroutineProfile,
"threadcreate": threadcreateProfile,
"heap": heapProfile,
"allocs": allocsProfile,
"block": blockProfile,
"mutex": mutexProfile,
}
pprof.StartCPUProfile
import "runtime/pprof"
f, err := os.Create(*cpuprofile)
if err != nil {
log.Fatal("could not create CPU profile: ", err)
}
defer f.Close() // error handling omitted for example
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal("could not start CPU profile: ", err)
}
defer pprof.StopCPUProfile()
Then run: go tool pprof -http=:8080 cpu.prof
pprof.WriteHeapProfile
import "runtime/pprof"
f, err := os.Create(*memprofile)
if err != nil {
log.Fatal("could not create memory profile: ", err)
}
defer f.Close() // error handling omitted for example
runtime.GC() // get up-to-date statistics
if err := pprof.WriteHeapProfile(f); err != nil {
log.Fatal("could not write memory profile: ", err)
}
Then run: go tool pprof -http=:8080 mem.prof
pprof.Lookup("block")
runtime.SetBlockProfileRate(100000000) // WARNING: Can cause some CPU overhead
f, err := os.Create(*blockprofile)
if err != nil {
log.Fatal("could not create block profile: ", err)
}
defer f.Close() // error handling omitted for example
defer pprof.Lookup("block").WriteTo(f, 0)
Then run: go tool pprof -http=:8080 block.prof
pprof.Lookup("mutex")
import "runtime/pprof"
runtime.SetMutexProfileFraction(100)
f, err := os.Create(*mutexprofile)
if err != nil {
log.Fatal("could not create mutex profile: ", err)
}
defer pprof.Lookup("mutex").WriteTo(f, 0)
Then run: go tool pprof -http=:8080 mutex.prof
pprof.Lookup("goroutine")
pprof.Lookup("goroutine")
f, err := os.Create(*goroutineprofile)
if err != nil {
log.Fatal("could not create goroutine profile: ", err)
}
defer pprof.Lookup("goroutine").WriteTo(f, 0)
Then run: go tool pprof -http=:8080 goroutine.prof
trace.Start
import "runtime/trace"
traceFile, err := os.Create(*traceprofile)
if err != nil {
panic(err)
}
defer traceFile.Close()
if err := trace.Start(traceFile); err != nil {
panic(err)
}
defer trace.Stop()
Then run: go tool trace trace.prof
Profiling with a REST handler
You can also enable a REST handler using http pprof to make calls into a running program. There is an example here
Profiling with GoLand
If you have access to GoLand and can simulate the issues locally,
you can check out article. There is also a slightly older article that might provide some more information.