Read this in other languages: Russian
gcscope is a terminal UI (TUI) and CLI for visualizing Go GC behavior in real time from gctrace, gcpacertrace, and runtime/metrics: GC cycles, stop-the-world (STW) pauses, heap live/goal dynamics, and GC pacer signals.
It is designed for fast feedback during performance work:
- spot problematic STW spikes (p99/max) under load
- see how GC frequency changes across runs
- see heap live approach heap goal as pacing becomes more aggressive
- compare two runs using snapshots (
diff)
- How It Works
- Install
- Quickstart (1 minute)
- Usage
- What You See (Metrics & Panels)
- Controls
- Configuration
- Snapshots
- Makefile Commands
- Notes / FAQ
- Development
- License
gcscope has two data sources:
run(primary): starts your binary, ensuresGODEBUGcontainsgctrace=1,gcpacertrace=1, parses the targetstderr.attach(secondary): polls an HTTP endpoint that exportsruntime/metricsin agcscope-friendly JSON format (viapkg/reporter).
No code changes are required for run. For attach, you add a small HTTP endpoint to the target service.
Pick one of the options below.
Install the gcscope CLI into your GOBIN:
go install github.com/timur-developer/gcscope/cmd/gcscope@latestAfter that you can use gcscope as a normal CLI (from any directory):
gcscope lab churn
gcscope run ./path/to/your-binary -- --your-flag value
gcscope attach http://127.0.0.1:8080/gcscope/metrics
gcscope diff ./a.json ./b.jsonBuilt-in help:
gcscope --help
gcscope run --helpDownload a prebuilt binary from GitHub Releases (Assets), pick the archive for your OS/arch, then extract it.
Run from the extracted folder:
Windows (PowerShell):
.\gcscope.exe lab churnmacOS / Linux:
chmod +x ./gcscope
./gcscope lab churnTo run gcscope from any directory, move it to a folder in your PATH (or add the extracted folder to PATH).
From source:
go run ./cmd/gcscope lab churnPrerequisites: Go 1.22+ and a reasonably large terminal window.
From source (no install):
go run ./cmd/gcscope lab churnOr via Makefile:
make lab-churnOpen in-app help any time: ? / h / f1.
- Build your target app as a binary:
go build -o ./myapp ./cmd/myapp- Run it under observation (note the
--separator for passing args to your program):
go run ./cmd/gcscope run ./myapp -- --your-flag value- In the UI:
- press
?to see all hotkeys - press
spaceto pause/resume - when paused, use
left/right(andhome/end) to scrub history - press
sto write a snapshot totmp/snapshots(by default)
If you want to use gcscope as a CLI, install it once and run gcscope ... directly (see Install).
Run your Go program under observation:
gcscope run ./path/to/your-binaryThis mode is intended for checking how Go's garbage collector behaves in your project during a real run.
run works with a compiled binary (not a .go file), so build your program first, then pass the resulting executable path to gcscope.
Need flags or want to pass args/flags to your program? See Configuration.
From source (no install):
go run ./cmd/gcscope run ./path/to/your-binaryMakefile shortcut:
make run TARGET=./path/to/your-binaryBuilt-in demo presets:
gcscope lab alloc
gcscope lab churn
gcscope lab idle
gcscope lab spikeWhat the presets mean (synthetic workloads):
alloc: steady small/medium allocations with some retention; heap live gradually grows and GC cadence stays relatively stablechurn: repeated large bursts with short retention; useful for stressing STW and the paceridle: mostly idle with occasional bursts; useful for observing low-frequency GC behaviorspike: light background traffic with periodic heavy bursts; makes heap and STW spikes easy to see
Attach to a running service that exposes runtime/metrics in gcscope JSON format.
- Add
pkg/reporterto your service:
package main
import (
"log"
"net/http"
"github.com/timur-developer/gcscope/pkg/reporter"
)
func main() {
rep := reporter.New()
mux := http.NewServeMux()
mux.Handle(rep.Path(), rep.Handler())
log.Fatal(http.ListenAndServe(":8080", mux))
}- Attach:
gcscope attach http://127.0.0.1:8080/gcscope/metricsNotes (attach mode):
- the endpoint payload is based on
runtime/metrics, so values differ fromrunmode - the target process environment (
GOGC,GOMEMLIMIT,GODEBUG) is not available; UI showsn/a
Compare two snapshots:
gcscope diff ./a.json ./b.jsonWhat diff prints:
- a short summary for snapshot A and B (
gc_cycles_total,heap_live_mb,stw_p50/p99/max_us) - delta (B-A) for
heap_live_mband STW window stats
gcscope keeps a sliding window of recent GC events (--window-size, default: 200) and shows both raw per-cycle values and derived stats over that window.
GC cycles total: current GC cycle numberlast STW (us): last cycle STW pause (sweep term + mark term, converted to microseconds)heap live (MB)/heap goal (MB): live heap and current goalheap: live/goal: a compact live-vs-goal indicator
max STW (us): max STW over the visible windowgc: rate asGCs/minand/or average GC intervalstw: bad STW count/percent (based on thresholds) and forced GC counttime since last GC,uptimestw thresholds:warn/bad(see Configuration)- snapshot status and snapshot directory
- env context (
GOGC,GOMEMLIMIT,GODEBUG) inrun/lab(not available inattach)
- Heap live over time (MB): time-series of heap live
- STW p50/p99/max over time (us): time-series derived from the sliding window
- STW per cycle: per-cycle bar chart; labels can show STW or heap live (toggle with
l)
For the currently selected cycle (live cursor, or scrubbed history when paused):
- GC #, time since start, forced
- STW total (us) + breakdown: sweep term / mark term
- heap (MB): start/end and live/goal
- gc cpu (%)
- pacer signals (when available): assist ratio, assist workers, pages swept
Full list of hotkeys is always available in the in-app Help (? / h / f1).
Core:
?/h/f1toggle Helpq/ctrl+cquitspacepause/resume live updatesleft/rightscrub history when pausedhome/endjump to first/last event when pausedswrite a snapshot
Layout and labels:
gtoggle layout (spaced/tight)ltoggle STW bar labels mode (GC+STW -> GC+Heap -> GC-only)
Charts:
zswitch focused chart (Heap/STW). Zoom/pan applies to the focused chart.+/-zoom Y for focused chart0reset Y zoom/pan for focused chartshift+up/shift+downpan Y for focused chart[/]zoom time span (X axis): all -> 1h -> 15m -> 5m -> 1m (and back)rreset focus, zoom/pan, and time span
Global flags (and their env overrides):
--window-size(GCSCOPE_WINDOW_SIZE) samples kept in memory (default: 200)--snapshot-path(GCSCOPE_SNAPSHOT_PATH) snapshot directory (default:tmp/snapshots)--exit-snapshot(GCSCOPE_EXIT_SNAPSHOT) write a snapshot on exit (default: true)--no-alt-screen(GCSCOPE_NO_ALT_SCREEN) disable alt screen buffer--stw-warn-us(GCSCOPE_STW_WARN_US) STW warning threshold (default: 200)--stw-bad-us(GCSCOPE_STW_BAD_US) STW bad threshold (default: 1000)
Mode-specific env vars:
GCSCOPE_RUN_TARGETGCSCOPE_ATTACH_URL,GCSCOPE_POLL_INTERVALGCSCOPE_LAB_PRESETGCSCOPE_DIFF_A,GCSCOPE_DIFF_B
All flags can be provided via their GCSCOPE_* env equivalents listed above.
Global flags (like --window-size, --stw-bad-us) go before the subcommand because they apply to all modes.
In run mode, -- separates gcscope arguments from the target program arguments. Everything after -- is passed to your binary unchanged.
Template:
gcscope [global flags] run <target-binary> -- [target args...]Example:
gcscope --window-size 500 --stw-bad-us 2000 run ./path/to/your-binary -- --your-flag value- Default directory:
tmp/snapshots - Manual snapshot: press
s - Exit snapshot: enabled by default; skipped if a manual snapshot was created recently
What a snapshot contains:
- current values (
gc_cycles_total,last_stw_us,heap_live_mb,heap_goal_mb) - window stats (
stw_p50_us,stw_p99_us,stw_max_us) - the list of recent GC events (the same window used by the UI), including parsed pacer fields when available
Snapshots are plain JSON files. They are useful for sharing, tracking regressions, and comparing two runs with gcscope diff.
make help prints all commands. Most common ones:
make ci: run lint + tests + buildmake lint: rungolangci-lintmake test: rungo test ./...make build: rungo build ./...(sanity check)make install: installgcscopeinto your Go bin directorymake lab(ormake lab-churn, etc.): run demo workloadsmake run TARGET=... ARGS="-- ...": run your binary under observationmake attach URL=...: attach to a running service (default URL ishttp://127.0.0.1:8080/gcscope/metrics)make diff A=... B=...: compare two snapshot files
Maintainers:
make testbin: rebuild embeddedlabbinaries for all supported OS/archmake release-snapshot: local GoReleaser build (--snapshot --clean)
attachmode cannot see the target process env (GOGC,GOMEMLIMIT,GODEBUG), so UI showsn/afor these fields.- If you see no updates, your program might simply not be hitting GC cycles yet (try a workload that allocates more, or use
lab churn). - If your terminal behaves oddly, try
--no-alt-screen(orGCSCOPE_NO_ALT_SCREEN=true). - Very small STW values may display as 0 due to
gctraceformatting.
make ci
make lint
make test
make buildMaintainers:
make testbin
make release-snapshotMIT. See LICENSE.



