Profiling Go services with pprof
Going Deep Dive in Profiling Tools: CPU Bottlenecks and Memory Leaks
Profiling is one of the most underrated yet powerful tools to diagnose performance issues. In this article, I’ll walk you through using Go’s built-in profiling tools to investigate CPU bottlenecks and identify memory leaks via heap profiling.
We’ll also use Apache JMeter to generate realistic load so we can capture meaningful profiling data.
📦 Installing Dependencies
First, make sure you have Go installed (>= 1.18 is recommended).
To install the profiling tool, you don't need any third-party package because Go provides built-in profiling via net/http/pprof
. But to visualize the results, Go tool pprof is handy.
1️⃣ Enable pprof
In your Go project, install the module if you haven’t yet:
go mod init your-module-name
Then, import and set up net/http/pprof
:
import (
_ "net/http/pprof"
"log"
"net/http"
)
func init() {
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
}
This exposes profiling endpoints at:
http://localhost:6060/debug/pprof/heap
http://localhost:6060/debug/pprof/profile
http://localhost:6060/debug/pprof/goroutine
- ...and more.
2️⃣ Install Graphviz (for visualization)
Graphviz is useful for visualizing the output from pprof:
On macOS:
brew install graphviz
On Ubuntu/Debian:
sudo apt-get install graphviz
📝 Example Code to Profile
Here’s a simple Go program that intentionally consumes CPU and memory:
package main
import (
_ "net/http/pprof"
"log"
"net/http"
"time"
)
func main() {
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
data := [][]byte{}
for {
// Simulate CPU work
go func() {
for i := 0; i < 1e7; i++ {
}
}()
// Simulate memory leak
chunk := make([]byte, 1024*1024) // 1MB
data = append(data, chunk)
time.Sleep(500 * time.Millisecond)
}
}
Run the app:
go run main.go
🚦 Load Testing with Apache JMeter
-
Install JMeter: Download here and unzip it.
-
Create a Test Plan:
- Add a Thread Group.
- Set number of users, ramp-up period, and loop count.
- Add an HTTP Request Sampler targeting
http://localhost:<your-app-port>/your-endpoint
.
- Start the Test: Hit Run and let JMeter hammer your endpoint while the app is running.
🔍 Capturing Profiles
CPU Profile (30 seconds by default)
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
Heap Profile
go tool pprof http://localhost:6060/debug/pprof/heap
Once in the pprof console, you can type:
top
or
web
(web
opens a visual graph if you have Graphviz installed.)
🚨 How to Act on Results
-
High CPU Usage: Look at the top functions consuming the most CPU. If unnecessary goroutines or heavy loops are the culprit, optimize or refactor.
-
Memory Leaks: The heap profile will show allocations that are not being released. Investigate slices, maps, or channels holding on to data longer than needed.
-
Goroutines: Monitor
http://localhost:6060/debug/pprof/goroutine?debug=2
to detect goroutine leaks.
✅ Conclusion
With Go’s built-in profiling tools and a bit of pressure testing using JMeter, you can catch performance bottlenecks early. Always act on your profiling data—whether that’s optimizing code, fixing leaks, or rethinking architecture. Profiling isn’t a one-off; it should be part of your regular performance checks.