Skip to main content

entrenar/dashboard/
snapshot.rs

1//! Metric and resource snapshot types for dashboard display.
2
3use serde::{Deserialize, Serialize};
4
5use super::Trend;
6use crate::storage::MetricPoint;
7
8/// A snapshot of metric values for dashboard display.
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub struct MetricSnapshot {
11    /// Metric key (e.g., "loss", "accuracy")
12    pub key: String,
13    /// Time-value pairs: (timestamp_ms, value)
14    pub values: Vec<(u64, f64)>,
15    /// Current trend direction
16    pub trend: Trend,
17}
18
19impl MetricSnapshot {
20    /// Create a new metric snapshot.
21    pub fn new(key: impl Into<String>, values: Vec<(u64, f64)>) -> Self {
22        let trend = Trend::from_values(&values.iter().map(|(_, v)| *v).collect::<Vec<_>>());
23        Self { key: key.into(), values, trend }
24    }
25
26    /// Create from metric points.
27    pub fn from_points(key: impl Into<String>, points: &[MetricPoint]) -> Self {
28        let values: Vec<(u64, f64)> = points
29            .iter()
30            .map(|p| {
31                let ts = p.timestamp.timestamp_millis() as u64;
32                (ts, p.value)
33            })
34            .collect();
35        Self::new(key, values)
36    }
37
38    /// Get the latest value.
39    pub fn latest(&self) -> Option<f64> {
40        self.values.last().map(|(_, v)| *v)
41    }
42
43    /// Get the minimum value.
44    pub fn min(&self) -> Option<f64> {
45        self.values.iter().map(|(_, v)| *v).reduce(f64::min)
46    }
47
48    /// Get the maximum value.
49    pub fn max(&self) -> Option<f64> {
50        self.values.iter().map(|(_, v)| *v).reduce(f64::max)
51    }
52
53    /// Get the mean value.
54    pub fn mean(&self) -> Option<f64> {
55        if self.values.is_empty() {
56            return None;
57        }
58        Some(self.values.iter().map(|(_, v)| *v).sum::<f64>() / self.values.len() as f64)
59    }
60
61    /// Check if metric is empty.
62    pub fn is_empty(&self) -> bool {
63        self.values.is_empty()
64    }
65
66    /// Get number of data points.
67    pub fn len(&self) -> usize {
68        self.values.len()
69    }
70}
71
72/// Resource usage snapshot for dashboard display.
73#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
74pub struct ResourceSnapshot {
75    /// GPU utilization (0.0 to 1.0)
76    pub gpu_util: f64,
77    /// CPU utilization (0.0 to 1.0)
78    pub cpu_util: f64,
79    /// Memory used in bytes
80    pub memory_used: u64,
81    /// Total memory in bytes
82    pub memory_total: u64,
83    /// GPU memory used in bytes (optional)
84    pub gpu_memory_used: Option<u64>,
85    /// Total GPU memory in bytes (optional)
86    pub gpu_memory_total: Option<u64>,
87}
88
89impl Default for ResourceSnapshot {
90    fn default() -> Self {
91        Self::new()
92    }
93}
94
95impl ResourceSnapshot {
96    /// Create a new resource snapshot with zero values.
97    pub fn new() -> Self {
98        Self {
99            gpu_util: 0.0,
100            cpu_util: 0.0,
101            memory_used: 0,
102            memory_total: 0,
103            gpu_memory_used: None,
104            gpu_memory_total: None,
105        }
106    }
107
108    /// Set GPU utilization.
109    pub fn with_gpu_util(mut self, util: f64) -> Self {
110        self.gpu_util = util.clamp(0.0, 1.0);
111        self
112    }
113
114    /// Set CPU utilization.
115    pub fn with_cpu_util(mut self, util: f64) -> Self {
116        self.cpu_util = util.clamp(0.0, 1.0);
117        self
118    }
119
120    /// Set memory usage.
121    pub fn with_memory(mut self, used: u64, total: u64) -> Self {
122        self.memory_used = used;
123        self.memory_total = total;
124        self
125    }
126
127    /// Set GPU memory usage.
128    pub fn with_gpu_memory(mut self, used: u64, total: u64) -> Self {
129        self.gpu_memory_used = Some(used);
130        self.gpu_memory_total = Some(total);
131        self
132    }
133
134    /// Get memory utilization as a fraction.
135    pub fn memory_util(&self) -> f64 {
136        if self.memory_total == 0 {
137            return 0.0;
138        }
139        self.memory_used as f64 / self.memory_total as f64
140    }
141
142    /// Get GPU memory utilization as a fraction.
143    pub fn gpu_memory_util(&self) -> Option<f64> {
144        match (self.gpu_memory_used, self.gpu_memory_total) {
145            (Some(used), Some(total)) if total > 0 => Some(used as f64 / total as f64),
146            _ => None,
147        }
148    }
149}