Generated banner for Golang Benchmark
Generated logo for Golang Benchmark

Golang Benchmark

Samuel BertheCommunityCreated by samberDistributed by Samuel Berthe

  • Skill
  • Tool integration
  • Software development
  • MIT
  • Low risk

From the author

Golang benchmarking, profiling, and performance measurement. Use when writing, running, or comparing Go benchmarks, profiling hot paths with pprof, interpreting CPU/memory/trace profiles, analyzing results with benchstat, setting up CI benchmark regression detection, or investigating production performance with Prometheus runtime metrics. Also use when the developer needs deep analysis on a specific performance indicator - this skill provides the measurement methodology, while `samber/cc-skills-golang@golang-performance` provides the optimization patterns.
— the description by samber

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a rigorous methodology for measuring Go performance. It covers the entire lifecycle: writing benchmarks with proper conventions, executing them under controlled conditions, generating profiles (CPU, memory, trace), and performing statistical comparisons using benchstat to distinguish real performance gains from noise. It emphasizes statistical significance and reproducible measurement over anecdotal testing.

What it does

  • Guides the creation of Go benchmarks using standard naming and file conventions.
  • Provides commands for running benchmarks with memory reporting and statistical iteration counts.
  • Facilitates the generation of CPU, memory, and execution trace profiles for deep analysis.
  • Uses benchstat to perform A/B comparisons with statistical confidence intervals.
  • Offers strategies for integrating performance regression detection into CI pipelines.
  • Explains how to document performance improvements in commit messages using benchmark data.

When to use it

  • When you need to measure the performance impact of a code change with statistical rigor.
  • When investigating performance bottlenecks using CPU or memory profiling.
  • When setting up automated performance regression gating in CI.
  • When comparing multiple optimization variants to identify the most effective approach.
  • When you need to verify that a code change has a measurable impact before committing.

Advantages

  • Promotes statistical rigor, preventing misleading conclusions from single benchmark runs.
  • Standardizes benchmark file naming and function ordering for better maintainability.
  • Integrates seamlessly with standard Go tooling and performance analysis utilities.
  • Provides clear guidance on avoiding common pitfalls like noisy neighbors and dead-code elimination.
  • Enables data-driven performance documentation in git history.

Usage examples

“How do I run my benchmarks 10 times to ensure statistical significance?”
The agent will provide the command: go test -run='^$' -bench=. -benchmem -count=10 ./pkg/...
“Compare my current changes against the main branch for performance regressions.”
The agent will guide you to use benchdiff to compare git refs, ensuring results are cached and statistically compared.
“How do I generate and analyze a CPU profile for my benchmark?”
The agent will show how to run go test with -cpuprofile, followed by using go tool pprof to interactively analyze the resulting file.
“How should I format my commit message when I optimize a function?”
The agent will provide a template including the hardware context and the benchstat output table to prove the performance gain.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/samber/golang-benchmark/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip golang-benchmark.zip -d .agents/skills

Documentation by samber

Persona: You are a Go performance measurement engineer. You never draw conclusions from a single benchmark run — statistical rigor and controlled conditions are prerequisites before any optimization decision.

Thinking mode: Reason as thoroughly as possible for benchmark analysis, profile interpretation, and performance comparison tasks — deep reasoning prevents misinterpreting profiling data and ensures statistically sound conclusions. On Claude Code, use ultrathink to trigger extended thinking explicitly.

Dependencies:

  • benchstat: go install golang.org/x/perf/cmd/benchstat@latest

Go Benchmarking & Performance Measurement

Performance improvement does not exist without measures — if you can measure it, you can improve it.

This skill covers the full measurement workflow: write a benchmark, run it, profile the result, compare before/after with statistical rigor, and track regressions in CI. For optimization patterns to apply after measurement, → See samber/cc-skills-golang@golang-performance skill. For pprof setup on running services, → See samber/cc-skills-golang@golang-troubleshooting skill.

Writing Benchmarks

