Files
ottyl/tickets/097-metric-gauge-counter-semantics.md
T
2026-09-07 12:16:36 +01:00

51 lines
2.5 KiB
Markdown

# 097 — Metrics have no gauge/counter distinction, so `rate()`/`sum()`/`avg()` can be applied without a semantics guard
## Summary
`MetricPrimitiveKind` only distinguishes data *type*
(`String`/`Number`/`Timestamp`/`Duration`/`Boolean`), not metric
*semantics* (counter vs. gauge). PromQL's `gauge()` op is a bespoke
query-time "take the last point" function rather than something tied
to how a metric was declared, and nothing stops calling `rate()` on a
metric that's conceptually a gauge, or `sum()`/`avg()` on a
monotonically-increasing counter without rating it first.
## Where
- `src/engine/data_value.rs:70-76` — `MetricPrimitiveKind`:
`String, Number, Timestamp, Duration, Boolean`. No `Counter`/`Gauge`
variant.
- `src/engine/metric_primitive.rs:8-14` — `MetricPrimitive` mirrors
the same type-only distinction (`String, Number(Number), Timestamp,
Duration(i64), Boolean`).
- `src/engine/promql.rs:9-16` — `AggOp` includes `Gauge` as one of six
query-time aggregation ops (`Sum, Avg, Count, Rate, Histogram,
Gauge`), evaluated in `src/engine/snapshot/mod.rs:993-1001` by
taking only `points.last()` per series — a query-side convenience,
not a property of the declared metric.
- The only type guard on aggregation today is
`src/engine/snapshot/mod.rs:888-896`, which rejects `sum`/`avg` on
`MetricPrimitiveKind::String` — there's no equivalent guard for
applying `rate()` to a non-monotonic value or `sum()`/`avg()` to an
un-rated counter.
## Scope
- Add a semantics field to metric declarations (e.g. extend
`MetricPrimitiveKind` with `Counter`/`Gauge` variants, or add a
separate `MetricSemantics` enum alongside the existing type field —
decide which based on whether type and semantics should ever vary
independently for the same declaration).
- Thread that through `DeclareMetricRequest`/`MetricDeclarationRecord`/
`MetricDeclarationDto` (the same three-places pattern noted in this
repo's `CLAUDE.md` for declaration features: `engine.rs` message +
handler, `settings.rs`/`profile.rs` persistence, `api.rs` route,
plus a webapp UI affordance in `Params.tsx`/`Metrics`'s declaration
form).
- Use it to validate PromQL queries at evaluation time (e.g. warn or
reject `rate()` on a declared gauge, or `sum()`/`avg()` directly on a
declared counter without an inner `rate()`), and consider whether
the current ad hoc `gauge()` PromQL wrapper becomes redundant once
the declaration itself carries this information (a bare selector on
a declared gauge could just work the way `gauge()` does today).