Runtime Hooks — Specification¶
Focus: Precise reference for the public Go APIs that observe, instrument, or influence the runtime — the
runtime,runtime/debug,runtime/metrics,runtime/pprof, andruntime/tracepackages, plus theGODEBUGenvironment knobs they cooperate with.Sources: -
runtime: https://pkg.go.dev/runtime -runtime/debug: https://pkg.go.dev/runtime/debug -runtime/metrics: https://pkg.go.dev/runtime/metrics -runtime/pprof: https://pkg.go.dev/runtime/pprof -runtime/trace: https://pkg.go.dev/runtime/trace - GODEBUG reference: https://pkg.go.dev/runtime#hdr-Environment_Variables
1. What a "runtime hook" is¶
A runtime hook is any API that lets a Go program inspect or change a property of the runtime — scheduler, garbage collector, finalizer queue, profiler, tracer, or crash machinery — at runtime. They are distinct from compile-time knobs (-gcflags, -ldflags) and from external tools (go tool pprof, dlv). Most hooks live in five packages:
| Package | What it covers |
|---|---|
runtime | Scheduler, goroutines, stacks, finalizers, low-level introspection |
runtime/debug | GC/memory tuning, panic & crash control, build info |
runtime/metrics | The modern, versioned metric stream replacing MemStats reads |
runtime/pprof | Programmatic capture of CPU, heap, goroutine, block, mutex profiles |
runtime/trace | Programmatic capture of execution traces |
GODEBUG is the environment-side counterpart: a comma-separated key/value list read by the runtime at startup and (for some keys) on debug.SetGCPercent-style updates.
2. runtime package — scheduler & introspection¶
| Function | Signature | Purpose | Notes |
|---|---|---|---|
GOMAXPROCS | func GOMAXPROCS(n int) int | Set / get max OS threads executing Go code | Returns previous; n <= 0 reports current without changing |
NumCPU | func NumCPU() int | Logical CPUs visible to the process | Honors cgroup CPU quotas on Linux from Go 1.25+ |
NumGoroutine | func NumGoroutine() int | Count of currently existing goroutines | Includes system goroutines |
Gosched | func Gosched() | Yield current P to scheduler | Rarely needed; not a sleep |
Goexit | func Goexit() | Terminate the calling goroutine after running deferreds | Crashes the process if called on the main goroutine after others exit |
GC | func GC() | Force a blocking GC cycle | For tests/benchmarks; not for production routines |
LockOSThread | func LockOSThread() | Bind goroutine to its current OS thread | Required for GUI, OpenGL, locale-sensitive C libs |
UnlockOSThread | func UnlockOSThread() | Release one binding | Calls nest; thread released only on full unwind |
Caller | func Caller(skip int) (pc uintptr, file string, line int, ok bool) | Single-frame stack lookup | skip=0 is the caller of Caller |
Callers | func Callers(skip int, pc []uintptr) int | Fill a slice with PCs | Pair with CallersFrames to symbolize |
CallersFrames | func CallersFrames(callers []uintptr) *Frames | Resolve PCs to symbol info | Lazy; pull with Next |
Stack | func Stack(buf []byte, all bool) int | Dump the current goroutine's stack (or all goroutines) | all=true triggers a brief STW |
SetFinalizer | func SetFinalizer(obj, finalizer any) | Register a function to run after obj becomes unreachable | One finalizer per object; resurrects |
KeepAlive | func KeepAlive(x any) | Ensure x is not collected before this point | Required at cgo boundaries |
AddCleanup (1.24+) | func AddCleanup[T,S any](ptr *T, cleanup func(S), arg S) Cleanup | Modern finalizer replacement | No resurrection; multiple cleanups allowed; safer than SetFinalizer |
ReadMemStats | func ReadMemStats(m *MemStats) | Snapshot of all heap/GC counters | Stops the world; prefer runtime/metrics for monitoring |
MemProfileRate | int | Sampling interval (bytes) for the heap profile | Default 512 KiB; set to 1 for precise, 0 to disable |
SetBlockProfileRate | func SetBlockProfileRate(rate int) | Sampling for block events | rate is approx. ns/block; 0 disables |
SetMutexProfileFraction | func SetMutexProfileFraction(rate int) int | Sampling for mutex contention | 1-in-N sampling |
SetCPUProfileRate | func SetCPUProfileRate(hz int) | Sampling rate for CPU profile | Default 100 Hz; usually set via pprof.StartCPUProfile |
Version | func Version() string | Runtime/compiler version string | e.g., go1.24.1 |
GOOS, GOARCH | string (consts) | Target platform identifiers |
3. runtime/debug — tuning, panic, crash, build info¶
| Function | Signature | Version | Purpose |
|---|---|---|---|
SetGCPercent | func SetGCPercent(percent int) int | all | Equivalent to GOGC; returns previous value; -1 disables |
SetMemoryLimit | func SetMemoryLimit(limit int64) int64 | 1.19+ | Soft cap on total runtime memory in bytes; -1 reports current |
SetMaxStack | func SetMaxStack(bytes int) int | all | Per-goroutine stack ceiling; panic on overflow |
SetMaxThreads | func SetMaxThreads(threads int) int | all | Hard cap on OS threads; runtime panics if exceeded |
SetPanicOnFault | func SetPanicOnFault(enabled bool) (old bool) | all | Convert SIGSEGV from a faulty memory access into a recoverable panic (per goroutine) |
SetTraceback | func SetTraceback(level string) | all | Detail of traceback on panic (none, single, all, system, crash) |
SetCrashOutput | func SetCrashOutput(f *os.File, opts CrashOptions) error | 1.23+ | Mirror panic/runtime-failure output to a second fd before crashing |
FreeOSMemory | func FreeOSMemory() | all | Force GC and return idle pages to the OS now |
ReadGCStats | func ReadGCStats(stats *GCStats) | all | Pause history, last GC wall clock, totals |
ReadBuildInfo | func ReadBuildInfo() (*BuildInfo, bool) | 1.12+ | Build metadata: module path, version, settings, dependencies, VCS info |
Stack | func Stack() []byte | all | Stack trace of the calling goroutine as a slice |
PrintStack | func PrintStack() | all | Same, but writes to stderr |
WriteHeapDump | func WriteHeapDump(fd uintptr) | all | Dump heap to a file descriptor in a documented binary format |
BuildInfo exposes module path/version, Settings (e.g., GOOS, vcs.revision, vcs.time), dependency list, and the main module's checksum. Read it once at startup for /version endpoints.
4. runtime/metrics — the modern metrics stream¶
Replaces ad-hoc ReadMemStats consumption with a stable, versioned, typed API.
| Function | Purpose |
|---|---|
metrics.All() []Description | List every metric the running runtime publishes |
metrics.Read(samples []Sample) | Fill samples (pre-populated with Name) with current values |
Each Sample carries a Value discriminated by Kind — uint64 (counters), float64 (CPU fractions), or *Float64Histogram (GC pauses, GC heap sizes).
Selected metric names you will use:
| Name | Kind | Replaces |
|---|---|---|
/memory/classes/total:bytes | uint64 | MemStats.Sys |
/memory/classes/heap/objects:bytes | uint64 | HeapAlloc |
/memory/classes/heap/free:bytes | uint64 | HeapIdle |
/memory/classes/heap/released:bytes | uint64 | HeapReleased |
/gc/heap/live:bytes (1.21+) | uint64 | last-cycle live set |
/gc/cycles/automatic:gc-cycles | uint64 | NumGC |
/gc/pauses:seconds | histogram | PauseNs distribution |
/sched/goroutines:goroutines | uint64 | NumGoroutine() |
/sched/latencies:seconds | histogram | scheduler wait time per goroutine |
/cpu/classes/gc/total:cpu-seconds | float64 | GCCPUFraction (derived) |
/cpu/classes/scavenge/total:cpu-seconds | float64 | scavenger CPU |
The histograms are cumulative bucket counts; export to Prometheus via the official collector.
5. runtime/pprof — programmatic profile capture¶
| Function | Purpose |
|---|---|
pprof.StartCPUProfile(w io.Writer) error | Begin CPU profile (default 100 Hz) |
pprof.StopCPUProfile() | Stop and flush the CPU profile |
pprof.WriteHeapProfile(w io.Writer) error | Snapshot the in-use heap |
pprof.Lookup(name string) *Profile | Look up a named profile (heap, goroutine, allocs, threadcreate, block, mutex) |
(*Profile).WriteTo(w io.Writer, debug int) error | Serialize profile; debug=0 = pprof binary, debug=1 = legacy text, debug=2 = goroutine dump |
pprof.Do(ctx, labels, fn) | Attach pprof labels to a goroutine for the duration of fn |
pprof.Labels(k1,v1,k2,v2,...) LabelSet | Build a label set |
pprof.SetGoroutineLabels(ctx) | Apply labels for the current goroutine |
net/http/pprof registers HTTP handlers on http.DefaultServeMux for the same profiles. Bind it to localhost or behind authenticated admin routes — it leaks symbol info.
6. runtime/trace — execution tracer¶
| Function | Purpose |
|---|---|
trace.Start(w io.Writer) error | Begin writing trace events |
trace.Stop() | Stop the tracer |
trace.WithRegion(ctx, name, fn) | Mark a named region in the trace |
trace.StartRegion(ctx, name) *Region | Lower-level region API; pair with End |
trace.NewTask(ctx, name) (context.Context, *Task) | Cross-goroutine logical task |
trace.Log(ctx, category, message) | Annotate the trace stream |
trace.IsEnabled() bool | Whether tracing is currently on (Go 1.21+) |
Traces are read with go tool trace file.out. Sizes grow ~5–20 MB/s of real time; capture short windows (seconds).
7. GODEBUG — environment knobs¶
GODEBUG=key=val,key=val,...
| Key | Effect |
|---|---|
gctrace=1 | One line per GC cycle to stderr |
gctrace=2 | Same plus per-phase CPU breakdown |
schedtrace=1000 | Scheduler summary every 1000 ms |
scheddetail=1 | Combine with schedtrace for per-P / per-G detail |
allocfreetrace=1 | Stack trace for every allocation and free (extremely loud) |
gcstoptheworld=1 | Force fully STW GC (debugging only) |
gcstoptheworld=2 | Same, plus disable concurrent sweep |
madvdontneed=1 | Use MADV_DONTNEED instead of MADV_FREE on Linux |
madvdontneed=0 | Use MADV_FREE (default on Linux ≥ 4.5) |
inittrace=1 | Print package init durations |
cgocheck=1 (default) / 2 | cgo pointer-passing validation level |
asyncpreemptoff=1 | Disable signal-based preemption |
tracebackancestors=N | Include ancestor goroutine stacks in panic dumps |
panicnil=1 | Allow panic(nil) (legacy behavior) |
tracefpunwindoff=1 | Force frame-pointer unwinder off |
Many GODEBUG settings can also be pinned per module via //go:debug directives in go.mod.
8. Signal interplay¶
The runtime installs handlers for SIGURG (async preemption), SIGSEGV/SIGBUS/SIGFPE (faults → panic), SIGPIPE (ignored when written to a closed pipe), SIGPROF (CPU profiler), and forwards others to user handlers registered via os/signal.
| API | Purpose |
|---|---|
signal.Notify(c chan<- os.Signal, sig ...os.Signal) | Subscribe to signals |
signal.Stop(c) | Unsubscribe |
signal.Reset(sig ...) | Restore default disposition |
signal.NotifyContext(parent, sig ...) (ctx, cancel) | Cancel a context when a signal arrives |
signal.NotifyContext is the modern shutdown idiom; pair with http.Server.Shutdown.
9. Exit, panic, and crash hooks¶
| Mechanism | Semantics |
|---|---|
os.Exit(code) | Immediate exit; no deferreds run; flush logs before calling |
runtime.Goexit() | Calling goroutine ends after running its deferreds; main goroutine exit triggers all-goroutine deadlock detection |
panic(v) | Unwinds the goroutine running deferreds; if unrecovered, kills the program with traceback |
recover() | Captures the panic value in a deferred call; otherwise returns nil |
debug.SetTraceback("crash") | On unrecovered panic, signal yourself with SIGABRT to produce a core dump |
debug.SetCrashOutput(f, opts) (1.23+) | Duplicate panic output to a second file (e.g., a structured log or S3 uploader) |
10. Stack control¶
| API | Purpose |
|---|---|
debug.SetMaxStack(n) | Cap per-goroutine stack at n bytes; default 1 GiB on 64-bit |
debug.SetMaxThreads(n) | Cap OS threads at n; default 10000 |
runtime.Stack(buf, all) | Dump stack(s) into buf |
debug.PrintStack() | Dump current goroutine stack to stderr |
runtime.LockOSThread() | Pin goroutine to its OS thread |
runtime.UnlockOSThread() | Release one nesting level |
11. Things that look like hooks but aren't¶
unsafe.Pointer,unsafe.Sizeof,unsafe.StringData— compile-time, not runtime.reflect— type introspection, not runtime tuning.os.Getpagesize— OS call, not a Go runtime hook.time.Tick,time.AfterFunc— runtime-backed but exposed viatime, notruntime.
12. Related references¶
- Runtime hooks proposal index: https://github.com/golang/proposal#runtime
GODEBUGhistory: https://pkg.go.dev/runtime#hdr-Environment_Variables- Diagnostics guide: https://go.dev/doc/diagnostics
runtime/metricsdesign (1.16): https://github.com/golang/proposal/blob/master/design/37112-metrics.mdSetMemoryLimitproposal: https://github.com/golang/proposal/blob/master/design/48409-soft-memory-limit.mdSetCrashOutputproposal: https://github.com/golang/proposal/blob/master/design/57175-crash-output.mdAddCleanupproposal: https://github.com/golang/proposal/blob/master/design/67535-cleanups.md