File and Ordering Conventions

Benchmark functions live in a _bench_test.go file named after the source file under benchmark, not after the individual function — parser.go -> parser_bench_test.go, containing BenchmarkParse, BenchmarkEncode, etc., not a separate benchmarkparse_test.go per function.

  • Keeping benchmarks in their own file (instead of mixed into parser_test.go) keeps go test -bench=. ./pkg/parser output free of unrelated Test* noise.

  • It separates fixtures sized for measurement (large inputs, long-lived setup) from those sized for correctness — the two rarely share the same shape.

  • The file still follows Go's one-test-file-per-source-file convention (→ See samber/cc-skills-golang@golang-testing skill), just with the _bench suffix marking its narrower purpose.

Order Benchmark* functions inside parser_bench_test.go to mirror the order of the functions/methods they measure in parser.go — a reader comparing the two files top to bottom should find BenchmarkParse at the same relative position as Parse.

b.Loop() (Go 1.24+) — preferred

For Go 1.24+, prefer b.Loop() for new benchmarks. It times only the loop body and keeps function arguments/results alive, which reduces dead-code-elimination mistakes.

func BenchmarkParse(b *testing.B) {
    data := loadFixture("large.json") // setup — excluded from timing
    for b.Loop() {
        Parse(data)  // compiler cannot eliminate this call
    }
}

Legacy b.N loops still compile and are fine to keep when preserving existing benchmarks or supporting Go <1.24. They are easier to get wrong: setup may need b.ResetTimer(), and results may need a sink if the compiler can eliminate the work. Go 1.26 fixed an earlier b.Loop() inlining limitation — benchmarks on 1.24–1.25 already benefit from b.Loop() but may miss inlining optimizations that 1.26 delivers.

Go 1.27's size-specialized allocator changes allocation-heavy benchmark baselines (faster sub-80-byte allocations, larger binaries) independent of any code change. Treat a benchstat comparison that straddles the Go 1.26→1.27 toolchain boundary as measuring the toolchain, not the code — rerun the "before" benchmark on the same toolchain as "after" before trusting the delta.

Memory tracking
func BenchmarkAlloc(b *testing.B) {
    b.ReportAllocs() // or run with -benchmem flag
    var sink []byte
    for b.Loop() {
        sink = make([]byte, 1024)
    }
    _ = sink
}

b.ReportMetric() adds custom metrics (e.g., throughput):

b.ReportMetric(float64(totalBytes)/b.Elapsed().Seconds(), "bytes/s") // b.Elapsed() is only valid inside b.Loop()
Sub-benchmarks and table-driven
func BenchmarkEncode(b *testing.B) {
    for _, size := range []int{64, 256, 4096} {
        b.Run(fmt.Sprintf("size=%d", size), func(b *testing.B) {
            data := make([]byte, size)
            for b.Loop() {
                Encode(data)
            }
        })
    }
}

Running Benchmarks

go test -bench=BenchmarkEncode -benchmem -count=10 ./pkg/... | tee bench.txt
FlagPurpose
-bench=.Run all benchmarks (regexp filter)
-benchmemReport allocations (B/op, allocs/op)
-count=10Run 10 times for statistical significance
-benchtime=3sMinimum time per benchmark (default 1s)
-cpu=1,2,4Run with different GOMAXPROCS values
-cpuprofile=cpu.profWrite CPU profile
-memprofile=mem.profWrite memory profile
-trace=trace.outWrite execution trace

Output format: BenchmarkEncode/size=64-8 5000000 230.5 ns/op 128 B/op 2 allocs/op — the -8 suffix is GOMAXPROCS, ns/op is time per operation, B/op is bytes allocated per op, allocs/op is heap allocation count per op.

Comparing Optimization Variants in Parallel

When several competing optimization hypotheses exist for the same bottleneck, implement each variant in its own isolated worktree via a separate sub-agent, so their code changes never collide in the shared working tree.

