MichaelMure/gogc98

Visualize the golang allocator and GC with the experience of a Win98 defragmenter

23

stars

9

commits

Go

primary language

Aug 15, 2026

updated

README

gogc98

Visualize the work of the go allocator and GC managing the heap of a live go process. See allocations happening live and the GC reclaiming space.

Fill that void you had in your life since you couldn't watch the Windows 98 defragmenter operate anymore.

demo

How it works

Go has had for a while extensive tracing in its runtime that can be enabled through the GODEBUG environment variable. While this is meant to be used for debugging, it can also be used for much sillier things like tracking all allocations and GC activity. The other notable part is flight recorder, a sort of ring buffer that records those traces, ready to be shipped out of process.

gogc98 is a small bridge process that polls a target Go program over HTTP for periodic flight recorder trace snapshots, decodes the traceallocfree runtime experiment's alloc/free/span events plus GC cycle boundaries out of them, and serves a live model of the heap over a websocket to a plain HTML/Canvas frontend.

The target program only needs one line of instrumentation (see below) and one environment variable — everything else runs out-of-process. While the flight recorder minimizes the allocations due to observing those events, recording every single allocation is somewhat expensive.

This is meant as a silly educational tool. I'd suggest not using it in production.

Quick start with the demo program

Requires Go 1.26+.

cd demo
make run
# open http://127.0.0.1:8080

This builds and runs both the bundled demo program (an allocation generator) and the gogc98 bridge/visualizer against it.

Using gogc98 in your own program

First, install the visualizer:

go install github.com/MichaelMure/gogc98@latest

Or grab a prebuilt binary from the releases page.

Two ways to instrument a program:

Option 1: import the probe package

probe.Start() starts an in-process flight recorder and serves its latest snapshot over HTTP — one goroutine, no other setup.

import "github.com/MichaelMure/gogc98/probe"

func main() {
    go probe.Start()
    // ... your program ...
}
go get github.com/MichaelMure/gogc98/probe

probe is its own Go module, separate from the bridge/visualizer — pulling it in doesn't drag in the visualizer's own dependencies (golang.org/x/exp/trace, golang.org/x/net), just runtime/trace and net/http from the standard library.

The process must be launched with GODEBUG=traceallocfree=1 set in its environment — the Go runtime reads GODEBUG before any Go code runs, including init(), so this can't be set from within the program itself. probe.Start() checks for it and exits with a clear error if it's missing.

GODEBUG=traceallocfree=1 go run .

Then run the bridge against it — no need to clone this repo first, go run fetches and runs it directly (defaults already match probe.Addr, so no flags are actually required):

go run github.com/MichaelMure/gogc98@latest -target http://127.0.0.1:7999/snapshot
Option 2: copy the minimal code

If you'd rather not take the dependency, this is the entire package — paste it into your own code:

import (
    "log"
    "net/http"
    "runtime/trace"
    "time"
)

func startGogc98Probe() {
    fr := trace.NewFlightRecorder(trace.FlightRecorderConfig{
        MinAge:   200 * time.Millisecond,
        MaxBytes: 8 << 20,
    })
    if err := fr.Start(); err != nil {
        log.Fatal("gogc98 probe: starting flight recorder: ", err)
    }

    mux := http.NewServeMux()
    mux.HandleFunc("/snapshot", func(w http.ResponseWriter, r *http.Request) {
        if _, err := fr.WriteTo(w); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
        }
    })
    log.Print("gogc98 probe: snapshot endpoint on http://127.0.0.1:7999/snapshot")
    log.Fatal(http.ListenAndServe("127.0.0.1:7999", mux))
}

Run it the same way, in its own goroutine, with the same GODEBUG=traceallocfree=1 requirement as above:

func main() {
    go startGogc98Probe()
    // ... your program ...
}
GODEBUG=traceallocfree=1 go run .

License

MIT — see LICENSE.

Contributors

MichaelMure

9 commits

MichaelMure/gogc98

