1use crate::Result;
2use serde::{Deserialize, Serialize};
3use std::time::Duration;
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct ThermalSensor {
8 pub name: String,
10 pub temperature: f32,
12 pub critical_temperature: Option<f32>,
14 pub max_temperature: Option<f32>,
16 pub sensor_type: String,
18 pub temperature_history: Vec<TemperatureReading>,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct TemperatureReading {
25 pub temperature: f32,
27 pub timestamp: std::time::SystemTime,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct FanInfo {
34 pub name: String,
36 pub speed_rpm: u32,
38 pub max_speed_rpm: Option<u32>,
40 pub speed_percent: Option<f32>,
42 pub controllable: bool,
44 pub fan_curve: Option<FanCurve>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct FanCurve {
51 pub curve_points: Vec<CurvePoint>,
53 pub hysteresis: f32,
55 pub min_speed_percent: f32,
57 pub max_speed_percent: f32,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct CurvePoint {
64 pub temperature: f32,
66 pub speed_percent: f32,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct ThrottlingPrediction {
73 pub will_throttle: bool,
75 pub time_to_throttle: Option<Duration>,
77 pub severity: ThrottlingSeverity,
79 pub recommendations: Vec<String>,
81 pub confidence: f64,
83}
84
85#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
87pub enum ThrottlingSeverity {
88 None,
90 Light,
92 Moderate,
94 Heavy,
96 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#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct CoolingRecommendation {
115 pub recommendation_type: CoolingRecommendationType,
117 pub description: String,
119 pub expected_temp_reduction: Option<f32>,
121 pub difficulty: ImplementationDifficulty,
123 pub cost_category: CostCategory,
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
129pub enum CoolingRecommendationType {
130 FanCurveOptimization,
132 ThermalPasteReplacement,
134 AdditionalFans,
136 CPUCoolerUpgrade,
138 GPUCooling,
140 CaseVentilation,
142 Undervolting,
144 WorkloadAdjustment,
146 EnvironmentalChanges,
148}
149
150#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
152pub enum ImplementationDifficulty {
153 Easy,
155 Moderate,
157 Difficult,
159 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
176pub enum CostCategory {
177 Free,
179 Low,
181 Medium,
183 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#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct ThermalInfo {
201 pub sensors: Vec<ThermalSensor>,
203 pub fans: Vec<FanInfo>,
205 pub thermal_status: ThermalStatus,
207 pub ambient_temperature: Option<f32>,
209 pub tdp_info: Option<TDPInfo>,
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct TDPInfo {
216 pub cpu_tdp: Option<f32>,
218 pub gpu_tdp: Option<f32>,
220 pub system_tdp: Option<f32>,
222 pub power_ratio: Option<f32>,
224}
225
226#[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 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 pub fn sensors(&self) -> &[ThermalSensor] {
268 &self.sensors
269 }
270
271 pub fn fans(&self) -> &[FanInfo] {
273 &self.fans
274 }
275
276 pub fn thermal_status(&self) -> &ThermalStatus {
278 &self.thermal_status
279 }
280
281 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 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 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 }
310 })
311 }
312
313 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 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 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 let critical_threshold = 90.0;
337 let temp_trend = self.calculate_temperature_trend();
338
339 let projected_temp_increase = workload_intensity * 10.0; 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; 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 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 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 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 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 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 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 } else if max_temp < 80.0 {
465 0.95 } else if max_temp < critical_temp {
467 0.85 } else {
469 0.70 }
471 }
472
473 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 if sensor.temperature_history.len() > 10 {
483 sensor.temperature_history.remove(0);
484 }
485
486 sensor.temperature = temperature;
488 }
489 }
490
491 fn calculate_temperature_trend(&self) -> f32 {
492 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 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 Ok(vec![])
523 }
524
525 fn query_fans() -> Result<Vec<FanInfo>> {
526 Ok(vec![])
529 }
530
531 fn query_ambient_temperature() -> Result<Option<f32>> {
532 Ok(None)
534 }
535
536 fn query_tdp_info() -> Result<Option<TDPInfo>> {
537 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}