use crate::ProfileEvent;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::Path;
use std::time::{Duration, SystemTime};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Hash, Eq)]
pub enum ThermalSensor {
CpuCore(u32),
CpuPackage,
GpuCore,
GpuMemory,
Motherboard,
Memory,
Storage,
Ambient,
Custom(String),
}
impl std::fmt::Display for ThermalSensor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ThermalSensor::CpuCore(id) => write!(f, "CPU Core {id}"),
ThermalSensor::CpuPackage => write!(f, "CPU Package"),
ThermalSensor::GpuCore => write!(f, "GPU Core"),
ThermalSensor::GpuMemory => write!(f, "GPU Memory"),
ThermalSensor::Motherboard => write!(f, "Motherboard"),
ThermalSensor::Memory => write!(f, "Memory"),
ThermalSensor::Storage => write!(f, "Storage"),
ThermalSensor::Ambient => write!(f, "Ambient"),
ThermalSensor::Custom(name) => write!(f, "{name}"),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum TemperatureUnit {
Celsius,
Fahrenheit,
Kelvin,
}
impl std::fmt::Display for TemperatureUnit {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TemperatureUnit::Celsius => write!(f, "°C"),
TemperatureUnit::Fahrenheit => write!(f, "°F"),
TemperatureUnit::Kelvin => write!(f, "K"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemperatureSample {
pub timestamp: SystemTime,
pub sensor: ThermalSensor,
pub temperature: f64,
pub unit: TemperatureUnit,
pub raw_value: Option<u64>,
pub critical_temp: Option<f64>,
pub max_temp: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ThermalEvent {
ThrottlingStart {
sensor: ThermalSensor,
temperature: f64,
threshold: f64,
},
ThrottlingEnd {
sensor: ThermalSensor,
temperature: f64,
},
CriticalTemperature {
sensor: ThermalSensor,
temperature: f64,
critical_threshold: f64,
},
RapidTemperatureRise {
sensor: ThermalSensor,
rate_per_second: f64,
threshold: f64,
},
ThermalShutdown {
sensor: ThermalSensor,
temperature: f64,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThrottlingInfo {
pub sensor: ThermalSensor,
pub start_time: SystemTime,
pub end_time: Option<SystemTime>,
pub max_temperature: f64,
pub duration: Option<Duration>,
pub performance_impact: f64, pub frequency_reduction: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThermalStats {
pub sensor: ThermalSensor,
pub sample_count: u64,
pub min_temperature: f64,
pub max_temperature: f64,
pub average_temperature: f64,
pub temperature_variance: f64,
pub time_above_threshold: Duration,
pub throttling_events: u64,
pub critical_events: u64,
pub temperature_slope: f64, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThermalConfig {
pub enabled_sensors: Vec<ThermalSensor>,
pub sampling_rate_hz: f64,
pub throttling_threshold: f64,
pub critical_threshold: f64,
pub rapid_rise_threshold: f64, pub enable_cpu_sensors: bool,
pub enable_gpu_sensors: bool,
pub enable_system_sensors: bool,
pub enable_hwmon: bool, pub enable_coretemp: bool, pub enable_k10temp: bool, pub enable_nvidia_ml: bool, pub enable_amd_gpu: bool, pub temperature_unit: TemperatureUnit,
}
impl Default for ThermalConfig {
fn default() -> Self {
Self {
enabled_sensors: vec![
ThermalSensor::CpuPackage,
ThermalSensor::GpuCore,
ThermalSensor::Ambient,
],
sampling_rate_hz: 5.0, throttling_threshold: 85.0, critical_threshold: 95.0, rapid_rise_threshold: 5.0, enable_cpu_sensors: true,
enable_gpu_sensors: true,
enable_system_sensors: true,
enable_hwmon: true,
enable_coretemp: true,
enable_k10temp: true,
enable_nvidia_ml: true,
enable_amd_gpu: true,
temperature_unit: TemperatureUnit::Celsius,
}
}
}
pub struct ThermalProfiler {
config: ThermalConfig,
samples: Vec<TemperatureSample>,
events: Vec<ThermalEvent>,
throttling_sessions: Vec<ThrottlingInfo>,
hwmon_monitor: Option<HwmonMonitor>,
coretemp_monitor: Option<CoretempMonitor>,
k10temp_monitor: Option<K10tempMonitor>,
nvidia_monitor: Option<NvidiaThermalMonitor>,
amd_gpu_monitor: Option<AmdGpuThermalMonitor>,
last_sample_time: Option<SystemTime>,
active_throttling: HashMap<ThermalSensor, ThrottlingInfo>,
}
impl ThermalProfiler {
pub fn new(config: ThermalConfig) -> Result<Self> {
let hwmon_monitor = if config.enable_hwmon {
Some(HwmonMonitor::new()?)
} else {
None
};
let coretemp_monitor = if config.enable_coretemp {
Some(CoretempMonitor::new()?)
} else {
None
};
let k10temp_monitor = if config.enable_k10temp {
Some(K10tempMonitor::new()?)
} else {
None
};
let nvidia_monitor = if config.enable_nvidia_ml {
Some(NvidiaThermalMonitor::new()?)
} else {
None
};
let amd_gpu_monitor = if config.enable_amd_gpu {
Some(AmdGpuThermalMonitor::new()?)
} else {
None
};
Ok(Self {
config,
samples: Vec::new(),
events: Vec::new(),
throttling_sessions: Vec::new(),
hwmon_monitor,
coretemp_monitor,
k10temp_monitor,
nvidia_monitor,
amd_gpu_monitor,
last_sample_time: None,
active_throttling: HashMap::new(),
})
}
pub fn start_monitoring(&mut self) -> Result<()> {
if let Some(hwmon) = &mut self.hwmon_monitor {
hwmon.start()?;
}
if let Some(coretemp) = &mut self.coretemp_monitor {
coretemp.start()?;
}
if let Some(k10temp) = &mut self.k10temp_monitor {
k10temp.start()?;
}
if let Some(nvidia) = &mut self.nvidia_monitor {
nvidia.start()?;
}
if let Some(amd_gpu) = &mut self.amd_gpu_monitor {
amd_gpu.start()?;
}
self.last_sample_time = Some(SystemTime::now());
Ok(())
}
pub fn stop_monitoring(&mut self) -> Result<()> {
if let Some(hwmon) = &mut self.hwmon_monitor {
hwmon.stop()?;
}
if let Some(coretemp) = &mut self.coretemp_monitor {
coretemp.stop()?;
}
if let Some(k10temp) = &mut self.k10temp_monitor {
k10temp.stop()?;
}
if let Some(nvidia) = &mut self.nvidia_monitor {
nvidia.stop()?;
}
if let Some(amd_gpu) = &mut self.amd_gpu_monitor {
amd_gpu.stop()?;
}
for (sensor, mut throttling) in self.active_throttling.drain() {
throttling.end_time = Some(SystemTime::now());
throttling.duration = throttling
.end_time
.and_then(|end| end.duration_since(throttling.start_time).ok());
self.throttling_sessions.push(throttling);
self.events.push(ThermalEvent::ThrottlingEnd {
sensor,
temperature: 0.0, });
}
Ok(())
}
pub fn collect_samples(&mut self) -> Result<Vec<TemperatureSample>> {
let mut new_samples = Vec::new();
let timestamp = SystemTime::now();
if let Some(last_time) = self.last_sample_time {
let elapsed = timestamp
.duration_since(last_time)
.unwrap_or(Duration::ZERO);
let sample_interval = Duration::from_secs_f64(1.0 / self.config.sampling_rate_hz);
if elapsed < sample_interval {
return Ok(new_samples);
}
}
if let Some(hwmon) = &self.hwmon_monitor {
new_samples.extend(hwmon.get_samples(timestamp)?);
}
if let Some(coretemp) = &self.coretemp_monitor {
new_samples.extend(coretemp.get_samples(timestamp)?);
}
if let Some(k10temp) = &self.k10temp_monitor {
new_samples.extend(k10temp.get_samples(timestamp)?);
}
if let Some(nvidia) = &self.nvidia_monitor {
new_samples.extend(nvidia.get_samples(timestamp)?);
}
if let Some(amd_gpu) = &self.amd_gpu_monitor {
new_samples.extend(amd_gpu.get_samples(timestamp)?);
}
new_samples.retain(|sample| self.config.enabled_sensors.contains(&sample.sensor));
for sample in &new_samples {
self.process_thermal_sample(sample)?;
}
self.samples.extend(new_samples.clone());
self.last_sample_time = Some(timestamp);
Ok(new_samples)
}
fn process_thermal_sample(&mut self, sample: &TemperatureSample) -> Result<()> {
if sample.temperature >= self.config.throttling_threshold {
if !self.active_throttling.contains_key(&sample.sensor) {
let throttling = ThrottlingInfo {
sensor: sample.sensor.clone(),
start_time: sample.timestamp,
end_time: None,
max_temperature: sample.temperature,
duration: None,
performance_impact: 0.0,
frequency_reduction: None,
};
self.active_throttling
.insert(sample.sensor.clone(), throttling);
self.events.push(ThermalEvent::ThrottlingStart {
sensor: sample.sensor.clone(),
temperature: sample.temperature,
threshold: self.config.throttling_threshold,
});
} else {
if let Some(throttling) = self.active_throttling.get_mut(&sample.sensor) {
throttling.max_temperature = throttling.max_temperature.max(sample.temperature);
}
}
} else {
if let Some(mut throttling) = self.active_throttling.remove(&sample.sensor) {
throttling.end_time = Some(sample.timestamp);
throttling.duration = throttling
.end_time
.and_then(|end| end.duration_since(throttling.start_time).ok());
self.throttling_sessions.push(throttling);
self.events.push(ThermalEvent::ThrottlingEnd {
sensor: sample.sensor.clone(),
temperature: sample.temperature,
});
}
}
if sample.temperature >= self.config.critical_threshold {
self.events.push(ThermalEvent::CriticalTemperature {
sensor: sample.sensor.clone(),
temperature: sample.temperature,
critical_threshold: self.config.critical_threshold,
});
}
if let Some(previous_sample) = self
.samples
.iter()
.rev()
.find(|s| s.sensor == sample.sensor)
{
let time_diff = sample
.timestamp
.duration_since(previous_sample.timestamp)
.unwrap_or(Duration::from_secs(1))
.as_secs_f64();
if time_diff > 0.0 {
let temp_rate = (sample.temperature - previous_sample.temperature) / time_diff;
if temp_rate >= self.config.rapid_rise_threshold {
self.events.push(ThermalEvent::RapidTemperatureRise {
sensor: sample.sensor.clone(),
rate_per_second: temp_rate,
threshold: self.config.rapid_rise_threshold,
});
}
}
}
Ok(())
}
pub fn calculate_thermal_stats(&self, sensor: &ThermalSensor) -> Option<ThermalStats> {
let sensor_samples: Vec<_> = self
.samples
.iter()
.filter(|s| s.sensor == *sensor)
.collect();
if sensor_samples.is_empty() {
return None;
}
let temperatures: Vec<f64> = sensor_samples.iter().map(|s| s.temperature).collect();
let min_temperature = temperatures.iter().copied().fold(f64::INFINITY, f64::min);
let max_temperature = temperatures
.iter()
.copied()
.fold(f64::NEG_INFINITY, f64::max);
let average_temperature = temperatures.iter().sum::<f64>() / temperatures.len() as f64;
let temperature_variance = temperatures
.iter()
.map(|t| (t - average_temperature).powi(2))
.sum::<f64>()
/ temperatures.len() as f64;
let time_above_threshold = sensor_samples
.iter()
.filter(|s| s.temperature >= self.config.throttling_threshold)
.count() as f64
/ self.config.sampling_rate_hz;
let throttling_events = self
.events
.iter()
.filter(|e| match e {
ThermalEvent::ThrottlingStart { sensor: s, .. } => s == sensor,
_ => false,
})
.count() as u64;
let critical_events = self
.events
.iter()
.filter(|e| match e {
ThermalEvent::CriticalTemperature { sensor: s, .. } => s == sensor,
_ => false,
})
.count() as u64;
let temperature_slope = if sensor_samples.len() > 1 {
let first = sensor_samples
.first()
.expect("sensor_samples should not be empty after length check");
let last = sensor_samples
.last()
.expect("sensor_samples should not be empty after length check");
let time_diff = last
.timestamp
.duration_since(first.timestamp)
.unwrap_or(Duration::from_secs(1))
.as_secs_f64();
if time_diff > 0.0 {
(last.temperature - first.temperature) / time_diff
} else {
0.0
}
} else {
0.0
};
Some(ThermalStats {
sensor: sensor.clone(),
sample_count: sensor_samples.len() as u64,
min_temperature,
max_temperature,
average_temperature,
temperature_variance,
time_above_threshold: Duration::from_secs_f64(time_above_threshold),
throttling_events,
critical_events,
temperature_slope,
})
}
pub fn analyze_thermal_performance_impact(
&self,
events: &[ProfileEvent],
) -> ThermalPerformanceAnalysis {
let mut analysis = ThermalPerformanceAnalysis {
total_throttling_time: Duration::ZERO,
performance_degradation: 0.0,
affected_operations: Vec::new(),
thermal_correlation: HashMap::new(),
recommendations: Vec::new(),
};
analysis.total_throttling_time = self
.throttling_sessions
.iter()
.filter_map(|t| t.duration)
.sum();
for event in events {
for throttling in &self.throttling_sessions {
let event_time = SystemTime::UNIX_EPOCH + Duration::from_micros(event.start_us);
if event_time >= throttling.start_time
&& throttling.end_time.map_or(true, |end| event_time <= end)
{
analysis.affected_operations.push(event.name.clone());
}
}
}
let total_events = events.len() as f64;
let affected_events = analysis.affected_operations.len() as f64;
analysis.performance_degradation = if total_events > 0.0 {
(affected_events / total_events) * 100.0
} else {
0.0
};
if analysis.total_throttling_time.as_secs() > 0 {
analysis
.recommendations
.push("Consider improving cooling system".to_string());
analysis
.recommendations
.push("Reduce workload intensity during high temperature periods".to_string());
}
if analysis.performance_degradation > 10.0 {
analysis
.recommendations
.push("Implement thermal-aware scheduling".to_string());
analysis
.recommendations
.push("Consider hardware upgrades for better thermal management".to_string());
}
analysis
}
pub fn export_csv(&self, path: &str) -> Result<()> {
let mut csv = String::new();
csv.push_str("timestamp,sensor,temperature_c,critical_temp,max_temp\n");
for sample in &self.samples {
let temp_c = match sample.unit {
TemperatureUnit::Celsius => sample.temperature,
TemperatureUnit::Fahrenheit => (sample.temperature - 32.0) * 5.0 / 9.0,
TemperatureUnit::Kelvin => sample.temperature - 273.15,
};
csv.push_str(&format!(
"{},{},{},{},{}\n",
sample
.timestamp
.duration_since(SystemTime::UNIX_EPOCH)
.expect("sample timestamp should be after UNIX_EPOCH")
.as_secs(),
sample.sensor,
temp_c,
sample.critical_temp.unwrap_or(0.0),
sample.max_temp.unwrap_or(0.0),
));
}
fs::write(path, csv)?;
Ok(())
}
pub fn get_events_in_range(&self, start: SystemTime, end: SystemTime) -> Vec<ThermalEvent> {
self.events.clone()
}
pub fn clear_data(&mut self) {
self.samples.clear();
self.events.clear();
self.throttling_sessions.clear();
self.active_throttling.clear();
}
pub fn sample_count(&self) -> usize {
self.samples.len()
}
pub fn event_count(&self) -> usize {
self.events.len()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ThermalPerformanceAnalysis {
pub total_throttling_time: Duration,
pub performance_degradation: f64, pub affected_operations: Vec<String>,
pub thermal_correlation: HashMap<String, f64>,
pub recommendations: Vec<String>,
}
pub struct HwmonMonitor {
enabled: bool,
sensor_paths: HashMap<ThermalSensor, String>,
}
impl HwmonMonitor {
pub fn new() -> Result<Self> {
let mut sensor_paths = HashMap::new();
if let Ok(entries) = fs::read_dir("/sys/class/hwmon") {
for entry in entries.flatten() {
let hwmon_path = entry.path();
if let Ok(name_content) = fs::read_to_string(hwmon_path.join("name")) {
let name = name_content.trim();
match name {
"coretemp" => {
if let Ok(temp_entries) = fs::read_dir(&hwmon_path) {
for temp_entry in temp_entries.flatten() {
let filename =
temp_entry.file_name().to_string_lossy().to_string();
if filename.starts_with("temp") && filename.ends_with("_input")
{
if let Ok(core_id) =
filename[4..filename.len() - 6].parse::<u32>()
{
sensor_paths.insert(
ThermalSensor::CpuCore(core_id),
temp_entry.path().to_string_lossy().to_string(),
);
}
}
}
}
}
"k10temp" => {
sensor_paths.insert(
ThermalSensor::CpuPackage,
hwmon_path.join("temp1_input").to_string_lossy().to_string(),
);
}
_ => {
if hwmon_path.join("temp1_input").exists() {
sensor_paths.insert(
ThermalSensor::Custom(name.to_string()),
hwmon_path.join("temp1_input").to_string_lossy().to_string(),
);
}
}
}
}
}
}
Ok(Self {
enabled: !sensor_paths.is_empty(),
sensor_paths,
})
}
pub fn start(&mut self) -> Result<()> {
Ok(())
}
pub fn stop(&mut self) -> Result<()> {
Ok(())
}
pub fn get_samples(&self, timestamp: SystemTime) -> Result<Vec<TemperatureSample>> {
let mut samples = Vec::new();
if self.enabled {
for (sensor, path) in &self.sensor_paths {
if let Ok(temp_str) = fs::read_to_string(path) {
if let Ok(temp_millic) = temp_str.trim().parse::<f64>() {
let temp_celsius = temp_millic / 1000.0;
samples.push(TemperatureSample {
timestamp,
sensor: sensor.clone(),
temperature: temp_celsius,
unit: TemperatureUnit::Celsius,
raw_value: Some(temp_millic as u64),
critical_temp: None,
max_temp: None,
});
}
}
}
}
Ok(samples)
}
}
pub struct CoretempMonitor {
enabled: bool,
core_count: usize,
}
impl CoretempMonitor {
pub fn new() -> Result<Self> {
let core_count = num_cpus::get();
let enabled = Path::new("/sys/devices/platform/coretemp.0").exists();
Ok(Self {
enabled,
core_count,
})
}
pub fn start(&mut self) -> Result<()> {
Ok(())
}
pub fn stop(&mut self) -> Result<()> {
Ok(())
}
pub fn get_samples(&self, timestamp: SystemTime) -> Result<Vec<TemperatureSample>> {
let mut samples = Vec::new();
if self.enabled {
for core in 0..self.core_count {
samples.push(TemperatureSample {
timestamp,
sensor: ThermalSensor::CpuCore(core as u32),
temperature: 45.0 + (core as f64 * 2.0), unit: TemperatureUnit::Celsius,
raw_value: None,
critical_temp: Some(100.0),
max_temp: Some(90.0),
});
}
}
Ok(samples)
}
}
pub struct K10tempMonitor {
enabled: bool,
}
impl K10tempMonitor {
pub fn new() -> Result<Self> {
let enabled = Path::new("/sys/devices/pci0000:00").exists(); Ok(Self { enabled })
}
pub fn start(&mut self) -> Result<()> {
Ok(())
}
pub fn stop(&mut self) -> Result<()> {
Ok(())
}
pub fn get_samples(&self, timestamp: SystemTime) -> Result<Vec<TemperatureSample>> {
let mut samples = Vec::new();
if self.enabled {
samples.push(TemperatureSample {
timestamp,
sensor: ThermalSensor::CpuPackage,
temperature: 50.0, unit: TemperatureUnit::Celsius,
raw_value: None,
critical_temp: Some(90.0),
max_temp: Some(80.0),
});
}
Ok(samples)
}
}
pub struct NvidiaThermalMonitor {
enabled: bool,
}
impl NvidiaThermalMonitor {
pub fn new() -> Result<Self> {
Ok(Self { enabled: false })
}
pub fn start(&mut self) -> Result<()> {
Ok(())
}
pub fn stop(&mut self) -> Result<()> {
Ok(())
}
pub fn get_samples(&self, timestamp: SystemTime) -> Result<Vec<TemperatureSample>> {
let mut samples = Vec::new();
if self.enabled {
samples.push(TemperatureSample {
timestamp,
sensor: ThermalSensor::GpuCore,
temperature: 65.0, unit: TemperatureUnit::Celsius,
raw_value: None,
critical_temp: Some(95.0),
max_temp: Some(85.0),
});
}
Ok(samples)
}
}
pub struct AmdGpuThermalMonitor {
enabled: bool,
}
impl AmdGpuThermalMonitor {
pub fn new() -> Result<Self> {
Ok(Self { enabled: false })
}
pub fn start(&mut self) -> Result<()> {
Ok(())
}
pub fn stop(&mut self) -> Result<()> {
Ok(())
}
pub fn get_samples(&self, timestamp: SystemTime) -> Result<Vec<TemperatureSample>> {
let mut samples = Vec::new();
if self.enabled {
samples.push(TemperatureSample {
timestamp,
sensor: ThermalSensor::GpuCore,
temperature: 70.0, unit: TemperatureUnit::Celsius,
raw_value: None,
critical_temp: Some(100.0),
max_temp: Some(90.0),
});
}
Ok(samples)
}
}
pub fn create_thermal_profiler() -> Result<ThermalProfiler> {
ThermalProfiler::new(ThermalConfig::default())
}
pub fn create_thermal_profiler_with_config(config: ThermalConfig) -> Result<ThermalProfiler> {
ThermalProfiler::new(config)
}
pub fn convert_temperature(temp: f64, from: TemperatureUnit, to: TemperatureUnit) -> f64 {
if from == to {
return temp;
}
let celsius = match from {
TemperatureUnit::Celsius => temp,
TemperatureUnit::Fahrenheit => (temp - 32.0) * 5.0 / 9.0,
TemperatureUnit::Kelvin => temp - 273.15,
};
match to {
TemperatureUnit::Celsius => celsius,
TemperatureUnit::Fahrenheit => celsius * 9.0 / 5.0 + 32.0,
TemperatureUnit::Kelvin => celsius + 273.15,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_thermal_config_creation() {
let config = ThermalConfig::default();
assert!(config.enabled_sensors.contains(&ThermalSensor::CpuPackage));
assert!(config.enabled_sensors.contains(&ThermalSensor::GpuCore));
assert_eq!(config.sampling_rate_hz, 5.0);
assert_eq!(config.throttling_threshold, 85.0);
}
#[test]
fn test_temperature_sample_creation() {
let sample = TemperatureSample {
timestamp: SystemTime::now(),
sensor: ThermalSensor::CpuCore(0),
temperature: 65.5,
unit: TemperatureUnit::Celsius,
raw_value: Some(65500),
critical_temp: Some(100.0),
max_temp: Some(90.0),
};
assert_eq!(sample.temperature, 65.5);
assert_eq!(sample.unit, TemperatureUnit::Celsius);
assert_eq!(sample.sensor, ThermalSensor::CpuCore(0));
}
#[test]
fn test_thermal_profiler_creation() {
let config = ThermalConfig::default();
let profiler = ThermalProfiler::new(config);
assert!(profiler.is_ok());
}
#[test]
fn test_temperature_conversion() {
assert_eq!(
convert_temperature(0.0, TemperatureUnit::Celsius, TemperatureUnit::Fahrenheit),
32.0
);
assert_eq!(
convert_temperature(100.0, TemperatureUnit::Celsius, TemperatureUnit::Fahrenheit),
212.0
);
assert_eq!(
convert_temperature(32.0, TemperatureUnit::Fahrenheit, TemperatureUnit::Celsius),
0.0
);
assert_eq!(
convert_temperature(212.0, TemperatureUnit::Fahrenheit, TemperatureUnit::Celsius),
100.0
);
assert_eq!(
convert_temperature(0.0, TemperatureUnit::Celsius, TemperatureUnit::Kelvin),
273.15
);
assert_eq!(
convert_temperature(100.0, TemperatureUnit::Celsius, TemperatureUnit::Kelvin),
373.15
);
assert_eq!(
convert_temperature(25.0, TemperatureUnit::Celsius, TemperatureUnit::Celsius),
25.0
);
}
#[test]
fn test_thermal_sensor_display() {
assert_eq!(ThermalSensor::CpuCore(0).to_string(), "CPU Core 0");
assert_eq!(ThermalSensor::CpuPackage.to_string(), "CPU Package");
assert_eq!(ThermalSensor::GpuCore.to_string(), "GPU Core");
assert_eq!(
ThermalSensor::Custom("Custom".to_string()).to_string(),
"Custom"
);
}
#[test]
fn test_temperature_unit_display() {
assert_eq!(TemperatureUnit::Celsius.to_string(), "°C");
assert_eq!(TemperatureUnit::Fahrenheit.to_string(), "°F");
assert_eq!(TemperatureUnit::Kelvin.to_string(), "K");
}
#[test]
fn test_thermal_stats_calculation() {
let mut profiler = ThermalProfiler::new(ThermalConfig::default()).unwrap();
profiler.samples = vec![
TemperatureSample {
timestamp: SystemTime::now(),
sensor: ThermalSensor::CpuCore(0),
temperature: 50.0,
unit: TemperatureUnit::Celsius,
raw_value: None,
critical_temp: None,
max_temp: None,
},
TemperatureSample {
timestamp: SystemTime::now(),
sensor: ThermalSensor::CpuCore(0),
temperature: 60.0,
unit: TemperatureUnit::Celsius,
raw_value: None,
critical_temp: None,
max_temp: None,
},
TemperatureSample {
timestamp: SystemTime::now(),
sensor: ThermalSensor::CpuCore(0),
temperature: 70.0,
unit: TemperatureUnit::Celsius,
raw_value: None,
critical_temp: None,
max_temp: None,
},
];
let stats = profiler
.calculate_thermal_stats(&ThermalSensor::CpuCore(0))
.unwrap();
assert_eq!(stats.sample_count, 3);
assert_eq!(stats.min_temperature, 50.0);
assert_eq!(stats.max_temperature, 70.0);
assert_eq!(stats.average_temperature, 60.0);
}
#[test]
fn test_hwmon_monitor_creation() {
let monitor = HwmonMonitor::new();
assert!(monitor.is_ok());
}
#[test]
fn test_coretemp_monitor_creation() {
let monitor = CoretempMonitor::new();
assert!(monitor.is_ok());
}
#[test]
fn test_thermal_event_matching() {
let event = ThermalEvent::ThrottlingStart {
sensor: ThermalSensor::CpuCore(0),
temperature: 90.0,
threshold: 85.0,
};
match event {
ThermalEvent::ThrottlingStart {
sensor,
temperature,
threshold,
} => {
assert_eq!(sensor, ThermalSensor::CpuCore(0));
assert_eq!(temperature, 90.0);
assert_eq!(threshold, 85.0);
}
_ => panic!("Wrong event type"),
}
}
#[test]
fn test_thermal_performance_analysis() {
let profiler = ThermalProfiler::new(ThermalConfig::default()).unwrap();
let events = vec![ProfileEvent {
name: "test_op".to_string(),
category: "test".to_string(),
start_us: 0,
duration_us: 1000,
thread_id: 0,
operation_count: Some(1),
flops: Some(100),
bytes_transferred: Some(1024),
stack_trace: Some("test trace".to_string()),
}];
let analysis = profiler.analyze_thermal_performance_impact(&events);
assert_eq!(analysis.total_throttling_time, Duration::ZERO);
assert_eq!(analysis.performance_degradation, 0.0);
assert!(analysis.affected_operations.is_empty());
}
}