20

I have an existing http server which I would like to profile. I have included _ "net/http/pprof"to my imports, and I already have http server running:

router := createRouter()
server := &http.Server {
    Addr:           ":8080",
    Handler:        router,
    ReadTimeout:    15*time.Second,
    WriteTimeout:   15*time.Second,
//  MaxHeaderBytes: 4096,
}

log.Fatal(server.ListenAndServe())

When I'm trying to access http://localhost:8080/debug/pprof/ I get 404 page not found.

That's what I get when using go tool pprof on a local machine:

userver@userver:~/Desktop/gotest$ go tool pprof http://192.168.0.27:8080/
Use of uninitialized value $prefix in concatenation (.) or string at /usr/lib/go/pkg/tool/linux_amd64/pprof line 3019.
Read http://192.168.0.27:8080/pprof/symbol
Failed to get the number of symbols from http://192.168.0.27:8080/pprof/symbol

userver@userver:~/Desktop/gotest$ go tool pprof http://localhost:8080/debug/pprof/profile
Read http://localhost:8080/debug/pprof/symbol
Failed to get the number of symbols from http://localhost:8080/debug/pprof/symbol

Same for a remote client:

MacBookAir:~ apple$ go tool pprof http://192.168.0.27:8080/
Use of uninitialized value $prefix in concatenation (.) or string at /usr/local/Cellar/go/1.3.2/libexec/pkg/tool/darwin_amd64/pprof line 3027.
Read http://192.168.0.27:8080/pprof/symbol
Failed to get the number of symbols from http://192.168.0.27:8080/pprof/symbol
Max Malysh
  • 29,384
  • 19
  • 111
  • 115

4 Answers4

29

It's not explicitly mentioned in the documentation, but net/http/pprof only registers its handlers with http.DefaultServeMux.

From the source:

func init() {
        http.Handle("/debug/pprof/", http.HandlerFunc(Index))
        http.Handle("/debug/pprof/cmdline", http.HandlerFunc(Cmdline))
        http.Handle("/debug/pprof/profile", http.HandlerFunc(Profile))
        http.Handle("/debug/pprof/symbol", http.HandlerFunc(Symbol))
        http.Handle("/debug/pprof/trace", http.HandlerFunc(Trace))
}

If you're not using the default mux you just have to register any/all of those you want with whatever mux you're using, e.g. something like mymux.HandleFunc("…", pprof.Index), etc.

Alternatively you can listen on a separate port (also possibly bound to only localhost if desired) with the default mux as you've shown.

Dave C
  • 7,729
  • 4
  • 49
  • 65
  • 2
    I have a question: Once going to `/debug/pprof/` there are several links that lead to 404 pages. In particular `heap` and `allocs` 404. And as you've shown above, I don't see routes for those paths provided in `net/http/pprof/pprof.go`'s `init()` func. How do I make those accessible? Would you happen to know how I can get those @Dave C? – Andrew B Sep 01 '20 at 16:01
  • If you use `router.PathPrefix("/debug/").Handler(http.DefaultServeMux)` the heap endpoint is added. It's not if you apply the `init` handlers. Not sure why... – andig Dec 10 '20 at 08:38
  • Note that when using Gorilla mux, there's a caveat - see [my answer](https://stackoverflow.com/a/71032595/485343) below for a workaround for the 404 errors. – rustyx Feb 08 '22 at 10:51
16

If you're using a github.com/gorilla/mux.Router you can simply hand off any request prefixed with /debug/ to the http.DefaultServeMux.

import _ "net/http/debug"
router := mux.NewRouter()
router.PathPrefix("/debug/").Handler(http.DefaultServeMux)
qed
  • 22,298
  • 21
  • 125
  • 196
Devon Peticolas
  • 430
  • 5
  • 9
14

Looks like the problem was in a *mux.Router used from github.com/gorilla/mux which I used as a Handler in my http.Server instance.

Solution: just launch one more server just for the pprof:

server := &http.Server {
    Addr:           ":8080",
    Handler:        router,
    ReadTimeout:    15*time.Second,
    WriteTimeout:   15*time.Second,
}
go func() {
    log.Println(http.ListenAndServe(":6060", nil))
}()
log.Fatal(server.ListenAndServe())
Max Malysh
  • 29,384
  • 19
  • 111
  • 115
3

Here's how to use pprof in combination with Gorilla mux. To avoid HTTP 404 errors we need to explicitly specify the route for /debug/pprof/{cmd} because Gorilla doesn't do prefix routing:

router := mux.NewRouter()
router.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index))
router.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline))
router.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile))
router.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol))
router.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace))
router.Handle("/debug/pprof/{cmd}", http.HandlerFunc(pprof.Index)) // special handling for Gorilla mux

err := http.ListenAndServe("127.0.0.1:9999", router)
log.Errorf("pprof server listen failed: %v", err) // note: http.ListenAndServe never returns nil
rustyx
  • 80,671
  • 25
  • 200
  • 267