hardware_query/
monitoring.rs

1//! Real-time hardware monitoring module
2//!
3//! This module provides continuous monitoring capabilities for hardware metrics,
4//! with configurable update intervals and event-driven notifications.
5
6use crate::{HardwareInfo, ThermalInfo, PowerProfile, Result, HardwareQueryError};
7use async_trait::async_trait;
8use serde::{Deserialize, Serialize};
9use std::sync::Arc;
10use std::time::{Duration, Instant};
11use tokio::sync::{broadcast, RwLock, Mutex};
12use tokio::time::interval;
13
14/// Hardware monitoring configuration
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct MonitoringConfig {
17    /// Update interval for hardware polling
18    pub update_interval: Duration,
19    /// Enable thermal monitoring
20    pub enable_thermal: bool,
21    /// Enable power monitoring
22    pub enable_power: bool,
23    /// Enable general hardware monitoring
24    pub enable_hardware: bool,
25    /// Temperature threshold for thermal alerts (Celsius)
26    pub thermal_threshold: f32,
27    /// Power threshold for power alerts (Watts)
28    pub power_threshold: Option<f32>,
29    /// Enable background monitoring
30    pub background_monitoring: bool,
31}
32
33impl Default for MonitoringConfig {
34    fn default() -> Self {
35        Self {
36            update_interval: Duration::from_secs(5),
37            enable_thermal: true,
38            enable_power: true,
39            enable_hardware: true,
40            thermal_threshold: 80.0,
41            power_threshold: None,
42            background_monitoring: true,
43        }
44    }
45}
46
47/// Hardware monitoring event
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub enum MonitoringEvent {
50    /// Thermal threshold exceeded
51    ThermalAlert {
52        sensor_name: String,
53        temperature: f32,
54        threshold: f32,
55        timestamp: std::time::SystemTime,
56    },
57    /// Power consumption alert
58    PowerAlert {
59        current_power: f32,
60        threshold: f32,
61        timestamp: std::time::SystemTime,
62    },
63    /// Hardware configuration changed
64    HardwareChanged {
65        change_type: HardwareChangeType,
66        description: String,
67        timestamp: std::time::SystemTime,
68    },
69    /// Monitoring error occurred
70    MonitoringError {
71        error: String,
72        timestamp: std::time::SystemTime,
73    },
74    /// Regular update with current metrics
75    MetricsUpdate {
76        hardware_info: Option<HardwareInfo>,
77        thermal_info: Option<ThermalInfo>,
78        power_profile: Option<PowerProfile>,
79        timestamp: std::time::SystemTime,
80    },
81}
82
83/// Type of hardware configuration change
84#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
85pub enum HardwareChangeType {
86    /// Device connected
87    DeviceConnected,
88    /// Device disconnected
89    DeviceDisconnected,
90    /// Driver changed
91    DriverChanged,
92    /// Configuration modified
93    ConfigurationChanged,
94    /// Performance state changed
95    PerformanceStateChanged,
96}
97
98/// Monitoring statistics
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct MonitoringStats {
101    /// Total monitoring events generated
102    pub total_events: u64,
103    /// Thermal alerts generated
104    pub thermal_alerts: u64,
105    /// Power alerts generated
106    pub power_alerts: u64,
107    /// Hardware change events
108    pub hardware_changes: u64,
109    /// Monitoring errors encountered
110    pub errors: u64,
111    /// Monitoring uptime
112    pub uptime: Duration,
113    /// Last update timestamp
114    pub last_update: std::time::SystemTime,
115    /// Average update interval
116    pub average_update_interval: Duration,
117}
118
119/// Hardware monitoring callback trait
120#[async_trait]
121pub trait MonitoringCallback: Send + Sync {
122    async fn on_event(&self, event: &MonitoringEvent);
123}
124
125/// Simple closure-based callback implementation
126pub struct ClosureCallback<F>
127where
128    F: Fn(&MonitoringEvent) + Send + Sync,
129{
130    callback: F,
131}
132
133impl<F> ClosureCallback<F>
134where
135    F: Fn(&MonitoringEvent) + Send + Sync,
136{
137    pub fn new(callback: F) -> Self {
138        Self { callback }
139    }
140}
141
142#[async_trait]
143impl<F> MonitoringCallback for ClosureCallback<F>
144where
145    F: Fn(&MonitoringEvent) + Send + Sync,
146{
147    async fn on_event(&self, event: &MonitoringEvent) {
148        (self.callback)(event);
149    }
150}
151
152/// Real-time hardware monitor
153pub struct HardwareMonitor {
154    config: MonitoringConfig,
155    callbacks: Arc<Mutex<Vec<Box<dyn MonitoringCallback>>>>,
156    event_sender: broadcast::Sender<MonitoringEvent>,
157    stats: Arc<RwLock<MonitoringStats>>,
158    running: Arc<RwLock<bool>>,
159    start_time: Instant,
160    last_hardware_info: Arc<RwLock<Option<HardwareInfo>>>,
161    last_thermal_info: Arc<RwLock<Option<ThermalInfo>>>,
162    last_power_profile: Arc<RwLock<Option<PowerProfile>>>,
163}
164
165impl HardwareMonitor {
166    /// Create a new hardware monitor with default configuration
167    pub fn new() -> Self {
168        Self::with_config(MonitoringConfig::default())
169    }
170
171    /// Create a new hardware monitor with custom configuration
172    pub fn with_config(config: MonitoringConfig) -> Self {
173        let (event_sender, _) = broadcast::channel(1000);
174        
175        Self {
176            config,
177            callbacks: Arc::new(Mutex::new(Vec::new())),
178            event_sender,
179            stats: Arc::new(RwLock::new(MonitoringStats {
180                total_events: 0,
181                thermal_alerts: 0,
182                power_alerts: 0,
183                hardware_changes: 0,
184                errors: 0,
185                uptime: Duration::from_secs(0),
186                last_update: std::time::SystemTime::now(),
187                average_update_interval: Duration::from_secs(0),
188            })),
189            running: Arc::new(RwLock::new(false)),
190            start_time: Instant::now(),
191            last_hardware_info: Arc::new(RwLock::new(None)),
192            last_thermal_info: Arc::new(RwLock::new(None)),
193            last_power_profile: Arc::new(RwLock::new(None)),
194        }
195    }
196
197    /// Add a monitoring callback
198    pub async fn add_callback<T: MonitoringCallback + 'static>(&self, callback: T) {
199        let mut callbacks = self.callbacks.lock().await;
200        callbacks.push(Box::new(callback));
201    }
202
203    /// Add a simple closure-based callback
204    pub async fn on_event<F>(&self, callback: F) 
205    where
206        F: Fn(&MonitoringEvent) + Send + Sync + 'static,
207    {
208        self.add_callback(ClosureCallback::new(callback)).await;
209    }
210
211    /// Add a thermal threshold callback
212    pub async fn on_thermal_threshold<F>(&self, threshold: f32, _callback: F)
213    where
214        F: Fn(&ThermalInfo) + Send + Sync + 'static,
215    {
216        self.on_event(move |event| {
217            if let MonitoringEvent::ThermalAlert { temperature, .. } = event {
218                if *temperature >= threshold {
219                    // This is a simplified version - in practice we'd pass the thermal info
220                    // For now, we'll trigger on any thermal alert above threshold
221                }
222            }
223        }).await;
224    }
225
226    /// Add a power threshold callback
227    pub async fn on_power_threshold<F>(&self, threshold: f32, _callback: F)
228    where
229        F: Fn(&PowerProfile) + Send + Sync + 'static,
230    {
231        self.on_event(move |event| {
232            if let MonitoringEvent::PowerAlert { current_power, .. } = event {
233                if *current_power >= threshold {
234                    // This is a simplified version - in practice we'd pass the power profile
235                }
236            }
237        }).await;
238    }
239
240    /// Subscribe to monitoring events
241    pub fn subscribe(&self) -> broadcast::Receiver<MonitoringEvent> {
242        self.event_sender.subscribe()
243    }
244
245    /// Start monitoring in the background
246    pub async fn start_monitoring(&self) -> Result<()> {
247        {
248            let mut running = self.running.write().await;
249            if *running {
250                return Err(HardwareQueryError::InvalidConfiguration(
251                    "Monitoring is already running".to_string()
252                ));
253            }
254            *running = true;
255        }
256
257        let config = self.config.clone();
258        let callbacks = Arc::clone(&self.callbacks);
259        let event_sender = self.event_sender.clone();
260        let stats = Arc::clone(&self.stats);
261        let running = Arc::clone(&self.running);
262        let last_hardware_info = Arc::clone(&self.last_hardware_info);
263        let last_thermal_info = Arc::clone(&self.last_thermal_info);
264        let last_power_profile = Arc::clone(&self.last_power_profile);
265
266        tokio::spawn(async move {
267            let mut interval = interval(config.update_interval);
268            let mut update_times = Vec::new();
269
270            while *running.read().await {
271                interval.tick().await;
272                let update_start = Instant::now();
273
274                // Query hardware information
275                let mut hardware_info = None;
276                let mut thermal_info = None;
277                let mut power_profile = None;
278                let mut events = Vec::new();
279
280                if config.enable_hardware {
281                    match HardwareInfo::query() {
282                        Ok(info) => {
283                            hardware_info = Some(info);
284                        }
285                        Err(e) => {
286                            events.push(MonitoringEvent::MonitoringError {
287                                error: format!("Failed to query hardware info: {}", e),
288                                timestamp: std::time::SystemTime::now(),
289                            });
290                        }
291                    }
292                }
293
294                if config.enable_thermal {
295                    match ThermalInfo::query() {
296                        Ok(info) => {
297                            // Check for thermal alerts
298                            for sensor in info.sensors() {
299                                if sensor.temperature >= config.thermal_threshold {
300                                    events.push(MonitoringEvent::ThermalAlert {
301                                        sensor_name: sensor.name.clone(),
302                                        temperature: sensor.temperature,
303                                        threshold: config.thermal_threshold,
304                                        timestamp: std::time::SystemTime::now(),
305                                    });
306                                }
307                            }
308                            thermal_info = Some(info);
309                        }
310                        Err(e) => {
311                            events.push(MonitoringEvent::MonitoringError {
312                                error: format!("Failed to query thermal info: {}", e),
313                                timestamp: std::time::SystemTime::now(),
314                            });
315                        }
316                    }
317                }
318
319                if config.enable_power {
320                    match PowerProfile::query() {
321                        Ok(profile) => {
322                            // Check for power alerts
323                            if let (Some(current_power), Some(threshold)) = 
324                                (profile.total_power_draw, config.power_threshold) {
325                                if current_power >= threshold {
326                                    events.push(MonitoringEvent::PowerAlert {
327                                        current_power,
328                                        threshold,
329                                        timestamp: std::time::SystemTime::now(),
330                                    });
331                                }
332                            }
333                            power_profile = Some(profile);
334                        }
335                        Err(e) => {
336                            events.push(MonitoringEvent::MonitoringError {
337                                error: format!("Failed to query power profile: {}", e),
338                                timestamp: std::time::SystemTime::now(),
339                            });
340                        }
341                    }
342                }
343
344                // Generate metrics update event
345                events.push(MonitoringEvent::MetricsUpdate {
346                    hardware_info: hardware_info.clone(),
347                    thermal_info: thermal_info.clone(),
348                    power_profile: power_profile.clone(),
349                    timestamp: std::time::SystemTime::now(),
350                });
351
352                // Update cached information
353                if let Some(info) = hardware_info {
354                    *last_hardware_info.write().await = Some(info);
355                }
356                if let Some(info) = thermal_info {
357                    *last_thermal_info.write().await = Some(info);
358                }
359                if let Some(profile) = power_profile {
360                    *last_power_profile.write().await = Some(profile);
361                }
362
363                // Send events and notify callbacks
364                for event in &events {
365                    // Send to broadcast channel
366                    let _ = event_sender.send(event.clone());
367
368                    // Notify callbacks
369                    let callbacks = callbacks.lock().await;
370                    for callback in callbacks.iter() {
371                        callback.on_event(event).await;
372                    }
373                }
374
375                // Update statistics
376                {
377                    let mut stats = stats.write().await;
378                    stats.total_events += events.len() as u64;
379                    
380                    for event in &events {
381                        match event {
382                            MonitoringEvent::ThermalAlert { .. } => stats.thermal_alerts += 1,
383                            MonitoringEvent::PowerAlert { .. } => stats.power_alerts += 1,
384                            MonitoringEvent::HardwareChanged { .. } => stats.hardware_changes += 1,
385                            MonitoringEvent::MonitoringError { .. } => stats.errors += 1,
386                            _ => {}
387                        }
388                    }
389
390                    stats.last_update = std::time::SystemTime::now();
391                    let update_duration = update_start.elapsed();
392                    update_times.push(update_duration);
393                    
394                    // Keep only last 100 update times for average calculation
395                    if update_times.len() > 100 {
396                        update_times.remove(0);
397                    }
398                    
399                    if !update_times.is_empty() {
400                        let total_time: Duration = update_times.iter().sum();
401                        stats.average_update_interval = total_time / update_times.len() as u32;
402                    }
403                }
404            }
405        });
406
407        Ok(())
408    }
409
410    /// Stop monitoring
411    pub async fn stop_monitoring(&self) {
412        *self.running.write().await = false;
413    }
414
415    /// Check if monitoring is currently running
416    pub async fn is_monitoring(&self) -> bool {
417        *self.running.read().await
418    }
419
420    /// Get current monitoring statistics
421    pub async fn get_stats(&self) -> MonitoringStats {
422        let mut stats = self.stats.read().await.clone();
423        stats.uptime = self.start_time.elapsed();
424        stats
425    }
426
427    /// Get the last cached hardware information
428    pub async fn get_last_hardware_info(&self) -> Option<HardwareInfo> {
429        self.last_hardware_info.read().await.clone()
430    }
431
432    /// Get the last cached thermal information
433    pub async fn get_last_thermal_info(&self) -> Option<ThermalInfo> {
434        self.last_thermal_info.read().await.clone()
435    }
436
437    /// Get the last cached power profile
438    pub async fn get_last_power_profile(&self) -> Option<PowerProfile> {
439        self.last_power_profile.read().await.clone()
440    }
441
442    /// Update monitoring configuration
443    pub async fn update_config(&mut self, new_config: MonitoringConfig) {
444        self.config = new_config;
445    }
446
447    /// Clear all callbacks
448    pub async fn clear_callbacks(&self) {
449        let mut callbacks = self.callbacks.lock().await;
450        callbacks.clear();
451    }
452}
453
454impl Default for HardwareMonitor {
455    fn default() -> Self {
456        Self::new()
457    }
458}