use crate::Result;
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThermalSensor {
pub name: String,
pub temperature: f32,
pub critical_temperature: Option<f32>,
pub max_temperature: Option<f32>,
pub sensor_type: String,
pub temperature_history: Vec<TemperatureReading>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemperatureReading {
pub temperature: f32,
pub timestamp: std::time::SystemTime,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FanInfo {
pub name: String,
pub speed_rpm: u32,
pub max_speed_rpm: Option<u32>,
pub speed_percent: Option<f32>,
pub controllable: bool,
pub fan_curve: Option<FanCurve>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FanCurve {
pub curve_points: Vec<CurvePoint>,
pub hysteresis: f32,
pub min_speed_percent: f32,
pub max_speed_percent: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CurvePoint {
pub temperature: f32,
pub speed_percent: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThrottlingPrediction {
pub will_throttle: bool,
pub time_to_throttle: Option<Duration>,
pub severity: ThrottlingSeverity,
pub recommendations: Vec<String>,
pub confidence: f64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ThrottlingSeverity {
None,
Light,
Moderate,
Heavy,
Severe,
}
impl std::fmt::Display for ThrottlingSeverity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ThrottlingSeverity::None => write!(f, "None"),
ThrottlingSeverity::Light => write!(f, "Light"),
ThrottlingSeverity::Moderate => write!(f, "Moderate"),
ThrottlingSeverity::Heavy => write!(f, "Heavy"),
ThrottlingSeverity::Severe => write!(f, "Severe"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CoolingRecommendation {
pub recommendation_type: CoolingRecommendationType,
pub description: String,
pub expected_temp_reduction: Option<f32>,
pub difficulty: ImplementationDifficulty,
pub cost_category: CostCategory,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum CoolingRecommendationType {
FanCurveOptimization,
ThermalPasteReplacement,
AdditionalFans,
CPUCoolerUpgrade,
GPUCooling,
CaseVentilation,
Undervolting,
WorkloadAdjustment,
EnvironmentalChanges,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ImplementationDifficulty {
Easy,
Moderate,
Difficult,
Expert,
}
impl std::fmt::Display for ImplementationDifficulty {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ImplementationDifficulty::Easy => write!(f, "Easy"),
ImplementationDifficulty::Moderate => write!(f, "Moderate"),
ImplementationDifficulty::Difficult => write!(f, "Difficult"),
ImplementationDifficulty::Expert => write!(f, "Expert"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum CostCategory {
Free,
Low,
Medium,
High,
}
impl std::fmt::Display for CostCategory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CostCategory::Free => write!(f, "Free"),
CostCategory::Low => write!(f, "Low cost"),
CostCategory::Medium => write!(f, "Medium cost"),
CostCategory::High => write!(f, "High cost"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThermalInfo {
pub sensors: Vec<ThermalSensor>,
pub fans: Vec<FanInfo>,
pub thermal_status: ThermalStatus,
pub ambient_temperature: Option<f32>,
pub tdp_info: Option<TDPInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TDPInfo {
pub cpu_tdp: Option<f32>,
pub gpu_tdp: Option<f32>,
pub system_tdp: Option<f32>,
pub power_ratio: Option<f32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ThermalStatus {
Normal,
Warm,
Hot,
Critical,
Unknown,
}
impl std::fmt::Display for ThermalStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ThermalStatus::Normal => write!(f, "Normal"),
ThermalStatus::Warm => write!(f, "Warm"),
ThermalStatus::Hot => write!(f, "Hot"),
ThermalStatus::Critical => write!(f, "Critical"),
ThermalStatus::Unknown => write!(f, "Unknown"),
}
}
}
impl ThermalInfo {
pub fn query() -> Result<Self> {
let sensors = Self::query_sensors()?;
let fans = Self::query_fans()?;
let thermal_status = Self::calculate_thermal_status(&sensors);
let ambient_temperature = Self::query_ambient_temperature()?;
let tdp_info = Self::query_tdp_info()?;
Ok(Self {
sensors,
fans,
thermal_status,
ambient_temperature,
tdp_info,
})
}
pub fn sensors(&self) -> &[ThermalSensor] {
&self.sensors
}
pub fn fans(&self) -> &[FanInfo] {
&self.fans
}
pub fn thermal_status(&self) -> &ThermalStatus {
&self.thermal_status
}
pub fn max_temperature(&self) -> Option<f32> {
self.sensors
.iter()
.map(|sensor| sensor.temperature)
.fold(None, |acc, temp| match acc {
None => Some(temp),
Some(max_temp) => Some(max_temp.max(temp)),
})
}
pub fn average_temperature(&self) -> Option<f32> {
if self.sensors.is_empty() {
None
} else {
let total: f32 = self.sensors.iter().map(|sensor| sensor.temperature).sum();
Some(total / self.sensors.len() as f32)
}
}
pub fn has_critical_temperature(&self) -> bool {
self.sensors.iter().any(|sensor| {
if let Some(critical) = sensor.critical_temperature {
sensor.temperature >= critical
} else {
sensor.temperature >= 90.0 }
})
}
pub fn cpu_temperature(&self) -> Option<f32> {
self.sensors
.iter()
.find(|sensor| sensor.sensor_type.to_lowercase().contains("cpu"))
.map(|sensor| sensor.temperature)
}
pub fn gpu_temperature(&self) -> Option<f32> {
self.sensors
.iter()
.find(|sensor| sensor.sensor_type.to_lowercase().contains("gpu"))
.map(|sensor| sensor.temperature)
}
pub fn predict_thermal_throttling(&self, workload_intensity: f32) -> ThrottlingPrediction {
let max_temp = self.max_temperature().unwrap_or(0.0);
let cpu_temp = self.cpu_temperature().unwrap_or(0.0);
let gpu_temp = self.gpu_temperature().unwrap_or(0.0);
let critical_threshold = 90.0;
let temp_trend = self.calculate_temperature_trend();
let projected_temp_increase = workload_intensity * 10.0; let projected_max_temp = max_temp + projected_temp_increase + temp_trend;
let will_throttle = projected_max_temp >= critical_threshold;
let time_to_throttle = if will_throttle && temp_trend > 0.0 {
let temp_diff = critical_threshold - max_temp;
let time_seconds = (temp_diff / temp_trend) * 60.0; Some(Duration::from_secs(time_seconds.max(0.0) as u64))
} else {
None
};
let severity = if projected_max_temp >= 95.0 {
ThrottlingSeverity::Severe
} else if projected_max_temp >= 90.0 {
ThrottlingSeverity::Heavy
} else if projected_max_temp >= 85.0 {
ThrottlingSeverity::Moderate
} else if projected_max_temp >= 80.0 {
ThrottlingSeverity::Light
} else {
ThrottlingSeverity::None
};
let mut recommendations = Vec::new();
if will_throttle {
recommendations.push("Reduce workload intensity".to_string());
recommendations.push("Increase fan speeds if possible".to_string());
if cpu_temp > gpu_temp {
recommendations.push("Focus on CPU cooling optimization".to_string());
} else if gpu_temp > cpu_temp {
recommendations.push("Focus on GPU cooling optimization".to_string());
}
}
let confidence = if temp_trend.abs() > 2.0 { 0.8 } else { 0.6 };
ThrottlingPrediction {
will_throttle,
time_to_throttle,
severity,
recommendations,
confidence,
}
}
pub fn suggest_cooling_optimizations(&self) -> Vec<CoolingRecommendation> {
let mut recommendations = Vec::new();
let max_temp = self.max_temperature().unwrap_or(0.0);
if max_temp > 85.0 {
recommendations.push(CoolingRecommendation {
recommendation_type: CoolingRecommendationType::FanCurveOptimization,
description: "Optimize fan curves for better cooling efficiency".to_string(),
expected_temp_reduction: Some(3.0),
difficulty: ImplementationDifficulty::Easy,
cost_category: CostCategory::Free,
});
if max_temp > 90.0 {
recommendations.push(CoolingRecommendation {
recommendation_type: CoolingRecommendationType::ThermalPasteReplacement,
description: "Consider replacing thermal paste on CPU/GPU".to_string(),
expected_temp_reduction: Some(5.0),
difficulty: ImplementationDifficulty::Moderate,
cost_category: CostCategory::Low,
});
}
}
let avg_fan_speed = self.fans
.iter()
.filter_map(|fan| fan.speed_percent)
.fold(0.0, |acc, speed| acc + speed) / self.fans.len().max(1) as f32;
if avg_fan_speed > 80.0 {
recommendations.push(CoolingRecommendation {
recommendation_type: CoolingRecommendationType::AdditionalFans,
description: "Current fans are running at high speeds. Consider adding more case fans".to_string(),
expected_temp_reduction: Some(4.0),
difficulty: ImplementationDifficulty::Moderate,
cost_category: CostCategory::Low,
});
}
if let Some(cpu_temp) = self.cpu_temperature() {
if cpu_temp > 85.0 {
recommendations.push(CoolingRecommendation {
recommendation_type: CoolingRecommendationType::CPUCoolerUpgrade,
description: "CPU temperatures are high. Consider upgrading CPU cooler".to_string(),
expected_temp_reduction: Some(8.0),
difficulty: ImplementationDifficulty::Moderate,
cost_category: CostCategory::Medium,
});
}
}
if let Some(ambient) = self.ambient_temperature {
if ambient > 30.0 {
recommendations.push(CoolingRecommendation {
recommendation_type: CoolingRecommendationType::EnvironmentalChanges,
description: "High ambient temperature detected. Improve room ventilation or use air conditioning".to_string(),
expected_temp_reduction: Some(ambient - 25.0),
difficulty: ImplementationDifficulty::Easy,
cost_category: CostCategory::Free,
});
}
}
recommendations
}
pub fn calculate_sustained_performance(&self) -> f64 {
let max_temp = self.max_temperature().unwrap_or(0.0);
let critical_temp = 90.0;
if max_temp < 70.0 {
1.0 } else if max_temp < 80.0 {
0.95 } else if max_temp < critical_temp {
0.85 } else {
0.70 }
}
pub fn update_sensor_history(&mut self, sensor_name: &str, temperature: f32) {
if let Some(sensor) = self.sensors.iter_mut().find(|s| s.name == sensor_name) {
sensor.temperature_history.push(TemperatureReading {
temperature,
timestamp: std::time::SystemTime::now(),
});
if sensor.temperature_history.len() > 10 {
sensor.temperature_history.remove(0);
}
sensor.temperature = temperature;
}
}
fn calculate_temperature_trend(&self) -> f32 {
let mut total_trend = 0.0;
let mut sensor_count = 0;
for sensor in &self.sensors {
if sensor.temperature_history.len() >= 3 {
let recent_temps: Vec<f32> = sensor.temperature_history
.iter()
.rev()
.take(3)
.map(|reading| reading.temperature)
.collect();
let trend = (recent_temps[0] - recent_temps[2]) / 2.0;
total_trend += trend;
sensor_count += 1;
}
}
if sensor_count > 0 {
total_trend / sensor_count as f32
} else {
0.0
}
}
fn query_sensors() -> Result<Vec<ThermalSensor>> {
Ok(vec![])
}
fn query_fans() -> Result<Vec<FanInfo>> {
Ok(vec![])
}
fn query_ambient_temperature() -> Result<Option<f32>> {
Ok(None)
}
fn query_tdp_info() -> Result<Option<TDPInfo>> {
Ok(None)
}
fn calculate_thermal_status(sensors: &[ThermalSensor]) -> ThermalStatus {
if sensors.is_empty() {
return ThermalStatus::Unknown;
}
let max_temp = sensors
.iter()
.map(|sensor| sensor.temperature)
.fold(0.0f32, |acc, temp| acc.max(temp));
if max_temp >= 90.0 {
ThermalStatus::Critical
} else if max_temp >= 80.0 {
ThermalStatus::Hot
} else if max_temp >= 70.0 {
ThermalStatus::Warm
} else {
ThermalStatus::Normal
}
}
}