How do you read a value from Go's runtime/metrics package, such as the live goroutine count?
answer
- you ask for names, not fields
- one slice in, values filled in place
- Sample.Name set, Sample.Value written
- a catalogue call lists what exists
- unsupported name comes back KindBad
basics
~10 sBuild a []metrics.Sample with each element's Name set to a metric name such as /sched/goroutines:goroutines, call metrics.Read on that slice, then switch on each Value's Kind to read a Uint64, Float64 or Float64Histogram.
solid answer
~40 sThe package is pull-based and batch-oriented. You declare what you want as a `[]metrics.Sample`, setting only the `Name` field of each element to a full metric name like `/sched/goroutines:goroutines` or `/gc/heap/allocs:bytes`, and pass the slice to `metrics.Read`, which fills each `Value` in place. You then switch on `sample.Value.Kind()` and call the matching accessor — `Uint64()`, `Float64()` or `Float64Histogram()` — because calling the wrong one panics. To discover what exists, `metrics.All()` returns a `Description` for every metric the running Go toolchain supports, carrying the name, a prose description, the `Kind`, and `Cumulative` (whether the value only ever increases). A name the running version does not support is not an error: after `Read` its `Kind` is `KindBad`, so a metrics daemon should skip it rather than crash.
code
go · 14 linessamples := []metrics.Sample{
{Name: "/sched/goroutines:goroutines"},
{Name: "/gc/heap/allocs:bytes"},
}
metrics.Read(samples) // fills each Value in place; returns nothing
for _, s := range samples {
switch s.Value.Kind() {
case metrics.KindUint64:
fmt.Println(s.Name, s.Value.Uint64())
case metrics.KindBad:
fmt.Println(s.Name, "not supported by this Go version")
}
}go deeper
Be ready to write the four lines from memory: build a Sample slice with Names, call Read, switch on Kind, read the value. Knowing one real metric name such as /sched/goroutines:goroutines shows you have actually run it.
Explain why the API is batch-and-fill-in-place rather than a getter per metric, and what KindBad means for a binary built by a different Go release. Mention that Description.Cumulative decides counter versus gauge.
Show the operating instinct: enumerate at start-up, log the names your build did not support, reuse one slice in a reporting goroutine, and copy anything you retain. An interviewer wants to hear that a missing metric degrades quietly rather than crashing.
Frame it as the contract between your process and whoever collects from it: the metric set is toolchain-dependent, so a dashboard built on hardcoded names silently goes blank after an upgrade unless something reports the names that resolved.
## What runtime/metrics is `runtime/metrics` is the Go runtime's own self-description: a flat namespace of numbers the runtime already maintains — allocation totals, GC cycle counts, GC pause distributions, goroutine counts, memory class breakdowns — exposed through one stable API instead of a hand-written struct per release. It reports the *runtime*, never your application: there is no request counter or queue depth in there, because the runtime has no idea what a request is. ## The name grammar Every metric name starts with `/` and ends with `:unit`. `/gc/heap/allocs:bytes` is a byte count of everything ever allocated; `/gc/cycles/total:gc-cycles` counts GC cycles; `/gc/pauses:seconds` is a distribution measured in seconds; `/sched/goroutines:goroutines` counts goroutines. The path segment tells you the subsystem, the suffix after the colon tells you the unit, and the two together are the whole identifier — there is no separate unit field to look up. Names are case-sensitive strings, so a typo is not a compile error. ## The three types involved ``` type Sample struct { Name string Value Value } ``` `Sample` is what you own. You fill `Name`; `Read` fills `Value`. `Value` is an opaque wrapper with a `Kind()` and three accessors: - `KindUint64` -> `Value.Uint64()` - `KindFloat64` -> `Value.Float64()` - `KindFloat64Histogram` -> `Value.Float64Histogram()` - `KindBad` -> nothing to read; this Go version has no such metric Calling an accessor that does not match the kind panics, which is why real code switches on `Kind()` rather than assuming. `Description`, returned in a slice by `metrics.All()`, is the catalogue entry: `Name`, a human-readable `Description`, the `Kind` you should expect, and `Cumulative`, which says whether the number only ever grows since process start. `Cumulative` is the field that decides how you export the value — a cumulative number is a counter you diff, a non-cumulative one is a gauge you report as-is. ## The call itself `metrics.Read(samples)` takes the slice and mutates it in place. It returns nothing. Two consequences follow. First, the package explicitly encourages you to build the slice once — typically at start-up in a small reporting daemon — and reuse it on every tick, rather than allocating a fresh slice each time. Second, because `Read` writes into the slice you handed it, anything you keep from a previous read must be copied out before the next call; that matters most for histograms, whose `Counts` slice can be overwritten in place. ## Discovering versus hardcoding The supported set of metrics is a property of the Go toolchain that built the binary, and it grows across releases. That is exactly why the API pairs `All()` with `KindBad`: a program can enumerate what this build supports at start-up, log the names it wanted but did not get, and carry on. Hardcoding a list and assuming every entry resolves is the common beginner mistake — it does not panic, it silently reports nothing, because a `KindBad` sample is easy to skip past without noticing. ## A typical first program Declare the names you care about, read once, and print. Then wrap that in a ticker inside a goroutine and you have the shape of every runtime-metrics reporter: one slice, one `Read` per tick, one loop over the samples that fans the numbers out to wherever they are being collected. The batching is not cosmetic — one `Read` with six names does strictly less work than six `Read` calls with one name each, because the runtime computes the shared underlying aggregates once per call. ## What it is not It is not a push system, not a time series database and not a history: `Read` gives you the value *now*. Rates, deltas and percentiles are yours to compute from successive reads. And it is not a replacement for instrumenting your own code — the cheapest way to count something your program does is a counter your program increments.
- Why bother reading metrics.Description before exporting a value?`Kind` tells you which accessor is legal — calling `Value.Uint64()` on a histogram panics — and `Cumulative` tells you whether the number is a counter you must diff or a gauge you report directly. The unit is in the name's suffix after the colon. Getting `Cumulative` wrong is how a monotonically rising allocation total ends up on a dashboard as if it were current heap usage.
- Should a reporting goroutine allocate a fresh []metrics.Sample on every tick?No. Build it once and reuse it — the package is designed for that, and it keeps the reporter itself off the allocation path. The one caveat is retention: `Read` overwrites in place, so anything you want to keep across ticks, in particular a histogram's `Counts`, must be copied out before the next call.
- Can runtime/metrics tell you how many requests your service handled?No. It only reports quantities the runtime itself maintains — goroutines, allocations, GC cycles, memory classes, scheduler latencies. Anything about your application's own work you have to count yourself, and an atomic counter you increment is far cheaper than anything the runtime could offer anyway.
The Sample slice is an order form: you write the item names, hand the whole form over once, and get it back with every price filled in — including a blank where the shop does not stock that item.
saying these in an interview costs you the question
- Thinks metrics.Read returns the values rather than filling the slice
- Hardcodes metric names and never checks for KindBad
- Calls Value.Uint64 without checking Value.Kind first
- Expects application-level counters like request rate in runtime/metrics
- Believes the metric name has a separate unit field