1#[macro_export]
29macro_rules! parse_metric {
30 ($line:expr, $suffix:expr, $ty:ty) => {{
31 let opt = $crate::parsing::common::after_colon_trimmed($line)
32 .and_then(|rest| rest.split_whitespace().next())
33 .map(|tok| {
34 let no_suffix = tok.trim_end_matches($suffix);
35 no_suffix.trim_end_matches('%').to_string()
37 })
38 .and_then(|num| $crate::parsing::common::parse_number::<$ty>(&num));
39 opt
40 }};
41}
42
43#[macro_export]
54macro_rules! parse_prometheus {
55 ($line:expr, $re:expr) => {{
56 if let Some(cap) = $re.captures($line.trim()) {
57 let name = cap.get(1).map(|m| m.as_str().to_string());
58 let labels = cap.get(2).map(|m| m.as_str().to_string());
59 let value = cap
60 .get(3)
61 .and_then(|m| m.as_str().parse::<f64>().ok())
62 .unwrap_or(0.0);
63 if let (Some(name), Some(labels)) = (name, labels) {
64 if name.len() > 256 || labels.len() > 1024 {
66 None
67 } else {
68 Some((name, labels, value))
69 }
70 } else {
71 None
72 }
73 } else {
74 None
75 }
76 }};
77}
78
79#[macro_export]
90macro_rules! extract_label_to_detail {
91 ($labels:expr, $label_key:expr, $detail_map:expr, $detail_key:expr) => {
92 if let Some(value) = $labels.get($label_key) {
93 $detail_map.insert($detail_key.to_string(), value.clone());
94 }
95 };
96 ($labels:expr, $key:expr, $detail_map:expr) => {
98 extract_label_to_detail!($labels, $key, $detail_map, $key);
99 };
100}
101
102#[macro_export]
114macro_rules! extract_labels_batch {
115 ($labels:expr, $detail_map:expr, [$($key:expr),* $(,)?]) => {
116 $(
117 if let Some(value) = $labels.get($key) {
118 $detail_map.insert($key.to_string(), value.clone());
119 }
120 )*
121 };
122}
123
124#[macro_export]
137macro_rules! update_metric_field {
138 ($metric_name:expr, $value:expr, $target:expr, {
139 $($name:expr => $field:ident as $type:ty),* $(,)?
140 }) => {
141 match $metric_name {
142 $(
143 $name => {
144 #[allow(unused_comparisons)]
146 let safe_value = if $value < 0.0 {
147 0 as $type
148 } else if $value > (<$type>::MAX as f64) {
149 <$type>::MAX
150 } else {
151 $value as $type
152 };
153 $target.$field = safe_value;
154 },
155 )*
156 _ => {}
157 }
158 };
159}
160
161#[macro_export]
170macro_rules! get_label_or_default {
171 ($labels:expr, $key:expr) => {
172 $labels
173 .get($key)
174 .map(|s| s.as_str())
175 .unwrap_or("")
176 .to_string()
177 };
178 ($labels:expr, $key:expr, $default:expr) => {
179 $labels
180 .get($key)
181 .map(|s| s.to_string())
182 .unwrap_or_else(|| $default.to_string())
183 };
184}
185
186#[macro_export]
194macro_rules! update_optional_field {
195 ($parent:expr, $optional_field:ident, $field:ident, $value:expr) => {
196 if let Some(ref mut inner) = $parent.$optional_field {
197 inner.$field = $value;
198 }
199 };
200}
201
202#[macro_export]
215macro_rules! extract_struct_fields {
216 ($detail:expr, $source:expr, {
217 $($key:literal => $field:ident),* $(,)?
218 }) => {
219 $(
220 $detail.insert($key.into(), $source.$field.clone());
221 )*
222 };
223}
224
225#[macro_export]
238macro_rules! insert_optional_fields {
239 ($detail:expr, $source:expr, {
240 $($key:literal => $field:ident),* $(,)?
241 }) => {
242 $(
243 if let Some(ref value) = $source.$field {
244 $detail.insert($key.into(), value.clone());
245 }
246 )*
247 };
248}
249
250#[macro_export]
259macro_rules! parse_colon_value {
260 ($line:expr, $type:ty) => {
261 $line
262 .split(':')
263 .nth(1)
264 .and_then(|s| s.split_whitespace().next())
265 .and_then(|s| s.parse::<$type>().ok())
266 };
267}
268
269#[macro_export]
279macro_rules! parse_prefixed_line {
280 ($line:expr, $prefix:expr, $type:ty) => {
281 if $line.starts_with($prefix) {
282 $line
283 .strip_prefix($prefix)
284 .and_then(|s| s.trim().split_whitespace().next())
285 .and_then(|s| s.parse::<$type>().ok())
286 } else {
287 None
288 }
289 };
290}
291
292#[cfg(test)]
293mod tests {
294 use regex::Regex;
295
296 #[test]
297 fn test_parse_metric_frequency() {
298 let line = "GPU HW active frequency: 444 MHz";
299 let v = parse_metric!(line, "MHz", u32);
300 assert_eq!(v, Some(444u32));
301 }
302
303 #[test]
304 fn test_parse_metric_percentage() {
305 let line = "E-Cluster HW active residency: 64.29% (details omitted)";
306 let v = parse_metric!(line, "%", f64);
307 assert!(v.is_some());
308 assert!((v.unwrap() - 64.29).abs() < 1e-6);
309 }
310
311 #[test]
312 fn test_parse_metric_power() {
313 let line = "CPU Power: 475 mW";
314 let v = parse_metric!(line, "mW", f64);
315 assert_eq!(v, Some(475.0));
316 }
317
318 #[test]
319 fn test_parse_metric_invalid() {
320 let line = "Invalid Line";
321 let v = parse_metric!(line, "MHz", u32);
322 assert!(v.is_none());
323 }
324
325 #[test]
326 fn test_parse_prometheus_success() {
327 let re = Regex::new(r"^all_smi_([^\{]+)\{([^}]+)\} ([\d\.]+)$").unwrap();
328 let line = r#"all_smi_gpu_utilization{gpu="RTX", uuid="GPU-1"} 25.5"#;
329 let parsed = parse_prometheus!(line, re);
330 assert!(parsed.is_some());
331 let (name, labels, value) = parsed.unwrap();
332 assert_eq!(name, "gpu_utilization");
333 assert!(labels.contains(r#"gpu="RTX""#));
334 assert_eq!(value, 25.5);
335 }
336
337 #[test]
338 fn test_parse_prometheus_invalid() {
339 let re = Regex::new(r"^all_smi_([^\{]+)\{([^}]+)\} ([\d\.]+)$").unwrap();
340 let line = "bad format";
341 let parsed = parse_prometheus!(line, re);
342 assert!(parsed.is_none());
343 }
344
345 #[test]
346 fn test_extract_label_to_detail() {
347 use std::collections::HashMap;
348
349 let mut labels = HashMap::new();
350 labels.insert("cuda_version".to_string(), "11.8".to_string());
351 labels.insert("driver_version".to_string(), "525.60.13".to_string());
352
353 let mut detail = HashMap::new();
354
355 extract_label_to_detail!(labels, "cuda_version", detail, "cuda_version");
356 assert_eq!(detail.get("cuda_version"), Some(&"11.8".to_string()));
357
358 extract_label_to_detail!(labels, "driver_version", detail);
359 assert_eq!(detail.get("driver_version"), Some(&"525.60.13".to_string()));
360
361 extract_label_to_detail!(labels, "non_existent", detail);
363 assert_eq!(detail.get("non_existent"), None);
364 }
365
366 #[test]
367 fn test_extract_labels_batch() {
368 use std::collections::HashMap;
369
370 let mut labels = HashMap::new();
371 labels.insert("cuda_version".to_string(), "11.8".to_string());
372 labels.insert("driver_version".to_string(), "525.60.13".to_string());
373 labels.insert("architecture".to_string(), "Ampere".to_string());
374
375 let mut detail = HashMap::new();
376
377 extract_labels_batch!(
378 labels,
379 detail,
380 [
381 "cuda_version",
382 "driver_version",
383 "architecture",
384 "non_existent"
385 ]
386 );
387
388 assert_eq!(detail.get("cuda_version"), Some(&"11.8".to_string()));
389 assert_eq!(detail.get("driver_version"), Some(&"525.60.13".to_string()));
390 assert_eq!(detail.get("architecture"), Some(&"Ampere".to_string()));
391 assert_eq!(detail.get("non_existent"), None);
392 }
393
394 #[test]
395 fn test_update_metric_field() {
396 struct TestStruct {
397 utilization: f64,
398 memory: u64,
399 temperature: u32,
400 }
401
402 let mut test = TestStruct {
403 utilization: 0.0,
404 memory: 0,
405 temperature: 0,
406 };
407
408 let metric_name = "gpu_utilization";
409 let value = 75.5;
410
411 update_metric_field!(metric_name, value, test, {
412 "gpu_utilization" => utilization as f64,
413 "gpu_memory_used_bytes" => memory as u64,
414 "gpu_temperature_celsius" => temperature as u32
415 });
416
417 assert_eq!(test.utilization, 75.5);
418
419 let metric_name = "gpu_memory_used_bytes";
420 let value = 1024.0;
421
422 update_metric_field!(metric_name, value, test, {
423 "gpu_utilization" => utilization as f64,
424 "gpu_memory_used_bytes" => memory as u64,
425 "gpu_temperature_celsius" => temperature as u32
426 });
427
428 assert_eq!(test.memory, 1024);
429 }
430
431 #[test]
432 fn test_get_label_or_default() {
433 use std::collections::HashMap;
434
435 let mut labels = HashMap::new();
436 labels.insert("gpu".to_string(), "RTX 4090".to_string());
437 labels.insert("index".to_string(), "2".to_string());
438
439 let gpu_name = get_label_or_default!(labels, "gpu");
440 assert_eq!(gpu_name, "RTX 4090");
441
442 let non_existent = get_label_or_default!(labels, "non_existent");
443 assert_eq!(non_existent, "");
444
445 let custom_default = get_label_or_default!(labels, "non_existent", "N/A");
446 assert_eq!(custom_default, "N/A");
447
448 let index = get_label_or_default!(labels, "index", "0");
449 assert_eq!(index, "2");
450 }
451}