Run the benchmarks serially, not concurrently. Concurrent benchmark runs share the same CPU — the noisy-neighbor effect contaminates ns/op and reintroduces the exact statistical noise -count and benchstat exist to eliminate. Implementing in parallel is safe (isolated worktrees, no file contention); measuring in parallel is not (shared hardware, real contention). Run each variant's benchmark one at a time, back in the main tree or sequentially per worktree.

Compare every variant's benchstat output against the same baseline report, keep the winner, and remove the worktrees for the rest.

Documenting Results in Commits

Paste benchstat output in the commit body when the change has a measurable performance impact. This documents why an optimization was made, prevents future readers from reverting it, and lets reviewers verify the claim without re-running benchmarks.

Commit format:

perf(parser): reduce Parse allocations 50% with sync.Pool

Replace per-call []byte allocation with a pooled buffer.

goos: linux / goarch: amd64 / cpu: AMD Ryzen 9 5950X
          │    old     │              new               │
          │  sec/op    │  sec/op     vs base            │
Parse-32    4.592µ ± 2%  3.041µ ± 1%  -33.78% (p=0.000 n=10)

          │   old    │             new              │
          │   B/op   │   B/op     vs base           │
Parse-32   1.024Ki ± 0%  0.512Ki ± 0%  -50.00% (p=0.000 n=10)

          │ old  │            new             │
          │ allocs/op │ allocs/op  vs base    │
Parse-32   12.00 ± 0%   6.000 ± 0%  -50.00% (p=0.000 n=10)

Rules:

  • Only include benchmarks directly affected by the change — strip unrelated rows

  • Never paste results with ~ (no statistical significance) — the improvement cannot be claimed

  • Include the hardware context line (goos/goarch/cpu) so results are reproducible

  • Use perf(scope): commit type for performance-only changes

Profiling from Benchmarks

Generate profiles directly from benchmark runs — no HTTP server needed:

# CPU profile
go test -bench=BenchmarkParse -cpuprofile=cpu.prof ./pkg/parser
go tool pprof cpu.prof

# Memory profile (alloc_objects shows GC churn, inuse_space shows leaks)
go test -bench=BenchmarkParse -memprofile=mem.prof ./pkg/parser
go tool pprof -alloc_objects mem.prof

# Execution trace
go test -bench=BenchmarkParse -trace=trace.out ./pkg/parser
go tool trace trace.out

For full pprof CLI reference (all commands, non-interactive mode, profile interpretation), see pprof Reference. For execution trace interpretation, see Trace Reference. For statistical comparison, see benchstat Reference.

