cpu-temp 0.1.0

An Intel CPU temperature monitoring library for Windows and Linux using MSR access
Documentation
/// Intel CPU temperature reading implementation
#[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};

/// Data structure for holding temperature information
#[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,
}

/// Windows-specific core thread structure
#[cfg(target_os = "windows")]
struct CoreThread {
    handle: JoinHandle<anyhow::Result<()>>,
    tx: Sender<CoreThreadCommand>,
}

/// Windows-specific core thread command
#[cfg(target_os = "windows")]
enum CoreThreadCommand {
    GetTemperature(Sender<anyhow::Result<CoreTemperatureData>>),
    Close,
}

/// Windows Intel CPU temperature monitor
#[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> {
        // Read TjMax value
        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);

        // Read package temperature
        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();
        }
    }
}

/// Linux Intel CPU temperature monitor (uses hwmon)
#[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()?;

        // First try to find coretemp, then fallback to any available sensor
        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)?;

            // Map hwmon temperatures to core temps
            // temp1 is usually package temperature in coretemp
            for (i, temp) in temps.iter().enumerate() {
                // Skip package temp for core temps list
                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, // Approximate mapping
                    core_temp,
                });
            }
        }

        // Read package temperature
        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() {
                // Fallback: use first temp as package temp
                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,
        })
    }
}