Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Profiling Go web application built with Gorilla's mux with net/http/pprof

Tags:

go

I have a relatively big web application written in Go that uses Gorilla's mux for routing. I recently realised that my web application is quite slow and I would like to profile the web application.

After reading about it, it seems that net/http/pprof is what I need. But I can't make it run with mux; even in the case of the most trivial web application.

Does anyone knows how to make that work?

Here is an example of a trivial code that does not work (i.e. nothing is served at /debug).

package main

import (
    "fmt"
    "github.com/gorilla/mux"
    "math"
    "net/http"
)
import _ "net/http/pprof"

func SayHello(w http.ResponseWriter, r *http.Request) {
    for i := 0; i < 1000000; i++ {
        math.Pow(36, 89)
    }
    fmt.Fprint(w, "Hello!")
}

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/hello", SayHello)
    http.ListenAndServe(":6060", r)
}
like image 282
user983716 Avatar asked Oct 25 '13 13:10

user983716


6 Answers

My preferred method for this is to just let net/http/pprof register itself to http.DefaultServeMux, and then pass all requests starting with /debug/pprof/ along:

package main

import (
    "net/http"
    _ "net/http/pprof"

    "github.com/gorilla/mux"
)

func main() {
    router := mux.NewRouter()
    router.PathPrefix("/debug/pprof/").Handler(http.DefaultServeMux)
    if err := http.ListenAndServe(":6060", router); err != nil {
        panic(err)
    }
}

I find that this approach is a lot more stable than one that depends on the implementation of a hidden initialization method, and also guarantees that you didn't miss anything.

like image 140
damien Avatar answered Oct 08 '22 18:10

damien


user983716 - Thanks for your question and solution!

I was not able to use the links from the web index (http://[my-server]/debug/pprof), until I added a few lines to your solution, like so:

...

func AttachProfiler(router *mux.Router) {
    router.HandleFunc("/debug/pprof/", pprof.Index)
    router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
    router.HandleFunc("/debug/pprof/profile", pprof.Profile)
    router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)

    // Manually add support for paths linked to by index page at /debug/pprof/
    router.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine"))
    router.Handle("/debug/pprof/heap", pprof.Handler("heap"))
    router.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate"))
    router.Handle("/debug/pprof/block", pprof.Handler("block"))
}

...

If anyone has the same problem, I hope this helps!

like image 34
dhoelle Avatar answered Oct 08 '22 17:10

dhoelle


Sorry for that question. The answer is in the init() function of pprof. One just need to add 4 functions from pprof to the mux router. Here is the fixed code from above.

package main

import (
    "fmt"
    "github.com/gorilla/mux"
    "math"

    "net/http"
)
import "net/http/pprof"

func AttachProfiler(router *mux.Router) {
    router.HandleFunc("/debug/pprof/", pprof.Index)
    router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
    router.HandleFunc("/debug/pprof/profile", pprof.Profile)
    router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
}

func SayHello(w http.ResponseWriter, r *http.Request) {
    for i := 0; i < 1000000; i++ {
        math.Pow(36, 89)
    }
    fmt.Fprint(w, "Hello!")
}

func main() {
    r := mux.NewRouter()
    AttachProfiler(r)
    r.HandleFunc("/hello", SayHello)
    http.ListenAndServe(":6060", r)
}
like image 26
user983716 Avatar answered Oct 08 '22 19:10

user983716


Previous examples not really work on my side.

To use pprof in an existing golang project with gorrila/mux, try to add :

...previous code

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/hello", SayHello)

    go func() {
        log.Fatal(http.ListenAndServe(":6061", http.DefaultServeMux))
    }()

    http.ListenAndServe(":6060", r)
}

then go to http://localhost:6061/debug/pprof/

like image 21
ryl Avatar answered Oct 08 '22 17:10

ryl


I did something else, I added another native http server on a different port and it just works out of the box

package main

import (
    "fmt"
    "log"
    "net/http"

    _ "net/http/pprof"
)


func main() {
    go func() {
        log.Println(http.ListenAndServe(":6060", nil))
    }()
    log.Fatalln(http.ListenAndServe(":8080", route.Handlers()))
}

Now the pprof endpoint is at : http://localhost:6060/debug/pprof/ and the applcation is running on port :8080

like image 39
Eran Chetzroni Avatar answered Oct 08 '22 18:10

Eran Chetzroni


Just so:

r := mux.NewRouter()
r.PathPrefix("/debug").Handler(http.DefaultServeMux)
like image 28
larryhu Avatar answered Oct 08 '22 18:10

larryhu