Reference Files

  • pprof Reference — Interactive and non-interactive analysis of CPU, memory, and goroutine profiles. Full CLI commands, profile types (CPU vs allocobjects vs inuse_space), web UI navigation, and interpretation patterns. Use this to dive deep into _where time and memory are being spent in your code.

  • benchstat Reference — Statistical comparison of benchmark runs with rigorous confidence intervals and p-value tests. Covers output reading, filtering old benchmarks, interleaving results for visual clarity, and regression detection. Use this when you need to prove a change made a meaningful performance difference, not just a lucky run.

  • Trace Reference — Execution tracer for understanding when and why code runs. Visualizes goroutine scheduling, garbage collection phases, network blocking, and custom span annotations. Use this when pprof (which shows where CPU goes) isn't enough — you need to see the timeline of what happened.

  • Diagnostic Tools — Quick reference for ancillary tools: fieldalignment (struct padding waste), GODEBUG (runtime logging flags), fgprof (frame graph profiles), race detector (concurrency bugs), and others. Use this when you have a specific symptom and need a focused diagnostic — don't reach for pprof if a simpler tool already answers your question.

  • Compiler Analysis — Low-level compiler optimization insights: escape analysis (when values move to the heap), inlining decisions (which function calls are eliminated), SSA dump (intermediate representation), and assembly output. Use this when benchmarks show allocations you didn't expect, or when you want to verify the compiler did what you intended.

  • CI Regression Detection — Automated performance regression gating in CI pipelines. Covers three tools (benchdiff for quick PR comparisons, cob for strict threshold-based gating, gobenchdata for long-term trend dashboards), noisy neighbor mitigation strategies (why cloud CI benchmarks vary 5-10% even on quiet machines), and self-hosted runner tuning to make benchmarks reproducible. Use this when you want to ensure pull requests don't silently slow down your codebase — detecting regressions early prevents shipping performance debt.

  • Investigation Session — Production performance troubleshooting workflow combining Prometheus runtime metrics (heap size, GC frequency, goroutine counts), PromQL queries to correlate metrics with code changes, runtime configuration flags (GODEBUG env vars to enable GC logging), and cost warnings (when you're hitting performance tax). Use this when production benchmarks look good but real traffic behaves differently.

  • Prometheus Go Metrics Reference — Complete listing of Go runtime metrics actually exposed as Prometheus metrics by prometheus/client_golang. Covers 30 default metrics, 40+ optional metrics (Go 1.17+), process metrics, and common PromQL queries. Distinguishes between runtime/metrics (Go internal data) and Prometheus metrics (what you scrape from /metrics). Use this when setting up monitoring dashboards or writing PromQL queries for production alerts.

Cross-References

  • → See samber/cc-skills-golang@golang-performance skill for optimization patterns to apply after measuring ("if X bottleneck, apply Y")

  • → See samber/cc-skills-golang@golang-troubleshooting skill for pprof setup on running services (enable, secure, capture), Delve debugger, GODEBUG flags, root cause methodology

  • → See samber/cc-skills-golang@golang-observability skill for everyday always-on monitoring, continuous profiling (Pyroscope), distributed tracing (OpenTelemetry)

  • → See samber/cc-skills-golang@golang-testing skill for general testing practices

  • → See samber/cc-skills@promql-cli skill for querying Prometheus runtime metrics in production to validate benchmark findings

Files

references/trace.md

Execution Trace Reference

go tool trace shows what pprof cannot: scheduling delays, GC stop-the-world phases, goroutine state transitions, and why goroutines are not running. pprof samples what's on-CPU; trace records every state transition at nanosecond precision.

Use the execution tracer when:

  • pprof shows low CPU% but latency is high (goroutines waiting, not working)

  • You suspect GC pauses are causing tail latency spikes

  • You need to understand goroutine scheduling and contention

  • You want to see the wall-clock timeline of concurrent operations

Table of Contents

  • Generating Traces

    • From benchmarks

    • From running service

    • From tests

    • From code (programmatic)

  • Full Command Reference

    • Opening traces

    • Extracting pprof profiles from traces

    • Full capture-to-analysis workflows

    • go tool trace flags summary

    • HTTP endpoints served by the web UI

  • Web UI

    • Main views

    • Navigating the trace viewer

    • Reading the timeline

  • What to Look For

    • Goroutine states

    • GC phases

    • Scheduling latency

    • Network/sync blocking

    • Goroutine creation and destruction

  • Custom Annotations

    • Tasks

    • Regions

    • Log messages

    • When to use annotations

  • Flight Recorder (Go 1.25+)

    • Setup

    • Snapshot on error

    • Trigger patterns

    • Analyzing a snapshot

    • Constraints

    • When to use flight recorder vs regular tracing

  • Overhead and Practical Limits

  • Trace vs pprof: When to Use Which

Generating Traces

From benchmarks
go test -bench=BenchmarkParse -trace=trace.out ./pkg/parser
go tool trace trace.out
From running service

Requires import _ "net/http/pprof":

# Capture 5 seconds of trace data (adjust duration as needed)
curl -o trace.out http://localhost:6060/debug/pprof/trace?seconds=5
go tool trace trace.out

Warning: traces generate data at MB/s. Keep captures short — 5-10 seconds is typical. Longer traces are unwieldy, slow to parse, and may consume significant memory when opened.

From tests
go test -trace=trace.out ./pkg/parser
go tool trace trace.out
From code (programmatic)
import "runtime/trace"

f, _ := os.Create("trace.out")
trace.Start(f)
defer trace.Stop()

Or capture a region of interest:

import "runtime/trace"

// Start tracing only when needed
f, _ := os.Create("trace.out")
trace.Start(f)

doExpensiveWork()

trace.Stop()
f.Close()

Full Command Reference

Opening traces
# Open trace in web browser (default — starts HTTP server, opens browser)
go tool trace trace.out

# Open on a specific port
go tool trace -http=:8080 trace.out

# Open on a specific host:port (e.g., for remote access)
go tool trace -http=0.0.0.0:8080 trace.out
Extracting pprof profiles from traces

go tool trace can convert trace data into pprof-compatible profiles. This bridges the two tools — you capture with the tracer (nanosecond events) and analyze with pprof (statistical aggregation with top, list, peek):

# Network blocking profile — where goroutines wait on network I/O
go tool trace -pprof=net trace.out > net.prof
go tool pprof -top net.prof

# Synchronization blocking profile — mutexes, channels, wait groups
go tool trace -pprof=sync trace.out > sync.prof
go tool pprof -top sync.prof

# Syscall blocking profile — system calls that block goroutines
go tool trace -pprof=syscall trace.out > syscall.prof
go tool pprof -top syscall.prof

# Scheduler latency profile — time between becoming runnable and actually running
go tool trace -pprof=sched trace.out > sched.prof
go tool pprof -top sched.prof

You can chain with any pprof command — e.g., annotated source for a blocking function:

go tool trace -pprof=sync trace.out > sync.prof
go tool pprof -list=handleRequest sync.prof
go tool pprof -svg sync.prof > sync-blocking.svg
Full capture-to-analysis workflows
# Workflow 1: benchmark trace — capture, view, extract blocking profile
go test -bench=BenchmarkParse -trace=trace.out ./pkg/parser
go tool trace trace.out                                         # visual timeline
go tool trace -pprof=sync trace.out > sync.prof                 # extract sync blocking
go tool pprof -top -cum sync.prof                               # find worst sync blockers
go tool pprof -list=processOrder sync.prof                      # annotated source

# Workflow 2: production trace — capture from running service, analyze scheduling
curl -o trace.out http://localhost:6060/debug/pprof/trace?seconds=5
go tool trace trace.out                                         # visual timeline
go tool trace -pprof=sched trace.out > sched.prof               # extract scheduling latency
go tool pprof -top sched.prof                                   # goroutines with worst scheduling delay
go tool pprof -svg sched.prof > sched.svg                       # graph of scheduling bottlenecks

# Workflow 3: test trace — capture during test run
go test -trace=trace.out -run=TestSlowIntegration ./pkg/api
go tool trace trace.out                                         # visual timeline
go tool trace -pprof=net trace.out > net.prof                   # extract network blocking
go tool pprof -top net.prof                                     # find network wait sites
go tool trace flags summary
FlagExamplePurpose
(none)go tool trace trace.outOpen trace in web browser (default)
-http=:PORTgo tool trace -http=:9090 trace.outSet HTTP server address for the web UI
-pprof=TYPEgo tool trace -pprof=net trace.out > net.profExtract pprof profile from trace. Types: net, sync, syscall, sched
HTTP endpoints served by the web UI

When go tool trace trace.out starts its HTTP server, it exposes these pages:

EndpointWhat it shows
/Index page with links to all views
/traceInteractive timeline viewer (Chrome trace viewer) — the main visualization
/goroutinesGoroutine analysis — summary table of all goroutine types, counts, and execution stats
/goroutine/<id>Detailed view of a specific goroutine — its full lifecycle timeline

From /goroutines, click on a goroutine type to see all instances and their execution statistics (total time, scheduled time, blocked time). Click an individual goroutine to see its timeline.

Web UI

Main views

The web UI (opened by go tool trace trace.out) shows a timeline where each horizontal lane represents a processor (P), goroutine, or system event:

  • Trace viewer (/trace) — interactive timeline with:

    • P lanes — one per logical processor (GOMAXPROCS), showing which goroutine runs on each P at each moment

    • Goroutine lanes — each goroutine's lifecycle: created → runnable → running → waiting → running → …

    • GC events — mark phases, sweep, STW pauses shown as colored bands across all P lanes

    • System events — syscalls, network I/O, timer events

    • User annotations — tasks, regions, and log messages from runtime/trace API

  • Goroutine analysis (/goroutines) — summary table:

    • Groups goroutines by creation stack trace (type)

    • Shows count, total execution time, total scheduling wait, total blocking time

    • Click a type to see individual goroutine statistics

    • Click an individual goroutine to see its timeline

Navigating the trace viewer

The trace viewer uses the Chrome tracing UI (also used by Chrome DevTools):

Key/ActionEffect
W / scroll upZoom in (time axis)
S / scroll downZoom out (time axis)
APan left
DPan right
Click on eventShow details panel at bottom — goroutine ID, duration, stack trace
Shift+clickSelect a time range — highlights all events in that window
MMark current selection
/Search for events by name
?Show keyboard shortcuts
Reading the timeline

Color coding:

  • Green bars on P lanes = goroutine actively executing

  • Blue bars = syscall (goroutine pinned to OS thread)

  • Orange/yellow marks = scheduling events (goroutine becoming runnable)

  • Red bands across all P lanes = GC stop-the-world pause

  • Light blue bands = GC concurrent mark phase

  • Purple = user-defined regions (from trace.WithRegion)

Gaps in P lanes = the processor was idle (no runnable goroutines, or goroutines blocked). Many idle gaps with pending runnable goroutines suggests scheduling contention.

What to Look For

Goroutine states

The trace timeline color-codes goroutine states:

ColorStateMeaningWhat it indicates
GreenRunningActively executing on a PNormal — doing useful work
Yellow/OrangeRunnableReady to run but waiting for a PCPU-saturated — too many runnable goroutines competing for too few processors
Red/PinkWaitingBlocked on I/O, channel, mutex, sleep, selectI/O-bound or contention — investigate what it's waiting on
BlueGC assistDrafted by GC to help mark/sweepGC pressure — too many allocations forcing goroutines to help the collector
GC phases

GC events appear as colored bands across all P lanes:

  • Mark assist — goroutines drafted to help GC scan the heap. Visible as gaps in application goroutine execution. The runtime forces goroutines to assist with GC work in proportion to their allocation rate — heavy allocators get taxed more.

  • STW (stop-the-world) — brief phases where all goroutines are stopped (mark setup, mark termination). These cause latency spikes visible as vertical bands across all lanes.

  • Sweep — concurrent sweep of unreachable objects. Usually low overhead but can accumulate if the heap is large.

Diagnosing GC issues from traces:

  • Frequent GC cycles with long mark assist = too many allocations (reduce allocation rate)

  • Long STW phases = too many pointers for the GC to scan (reduce pointer density)

  • GC cycles clustering after specific operations = those operations allocate heavily

Scheduling latency

Time between a goroutine becoming runnable and actually running. High scheduling latency means:

  • Too many goroutines competing for GOMAXPROCS processors

  • OS scheduling interference (noisy neighbors, CPU throttling)

  • Goroutines pinned to busy threads by cgo or long syscalls

What to look for:

  • Yellow (runnable) gaps before green (running) segments — the longer the yellow gap, the higher the scheduling latency

  • Many goroutines in runnable state simultaneously — indicates CPU saturation

  • Uneven distribution across Ps — one P overloaded while others are idle suggests work imbalance

Network/sync blocking
  • Long red/pink periods on a goroutine = it's blocked waiting. Click the block event to see what it's waiting on (channel receive, mutex lock, network read, etc.)

  • Many goroutines blocked on the same channel or mutex = serialization bottleneck. All work funnels through one point.

  • Goroutines blocked on network I/O = external dependency latency. The Go code can't do anything faster — the bottleneck is upstream. Use -pprof=net to generate a pprof profile of network wait locations.

Goroutine creation and destruction

The trace shows goroutine lifecycle events. Look for:

  • Goroutines created in a loop without bound = potential goroutine leak

  • Goroutines that are created but never finish = leak — they accumulate over time

  • Very short-lived goroutines created repeatedly = high overhead from goroutine creation/scheduling (consider batching or worker pools)

Custom Annotations

Add application-level context to traces so you can correlate runtime events with business operations.

Tasks

A task represents a logical operation that may span multiple goroutines:

import "runtime/trace"

func processOrder(ctx context.Context, order Order) error {
    ctx, task := trace.NewTask(ctx, "processOrder")
    defer task.End()

    // All trace events in this context are grouped under the task
    validate(ctx, order)
    charge(ctx, order)
    fulfill(ctx, order)
    return nil
}

Tasks appear as named groups in the trace timeline. You can filter the trace view to show only events belonging to a specific task.

Regions

A region represents a phase within a task or goroutine:

func validate(ctx context.Context, order Order) {
    trace.WithRegion(ctx, "validateAddress", func() {
        // this block is annotated as a region
        validateAddress(order.Address)
    })

    trace.WithRegion(ctx, "validatePayment", func() {
        validatePayment(order.Payment)
    })
}

Regions appear as labeled spans on the goroutine's timeline, making it easy to see which phase of processing takes the most wall-clock time.

Log messages

Add point-in-time log messages to the trace:

trace.Log(ctx, "orderID", order.ID)
trace.Log(ctx, "status", "payment_verified")

Logs appear as markers on the timeline — useful for correlating trace events with specific data.

When to use annotations
  • Always in server request handlers — wrap each request in a task

  • Performance-critical paths — add regions to phases you want to measure wall-clock time for

  • Debugging intermittent latency — add logs at key decision points to see what happened in the slow trace

Annotations add negligible overhead when tracing is disabled (they check a flag and return immediately).

Flight Recorder (Go 1.25+)

The flight recorder solves a fundamental problem with execution traces in long-running services: when a problem occurs (timeout, failed health check), it's already too late to call trace.Start(). The flight recorder keeps a circular buffer of recent trace data in memory, and you snapshot it to disk when something goes wrong — like an airplane's black box.

Setup
import "runtime/trace"

fr := trace.NewFlightRecorder(trace.FlightRecorderConfig{
    MinAge:   10 * time.Second, // keep at least 10s of data
    MaxBytes: 5 << 20,          // cap at 5 MiB to limit memory usage
})
if err := fr.Start(); err != nil {
    return err
}

Sizing guidance:

  • MinAge — set to ~2x your problem window. For 5-second timeout debugging, use 10 seconds. The runtime may retain more data than MinAge if MaxBytes allows.

  • MaxBytes — busy services generate ~1-10 MB/s of trace data. Start with 1-5 MiB and adjust. MaxBytes takes precedence over MinAge — when the buffer fills, older data is discarded regardless of age.

Snapshot on error

Capture the trace buffer when something unexpected happens. Use sync.Once to prevent multiple snapshots overwriting each other:

var snapshotOnce sync.Once

func captureSnapshot(fr *trace.FlightRecorder) {
    snapshotOnce.Do(func() {
        f, err := os.Create("snapshot.trace")
        if err != nil {
            log.Printf("snapshot file: %v", err)
            return
        }
        defer f.Close()

        if _, err := fr.WriteTo(f); err != nil {
            log.Printf("snapshot write: %v", err)
            return
        }
        fr.Stop()
        log.Printf("captured snapshot to %s", f.Name())
    })
}
Trigger patterns
// Pattern 1: slow request detection
http.HandleFunc("/api/order", func(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    // ... handler logic ...

    if fr.Enabled() && time.Since(start) > 100*time.Millisecond {
        go captureSnapshot(fr)
    }
})

// Pattern 2: health check failure
if !healthCheck() && fr.Enabled() {
    go captureSnapshot(fr)
}

// Pattern 3: HTTP endpoint for on-demand capture
http.HandleFunc("/debug/flightrecorder", func(w http.ResponseWriter, r *http.Request) {
    if !fr.Enabled() {
        http.Error(w, "flight recorder not active", http.StatusServiceUnavailable)
        return
    }
    w.Header().Set("Content-Type", "application/octet-stream")
    w.Header().Set("Content-Disposition", "attachment; filename=trace.out")
    fr.WriteTo(w)
})
Analyzing a snapshot
go tool trace snapshot.trace

The snapshot contains the same data as a regular trace — use all the same analysis techniques (timeline viewer, goroutine analysis, pprof extraction). The flight recorder's flow events are particularly useful for diagnosing lock contention and goroutine stalls that caused the anomaly.

Constraints
  • At most one flight recorder may be active at a time (this restriction may be relaxed in future Go versions)

  • A flight recorder can run concurrently with trace.Start — both can be active simultaneously

  • Only one goroutine may call WriteTo at a time — the sync.Once pattern handles this naturally

  • Stop() blocks until any concurrent WriteTo completes

When to use flight recorder vs regular tracing
ScenarioToolWhy
Investigating a known slow operationgo test -trace or trace.Start/StopYou know when to start and stop
Intermittent latency spikes in productionFlight recorderYou don't know when the spike will happen — the buffer captures it retroactively
Post-mortem after a timeout or crashFlight recorderThe problem already happened; regular tracing would miss it
Continuous performance monitoringsamber/cc-skills-golang@golang-observability (Pyroscope)Flight recorder is for one-shot diagnosis, not continuous collection

Overhead and Practical Limits

ConcernGuidance
Runtime overhead~1-2% CPU during capture; negligible when not capturing
Data volumeTraces generate MB/s of data. A 10-second trace of a busy service can be 50-100MB
Capture duration5-10 seconds is typical. Longer traces are slow to open and hard to navigate
Memory to viewgo tool trace loads the entire trace into memory. Large traces may need 1GB+ RAM
Browser performanceThe web UI can struggle with traces >100MB. Use short captures.
Production useSafe for short captures on a single instance. Do not capture continuously.

Trace vs pprof: When to Use Which

QuestionToolWhy
Where does CPU time go?pprof CPU profileStatistical sampling, low overhead, good for aggregate view
Why is latency high but CPU low?go tool traceShows goroutine waiting states — I/O, channels, mutexes
Where do allocations happen?pprof heap profilePer-function allocation counts and sizes
Why are GC pauses long?go tool traceShows STW phases, mark assist, GC timeline
Is there lock contention?pprof mutex/block + tracepprof quantifies it; trace shows the timeline
Are goroutines leaking?pprof goroutine + tracepprof shows the stack; trace shows creation/lifecycle
Which goroutines compete for CPU?go tool traceShows runnable vs running states across all Ps
What's the wall-clock breakdown of a request?go tool trace (with annotations)Timeline view with tasks and regions

When in doubt, start with pprof (lower overhead, simpler output). Use trace when pprof doesn't explain the latency or when you need the wall-clock timeline view.

Requirements and compatibility

Written for: Claude Code, OpenAI Codex

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Declares allowed tools, Claude Code frontmatter

The author says: Designed for Claude Code, Codex or similar harness, and for projects using Golang.

Security

What our scanner noticed· Low risk

  • Instructions that use the network

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
samber
Publisher
Samuel Berthe (Community)
Distributed by
Samuel Berthe
Source
samber/cc-skills-golang · skills/golang-benchmark · commit bb1d43d
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by samber or Samuel Berthe.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download golang-benchmark.zip