# 022 — Metrics rethink: counters as the primitive, histograms/gauges derived ## Summary The wishlist calls for rethinking the metrics model: everything a metric declares should fundamentally be a **counter** (an occurrence at a point in time, optionally labeled). Histograms and gauges are not separate declaration kinds — they're *derived views* computed from counter data (bucketing counter events into histogram buckets; reducing counter series into a gauge-style "current value"). This changes how `MetricDeclaration`/`BucketValue` are modeled and how the already-shipped PromQL-style query support (`promql.rs`) evaluates `rate()`/`sum()`/histogram/gauge-shaped queries. ## Scope - Re-express metric declarations as counter declarations: a match increments a counter for the resolved label set at the matched timestamp. This is close to today's `BucketValue{values: Vec<(Sequence, Timestamp, MetricPrimitive)>}` — audit whether the existing bucket representation already is this, or whether captured non-counter values (e.g. a captured `Number` used as a gauge reading today) need to be reinterpreted as counter increments plus a separately-derived gauge. Document the resulting semantics clearly since this is a model change to code that already exists and has callers (`engine.rs`, `promql.rs`, `Metrics.tsx`, `Dashboards.tsx`). - Histogram derivation: given a counter's events over a time range, bucket them (fixed-width or user-specified boundaries) and expose bucket counts — likely as a new PromQL-evaluable form (e.g. `histogram(metric_name[param])`) or a query option, consumed the same way existing series are. - Gauge derivation: given a counter's events, derive a "last value" / "value at time T" series from the most recent matching counter event — likely as another derived query form (e.g. `gauge(metric_name[param])`), rather than a distinct declaration/storage kind. - Update `promql.rs`'s evaluator to support these derived forms without breaking existing raw counter queries. - Update `Metrics.tsx`'s declaration form/help text and `Dashboards.tsx`'s panel config if the query surface changes. ## Acceptance criteria - A declared metric behaves as a counter: each match is an increment/event, not an arbitrary overwritten value. - A histogram-derived query against a counter with several distinct captured values buckets them correctly across a seeded fixture. - A gauge-derived query against a counter returns the most-recent value at a given point in the selected time range. - Existing raw counter queries (sum/rate over a plain counter, as today) continue to work unchanged. ## Depends on None — this is a rework of the already-shipped metric/bucket/PromQL code, not new foundational work. [023](023-duration-param.md), [025](025-default-dashboard-seed.md) build on the resulting model.