Visualize the golang allocator and GC with the experience of a Win98 defragmenter

23

stars

9

commits

Go

primary language

Aug 15, 2026

updated

README

gogc98

Visualize the work of the go allocator and GC managing the heap of a live go process. See allocations happening live and the GC reclaiming space.

Fill that void you had in your life since you couldn't watch the Windows 98 defragmenter operate anymore.

demo

How it works

Go has had for a while extensive tracing in its runtime that can be enabled through the GODEBUG environment variable. While this is meant to be used for debugging, it can also be used for much sillier things like tracking all allocations and GC activity. The other notable part is flight recorder, a sort of ring buffer that records those traces, ready to be shipped out of process.

gogc98 is a small bridge process that polls a target Go program over HTTP for periodic flight recorder trace snapshots, decodes the traceallocfree runtime experiment's alloc/free/span events plus GC cycle boundaries out of them, and serves a live model of the heap over a websocket to a plain HTML/Canvas frontend.

The target program only needs one line of instrumentation (see below) and one environment variable — everything else runs out-of-process. While the flight recorder minimizes the allocations due to observing those events, recording every single allocation is somewhat expensive.

This is meant as a silly educational tool. I'd suggest not using it in production.

Quick start with the demo program

Requires Go 1.26+.

cd demo
make run
# open http://127.0.0.1:8080

This builds and runs both the bundled demo program (an allocation generator) and the gogc98 bridge/visualizer against it.

Using gogc98 in your own program

First, install the visualizer:

go install github.com/MichaelMure/gogc98@latest

Or grab a prebuilt binary from the releases page.

Two ways to instrument a program:

Option 1: import the probe package

probe.Start() starts an in-process flight recorder and serves its latest snapshot over HTTP — one goroutine, no other setup.

import "github.com/MichaelMure/gogc98/probe"

func main() {
    go probe.Start()
    // ... your program ...
}
go get github.com/MichaelMure/gogc98/probe

probe is its own Go module, separate from the bridge/visualizer — pulling it in doesn't drag in the visualizer's own dependencies (golang.org/x/exp/trace, golang.org/x/net), just runtime/trace and net/http from the standard library.

The process must be launched with GODEBUG=traceallocfree=1 set in its environment — the Go runtime reads GODEBUG before any Go code runs, including init(), so this can't be set from within the program itself. probe.Start() checks for it and exits with a clear error if it's missing.

GODEBUG=traceallocfree=1 go run .

Then run the bridge against it — no need to clone this repo first, go run fetches and runs it directly (defaults already match probe.Addr, so no flags are actually required):

go run github.com/MichaelMure/gogc98@latest -target http://127.0.0.1:7999/snapshot
Option 2: copy the minimal code

If you'd rather not take the dependency, this is the entire package — paste it into your own code:

import (
    "log"
    "net/http"
    "runtime/trace"
    "time"
)

func startGogc98Probe() {
    fr := trace.NewFlightRecorder(trace.FlightRecorderConfig{
        MinAge:   200 * time.Millisecond,
        MaxBytes: 8 << 20,
    })
    if err := fr.Start(); err != nil {
        log.Fatal("gogc98 probe: starting flight recorder: ", err)
    }

    mux := http.NewServeMux()
    mux.HandleFunc("/snapshot", func(w http.ResponseWriter, r *http.Request) {
        if _, err := fr.WriteTo(w); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
        }
    })
    log.Print("gogc98 probe: snapshot endpoint on http://127.0.0.1:7999/snapshot")
    log.Fatal(http.ListenAndServe("127.0.0.1:7999", mux))
}

Run it the same way, in its own goroutine, with the same GODEBUG=traceallocfree=1 requirement as above:

func main() {
    go startGogc98Probe()
    // ... your program ...
}
GODEBUG=traceallocfree=1 go run .

License

MIT — see LICENSE.

Contributors

MichaelMure

9 commits

Languages

Go

47.8%

JavaScript

33.2%

HTML

13.4%

CSS

5.7%