statsviz: live Go runtime plots in a browser tab, no exporter required
Visualise Go runtime metrics in real time
At a glance
- What is it?
- A small Go library that registers a websocket and a dashboard into your existing HTTP mux, then streams runtime/metrics to the browser once a second so you can watch the garbage collector rather than guess at it.
- Who is it for?
- statsviz solves a narrow problem properly: the moment after you suspect a garbage collection or scheduler problem and want to watch the numbers move rather than reconstruct them from an aggregated histogram. It costs two handler registrations and one dependency on gorilla/websocket, and it needs no collector, no scrape endpoint and no dashboard stack.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 7, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Installing the library and wiring three lines into a main function
Statsviz is a library rather than a binary, so installation is the ordinary Go module fetch:
go get github.com/arl/statsviz@latestThe integration is three lines in your main function. Create a mux, register statsviz on it, and serve it:
mux := http.NewServeMux()
statsviz.Register(mux)
go func() {
log.Println(http.ListenAndServe("localhost:8080", mux))
}()That is the whole thing. You open a browser at `http://localhost:8080/debug/statsviz` and the dashboard appears. No configuration file, no environment variable, no scrape endpoint to add to your infrastructure.
The dependency list in `go.mod` explains why this stays small. There are two direct requirements, `github.com/gorilla/websocket` at v1.5.3 and `github.com/rogpeppe/go-internal` at v1.15.0, plus `golang.org/x/sys` and `golang.org/x/tools` as indirect dependencies. The module targets Go 1.25. For a debugging tool that lives in your binary during development, that is a light footprint.
Two handlers, one websocket and one dashboard
The README explains the architecture in two parts, and the explanation is short enough to quote. The `Ws` handler serves a websocket endpoint. When a client connects, your program's `runtime/metrics` are sent to the browser once per second over that connection. The `Index` handler serves the statsviz user interface at `/debug/statsviz`, and when that page loads, the UI opens the websocket and starts receiving data points.
The consequence of that design is worth being explicit about, because it decides when this tool is useful. Data only exists while a browser tab is open and connected. Close the tab and the sampling stops. There is no ring buffer on the server and no history you can scroll back into, so statsviz is for watching a live process, not for reviewing what happened at three in the morning.
That constraint is the reason it feels fast. No time series database, no aggregation window, no label cardinality. The metrics are sampled in-process and pushed straight down an open socket, so the delay between a garbage collection happening and a line moving on screen is about one sampling interval.
Mounting at a custom path, behind TLS or inside a framework
The default registration assumes a plain `http.ServeMux` on plain HTTP at `/debug/statsviz`. The README lists four cases where that does not fit, and one path for all of them: call `statsviz.NewServer()` instead and take the handlers yourself.
srv, err := statsviz.NewServer(); // Create server or handle error
if err != nil { /* handle error */ }
srv.Index() // UI (dashboard) handler func
srv.Ws() // Websocket handler funcThe four cases the README calls out are using some HTTP framework, wanting the dashboard at `/my/path/to/statsviz`, wanting it under `https://`, and wanting it behind middleware. Note that `NewServer()` returns an error, which the terse snippet renders as a comment. The `Index()` and `Ws()` methods then give you ordinary `http.HandlerFunc` values you can hand to a framework router or wrap in your own middleware.
The `_example/` directory holds worked examples for each situation, and the list in the README names them: the standard mux or your own, wrapping the handler in middleware, registering at `/foo/bar` instead, using HTTPS, and registering with a set of Go HTTP libraries. Echo, fasthttp, fiber and gin are named explicitly, with credit to contributors for the rest. Framework support here is community driven rather than a first party integration surface, so if you use something unusual the `_example/` directory is the place to look before writing your own adapter.
Which plots exist and why some appear only on newer Go versions
The plot list is the substance of the tool. It covers heap and object counts, goroutines, GC pauses, GC cycles, GC scan work, GC stack size, allocation and free rates, CPU split across overall, GC and scavenger, and CGO calls. That CPU split is what separates this from a plain memory graph, because the question that matters when the scavenger is running is whether you are spending CPU on collection or on your program.
Visibility of any given plot depends on two things. The first is your Go version, since some plots only exist in newer releases, and the second is the category filter, because each plot belongs to one or more categories and all of them are shown by default. If a plot you expected is missing, check the Go version before assuming the tool is broken.
The web interface has four top bar controls, and each does something a static graph cannot. A category selector filters the visible plots. A time range selector sets the span on screen rather than leaving it fixed. A toggle shows or hides the vertical lines representing garbage collection events, which is the fastest way to see whether a pause spike lines up with a collection. A play control pauses or resumes plot updates, so you can freeze a moment while you read it.
Plot controls are also described per plot, so individual graphs can be adjusted rather than only globally toggled. The README documents the interface largely through annotated screenshots under `readme-docs/`, which is a reasonable choice for a UI but does mean the text says less about behaviour than a prose manual would.
Plotting your own numbers alongside the runtime ones
There is a second half to the tool that the table of contents calls user plots, and `userplot.go` sits at the root of the tree next to `statsviz.go`, `clients.go` and the test files. The runtime plots come from `runtime/metrics`, but application level counters usually explain why the runtime numbers look the way they do, and a way to draw those next to the GC lines is what makes the tool genuinely useful.
The repository layout is small and readable. `statsviz.go` is the public entry point, `clients.go` handles the websocket clients, `userplot.go` the custom plot path, and `internal/` holds the implementation. Tests sit at the root as `statsviz_test.go`, `userplot_test.go`, `sync_test.go`, `netconn_test.go` and `examples_test.go`, which means the examples are compiled and exercised rather than being documentation that drifts. There is a `testdata/` directory and a `codecov.yml`, so coverage is tracked.
There are no tagged releases listed, so the version comes from `go get ...@latest` resolving a pseudo version. The changelog lives in `CHANGELOG.md` at the root rather than only in release notes, which is the more useful of the two when you are trying to work out what changed. The repository is not archived, with the last push recorded as 2026-09-17, 3,646 stars, 124 forks and 10 open issues, and the project is MIT licensed.
Where this sits against pprof and a metrics pipeline
The honest comparison is with `net/http/pprof`, which every Go program already has and which is usually the first thing anyone reaches for. pprof is the right tool when you want a heap profile at a point in time or a CPU profile over a recorded window, and it tells you exactly which function is responsible. What it does not do is let you watch the runtime's internal counters move while you reproduce a problem, which is the specific frustration statsviz addresses.
The other comparison is with exporting `runtime/metrics` to Prometheus or an OpenTelemetry collector. That approach wins decisively on anything involving history, alerting, multiple machines or comparing a run against last week. It also adds infrastructure, and the sampling rate you configure will not tell you about the 40 millisecond GC pause that just happened, because by the time the scraper arrives the event is over.
So the practical shape is that statsviz is a development and debugging tool, kept close to the code, used on a machine where you already have the application running. It is not a monitoring system and does not try to be. The scope statement in the repository description, visualise Go runtime metrics in real time, is accurate, and the absence of any retention story is a design choice rather than a missing feature.
Editorial conclusion
statsviz solves a narrow problem properly: the moment after you suspect a garbage collection or scheduler problem and want to watch the numbers move rather than reconstruct them from an aggregated histogram. It costs two handler registrations and one dependency on gorilla/websocket, and it needs no collector, no scrape endpoint and no dashboard stack. What it deliberately does not do is retain data, alert, or survive past your browser session, and the README is direct about that. If you need history, push the same runtime/metrics into Prometheus instead. Start with `statsviz.Register(mux)` on a local run, and if your router or TLS setup is not the plain standard library, read the `_example/` directory before guessing, because that is where the framework-specific wiring is documented.
Frequently asked questions
What is statsviz used for?
It gives a live view of a running Go program's `runtime/metrics` in a browser: heap and object counts, goroutines, GC pauses, GC cycles, scan work, allocation and free rates, and CPU split between the program, the collector and the scavenger. Sampling happens in process and is pushed over a websocket once per second, so it is meant for watching a live process rather than reviewing history.
How do I add statsviz to an existing Go application?
Create a mux, call `statsviz.Register(mux)`, and serve it, then open `http://localhost:8080/debug/statsviz`. If you use a framework, need a different path, want HTTPS, or need the handlers behind middleware, call `statsviz.NewServer()` and use its `Index()` and `Ws()` methods instead. Worked examples for those cases are in the `_example/` directory.
Does statsviz keep any history after I close the browser tab?
No. Metrics are sent over the websocket only while a client is connected, once per second, and nothing is stored on the server. That is why it feels immediate, and why it is not a substitute for exporting `runtime/metrics` to Prometheus or an OpenTelemetry collector when you need history, alerting or comparisons over time.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/arl-statsviz)