Go Profiling with pprof: A Step-by-Step Guide

Janith Hathnagoda
3 min readJun 10, 2024

--

Hey there! If you’re diving into Go and want to make sure your application runs smoothly, profiling is your best friend. Profiling helps you understand what’s going on under the hood of your code. Luckily, Go has a built-in tool called pprof that makes this super easy. I’ll walk through how to set it up and use it together.

Getting Started with pprof

First things first, you need to import the pprof package. Don’t worry, it’s really straightforward:

import (
_ "net/http/pprof"
)

Notice the underscore (_) before the package name? That’s key because it lets you include the package without having to call it directly in your code. This package sets up profiling endpoints for our Go server automatically.

Setting Up Profiling Endpoints

With pprof imported, it registers several handlers for your server. You can access these endpoints to get profiling data:

  • {BaseURL}/debug/pprof/profile: This gives you CPU profiling data.
  • {BaseURL}/debug/pprof/heap: This shows memory allocations.

Here’s a simple example to get you started:

package main

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

func main() {
// Start the pprof server in a separate goroutine
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(1 * time.Second)
w.Write([]byte("Hello, world!"))
})

log.Println("Starting server on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}

In this setup, our profiling server runs on localhost:6060, and the main application runs on localhost:8080. Neat, right?

Analyzing Your Profiles

Now, to actually analyze your profiling data, you can use the go tool pprof command. For example, to capture a 30-second CPU profile:

go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30

Result:

CPU Profiling data of top 10 nodes

You can also generate visualizations like flame graphs, which are super helpful for understanding what’s happening:

go tool pprof -http=:8081 http://localhost:6060/debug/pprof/profile?seconds=30

Flame Graph

In this flame graph, you can see the call stack and the CPU time taken for each function, making it easier to identify performance bottlenecks. By visualizing this data, you can quickly pinpoint which parts of your code are consuming the most resources and need optimization.

One Server to Rule Them All

If you’d rather not deal with multiple servers, you can combine your application and profiling on the same server. Here’s how:

package main

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

func main() {
mux := http.NewServeMux()

// Register pprof handlers
mux.HandleFunc("/debug/pprof/", http.DefaultServeMux.ServeHTTP)
mux.HandleFunc("/debug/pprof/profile", http.DefaultServeMux.ServeHTTP)
mux.HandleFunc("/debug/pprof/heap", http.DefaultServeMux.ServeHTTP)

// Register your application handlers
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(1 * time.Second)
w.Write([]byte("Hello, world!"))
})

log.Println("Starting server on :8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}

Now, both your application and profiling endpoints are running on localhost:8080.

Take Your Profiling to the Next Level

Want to see how your application performs under different conditions? Tools like Locust or JMeter can simulate various loads and HTTP requests, helping you identify performance bottlenecks.

Profiling with pprof gives you a powerful way to keep your Go application running smoothly. It's not just about finding problems; it's about understanding your code better and making it more efficient. Happy coding, and happy profiling!

--

--

Janith Hathnagoda

Undergraduate who's trying to make a meaningful impact in the tech industry.