metrale_telemetry/
level.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-26: The telemetry [`Level`] and the validated [`TelemetryConfig`].
4//!
5//! Serving takes the level from `--telemetry` and the NVML device from
6//! `--gpu-ordinal`; the cadences are the constants below, through
7//! [`TelemetryConfig::serving`].
8//!
9//! Owner: telemetry.
10//! Invariants: a [`TelemetryConfig`]'s device period lies in
11//! [`MIN_DEVICE_PERIOD`]..=[`MAX_DEVICE_PERIOD`] and its kernel span stride is
12//! at least 1.
13
14use std::time::Duration;
15
16/// 2026-09-26: The measurement level. Each level measures everything the
17/// levels below it measure.
18#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
19#[repr(u8)]
20pub enum Level {
21    /// 2026-09-26: Nothing is measured. Every hot-path entry point is one
22    /// relaxed atomic load and a return: no clock read, no allocation.
23    Off = 0,
24    /// 2026-09-26: Device sampling (L0), scheduler, cache, spec and request
25    /// instruments (L2–L5), and energy attribution.
26    Basic = 1,
27    /// 2026-09-26: Basic plus GPU event spans (L1): every serve lane each
28    /// step, and every eager kernel launch on one step in
29    /// [`TelemetryConfig::kernel_span_every`].
30    Kernel = 2,
31}
32
33impl Level {
34    /// 2026-09-26: The accepted spellings, in level order. The `--telemetry`
35    /// validator checks against this list.
36    pub const NAMES: [&'static str; 3] = ["off", "basic", "kernel"];
37
38    pub const fn as_str(self) -> &'static str {
39        Self::NAMES[self as usize]
40    }
41
42    /// 2026-09-26: Inverse of `self as u8`; any other byte reads as `Off`.
43    pub const fn from_u8(v: u8) -> Self {
44        match v {
45            1 => Self::Basic,
46            2 => Self::Kernel,
47            _ => Self::Off,
48        }
49    }
50}
51
52impl std::fmt::Display for Level {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        f.write_str(self.as_str())
55    }
56}
57
58impl std::str::FromStr for Level {
59    type Err = ConfigError;
60
61    /// 2026-09-26: Exact, lower-case spellings only: `Basic`, `on` and `1`
62    /// are refused.
63    fn from_str(s: &str) -> Result<Self, ConfigError> {
64        match s {
65            "off" => Ok(Self::Off),
66            "basic" => Ok(Self::Basic),
67            "kernel" => Ok(Self::Kernel),
68            other => Err(ConfigError::UnknownLevel(other.to_string())),
69        }
70    }
71}
72
73/// 2026-09-26: Device sampling cadence when serving: 10 Hz.
74pub const DEVICE_SAMPLE_PERIOD: Duration = Duration::from_millis(100);
75/// 2026-09-26: When serving at level `Kernel`, per-kernel spans are recorded on
76/// one step in this many.
77pub const KERNEL_SPAN_EVERY: u32 = 64;
78/// 2026-09-26: The shortest device period [`TelemetryConfig::new`] accepts.
79pub const MIN_DEVICE_PERIOD: Duration = Duration::from_millis(10);
80/// 2026-09-26: The longest device period [`TelemetryConfig::new`] accepts.
81pub const MAX_DEVICE_PERIOD: Duration = Duration::from_secs(10);
82
83/// 2026-09-26: Why a level or configuration was refused.
84#[derive(Clone, Debug, PartialEq, Eq)]
85pub enum ConfigError {
86    UnknownLevel(String),
87    DevicePeriodOutOfRange(Duration),
88    ZeroKernelSpanStride,
89}
90
91impl std::fmt::Display for ConfigError {
92    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93        match self {
94            Self::UnknownLevel(s) => write!(
95                f,
96                "unknown telemetry level {s:?}: expected one of {}",
97                Level::NAMES.join(", ")
98            ),
99            Self::DevicePeriodOutOfRange(p) => write!(
100                f,
101                "device sample period {p:?} is outside [{MIN_DEVICE_PERIOD:?}, {MAX_DEVICE_PERIOD:?}]"
102            ),
103            Self::ZeroKernelSpanStride => {
104                f.write_str("kernel span stride must be at least 1 (every step)")
105            }
106        }
107    }
108}
109
110impl std::error::Error for ConfigError {}
111
112/// 2026-09-26: A validated telemetry configuration. Its fields are private,
113/// and [`TelemetryConfig::new`] (which `serving` calls) is the only constructor.
114#[derive(Clone, Copy, Debug, PartialEq, Eq)]
115pub struct TelemetryConfig {
116    level: Level,
117    device_period: Duration,
118    kernel_span_every: u32,
119    device_index: u32,
120}
121
122impl TelemetryConfig {
123    pub fn new(
124        level: Level,
125        device_period: Duration,
126        kernel_span_every: u32,
127        device_index: u32,
128    ) -> Result<Self, ConfigError> {
129        if !(MIN_DEVICE_PERIOD..=MAX_DEVICE_PERIOD).contains(&device_period) {
130            return Err(ConfigError::DevicePeriodOutOfRange(device_period));
131        }
132        if kernel_span_every == 0 {
133            return Err(ConfigError::ZeroKernelSpanStride);
134        }
135        Ok(Self {
136            level,
137            device_period,
138            kernel_span_every,
139            device_index,
140        })
141    }
142
143    /// 2026-09-26: The serving configuration: `level` at
144    /// [`DEVICE_SAMPLE_PERIOD`] and [`KERNEL_SPAN_EVERY`], sampling NVML device
145    /// `device_index`.
146    pub fn serving(level: Level, device_index: u32) -> Self {
147        Self::new(level, DEVICE_SAMPLE_PERIOD, KERNEL_SPAN_EVERY, device_index)
148            .expect("the pinned cadences are within range")
149    }
150
151    pub fn level(&self) -> Level {
152        self.level
153    }
154    pub fn device_period(&self) -> Duration {
155        self.device_period
156    }
157    pub fn kernel_span_every(&self) -> u32 {
158        self.kernel_span_every
159    }
160    pub fn device_index(&self) -> u32 {
161        self.device_index
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    fn every_listed_name_parses_to_its_own_level() {
171        for name in Level::NAMES {
172            let level: Level = name.parse().unwrap();
173            assert_eq!(level.as_str(), name);
174            assert_eq!(Level::from_u8(level as u8), level);
175        }
176    }
177
178    #[test]
179    fn near_miss_spellings_are_refused_not_guessed() {
180        for bad in ["Basic", "on", "1", "", "kernels", " off"] {
181            assert_eq!(
182                bad.parse::<Level>(),
183                Err(ConfigError::UnknownLevel(bad.to_string())),
184                "{bad:?}"
185            );
186        }
187    }
188
189    #[test]
190    fn an_unknown_level_byte_reads_as_off() {
191        assert_eq!(Level::from_u8(3), Level::Off);
192        assert_eq!(Level::from_u8(255), Level::Off);
193    }
194
195    #[test]
196    fn a_period_outside_the_range_is_refused_at_both_ends() {
197        let fast = Duration::from_millis(9);
198        let slow = Duration::from_millis(10_001);
199        assert_eq!(
200            TelemetryConfig::new(Level::Basic, fast, 1, 0),
201            Err(ConfigError::DevicePeriodOutOfRange(fast))
202        );
203        assert_eq!(
204            TelemetryConfig::new(Level::Basic, slow, 1, 0),
205            Err(ConfigError::DevicePeriodOutOfRange(slow))
206        );
207        assert!(TelemetryConfig::new(Level::Basic, MIN_DEVICE_PERIOD, 1, 0).is_ok());
208        assert!(TelemetryConfig::new(Level::Basic, MAX_DEVICE_PERIOD, 1, 0).is_ok());
209    }
210
211    #[test]
212    fn a_zero_kernel_stride_is_refused() {
213        assert_eq!(
214            TelemetryConfig::new(Level::Kernel, DEVICE_SAMPLE_PERIOD, 0, 0),
215            Err(ConfigError::ZeroKernelSpanStride)
216        );
217    }
218
219    #[test]
220    fn the_serving_config_is_ten_hertz() {
221        let c = TelemetryConfig::serving(Level::Kernel, 0);
222        assert_eq!(c.device_period(), Duration::from_millis(100));
223        assert_eq!(c.kernel_span_every(), KERNEL_SPAN_EVERY);
224        assert_eq!(c.level(), Level::Kernel);
225    }
226}