src/go/pkg/metrix/README.md
metrix is the metrics storage and read API used by go.d ModuleV2 collectors and runtime/internal components.
Audience: ModuleV2 collector authors and framework contributors.
See also: charttpl (template DSL), chartengine (compile + plan).
| Consumer | Store type | Typical usage |
|---|---|---|
Collector jobs (ModuleV2) | CollectorStore | Cycle-scoped writes, snapshot reads, chart planning input |
| Internal/runtime instrumentation | RuntimeStore | Stateful immediate-commit writes, runtime metrics planning |
CollectorStore writes are staged between BeginCycle and CommitCycleSuccess. Nothing is visible to readers until commit.RuntimeStore writes are committed immediately (no cycle API). Each write produces a new overlay snapshot.map[string]string) are sorted and encoded into a canonical key that uniquely identifies a series (metric name + labels).FreshnessCycle = series must be observed in the latest successful cycle to be visible.
FreshnessCommitted = series is visible as long as it's committed, even if not re-observed.WindowCumulative = observations accumulate across cycles.
WindowCycle = observations reset each cycle.| Interface | Key methods | Notes |
|---|---|---|
CollectorStore | Read(...), Write() | Default collector-facing store |
RuntimeStore | Read(...), Write() | Stateful-only writes |
CycleManagedStore | CycleController() | Runtime/orchestrator-only cycle control |
Reader | Value/Delta/Histogram/Summary/StateSet/MeasureSet/... | Immutable snapshot read API |
| Phase | Action |
|---|---|
| Begin | Open staged frame (BeginCycle) |
| Collect | Collector writes metrics through Write().SnapshotMeter(...) or Write().StatefulMeter(...) |
| Success | CommitCycleSuccess publishes new snapshot and advances success sequence |
| Failure | AbortCycle drops staged writes |
ModuleV2 collectors should write metrics only; cycle control is handled by job runtime.
[!CAUTION] Calling snapshot-mode record methods (
ObserveTotal,ObservePoint) on aRuntimeStorepanics.
FreshnessCommitted semantics; other freshness policies are rejected.| Mode | Typical meter | Freshness default | Window default |
|---|---|---|---|
| Snapshot | SnapshotMeter(...) | FreshnessCycle | WindowCumulative |
| Stateful | StatefulMeter(...) | FreshnessCommitted | WindowCumulative |
| Option | Scope |
|---|---|
WithFreshness(...) | Freshness policy override (subject to mode constraints) |
WithWindow(...) | Stateful histogram/summary window mode |
WithHistogramBounds(...) | Histogram bucket boundaries |
WithSummaryQuantiles(...) | Summary quantile output (required for quantile series in flattened view) |
WithSummaryReservoirSize(...) | Stateful summary estimator size |
WithStateSetStates(...) | StateSet allowed states |
WithStateSetMode(...) | ModeBitSet (multiple simultaneous active states) or ModeEnum (exactly one active state) |
WithMeasureSetFields(...) | MeasureSet fixed ordered field schema (required for MeasureSet instruments) |
WithDescription(...), WithChartFamily(...), WithUnit(...), WithFloat(...) | Metric metadata hints for downstream consumers (e.g., autogen chart identity + float SET mode) |
MeasureSet stores one logical metric family with a fixed ordered list of named numeric fields.MeasureSet family is either gauge-like or counter-like; semantics are never mixed per field.Description, ChartFamily, and Unit apply to the whole family.MeasureFieldSpec declares per-field Name and Float.MeasureSet as a structured family, similar to StateSet; flatten remains the generic reader/tooling path.| Mode | Gauge-like family | Counter-like family |
|---|---|---|
| Snapshot | MeasureSetGauge(...).ObservePoint(...) or preferred ObserveFields(...) | MeasureSetCounter(...).ObserveTotalPoint(...) or preferred ObserveTotalFields(...) |
| Stateful | MeasureSetGauge(...).SetPoint(...), AddPoint(...), SetFields(...), AddFields(...), SetField(...), AddField(...) | MeasureSetCounter(...).AddPoint(...), AddFields(...), AddField(...) |
MeasureSetPoint values whenever practical.ObservePoint(...), ObserveTotalPoint(...))ObserveFields(...), ObserveTotalFields(...))SetField(...), AddField(...))MeasureSet still models one sampled family point per collect cycle in snapshot mode.SetField(...) overwrites one field on top of the committed/staged family.AddField(...) and counter-like AddField(...) apply a delta to one field.store := metrix.NewCollectorStore()
meter := store.Write().SnapshotMeter("svc")
latency := meter.MeasureSetGauge(
"latency",
metrix.WithMeasureSetFields(
metrix.MeasureFieldSpec{Name: "value"},
metrix.MeasureFieldSpec{Name: "ratio", Float: true},
),
metrix.WithUnit("seconds"),
)
latency.ObserveFields(map[string]metrix.SampleValue{
"value": 1.5,
"ratio": 0.5,
})
Re-registering the same metric name with a different MeasureSet schema is rejected like other structured families.
store := metrix.NewRuntimeStore()
meter := store.Write().StatefulMeter("svc")
usage := meter.MeasureSetGauge(
"usage",
metrix.WithMeasureSetFields(
metrix.MeasureFieldSpec{Name: "value"},
metrix.MeasureFieldSpec{Name: "limit"},
),
)
usage.SetFields(map[string]metrix.SampleValue{
"value": 10,
"limit": 20,
})
usage.SetField("value", 15)
usage.AddField("limit", 3)
This yields a committed MeasureSet point equivalent to:
metrix.MeasureSetPoint{Values: []metrix.SampleValue{15, 23}}
Read(...) accepts option functions that control two independent axes:
ReadRaw()) — bypasses freshness filtering, returning all committed series regardless of when they were last observed.ReadFlatten()) — projects complex types (Histogram, Summary, StateSet, MeasureSet) into individual scalar series.| Read options | Visibility | Shape |
|---|---|---|
Read() | Freshness-filtered | Canonical typed families |
Read(ReadRaw()) | All committed series | Canonical typed families |
Read(ReadFlatten()) | Freshness-filtered | Flattened scalar view |
Read(ReadRaw(), ReadFlatten()) | All committed series | Flattened scalar view |
Read(ReadFlatten()) projects non-scalar families into scalar series:
| Source kind | Flattened outputs |
|---|---|
| Histogram | <name>_bucket{le=...}, <name>_count, <name>_sum |
| Summary | <name>_count, <name>_sum (always); <name>{quantile=...} (only when WithSummaryQuantiles() is configured) |
| StateSet | <name>{<name>=state} with scalar 0/1 values |
| MeasureSet | <name>_<field>{measure_field=field}; flattened kind follows family semantics (Gauge or Counter) |
Flatten metadata is exposed via SeriesMeta.Kind, SeriesMeta.SourceKind, and SeriesMeta.FlattenRole.
MeasureSet flattening keeps per-field metric names for MetricMeta(name) compatibility and also adds a synthetic measure_field=<field> label. This gives chartengine explicit field identity without widening the reader metadata API.
store := metrix.NewCollectorStore()
meter := store.Write().SnapshotMeter("mysql")
qps := meter.Counter("queries_total")
qps.ObserveTotal(42)
reader := store.Read(metrix.ReadRaw(), metrix.ReadFlatten())
value, ok := reader.Value("mysql.queries_total", nil)
_ = value
_ = ok
reader := store.Read()
point, ok := reader.MeasureSet("svc.latency", nil)
_ = point
_ = ok
For a complete collector integration pattern (cycle management, error handling), see how-to-write-a-collector.md.
LabelSet is store-owned; do not share between different stores.Delta() requires contiguous sequence (N, N+1).
In CollectorStore this is per-cycle: missing one successful cycle breaks the delta.
In RuntimeStore this is per-series per-write: the sequence always increments on each write, so skipping a write cycle does not break deltas.FreshnessCommitted.RuntimeStore rejects snapshot-mode instrument registration with an error.
Calling snapshot-mode record methods (ObserveTotal, ObservePoint) panics.RuntimeStore supports both gauge-like and counter-like MeasureSet families, but only through StatefulMeter(...).ObserveFields(...), ObserveTotalFields(...), SetFields(...), and AddFields(...) require the exact declared field set. Snapshot singular field writes are intentionally absent in phase 1.WindowCycle requires (and silently forces) FreshnessCycle. Setting an explicit non-Cycle freshness with WindowCycle returns an error.MeasureSet series use per-field metric names like <name>_<field> and also carry a synthetic measure_field=<field> label.MeasureSet families reject negative AddPoint(...) deltas, just like scalar counters.CollectorStore evicts series not seen for 10 successful cycles by default.| Area | Implementation pattern |
|---|---|
| Snapshot publish | Read snapshots are immutable and atomically swapped |
| Collector commit | Staged frame merges into new snapshot on successful cycle commit |
| Runtime commit | Overlay/compaction strategy with retention pruning |
| Iteration | Name-indexed deterministic iteration for reader traversal |
| Identity | Canonical metric+labels key with stable SeriesIdentity hash |