#[cfg(target_os = "windows")]
use std::thread::{self, JoinHandle};
#[cfg(target_os = "windows")]
use crossbeam_channel::{unbounded, Receiver, Sender};
#[cfg(target_os = "linux")]
use crate::temperature::hwmon;
use crate::temperature::sensor::{CoreTemperature, PackageTemperature};
#[cfg(target_os = "windows")]
use crate::{cpu::info::CpuInfo, msr::intel_msr::IntelMsr};
#[derive(Debug)]
pub struct TemperatureData {
pub tj_max: f32,
pub core_temps: Vec<CoreTemperatureData>,
pub package_temp: anyhow::Result<PackageTemperature>,
}
#[derive(Debug, Clone)]
pub struct CoreTemperatureData {
pub logical_id: usize,
pub physical_id: usize,
pub core_temp: CoreTemperature,
}
#[cfg(target_os = "windows")]
struct CoreThread {
handle: JoinHandle<anyhow::Result<()>>,
tx: Sender<CoreThreadCommand>,
}
#[cfg(target_os = "windows")]
enum CoreThreadCommand {
GetTemperature(Sender<anyhow::Result<CoreTemperatureData>>),
Close,
}
#[cfg(target_os = "windows")]
pub struct IntelCpuTemperature {
core_threads: Vec<CoreThread>,
package_sensor: crate::temperature::sensor::TemperatureSensor,
}
#[cfg(target_os = "windows")]
impl IntelCpuTemperature {
pub fn new() -> anyhow::Result<Self> {
let mut core_ids = CpuInfo::get_core_cpu_mapping()
.map(|m| m.iter().filter_map(|c| c.first().copied()).collect())
.unwrap_or_else(|_| vec![0]);
core_ids.sort();
let core_threads = core_ids
.iter()
.enumerate()
.map(|(physical_id, &logical_id)| {
let (tx, rx) = unbounded::<CoreThreadCommand>();
let handle = thread::spawn(move || {
let res = Self::core_thread(physical_id, logical_id, rx);
if let Err(e) = &res {
eprintln!("{:?}", e);
}
res
});
CoreThread { handle, tx }
})
.collect();
let mut msr = IntelMsr::new()?;
Ok(IntelCpuTemperature {
core_threads,
package_sensor: crate::temperature::sensor::TemperatureSensor::new(&mut msr)?,
})
}
fn core_thread(
physical_id: usize,
logical_id: usize,
rx: Receiver<CoreThreadCommand>,
) -> anyhow::Result<()> {
const MAX_TRIES: usize = 5;
affinity::set_thread_affinity([logical_id]).map_err(|e| anyhow::anyhow!("{e}"))?;
let success = (0..MAX_TRIES).any(|_| {
thread::yield_now();
affinity::get_thread_affinity().is_ok_and(|f| f.len() == 1 && f[0] == logical_id)
});
if !success {
anyhow::bail!(
"Failed to set core affinity for core {}, core ids: {:?}",
logical_id,
affinity::get_thread_affinity()
);
}
let mut msr = IntelMsr::new()?;
let sensor = crate::temperature::sensor::TemperatureSensor::new(&mut msr)?;
loop {
match rx.recv()? {
CoreThreadCommand::Close => return Ok(()),
CoreThreadCommand::GetTemperature(sender) => {
match sensor.read_core_temperature(&mut msr) {
Ok(core_temp) => {
let _ = sender.send(Ok(CoreTemperatureData {
physical_id,
logical_id,
core_temp,
}));
}
Err(e) => {
let _ = sender.send(Err(e.context(format!(
"Failed to read core {} temperature",
logical_id,
))));
}
}
}
}
}
}
pub fn get_temperatures(&self, msr: &mut IntelMsr) -> anyhow::Result<TemperatureData> {
let tj_max = if let Ok(tj_max) = self.package_sensor.read_tj_max_from_msr(msr) {
tj_max
} else {
self.package_sensor.tj_max
};
let (tx, rx) = unbounded();
let mut core_temps = Vec::new();
for thread in &self.core_threads {
thread
.tx
.send(CoreThreadCommand::GetTemperature(tx.clone()))
.ok();
}
drop(tx);
for _ in &self.core_threads {
match rx.recv() {
Ok(Ok(data)) => {
core_temps.push(data);
}
Ok(Err(e)) => {
eprintln!("Received error from core thread: {:?}", e);
}
Err(e) => {
eprintln!("Channel communication error: {}", e);
}
}
}
core_temps.sort_by_key(|d| d.physical_id);
let package_temp = self.package_sensor.read_package_temperature(msr);
Ok(TemperatureData {
tj_max,
core_temps,
package_temp,
})
}
}
#[cfg(target_os = "windows")]
impl Drop for IntelCpuTemperature {
fn drop(&mut self) {
for t in self.core_threads.drain(..) {
let _ = t.tx.send(CoreThreadCommand::Close);
let _ = t.handle.join();
}
}
}
#[cfg(target_os = "linux")]
pub struct IntelCpuTemperature {
hwmon_sensor: Option<hwmon::HwmonSensor>,
tj_max: f32,
}
#[cfg(target_os = "linux")]
impl IntelCpuTemperature {
pub fn new() -> anyhow::Result<Self> {
let sensors = hwmon::find_cpu_hwmon_sensors()?;
let hwmon_sensor = sensors
.iter()
.find(|s| s.name.contains("coretemp"))
.cloned()
.or_else(|| sensors.first().cloned());
let tj_max = hwmon::get_tj_max();
Ok(IntelCpuTemperature {
hwmon_sensor,
tj_max,
})
}
pub fn get_temperatures(&self, _msr: &mut ()) -> anyhow::Result<TemperatureData> {
let mut core_temps = Vec::new();
if let Some(ref sensor) = self.hwmon_sensor {
let temps = hwmon::read_hwmon_temperatures(sensor)?;
for (i, temp) in temps.iter().enumerate() {
if temp.id == 1 && temp.name.to_lowercase().contains("package") {
continue;
}
let core_temp = CoreTemperature {
tj_max: self.tj_max,
offset: 0.0,
temperature: temp.temperature,
};
core_temps.push(CoreTemperatureData {
logical_id: i,
physical_id: i / 2, core_temp,
});
}
}
let package_temp = if let Some(ref sensor) = self.hwmon_sensor {
let temps = hwmon::read_hwmon_temperatures(sensor)?;
let pkg_temp = temps
.iter()
.find(|t| t.name.to_lowercase().contains("package"));
if let Some(pt) = pkg_temp {
Ok(PackageTemperature {
tj_max: self.tj_max,
offset: 0.0,
temperature: pt.temperature,
})
} else if let Some(first) = temps.first() {
Ok(PackageTemperature {
tj_max: self.tj_max,
offset: 0.0,
temperature: first.temperature,
})
} else {
Err(anyhow::anyhow!("No package temperature available"))
}
} else {
Err(anyhow::anyhow!("No hwmon sensor found"))
};
Ok(TemperatureData {
tj_max: self.tj_max,
core_temps,
package_temp,
})
}
}