profiling

profiling bridges Go's built-in net/http/pprof profiling tool and the httprouter request router, allowing all pprof endpoints to be served through a single wildcard route without manual per-handler registration.

Part of nurago, a collection of independent Go packages for backend services.

import "github.com/tecnickcom/nurago/pkg/profiling"

Package profiling bridges Go’s built-in net/http/pprof profiling tool and the httprouter request router, allowing all pprof endpoints to be served through a single wildcard route without manual per-handler registration.

PProfHandler is a single http.HandlerFunc that reads an *option wildcard parameter from the httprouter request context and dispatches to the correct pprof handler. Registering one wildcard route is all that is needed:

router.HandlerFunc(http.MethodGet, "/pprof/*option", profiling.PProfHandler)

Supported Endpoints

The following pprof paths are handled after the wildcard prefix:

/pprof/             - interactive index page listing all available profiles
/pprof/cmdline      - running program's command line
/pprof/profile      - 30-second (or ?seconds=N) CPU profile
/pprof/symbol       - symbol lookup for program counters
/pprof/trace        - execution trace (use ?seconds=N to set duration)
/pprof/<name>       - any named runtime profile, e.g. heap, goroutine,
                      allocs, block, mutex, threadcreate

The handler does not touch http.DefaultServeMux, and it reads the endpoint from httprouter’s context parameter, so the mount prefix can be anything (for example /debug/pprof/*option). Named profiles are forwarded to pprof.Handler, so no code changes are needed when the Go runtime adds new profiles.

Caveats

The route’s wildcard parameter must be named exactly WildcardParamName (“option”); PProfHandler reads that parameter to select the endpoint. A route registered with a different wildcard name (e.g. /pprof/*path) would make the handler serve the index page for every request.

The /pprof/profile and /pprof/trace endpoints block for the requested duration (?seconds=N). Mount this handler on a route that is exempt from any per-request timeout, otherwise long profiles are truncated. The httpserver integration disables the request timeout for the pprof route for this reason.

The index page served at the empty path links to the other profiles using relative URLs (e.g. heap?debug=1), so it must be reached at a path ending in a slash (/pprof/). httprouter’s default RedirectTrailingSlash handling redirects /pprof to /pprof/, which keeps those links working; keep that redirect enabled.

Security Note

pprof endpoints expose detailed internals of a running process (memory layout, goroutine stacks, CPU traces). Mount this handler only on an internal or administrative server that is not reachable from the public internet, and protect it with authentication middleware appropriate for your environment.

Integration

The httpserver package registers PProfHandler as the default pprof handler on its internal router. See pkg/httpserver/config.go for a complete integration example.

When To Use

  • Your service routes with httprouter and you want pprof without registering each handler.
  • pprof must be mounted on an internal admin server rather than on http.DefaultServeMux.

Example

router := httprouter.New()

// One wildcard route serves every pprof endpoint. The mount prefix is
// arbitrary, as the handler reads the endpoint from the wildcard
// parameter rather than from the request path.
router.HandlerFunc(
	http.MethodGet,
	"/pprof/*"+profiling.WildcardParamName,
	profiling.PProfHandler,
)

srv := httptest.NewServer(router)
defer srv.Close()

// The goroutine profile is a named runtime profile forwarded to
// pprof.Handler, so it needs no explicit registration.
req, err := http.NewRequestWithContext(
	context.TODO(),
	http.MethodGet,
	srv.URL+"/pprof/goroutine?debug=1",
	nil,
)
if err != nil {
	fmt.Println(err)

	return
}

resp, err := srv.Client().Do(req)
if err != nil {
	fmt.Println(err)

	return
}

defer func() { _ = resp.Body.Close() }()

fmt.Println(resp.StatusCode)

// Output:
// 200

Full source is in example_profiling_test.go. More runnable examples are on pkg.go.dev.

Dependencies

Importing this package pulls 1 external module:

  • github.com/julienschmidt/httprouter