hardware_query/
thermal.rs

1use crate::Result;
2use serde::{Deserialize, Serialize};
3use std::time::Duration;
4
5/// Thermal sensor information
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct ThermalSensor {
8    /// Sensor name
9    pub name: String,
10    /// Current temperature in Celsius
11    pub temperature: f32,
12    /// Critical temperature threshold
13    pub critical_temperature: Option<f32>,
14    /// Maximum recorded temperature
15    pub max_temperature: Option<f32>,
16    /// Sensor type (CPU, GPU, System, etc.)
17    pub sensor_type: String,
18    /// Historical temperature readings (last 10 readings)
19    pub temperature_history: Vec<TemperatureReading>,
20}
21
22/// Temperature reading with timestamp
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct TemperatureReading {
25    /// Temperature in Celsius
26    pub temperature: f32,
27    /// Timestamp of the reading
28    pub timestamp: std::time::SystemTime,
29}
30
31/// Fan information
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct FanInfo {
34    /// Fan name
35    pub name: String,
36    /// Current fan speed in RPM
37    pub speed_rpm: u32,
38    /// Maximum fan speed in RPM
39    pub max_speed_rpm: Option<u32>,
40    /// Fan speed percentage (0-100)
41    pub speed_percent: Option<f32>,
42    /// Is fan controllable
43    pub controllable: bool,
44    /// Fan curve settings (if available)
45    pub fan_curve: Option<FanCurve>,
46}
47
48/// Fan curve configuration
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct FanCurve {
51    /// Temperature to fan speed mappings
52    pub curve_points: Vec<CurvePoint>,
53    /// Hysteresis in degrees Celsius
54    pub hysteresis: f32,
55    /// Minimum fan speed percentage
56    pub min_speed_percent: f32,
57    /// Maximum fan speed percentage  
58    pub max_speed_percent: f32,
59}
60
61/// Single point on a fan curve
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct CurvePoint {
64    /// Temperature in Celsius
65    pub temperature: f32,
66    /// Fan speed percentage (0-100)
67    pub speed_percent: f32,
68}
69
70/// Thermal throttling prediction
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct ThrottlingPrediction {
73    /// Will throttling occur
74    pub will_throttle: bool,
75    /// Time until throttling occurs (if applicable)
76    pub time_to_throttle: Option<Duration>,
77    /// Predicted throttling severity
78    pub severity: ThrottlingSeverity,
79    /// Recommended actions to prevent throttling
80    pub recommendations: Vec<String>,
81    /// Confidence level of prediction (0.0 to 1.0)
82    pub confidence: f64,
83}
84
85/// Severity of thermal throttling
86#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
87pub enum ThrottlingSeverity {
88    /// No throttling
89    None,
90    /// Light throttling (< 10% performance loss)
91    Light,
92    /// Moderate throttling (10-25% performance loss)
93    Moderate,
94    /// Heavy throttling (25-50% performance loss)
95    Heavy,
96    /// Severe throttling (> 50% performance loss)
97    Severe,
98}
99
100impl std::fmt::Display for ThrottlingSeverity {
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        match self {
103            ThrottlingSeverity::None => write!(f, "None"),
104            ThrottlingSeverity::Light => write!(f, "Light"),
105            ThrottlingSeverity::Moderate => write!(f, "Moderate"),
106            ThrottlingSeverity::Heavy => write!(f, "Heavy"),
107            ThrottlingSeverity::Severe => write!(f, "Severe"),
108        }
109    }
110}
111
112/// Cooling optimization recommendation
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct CoolingRecommendation {
115    /// Recommendation type
116    pub recommendation_type: CoolingRecommendationType,
117    /// Human-readable description
118    pub description: String,
119    /// Expected temperature reduction in Celsius
120    pub expected_temp_reduction: Option<f32>,
121    /// Implementation difficulty
122    pub difficulty: ImplementationDifficulty,
123    /// Estimated cost category
124    pub cost_category: CostCategory,
125}
126
127/// Type of cooling recommendation
128#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
129pub enum CoolingRecommendationType {
130    /// Fan curve optimization
131    FanCurveOptimization,
132    /// Thermal paste replacement
133    ThermalPasteReplacement,
134    /// Additional case fans
135    AdditionalFans,
136    /// CPU cooler upgrade
137    CPUCoolerUpgrade,
138    /// GPU cooling solution
139    GPUCooling,
140    /// Case ventilation improvement
141    CaseVentilation,
142    /// Undervolting components
143    Undervolting,
144    /// Workload adjustment
145    WorkloadAdjustment,
146    /// Environmental changes
147    EnvironmentalChanges,
148}
149
150/// Implementation difficulty level
151#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
152pub enum ImplementationDifficulty {
153    /// Easy to implement (software changes)
154    Easy,
155    /// Moderate difficulty (minor hardware changes)
156    Moderate,
157    /// Difficult (major hardware changes)
158    Difficult,
159    /// Expert level (professional installation recommended)
160    Expert,
161}
162
163impl std::fmt::Display for ImplementationDifficulty {
164    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165        match self {
166            ImplementationDifficulty::Easy => write!(f, "Easy"),
167            ImplementationDifficulty::Moderate => write!(f, "Moderate"),
168            ImplementationDifficulty::Difficult => write!(f, "Difficult"),
169            ImplementationDifficulty::Expert => write!(f, "Expert"),
170        }
171    }
172}
173
174/// Cost category for recommendations
175#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
176pub enum CostCategory {
177    /// Free (software/settings changes)
178    Free,
179    /// Low cost (< $50)
180    Low,
181    /// Medium cost ($50-200)
182    Medium,
183    /// High cost (> $200)
184    High,
185}
186
187impl std::fmt::Display for CostCategory {
188    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
189        match self {
190            CostCategory::Free => write!(f, "Free"),
191            CostCategory::Low => write!(f, "Low cost"),
192            CostCategory::Medium => write!(f, "Medium cost"),
193            CostCategory::High => write!(f, "High cost"),
194        }
195    }
196}
197
198/// System thermal information
199#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct ThermalInfo {
201    /// Temperature sensors
202    pub sensors: Vec<ThermalSensor>,
203    /// System fans
204    pub fans: Vec<FanInfo>,
205    /// Overall system temperature status
206    pub thermal_status: ThermalStatus,
207    /// Ambient temperature (if available)
208    pub ambient_temperature: Option<f32>,
209    /// Thermal design power (TDP) information
210    pub tdp_info: Option<TDPInfo>,
211}
212
213/// Thermal Design Power information
214#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct TDPInfo {
216    /// CPU TDP in watts
217    pub cpu_tdp: Option<f32>,
218    /// GPU TDP in watts  
219    pub gpu_tdp: Option<f32>,
220    /// System TDP in watts
221    pub system_tdp: Option<f32>,
222    /// Current power consumption vs TDP ratio
223    pub power_ratio: Option<f32>,
224}
225
226/// System thermal status
227#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
228pub enum ThermalStatus {
229    Normal,
230    Warm,
231    Hot,
232    Critical,
233    Unknown,
234}
235
236impl std::fmt::Display for ThermalStatus {
237    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238        match self {
239            ThermalStatus::Normal => write!(f, "Normal"),
240            ThermalStatus::Warm => write!(f, "Warm"),
241            ThermalStatus::Hot => write!(f, "Hot"),
242            ThermalStatus::Critical => write!(f, "Critical"),
243            ThermalStatus::Unknown => write!(f, "Unknown"),
244        }
245    }
246}
247
248impl ThermalInfo {
249    /// Query thermal information
250    pub fn query() -> Result<Self> {
251        let sensors = Self::query_sensors()?;
252        let fans = Self::query_fans()?;
253        let thermal_status = Self::calculate_thermal_status(&sensors);
254        let ambient_temperature = Self::query_ambient_temperature()?;
255        let tdp_info = Self::query_tdp_info()?;
256
257        Ok(Self {
258            sensors,
259            fans,
260            thermal_status,
261            ambient_temperature,
262            tdp_info,
263        })
264    }
265
266    /// Get temperature sensors
267    pub fn sensors(&self) -> &[ThermalSensor] {
268        &self.sensors
269    }
270
271    /// Get system fans
272    pub fn fans(&self) -> &[FanInfo] {
273        &self.fans
274    }
275
276    /// Get thermal status
277    pub fn thermal_status(&self) -> &ThermalStatus {
278        &self.thermal_status
279    }
280
281    /// Get maximum temperature across all sensors
282    pub fn max_temperature(&self) -> Option<f32> {
283        self.sensors
284            .iter()
285            .map(|sensor| sensor.temperature)
286            .fold(None, |acc, temp| match acc {
287                None => Some(temp),
288                Some(max_temp) => Some(max_temp.max(temp)),
289            })
290    }
291
292    /// Get average temperature across all sensors
293    pub fn average_temperature(&self) -> Option<f32> {
294        if self.sensors.is_empty() {
295            None
296        } else {
297            let total: f32 = self.sensors.iter().map(|sensor| sensor.temperature).sum();
298            Some(total / self.sensors.len() as f32)
299        }
300    }
301
302    /// Check if any sensor is at critical temperature
303    pub fn has_critical_temperature(&self) -> bool {
304        self.sensors.iter().any(|sensor| {
305            if let Some(critical) = sensor.critical_temperature {
306                sensor.temperature >= critical
307            } else {
308                sensor.temperature >= 90.0 // Default critical threshold
309            }
310        })
311    }
312
313    /// Get CPU temperature (if available)
314    pub fn cpu_temperature(&self) -> Option<f32> {
315        self.sensors
316            .iter()
317            .find(|sensor| sensor.sensor_type.to_lowercase().contains("cpu"))
318            .map(|sensor| sensor.temperature)
319    }
320
321    /// Get GPU temperature (if available)
322    pub fn gpu_temperature(&self) -> Option<f32> {
323        self.sensors
324            .iter()
325            .find(|sensor| sensor.sensor_type.to_lowercase().contains("gpu"))
326            .map(|sensor| sensor.temperature)
327    }
328
329    /// Predict thermal throttling based on current conditions
330    pub fn predict_thermal_throttling(&self, workload_intensity: f32) -> ThrottlingPrediction {
331        let max_temp = self.max_temperature().unwrap_or(0.0);
332        let cpu_temp = self.cpu_temperature().unwrap_or(0.0);
333        let gpu_temp = self.gpu_temperature().unwrap_or(0.0);
334
335        // Simple prediction algorithm - would be enhanced with ML models
336        let critical_threshold = 90.0;
337        let temp_trend = self.calculate_temperature_trend();
338        
339        // Factor in workload intensity
340        let projected_temp_increase = workload_intensity * 10.0; // Simplified calculation
341        let projected_max_temp = max_temp + projected_temp_increase + temp_trend;
342
343        let will_throttle = projected_max_temp >= critical_threshold;
344        let time_to_throttle = if will_throttle && temp_trend > 0.0 {
345            let temp_diff = critical_threshold - max_temp;
346            let time_seconds = (temp_diff / temp_trend) * 60.0; // Convert to seconds
347            Some(Duration::from_secs(time_seconds.max(0.0) as u64))
348        } else {
349            None
350        };
351
352        let severity = if projected_max_temp >= 95.0 {
353            ThrottlingSeverity::Severe
354        } else if projected_max_temp >= 90.0 {
355            ThrottlingSeverity::Heavy
356        } else if projected_max_temp >= 85.0 {
357            ThrottlingSeverity::Moderate
358        } else if projected_max_temp >= 80.0 {
359            ThrottlingSeverity::Light
360        } else {
361            ThrottlingSeverity::None
362        };
363
364        let mut recommendations = Vec::new();
365        if will_throttle {
366            recommendations.push("Reduce workload intensity".to_string());
367            recommendations.push("Increase fan speeds if possible".to_string());
368            if cpu_temp > gpu_temp {
369                recommendations.push("Focus on CPU cooling optimization".to_string());
370            } else if gpu_temp > cpu_temp {
371                recommendations.push("Focus on GPU cooling optimization".to_string());
372            }
373        }
374
375        let confidence = if temp_trend.abs() > 2.0 { 0.8 } else { 0.6 };
376
377        ThrottlingPrediction {
378            will_throttle,
379            time_to_throttle,
380            severity,
381            recommendations,
382            confidence,
383        }
384    }
385
386    /// Get cooling optimization recommendations
387    pub fn suggest_cooling_optimizations(&self) -> Vec<CoolingRecommendation> {
388        let mut recommendations = Vec::new();
389        let max_temp = self.max_temperature().unwrap_or(0.0);
390
391        if max_temp > 85.0 {
392            // High temperature recommendations
393            recommendations.push(CoolingRecommendation {
394                recommendation_type: CoolingRecommendationType::FanCurveOptimization,
395                description: "Optimize fan curves for better cooling efficiency".to_string(),
396                expected_temp_reduction: Some(3.0),
397                difficulty: ImplementationDifficulty::Easy,
398                cost_category: CostCategory::Free,
399            });
400
401            if max_temp > 90.0 {
402                recommendations.push(CoolingRecommendation {
403                    recommendation_type: CoolingRecommendationType::ThermalPasteReplacement,
404                    description: "Consider replacing thermal paste on CPU/GPU".to_string(),
405                    expected_temp_reduction: Some(5.0),
406                    difficulty: ImplementationDifficulty::Moderate,
407                    cost_category: CostCategory::Low,
408                });
409            }
410        }
411
412        // Check fan utilization
413        let avg_fan_speed = self.fans
414            .iter()
415            .filter_map(|fan| fan.speed_percent)
416            .fold(0.0, |acc, speed| acc + speed) / self.fans.len().max(1) as f32;
417
418        if avg_fan_speed > 80.0 {
419            recommendations.push(CoolingRecommendation {
420                recommendation_type: CoolingRecommendationType::AdditionalFans,
421                description: "Current fans are running at high speeds. Consider adding more case fans".to_string(),
422                expected_temp_reduction: Some(4.0),
423                difficulty: ImplementationDifficulty::Moderate,
424                cost_category: CostCategory::Low,
425            });
426        }
427
428        // CPU specific recommendations
429        if let Some(cpu_temp) = self.cpu_temperature() {
430            if cpu_temp > 85.0 {
431                recommendations.push(CoolingRecommendation {
432                    recommendation_type: CoolingRecommendationType::CPUCoolerUpgrade,
433                    description: "CPU temperatures are high. Consider upgrading CPU cooler".to_string(),
434                    expected_temp_reduction: Some(8.0),
435                    difficulty: ImplementationDifficulty::Moderate,
436                    cost_category: CostCategory::Medium,
437                });
438            }
439        }
440
441        // Environmental recommendations
442        if let Some(ambient) = self.ambient_temperature {
443            if ambient > 30.0 {
444                recommendations.push(CoolingRecommendation {
445                    recommendation_type: CoolingRecommendationType::EnvironmentalChanges,
446                    description: "High ambient temperature detected. Improve room ventilation or use air conditioning".to_string(),
447                    expected_temp_reduction: Some(ambient - 25.0),
448                    difficulty: ImplementationDifficulty::Easy,
449                    cost_category: CostCategory::Free,
450                });
451            }
452        }
453
454        recommendations
455    }
456
457    /// Calculate sustained performance capability considering thermal limits
458    pub fn calculate_sustained_performance(&self) -> f64 {
459        let max_temp = self.max_temperature().unwrap_or(0.0);
460        let critical_temp = 90.0;
461        
462        if max_temp < 70.0 {
463            1.0 // Full performance
464        } else if max_temp < 80.0 {
465            0.95 // Slight performance reduction
466        } else if max_temp < critical_temp {
467            0.85 // Moderate performance reduction
468        } else {
469            0.70 // Significant throttling expected
470        }
471    }
472
473    /// Update temperature history for a sensor
474    pub fn update_sensor_history(&mut self, sensor_name: &str, temperature: f32) {
475        if let Some(sensor) = self.sensors.iter_mut().find(|s| s.name == sensor_name) {
476            sensor.temperature_history.push(TemperatureReading {
477                temperature,
478                timestamp: std::time::SystemTime::now(),
479            });
480
481            // Keep only last 10 readings
482            if sensor.temperature_history.len() > 10 {
483                sensor.temperature_history.remove(0);
484            }
485
486            // Update current temperature
487            sensor.temperature = temperature;
488        }
489    }
490
491    fn calculate_temperature_trend(&self) -> f32 {
492        // Calculate average temperature trend across all sensors
493        let mut total_trend = 0.0;
494        let mut sensor_count = 0;
495
496        for sensor in &self.sensors {
497            if sensor.temperature_history.len() >= 3 {
498                let recent_temps: Vec<f32> = sensor.temperature_history
499                    .iter()
500                    .rev()
501                    .take(3)
502                    .map(|reading| reading.temperature)
503                    .collect();
504
505                // Simple linear trend calculation
506                let trend = (recent_temps[0] - recent_temps[2]) / 2.0;
507                total_trend += trend;
508                sensor_count += 1;
509            }
510        }
511
512        if sensor_count > 0 {
513            total_trend / sensor_count as f32
514        } else {
515            0.0
516        }
517    }
518
519    fn query_sensors() -> Result<Vec<ThermalSensor>> {
520        // Platform-specific implementation would go here
521        // For now, return empty vector
522        Ok(vec![])
523    }
524
525    fn query_fans() -> Result<Vec<FanInfo>> {
526        // Platform-specific implementation would go here
527        // For now, return empty vector
528        Ok(vec![])
529    }
530
531    fn query_ambient_temperature() -> Result<Option<f32>> {
532        // Platform-specific implementation would go here
533        Ok(None)
534    }
535
536    fn query_tdp_info() -> Result<Option<TDPInfo>> {
537        // Platform-specific implementation would go here
538        Ok(None)
539    }
540
541    fn calculate_thermal_status(sensors: &[ThermalSensor]) -> ThermalStatus {
542        if sensors.is_empty() {
543            return ThermalStatus::Unknown;
544        }
545
546        let max_temp = sensors
547            .iter()
548            .map(|sensor| sensor.temperature)
549            .fold(0.0f32, |acc, temp| acc.max(temp));
550
551        if max_temp >= 90.0 {
552            ThermalStatus::Critical
553        } else if max_temp >= 80.0 {
554            ThermalStatus::Hot
555        } else if max_temp >= 70.0 {
556            ThermalStatus::Warm
557        } else {
558            ThermalStatus::Normal
559        }
560    }
561}