metrix
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).
Purpose
| 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 |
Core Concepts
- Immutable reads — Readers observe immutable snapshots that are swapped atomically on commit. Multiple goroutines can read concurrently without locking.
- Cycle-scoped collector writes —
CollectorStorewrites are staged betweenBeginCycleandCommitCycleSuccess. Nothing is visible to readers until commit. - Stateful runtime writes —
RuntimeStorewrites are committed immediately (no cycle API). Each write produces a new overlay snapshot. - Label canonicalization — Label maps (
map[string]string) are sorted and encoded into a canonical key that uniquely identifies a series (metric name + labels). - Typed + flattened views — Reader supports canonical typed families (Histogram, Summary, StateSet, MeasureSet) and a flattened scalar view where complex types are projected into individual scalar series.
Key Definitions
- Freshness controls which series appear in non-raw reads.
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. - Window controls how stateful histogram/summary instruments accumulate observations.
WindowCumulative= observations accumulate across cycles.WindowCycle= observations reset each cycle.
Stores and Interfaces
| 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 |
Write Model
Collector store
| 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.
Runtime store
- No cycle API — writes commit immediately.
- Stateful only — snapshot-mode instrument registration returns an error.
[!CAUTION] Calling snapshot-mode record methods (
ObserveTotal,ObservePoint) on aRuntimeStorepanics.
- Fixed freshness — runtime store enforces
FreshnessCommittedsemantics; other freshness policies are rejected.
Instrument Modes and Defaults
| Mode | Typical meter | Freshness default | Window default |
|---|---|---|---|
| Snapshot | SnapshotMeter(...) |
FreshnessCycle |
WindowCumulative |
| Stateful | StatefulMeter(...) |
FreshnessCommitted |
WindowCumulative |
Instrument Options
| 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
- Structured numeric family —
MeasureSetstores one logical metric family with a fixed ordered list of named numeric fields. - Family-level semantics — one
MeasureSetfamily is either gauge-like or counter-like; semantics are never mixed per field. - Family-level metadata —
Description,ChartFamily, andUnitapply to the whole family. - Field-level schema —
MeasureFieldSpecdeclares per-fieldNameandFloat. - Chartengine integration — chart autogen treats
MeasureSetas a structured family, similar toStateSet; flatten remains the generic reader/tooling path.
Writers
| 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(...) |
Phase-1 write contract
- Preferred collector-facing API — use named write helpers instead of raw positional
MeasureSetPointvalues whenever practical. - Snapshot handles support:
- full-family positional writes (
ObservePoint(...),ObserveTotalPoint(...)) - full-family named writes (
ObserveFields(...),ObserveTotalFields(...))
- full-family positional writes (
- Stateful handles support:
- full-family positional writes
- full-family named writes
- singular field writes (
SetField(...),AddField(...))
- Snapshot singular field writes do not exist in phase 1.
MeasureSetstill models one sampled family point per collect cycle in snapshot mode.- Partial snapshot field visibility/completeness semantics are intentionally deferred.
- Named full-family writes require the exact declared field set.
- Missing fields panic.
- Unknown extra fields panic.
- Stateful singular field writes update only the addressed field.
- Gauge-like
SetField(...)overwrites one field on top of the committed/staged family. - Gauge-like
AddField(...)and counter-likeAddField(...)apply a delta to one field.
- Gauge-like
Schema example
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.
Stateful singular-write example
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 Modes
Read(...) accepts option functions that control two independent axes:
- Raw (
ReadRaw()) — bypasses freshness filtering, returning all committed series regardless of when they were last observed. - Flatten (
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 |
Flattened View Mapping
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.
Minimal Usage Snippets
Collector write path
store := metrix.NewCollectorStore()
meter := store.Write().SnapshotMeter("mysql")
qps := meter.Counter("queries_total")
qps.ObserveTotal(42)
Read path for planning
reader := store.Read(metrix.ReadRaw(), metrix.ReadFlatten())
value, ok := reader.Value("mysql.queries_total", nil)
_ = value
_ = ok
Direct MeasureSet read
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.
Contracts and Pitfalls
- Label sets —
LabelSetis store-owned; do not share between different stores. - Counter deltas —
Delta()requires contiguous sequence (N, N+1). InCollectorStorethis is per-cycle: missing one successful cycle breaks the delta. InRuntimeStorethis is per-series per-write: the sequence always increments on each write, so skipping a write cycle does not break deltas. - Snapshot freshness — Snapshot-mode instruments cannot use
FreshnessCommitted. - Runtime writes —
RuntimeStorerejects snapshot-mode instrument registration with an error. Calling snapshot-mode record methods (ObserveTotal,ObservePoint) panics. - MeasureSet runtime writes —
RuntimeStoresupports both gauge-like and counter-likeMeasureSetfamilies, but only throughStatefulMeter(...). - MeasureSet named writes —
ObserveFields(...),ObserveTotalFields(...),SetFields(...), andAddFields(...)require the exact declared field set. Snapshot singular field writes are intentionally absent in phase 1. - Window/freshness coupling — Stateful histogram/summary with
WindowCyclerequires (and silently forces)FreshnessCycle. Setting an explicit non-Cycle freshness withWindowCyclereturns an error. - Schema stability — Re-registering an existing metric name with different kind/mode/schema returns an error (or panics in strict runtime paths).
- MeasureSet flatten naming — Flattened
MeasureSetseries use per-field metric names like<name>_<field>and also carry a syntheticmeasure_field=<field>label. - MeasureSet counter semantics — Stateful counter-like
MeasureSetfamilies reject negativeAddPoint(...)deltas, just like scalar counters. - Summary NaN quantiles — a summary point may carry NaN quantile values (e.g. an empty observation window); they are stored (only Inf is rejected) and render as a chart gap downstream (chartengine emits
SETEMPTY). Count and Sum must still be finite. - Collector retention —
CollectorStoreevicts series not seen for 10 successful cycles by default.
Internal Architecture Notes
| 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 |