feat(coderd): add consolidated /debug/profile endpoint for pprof collection (#22892)

## Summary

Adds a new `GET /api/v2/debug/profile` endpoint that collects multiple
pprof profiles in a single request and returns them as a tar.gz archive.
This allows collecting profiles (including block and mutex) without
requiring `CODER_PPROF_ENABLE` to be set, and without restarting
`coderd`.

Closes #21679

## What it does

The endpoint:
- Temporarily enables block and mutex profiling (normally disabled at
runtime)
- Runs CPU profile and/or trace for a configurable duration (default
10s, max 60s)
- Collects snapshot profiles (heap, allocs, block, mutex, goroutine,
threadcreate)
- Returns a tar.gz archive containing all requested `.prof` files
- Uses an atomic bool to prevent concurrent collections (returns 409
Conflict)
- Is protected by the existing debug endpoint RBAC (owner-only)

**Supported profile types:** cpu, heap, allocs, block, mutex, goroutine,
threadcreate, trace

**Query parameters:**
- `duration`: How long to run timed profiles (default: `10s`, max:
`60s`)
- `profiles`: Comma-separated list of profile types (default:
`cpu,heap,allocs,block,mutex,goroutine`)

## Additional changes

- **SDK client method** (`codersdk.Client.DebugCollectProfile`) for easy
programmatic access
- **`coder support bundle --pprof` integration**: tries the consolidated
endpoint first, falls back to individual `/debug/pprof/*` endpoints for
older servers
- **8 new tests** covering defaults, custom profiles, trace+CPU,
validation errors, authorization, and conflict detection
This commit is contained in:
Kacper Sawicki
2026-03-13 14:09:39 +00:00
committed by GitHub
parent cc6716c730
commit df2360f56a
8 changed files with 783 additions and 2 deletions
+13 -1
View File
@@ -627,7 +627,8 @@ func New(options *Options) *API {
options.Database,
options.Pubsub,
),
dbRolluper: options.DatabaseRolluper,
dbRolluper: options.DatabaseRolluper,
ProfileCollector: defaultProfileCollector{},
}
api.WorkspaceAppsProvider = workspaceapps.NewDBTokenProvider(
ctx,
@@ -1732,6 +1733,8 @@ func New(options *Options) *API {
}
r.Method("GET", "/expvar", expvar.Handler()) // contains DERP metrics as well as cmdline and memstats
r.Post("/profile", api.debugCollectProfile)
r.Route("/pprof", func(r chi.Router) {
r.Use(func(next http.Handler) http.Handler {
// Some of the pprof handlers strip the `/debug/pprof`
@@ -2019,6 +2022,15 @@ type API struct {
// gitSyncWorker refreshes stale chat diff statuses in the
// background.
gitSyncWorker *gitsync.Worker
// ProfileCollector abstracts the runtime/pprof and runtime/trace
// calls used by the /debug/profile endpoint. Tests override this
// with a stub to avoid process-global side-effects.
ProfileCollector ProfileCollector
// ProfileCollecting is used as a concurrency guard so that only one
// profile collection (via /debug/profile) can run at a time. The CPU
// profiler is process-global, so concurrent collections would fail.
ProfileCollecting atomic.Bool
}
// Close waits for all WebSocket connections to drain before returning.