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

2.5 KiB

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).