1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum BackendMetricKind {
10 Counter,
12 Gauge,
14 Max,
16 Histogram,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum BackendMetricUnit {
23 One,
25 Bytes,
27 Seconds,
29}
30
31impl BackendMetricUnit {
32 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum BackendMetricUpdatePoint {
45 LedgerTransition,
47 ReportReady,
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub struct BackendMetricLabel {
56 pub(crate) name: &'static str,
57 pub(crate) cardinality: usize,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub struct BackendMetricDescriptor {
63 pub metric: BackendMetric,
66 pub name: &'static str,
68 pub unit: BackendMetricUnit,
70 pub kind: BackendMetricKind,
72 pub labels: &'static [BackendMetricLabel],
74 pub buckets: &'static [u64],
78 pub update_point: BackendMetricUpdatePoint,
80 pub max_series: usize,
82}
83
84#[derive(Debug, Clone, PartialEq)]
89pub struct BackendDurationHistogramProjection {
90 pub bucket_bounds: Vec<f64>,
92 pub sum: f64,
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
99pub struct BackendDurationProjectionError {
100 pub metric_name: &'static str,
102 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
114pub 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];
142pub 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
147macro_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 #[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 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
185pub const fn backend_metric_descriptors() -> &'static [BackendMetricDescriptor] {
187 BACKEND_METRIC_DESCRIPTORS
188}
189
190impl BackendMetric {
191 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}