Documentation
¶
Overview ¶
Package easypprof exposes Go's pprof endpoints in a way that is safe to run against a production service.
The usual one-liner, import _ "net/http/pprof", registers the profiling handlers on http.DefaultServeMux. If anything in the process serves that mux on a public port, the endpoints are public too: they leak internals and /debug/pprof/profile?seconds=600 is a cheap denial of service. easypprof instead:
- serves pprof on its own listener and mux (127.0.0.1:6060 by default), never on http.DefaultServeMux;
- refuses to bind a non-loopback address without authentication;
- authenticates callers with named tokens (Bearer or Basic) or a custom Authorizer, and can additionally restrict callers to CIDR ranges;
- caps the duration of every ?seconds= profile and limits concurrent pulls;
- writes an audit log entry (who, what, when, result) for every request;
- hides /debug/pprof/cmdline by default, because command lines often carry DSNs and other secrets;
- turns on sampled block and mutex profiling, which are off by default in the Go runtime and are the profiles that show lock and I/O contention;
- on Go 1.25+, runs a runtime/trace flight recorder you can snapshot when something goes wrong.
Typical use, on a dedicated listener:
srv, err := easypprof.New(easypprof.Config{
Tokens: map[string]string{"oncall": os.Getenv("PPROF_TOKEN")},
})
if err != nil {
log.Fatal(err)
}
if err := srv.Start(); err != nil {
log.Fatal(err)
}
defer srv.Shutdown(context.Background())
Or embedded in an existing server's mux, with no extra port. This mode always requires Tokens or Authorizer:
if err := srv.Mount(mux); err != nil {
log.Fatal(err)
}
defer srv.Shutdown(context.Background())
Either way, profiling can be switched off and on at runtime, for example from an admin endpoint, without unmounting or closing anything:
srv.Disable() // endpoints answer 503, runtime profiling rates restored srv.Enable()
Index ¶
- Constants
- Variables
- func Do(ctx context.Context, fn func(context.Context), labels ...string)
- func Label(name string, h http.Handler) http.Handler
- type Config
- type FlightRecorderConfig
- type Server
- func (s *Server) Addr() string
- func (s *Server) Disable()
- func (s *Server) Enable() error
- func (s *Server) Enabled() bool
- func (s *Server) Handler() (http.Handler, error)
- func (s *Server) Mount(mux *http.ServeMux) error
- func (s *Server) Shutdown(ctx context.Context) error
- func (s *Server) Snapshot(reason string) (string, error)
- func (s *Server) SnapshotTo(w io.Writer) (int64, error)
- func (s *Server) Start() error
Constants ¶
const ( DefaultAddr = "127.0.0.1:6060" DefaultMaxProfileDuration = 60 * time.Second DefaultMaxConcurrent = 2 DefaultBlockProfileRate = 10_000 // sample blocking events of ~10µs and longer DefaultMutexProfileFraction = 100 // sample ~1% of mutex contention events )
Defaults applied by New when the corresponding Config field is zero.
const PathPrefix = "/debug/pprof/"
PathPrefix is where the pprof endpoints are served.
Variables ¶
var ( // ErrFlightRecorderUnsupported is returned by New when FlightRecorder is // set but the binary was built with a Go version older than 1.25. ErrFlightRecorderUnsupported = errors.New("easypprof: flight recorder requires Go 1.25 or newer") // ErrFlightRecorderDisabled is returned by Snapshot methods when // Config.FlightRecorder was not set. ErrFlightRecorderDisabled = errors.New("easypprof: flight recorder is not enabled") // ErrFlightRecorderStopped is returned by Snapshot methods when the // flight recorder is configured but not running: before Start or Mount, // while the server is disabled, or after Shutdown. ErrFlightRecorderStopped = errors.New("easypprof: flight recorder is not running") // ErrSnapshotThrottled is returned by Snapshot when the previous snapshot // was taken less than FlightRecorderConfig.MinInterval ago. ErrSnapshotThrottled = errors.New("easypprof: snapshot throttled") // ErrNoSnapshotDir is returned by Snapshot when FlightRecorderConfig.Dir // is empty. Use SnapshotTo to write somewhere else. ErrNoSnapshotDir = errors.New("easypprof: FlightRecorderConfig.Dir is not set") )
Functions ¶
func Do ¶
Do runs fn with the given pprof labels attached to the current goroutine and to every goroutine fn starts. CPU and goroutine profile samples taken while fn runs carry the labels, so you can filter a profile with `go tool pprof -tagfocus op=post_entry`.
labels are key/value pairs and must have even length:
easypprof.Do(ctx, func(ctx context.Context) {
postEntry(ctx, entry)
}, "op", "post_entry", "ledger", "settlement")
Keep label values low-cardinality (operation names, not account IDs): they end up in profiles that may leave the production environment.
Types ¶
type Config ¶
type Config struct {
// Addr is the TCP address to listen on. Defaults to DefaultAddr.
//
// Binding to anything other than a loopback address requires Tokens or
// Authorizer, unless AllowUnauthenticated is set. Network position is not
// authentication.
Addr string
// Tokens maps a caller name to a secret token. The name is recorded in the
// audit log. Callers authenticate with either
//
// Authorization: Bearer <token>
//
// or HTTP Basic auth with the name as the user and the token as the
// password. Every token must be at least 16 characters long.
Tokens map[string]string
// Authorizer, if set, is used instead of Tokens. It returns the identity
// of the caller for the audit log and whether the request is allowed. Use
// it to plug in mTLS client certificates, OIDC, or a service mesh header.
Authorizer func(r *http.Request) (subject string, ok bool)
// AllowedCIDRs, if non-empty, restricts callers to these networks, based
// on the connection's remote address. X-Forwarded-For is never trusted.
AllowedCIDRs []string
// AllowUnauthenticated permits listening on a non-loopback address with no
// Tokens or Authorizer. Only set it when something in front of the server
// already authenticates callers.
AllowUnauthenticated bool
// MaxProfileDuration caps the ?seconds= parameter accepted by profile,
// trace and delta profile endpoints. Defaults to DefaultMaxProfileDuration.
MaxProfileDuration time.Duration
// MaxConcurrent limits how many profile requests run at once. Extra
// requests get 429 Too Many Requests. Defaults to DefaultMaxConcurrent.
MaxConcurrent int
// BlockProfileRate is passed to runtime.SetBlockProfileRate on Start.
// 0 means DefaultBlockProfileRate; a negative value leaves block
// profiling off.
BlockProfileRate int
// MutexProfileFraction is passed to runtime.SetMutexProfileFraction on
// Start. 0 means DefaultMutexProfileFraction; a negative value leaves
// mutex profiling off.
MutexProfileFraction int
// EnableCmdline serves /debug/pprof/cmdline. It is off by default because
// process arguments frequently contain secrets.
EnableCmdline bool
// DisableFullGoroutineDump rejects /debug/pprof/goroutine?debug=2. That
// dump stops the world for its whole duration, which can be long in a
// process with many goroutines.
DisableFullGoroutineDump bool
// FlightRecorder enables the runtime/trace flight recorder (Go 1.25+).
// On older Go versions New returns ErrFlightRecorderUnsupported.
FlightRecorder *FlightRecorderConfig
// Logger receives audit and lifecycle logs. Defaults to slog.Default().
Logger *slog.Logger
}
Config configures a Server. The zero value is valid and gives a server on 127.0.0.1:6060 with no authentication, which is reachable only from the same host or network namespace (for example through kubectl port-forward).
type FlightRecorderConfig ¶
type FlightRecorderConfig struct {
// MinAge is how much recent trace history to keep. Defaults to 10s.
MinAge time.Duration
// MaxBytes caps the memory used by the buffer; it takes precedence over
// MinAge. Defaults to 16 MiB.
MaxBytes uint64
// Dir is where Snapshot writes trace files. Created with mode 0700 if it
// does not exist. Leave empty to use only SnapshotTo and the HTTP
// endpoint.
Dir string
// MinInterval is the minimum time between two Snapshot calls that write
// files. It makes Snapshot safe to call from a hot path. Defaults to 1m.
MinInterval time.Duration
}
FlightRecorderConfig configures the runtime/trace flight recorder. The recorder keeps the last few seconds of execution trace in memory so that, when something goes wrong (a latency spike, a timeout), you can write out what the scheduler, GC and goroutines were doing just before it.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is a hardened pprof debug server. Create it with New, then call Start and, on the way out, Shutdown.
func New ¶
New validates cfg and builds a Server. It does not touch the runtime or open a socket; Start does that.
func (*Server) Addr ¶
Addr returns the address the server is listening on, or "" before Start. Useful when Config.Addr uses port 0.
func (*Server) Disable ¶
func (s *Server) Disable()
Disable turns profiling off without closing the listener or unmounting the handler: endpoints answer 503 Service Unavailable, the runtime profiling rates are restored and the flight recorder is stopped. Profiles already in progress run to completion. It is idempotent. Called before Start or Mount, the server starts switched off.
func (*Server) Enable ¶
Enable turns profiling back on after Disable: it re-applies the block and mutex rates, restarts the flight recorder (with an empty buffer) and lets requests through. It is idempotent. Before Start or Mount it only records the intent. After Shutdown it records the intent, but the handler keeps answering 503 until the server is started or mounted again.
func (*Server) Enabled ¶
Enabled reports whether profiling is switched on. It reflects the last Enable or Disable call, whether or not the server is running.
func (*Server) Handler ¶
Handler applies the runtime settings Start would (block and mutex rates, flight recorder) and returns the guarded pprof handler, for serving on an existing server instead of a dedicated listener. Serve it at PathPrefix; with a ServeMux, use Mount.
The host server's address is unknown here, so Handler requires Tokens or Authorizer unless AllowUnauthenticated is set. Call Shutdown on the way out to restore the runtime; the handler then answers 503 Service Unavailable.
func (*Server) Shutdown ¶
Shutdown stops the server, waiting for in-flight profiles until ctx is done, then stops the flight recorder and restores the runtime profiling rates. In mounted mode there is no listener to stop; the handler starts answering 503.
func (*Server) Snapshot ¶
Snapshot writes the flight recorder's buffer to a new file in FlightRecorderConfig.Dir and returns its path. reason ends up in the file name and the log, e.g. "p99-over-250ms".
It is throttled by MinInterval and returns ErrSnapshotThrottled when called too often, so it is fine to call it every time a latency threshold trips:
if elapsed > 250*time.Millisecond {
go srv.Snapshot("slow-transfer")
}
func (*Server) SnapshotTo ¶
SnapshotTo writes the flight recorder's buffer to w, for example to upload it to object storage. It is not throttled.