Skip to main content

query_runtime/metrics/
backend_descriptor.rs

1//! Backend-owned metric descriptors.
2//!
3//! This catalog is deliberately separate from [`super::QueryMetrics`].  The
4//! latter contains query-local rows and is therefore not a valid input to a
5//! fixed-cardinality backend store.
6
7/// The storage semantics of a backend metric.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum BackendMetricKind {
10    /// Monotonic cumulative count over the backend generation.
11    Counter,
12    /// Current value projected from an admission ledger transition.
13    Gauge,
14    /// Largest recorded value over the backend generation.
15    Max,
16    /// Cumulative counts and sum for a bounded set of duration buckets.
17    Histogram,
18}
19
20/// UCUM unit carried by a backend descriptor.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum BackendMetricUnit {
23    /// Dimensionless count, exported with UCUM unit `1`.
24    One,
25    /// Byte count, exported with UCUM unit `By`.
26    Bytes,
27    /// Duration in seconds, exported with UCUM unit `s`.
28    Seconds,
29}
30
31impl BackendMetricUnit {
32    /// Returns the descriptor's stable UCUM unit label.
33    pub const fn as_ucum(self) -> &'static str {
34        match self {
35            Self::One => "1",
36            Self::Bytes => "By",
37            Self::Seconds => "s",
38        }
39    }
40}
41
42/// The lifecycle transition that owns a metric update.
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum BackendMetricUpdatePoint {
45    /// An authoritative device ledger mutation updates the metric.
46    LedgerTransition,
47    /// Publication of a sealed attempt report updates the metric.
48    ReportReady,
49}
50
51/// A closed label domain. `cardinality` is a contract bound on the number of
52/// label values, set in the descriptor and not computed from observed samples.
53/// Stores must reject values outside the corresponding closed domain.
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub struct BackendMetricLabel {
56    pub(crate) name: &'static str,
57    pub(crate) cardinality: usize,
58}
59
60/// One backend metric's immutable registration metadata.
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub struct BackendMetricDescriptor {
63    /// Stable identity used by the owning snapshot projection. Exporters
64    /// iterate descriptors and never infer a metric from its display name.
65    pub metric: BackendMetric,
66    /// Stable metric registration name used by exporters.
67    pub name: &'static str,
68    /// Export unit, including the conversion target for duration samples.
69    pub unit: BackendMetricUnit,
70    /// Storage and exporter aggregation semantics.
71    pub kind: BackendMetricKind,
72    /// Closed label dimensions in the order used by projected sample attributes.
73    pub labels: &'static [BackendMetricLabel],
74    /// Internal histogram boundary values. Duration bounds remain exact
75    /// nanoseconds until [`project_backend_duration_histogram`] converts them
76    /// to this descriptor's declared export unit.
77    pub buckets: &'static [u64],
78    /// Runtime lifecycle transition that records this metric.
79    pub update_point: BackendMetricUpdatePoint,
80    /// Maximum accepted series count across the closed label domains.
81    pub max_series: usize,
82}
83
84/// Duration histogram values converted to the descriptor's declared unit.
85///
86/// Histogram count and cumulative bucket counts are dimensionless, so callers
87/// must copy them from the snapshot unchanged.
88#[derive(Debug, Clone, PartialEq)]
89pub struct BackendDurationHistogramProjection {
90    /// Explicit finite histogram bounds in the descriptor's export unit.
91    pub bucket_bounds: Vec<f64>,
92    /// Total observed duration in the descriptor's export unit.
93    pub sum: f64,
94}
95
96/// A duration descriptor cannot project the backend's nanosecond storage into
97/// its declared unit.
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub struct BackendDurationProjectionError {
100    /// Metric whose descriptor requests an unsupported duration unit.
101    pub metric_name: &'static str,
102    /// Declared unit that cannot represent the nanosecond duration projection.
103    pub unit: BackendMetricUnit,
104}
105
106impl std::fmt::Display for BackendDurationProjectionError {
107    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        write!(formatter, "{} cannot project a duration with UCUM unit {}", self.metric_name, self.unit.as_ucum())
109    }
110}
111
112impl std::error::Error for BackendDurationProjectionError {}
113
114/// Projects the duration descriptor's internal nanosecond bounds and sum into
115/// its declared export unit.
116///
117/// This is the sole duration conversion boundary. It intentionally omits the
118/// dimensionless `count` and `bucket_counts`, which consumers must preserve
119/// from the fixed backend histogram snapshot.
120pub fn project_backend_duration_histogram(
121    descriptor: &BackendMetricDescriptor,
122    sum_ns: u64,
123) -> Result<BackendDurationHistogramProjection, BackendDurationProjectionError> {
124    match descriptor.unit {
125        BackendMetricUnit::Seconds => Ok(BackendDurationHistogramProjection {
126            bucket_bounds: descriptor.buckets.iter().map(|bound_ns| *bound_ns as f64 / 1_000_000_000.0).collect(),
127            sum: sum_ns as f64 / 1_000_000_000.0,
128        }),
129        unit => Err(BackendDurationProjectionError { metric_name: descriptor.name, unit }),
130    }
131}
132
133const DEVICE: BackendMetricLabel = BackendMetricLabel { name: "device.ordinal", cardinality: 64 };
134const OUTCOME: BackendMetricLabel = BackendMetricLabel { name: "attempt.outcome", cardinality: 8 };
135const DISPOSITION: BackendMetricLabel = BackendMetricLabel { name: "attempt.disposition", cardinality: 4 };
136const DURATION: BackendMetricLabel = BackendMetricLabel { name: "attempt.duration", cardinality: 4 };
137
138const EMPTY_BUCKETS: &[u64] = &[];
139const DEVICE_LABELS: &[BackendMetricLabel] = &[DEVICE];
140const ATTEMPT_LABELS: &[BackendMetricLabel] = &[DISPOSITION, OUTCOME];
141const ATTEMPT_DURATION_LABELS: &[BackendMetricLabel] = &[DISPOSITION, OUTCOME, DURATION];
142/// Fixed histogram boundaries shared by the descriptor and backend store.
143pub const BACKEND_ATTEMPT_DURATION_BUCKETS_NS: [u64; 7] =
144    [1_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000, 1_000_000_000];
145const DURATION_BUCKETS_NS: &[u64] = &BACKEND_ATTEMPT_DURATION_BUCKETS_NS;
146
147/// Single ordered source for the backend descriptor registry.
148macro_rules! for_each_backend_metric {
149    ($macro:ident) => {
150        $macro! {
151            "Current admitted attempts whose grant is active or being cleaned up."; admission_active_attempts => "algeon.gpu.admission.active_attempts": BackendMetricUnit::One, BackendMetricKind::Gauge, DEVICE_LABELS, EMPTY_BUCKETS, BackendMetricUpdatePoint::LedgerTransition, 64;
152            "Current bytes committed to immutable active or cleaning query grants."; admission_live_grant_bytes => "algeon.gpu.admission.live_grant": BackendMetricUnit::Bytes, BackendMetricKind::Gauge, DEVICE_LABELS, EMPTY_BUCKETS, BackendMetricUpdatePoint::LedgerTransition, 64;
153            "Current bytes retained by live resident owners."; admission_resident_owner_bytes => "algeon.gpu.admission.resident_owner": BackendMetricUnit::Bytes, BackendMetricKind::Gauge, DEVICE_LABELS, EMPTY_BUCKETS, BackendMetricUpdatePoint::LedgerTransition, 64;
154            "Maximum resident-owner bytes over the backend generation."; admission_max_resident_owner_bytes => "algeon.gpu.admission.max_resident_owner": BackendMetricUnit::Bytes, BackendMetricKind::Max, DEVICE_LABELS, EMPTY_BUCKETS, BackendMetricUpdatePoint::LedgerTransition, 64;
155            "Terminal attempts counted when their sealed report becomes ready."; attempts_completed => "algeon.gpu.attempts.completed": BackendMetricUnit::One, BackendMetricKind::Counter, ATTEMPT_LABELS, EMPTY_BUCKETS, BackendMetricUpdatePoint::ReportReady, 32;
156            "Sealed attempt durations grouped by coverage, outcome and duration kind."; attempts_duration => "algeon.gpu.attempts.duration": BackendMetricUnit::Seconds, BackendMetricKind::Histogram, ATTEMPT_DURATION_LABELS, DURATION_BUCKETS_NS, BackendMetricUpdatePoint::ReportReady, 128;
157            "Current cached resident bytes eligible for eviction."; cache_evictable_resident_bytes => "algeon.gpu.cache.evictable.resident": BackendMetricUnit::Bytes, BackendMetricKind::Gauge, DEVICE_LABELS, EMPTY_BUCKETS, BackendMetricUpdatePoint::LedgerTransition, 64;
158            "Current cached resident bytes pinned by active use."; cache_pinned_in_use_bytes => "algeon.gpu.cache.pinned.in_use": BackendMetricUnit::Bytes, BackendMetricKind::Gauge, DEVICE_LABELS, EMPTY_BUCKETS, BackendMetricUpdatePoint::LedgerTransition, 64;
159        }
160    };
161}
162
163macro_rules! define_backend_metric_ids {
164    ($( $doc:literal; $field:ident => $name:literal: $unit:expr, $kind:expr, $labels:ident, $buckets:ident, $point:expr, $max:literal; )*) => {
165        /// Stable identities for the bounded backend descriptor registry.
166        #[expect(
167            non_camel_case_types,
168            reason = "metric identifiers are generated in snake_case matching descriptor fields"
169        )]
170        #[repr(usize)]
171        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
172        pub enum BackendMetric {
173            $( #[doc = $doc] $field, )*
174        }
175
176        /// Canonical backend descriptors in stable registration order.
177        pub const BACKEND_METRIC_DESCRIPTORS: &[BackendMetricDescriptor] = &[
178            $( BackendMetricDescriptor { metric: BackendMetric::$field, name: $name, unit: $unit, kind: $kind, labels: $labels, buckets: $buckets, update_point: $point, max_series: $max }, )*
179        ];
180    };
181}
182
183for_each_backend_metric!(define_backend_metric_ids);
184
185/// Returns the descriptor registry in stable contract order.
186pub const fn backend_metric_descriptors() -> &'static [BackendMetricDescriptor] {
187    BACKEND_METRIC_DESCRIPTORS
188}
189
190impl BackendMetric {
191    /// Returns this metric's one canonical descriptor-table entry.
192    pub(crate) const fn descriptor(self) -> &'static BackendMetricDescriptor {
193        &BACKEND_METRIC_DESCRIPTORS[self as usize]
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200
201    const GOLDEN: &[(&str, BackendMetricUnit, BackendMetricKind, BackendMetricUpdatePoint, usize)] = &[
202        (
203            "algeon.gpu.admission.active_attempts",
204            BackendMetricUnit::One,
205            BackendMetricKind::Gauge,
206            BackendMetricUpdatePoint::LedgerTransition,
207            64,
208        ),
209        (
210            "algeon.gpu.admission.live_grant",
211            BackendMetricUnit::Bytes,
212            BackendMetricKind::Gauge,
213            BackendMetricUpdatePoint::LedgerTransition,
214            64,
215        ),
216        (
217            "algeon.gpu.admission.resident_owner",
218            BackendMetricUnit::Bytes,
219            BackendMetricKind::Gauge,
220            BackendMetricUpdatePoint::LedgerTransition,
221            64,
222        ),
223        (
224            "algeon.gpu.admission.max_resident_owner",
225            BackendMetricUnit::Bytes,
226            BackendMetricKind::Max,
227            BackendMetricUpdatePoint::LedgerTransition,
228            64,
229        ),
230        (
231            "algeon.gpu.attempts.completed",
232            BackendMetricUnit::One,
233            BackendMetricKind::Counter,
234            BackendMetricUpdatePoint::ReportReady,
235            32,
236        ),
237        (
238            "algeon.gpu.attempts.duration",
239            BackendMetricUnit::Seconds,
240            BackendMetricKind::Histogram,
241            BackendMetricUpdatePoint::ReportReady,
242            128,
243        ),
244        (
245            "algeon.gpu.cache.evictable.resident",
246            BackendMetricUnit::Bytes,
247            BackendMetricKind::Gauge,
248            BackendMetricUpdatePoint::LedgerTransition,
249            64,
250        ),
251        (
252            "algeon.gpu.cache.pinned.in_use",
253            BackendMetricUnit::Bytes,
254            BackendMetricKind::Gauge,
255            BackendMetricUpdatePoint::LedgerTransition,
256            64,
257        ),
258    ];
259
260    #[test]
261    fn descriptor_table_golden_is_stable() {
262        assert_eq!(BACKEND_METRIC_DESCRIPTORS.len(), GOLDEN.len());
263        for (actual, expected) in BACKEND_METRIC_DESCRIPTORS.iter().zip(GOLDEN) {
264            assert_eq!((actual.name, actual.unit, actual.kind, actual.update_point, actual.max_series), *expected);
265            assert_eq!(actual.metric.descriptor(), actual, "{} must retain its descriptor identity", actual.name);
266        }
267    }
268
269    #[test]
270    fn label_products_fit_declared_series_bounds() {
271        for descriptor in BACKEND_METRIC_DESCRIPTORS {
272            let product = descriptor.labels.iter().map(|label| label.cardinality).product::<usize>();
273            assert!(product <= descriptor.max_series, "{} exceeds max_series", descriptor.name);
274            if descriptor.kind == BackendMetricKind::Histogram {
275                assert!(!descriptor.buckets.is_empty());
276            } else {
277                assert!(descriptor.buckets.is_empty());
278            }
279        }
280    }
281
282    #[test]
283    fn labels_are_closed_and_measurement_names_are_not_dimensions() {
284        let forbidden = ["query_id", "attempt_id", "sql", "user", "path", "uri", "message", "node_id"];
285        for descriptor in BACKEND_METRIC_DESCRIPTORS {
286            for label in descriptor.labels {
287                assert!(!forbidden.contains(&label.name));
288                assert!(!label.name.ends_with("_bytes"));
289                assert!(!label.name.ends_with("_rows"));
290                assert!(!label.name.ends_with("_ns"));
291            }
292        }
293    }
294
295    #[test]
296    fn names_and_units_follow_backend_conventions() {
297        for descriptor in BACKEND_METRIC_DESCRIPTORS {
298            assert!(descriptor.name.starts_with("algeon."));
299            assert!(
300                descriptor.name.chars().all(|c| c == '.' || c == '_' || c.is_ascii_lowercase() || c.is_ascii_digit())
301            );
302            assert!(matches!(descriptor.unit.as_ucum(), "1" | "By" | "s"));
303        }
304    }
305}