fgprof
A sampling wall-clock profiler for Go that captures both On-CPU computation and Off-CPU waiting time in a single pprof-compatible profile.
Repository Health
Technical Analysis
fgprof is a sampling profiler for Go programs that closes a blind spot left by the standard library’s built-in profilers. Go’s CPU profiler samples only goroutines actively running on a core, so time spent blocked on I/O, network calls, channels, or locks never shows up; the trace profiler can see blocking time but not CPU time, and neither view combines the two. fgprof instead calls runtime.GoroutineProfile 99 times per second, capturing every goroutine’s stack regardless of its scheduling state, then aggregates the samples into a single wall-clock profile that mixes On-CPU and Off-CPU time together.
The library ships as a small, dependency-light package with an http.Handler that slots in next to net/http/pprof, and it emits output in both pprof’s binary protobuf format and Brendan Gregg’s plaintext folded-stack format used by the FlameGraph toolchain. Because it reuses go tool pprof and existing flame-graph tooling, a team can add wall-clock visibility to an existing service with a single line of code and immediately see where a mixed I/O/CPU workload is actually spending its time.
What You Get
fgprof.Start()- begins wallclock profiling of the whole process and returns a stop function that finalizes and writes the profile in the requested format.fgprof.Handler()- anhttp.Handlerthat plugs directly into an existing mux alongsidenet/http/pprof, acceptingsecondsandformatquery parameters.- Dual export formats - profiles can be written as pprof’s binary protobuf format for
go tool pprofor as Brendan Gregg’s folded-stack format for FlameGraph. - Goroutine-state agnostic sampling - uses
runtime.GoroutineProfileinstead of the SIGPROF-based CPU sampler, so blocked, sleeping, and waiting goroutines are captured just like running ones. - Self-frame filtering - the profiler automatically excludes its own sampling goroutine’s frames from the exported profile so it doesn’t pollute the results.
Common Use Cases
- Diagnosing mixed I/O/CPU workloads - a backend engineer profiling a service that both processes CPU-bound requests and waits on downstream network calls, needing one profile that attributes real wall-clock time to both.
- Explaining slow endpoints hidden from the CPU profiler - an SRE investigating a handler that looks fast under
net/http/pprofCPU sampling but is actually slow in production, using fgprof to reveal time spent blocked on database or HTTP calls. - Flame-graphing legacy services - a team retrofitting an existing HTTP server with a single
fgprof.Handler()call to get flame-graph visibility without redesigning their instrumentation. - Ad-hoc profiling of batch jobs - a developer wrapping a long-running CLI or batch task with
fgprof.Start/stop()to capture a wall-clock profile of a one-off run and export it as pprof for later analysis.
Under The Hood
Architecture
fgprof is deliberately flat: a single fgprof.go file implements Start(), a profiler type wrapping runtime.GoroutineProfile, and a wallclockProfile type that accumulates symbolized stacks keyed by raw stack trace and exports them to pprof or folded format; handler.go is a thin http.Handler wrapper around Start() that adds seconds/format query-parameter parsing. Start() launches a background goroutine driven by a time.Ticker at 99Hz that calls profiler.GoroutineProfile() and feeds the resulting stack records into wallclockProfile.Add(); the returned stop closure halts the ticker, computes the actual achieved sample rate (to correct for scheduler jitter), and calls Export(). There is no dependency injection, no internal package layering, and no shared mutable state beyond the single profile struct — the whole implementation is intentionally small enough to read end to end in one sitting, which is itself a stated design goal in the README.
Tech Stack
The module targets Go 1.14 and depends on only two external packages: github.com/google/pprof/profile for building and serializing the pprof protobuf format, and github.com/stretchr/testify for test assertions; everything else is standard library (runtime, net/http, time, sort, strings, math, io, fmt). There is no build tooling beyond go build/go test, no database or web framework involved, and the only “deployment target” is being imported into another Go binary — CI (GitHub Actions, .github/workflows/go.yml) builds the bundled example and runs go test -v . on every push and PR to master.
Code Quality
Testing is present and reasonably thorough for the package’s scope: fgprof_test.go has a table-driven smoke test (TestStart) that exercises both output formats plus a zero-duration edge case, a golden-output test (Test_toPprof) that asserts an exact rendered pprof text dump, and benchmarks (BenchmarkProfiler, BenchmarkProfilerGoroutines, BenchmarkStackCounter) that specifically measure overhead as goroutine counts scale from 1 to over a million — directly validating the README’s stated overhead caveat. Error handling is explicit rather than swallowed (Export/Start’s stop function return error; the HTTP handler responds with a 400 and a message on bad input). Naming is clear and idiomatic Go, the Format type is a small string-based enum rather than a bare string, and there’s no linter config beyond what go vet/gofmt enforce implicitly through CI.
What Makes It Unique
The library’s core insight is a targeted workaround for a well-known blind spot in Go’s tooling: the built-in CPU profiler samples via SIGPROF, which only fires for threads that are actively executing, so goroutines parked on non-blocking I/O, channels, or locks are invisible to it, while the trace tool’s blocking profile can’t be combined with CPU time in one view. fgprof sidesteps both limitations by driving its own sampling loop against runtime.GoroutineProfile, which reports every goroutine’s stack regardless of scheduling state, and reusing the existing pprof/FlameGraph ecosystems for visualization rather than inventing new tooling. This narrow, well-executed fix — rather than a broad feature set — is why the library remains widely referenced in the Go performance-tooling space despite being under 350 lines of code.
Used by 5 apps in this directory
Gitea
Devops · Developer Tools · Project Management
Self-hosted DevOps in a single Go binary — Git hosting, GitHub Actions-compatible CI/CD, and 30+ package registries without any SaaS dependency.
MinIO
File Storage
High-performance, S3-compatible object storage built for AI/ML and analytics workloads — run it anywhere from a laptop to a petabyte-scale cluster.
Pyroscope
Monitoring · Devops · Developer Tools
An open-source, horizontally scalable continuous profiling platform that pinpoints CPU, memory, and I/O bottlenecks down to the exact line of code, built by Grafana Labs alongside Loki, Tempo, and Mimir.
SpiceDB
Security · Authentication · Databases
An open source, Google Zanzibar-inspired authorization database that models permissions as relationships and evaluates fine-grained access checks at massive scale with single-digit millisecond latency.
TiDB
Databases · AI Development
AI-Native Distributed SQL Database for Agentic Workloads