1use std::time::Duration;
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
19#[repr(u8)]
20pub enum Level {
21 Off = 0,
24 Basic = 1,
27 Kernel = 2,
31}
32
33impl Level {
34 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 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 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
73pub const DEVICE_SAMPLE_PERIOD: Duration = Duration::from_millis(100);
75pub const KERNEL_SPAN_EVERY: u32 = 64;
78pub const MIN_DEVICE_PERIOD: Duration = Duration::from_millis(10);
80pub const MAX_DEVICE_PERIOD: Duration = Duration::from_secs(10);
82
83#[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#[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 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}