Skip to main content

cpu_temp/cpu/
intel.rs

1/// Intel CPU temperature reading implementation
2#[cfg(target_os = "windows")]
3use std::thread::{self, JoinHandle};
4
5#[cfg(target_os = "windows")]
6use crossbeam_channel::{unbounded, Receiver, Sender};
7
8#[cfg(target_os = "linux")]
9use crate::temperature::hwmon;
10use crate::temperature::sensor::{CoreTemperature, PackageTemperature};
11#[cfg(target_os = "windows")]
12use crate::{cpu::info::CpuInfo, msr::intel_msr::IntelMsr};
13
14/// Data structure for holding temperature information
15#[derive(Debug)]
16pub struct TemperatureData {
17    pub tj_max: f32,
18    pub core_temps: Vec<CoreTemperatureData>,
19    pub package_temp: anyhow::Result<PackageTemperature>,
20}
21
22#[derive(Debug, Clone)]
23pub struct CoreTemperatureData {
24    pub logical_id: usize,
25    pub physical_id: usize,
26    pub core_temp: CoreTemperature,
27}
28
29/// Windows-specific core thread structure
30#[cfg(target_os = "windows")]
31struct CoreThread {
32    handle: JoinHandle<anyhow::Result<()>>,
33    tx: Sender<CoreThreadCommand>,
34}
35
36/// Windows-specific core thread command
37#[cfg(target_os = "windows")]
38enum CoreThreadCommand {
39    GetTemperature(Sender<anyhow::Result<CoreTemperatureData>>),
40    Close,
41}
42
43/// Windows Intel CPU temperature monitor
44#[cfg(target_os = "windows")]
45pub struct IntelCpuTemperature {
46    core_threads: Vec<CoreThread>,
47    package_sensor: crate::temperature::sensor::TemperatureSensor,
48}
49
50#[cfg(target_os = "windows")]
51impl IntelCpuTemperature {
52    pub fn new() -> anyhow::Result<Self> {
53        let mut core_ids = CpuInfo::get_core_cpu_mapping()
54            .map(|m| m.iter().filter_map(|c| c.first().copied()).collect())
55            .unwrap_or_else(|_| vec![0]);
56
57        core_ids.sort();
58
59        let core_threads = core_ids
60            .iter()
61            .enumerate()
62            .map(|(physical_id, &logical_id)| {
63                let (tx, rx) = unbounded::<CoreThreadCommand>();
64                let handle = thread::spawn(move || {
65                    let res = Self::core_thread(physical_id, logical_id, rx);
66                    if let Err(e) = &res {
67                        eprintln!("{:?}", e);
68                    }
69                    res
70                });
71                CoreThread { handle, tx }
72            })
73            .collect();
74
75        let mut msr = IntelMsr::new()?;
76
77        Ok(IntelCpuTemperature {
78            core_threads,
79            package_sensor: crate::temperature::sensor::TemperatureSensor::new(&mut msr)?,
80        })
81    }
82
83    fn core_thread(
84        physical_id: usize,
85        logical_id: usize,
86        rx: Receiver<CoreThreadCommand>,
87    ) -> anyhow::Result<()> {
88        const MAX_TRIES: usize = 5;
89        affinity::set_thread_affinity([logical_id]).map_err(|e| anyhow::anyhow!("{e}"))?;
90        let success = (0..MAX_TRIES).any(|_| {
91            thread::yield_now();
92            affinity::get_thread_affinity().is_ok_and(|f| f.len() == 1 && f[0] == logical_id)
93        });
94
95        if !success {
96            anyhow::bail!(
97                "Failed to set core affinity for core {}, core ids: {:?}",
98                logical_id,
99                affinity::get_thread_affinity()
100            );
101        }
102
103        let mut msr = IntelMsr::new()?;
104        let sensor = crate::temperature::sensor::TemperatureSensor::new(&mut msr)?;
105
106        loop {
107            match rx.recv()? {
108                CoreThreadCommand::Close => return Ok(()),
109                CoreThreadCommand::GetTemperature(sender) => {
110                    match sensor.read_core_temperature(&mut msr) {
111                        Ok(core_temp) => {
112                            let _ = sender.send(Ok(CoreTemperatureData {
113                                physical_id,
114                                logical_id,
115                                core_temp,
116                            }));
117                        }
118                        Err(e) => {
119                            let _ = sender.send(Err(e.context(format!(
120                                "Failed to read core {} temperature",
121                                logical_id,
122                            ))));
123                        }
124                    }
125                }
126            }
127        }
128    }
129
130    /// 获取所有温度传感器
131    pub fn get_temperatures(&self, msr: &mut IntelMsr) -> anyhow::Result<TemperatureData> {
132        // Read TjMax value
133        let tj_max = if let Ok(tj_max) = self.package_sensor.read_tj_max_from_msr(msr) {
134            tj_max
135        } else {
136            self.package_sensor.tj_max
137        };
138
139        // 创建一个通道用于收集各个核心的温度结果
140        let (tx, rx) = unbounded();
141        let mut core_temps = Vec::new();
142
143        // 向所有核心线程发送读取指令
144        for thread in &self.core_threads {
145            thread
146                .tx
147                .send(CoreThreadCommand::GetTemperature(tx.clone()))
148                .ok();
149        }
150        drop(tx);
151
152        // 等待并处理所有核心的结果
153        for _ in &self.core_threads {
154            match rx.recv() {
155                Ok(Ok(data)) => {
156                    core_temps.push(data);
157                }
158                Ok(Err(e)) => {
159                    eprintln!("Received error from core thread: {:?}", e);
160                }
161                Err(e) => {
162                    eprintln!("Channel communication error: {}", e);
163                }
164            }
165        }
166
167        core_temps.sort_by_key(|d| d.physical_id);
168
169        // Read package temperature
170        let package_temp = self.package_sensor.read_package_temperature(msr);
171
172        Ok(TemperatureData {
173            tj_max,
174            core_temps,
175            package_temp,
176        })
177    }
178}
179
180#[cfg(target_os = "windows")]
181impl Drop for IntelCpuTemperature {
182    fn drop(&mut self) {
183        for t in self.core_threads.drain(..) {
184            let _ = t.tx.send(CoreThreadCommand::Close);
185            let _ = t.handle.join();
186        }
187    }
188}
189
190/// Linux Intel CPU temperature monitor (uses hwmon)
191#[cfg(target_os = "linux")]
192pub struct IntelCpuTemperature {
193    hwmon_sensor: Option<hwmon::HwmonSensor>,
194    tj_max: f32,
195}
196
197#[cfg(target_os = "linux")]
198impl IntelCpuTemperature {
199    pub fn new() -> anyhow::Result<Self> {
200        let sensors = hwmon::find_cpu_hwmon_sensors()?;
201
202        // First try to find coretemp, then fallback to any available sensor
203        let hwmon_sensor = sensors
204            .iter()
205            .find(|s| s.name.contains("coretemp"))
206            .cloned()
207            .or_else(|| sensors.first().cloned());
208
209        let tj_max = hwmon::get_tj_max();
210
211        Ok(IntelCpuTemperature {
212            hwmon_sensor,
213            tj_max,
214        })
215    }
216
217    /// 获取所有温度传感器
218    pub fn get_temperatures(&self, _msr: &mut ()) -> anyhow::Result<TemperatureData> {
219        let mut core_temps = Vec::new();
220
221        if let Some(ref sensor) = self.hwmon_sensor {
222            let temps = hwmon::read_hwmon_temperatures(sensor)?;
223
224            // Map hwmon temperatures to core temps
225            // temp1 is usually package temperature in coretemp
226            for (i, temp) in temps.iter().enumerate() {
227                // Skip package temp for core temps list
228                if temp.id == 1 && temp.name.to_lowercase().contains("package") {
229                    continue;
230                }
231
232                let core_temp = CoreTemperature {
233                    tj_max: self.tj_max,
234                    offset: 0.0,
235                    temperature: temp.temperature,
236                };
237
238                core_temps.push(CoreTemperatureData {
239                    logical_id: i,
240                    physical_id: i / 2, // Approximate mapping
241                    core_temp,
242                });
243            }
244        }
245
246        // Read package temperature
247        let package_temp = if let Some(ref sensor) = self.hwmon_sensor {
248            let temps = hwmon::read_hwmon_temperatures(sensor)?;
249            let pkg_temp = temps
250                .iter()
251                .find(|t| t.name.to_lowercase().contains("package"));
252
253            if let Some(pt) = pkg_temp {
254                Ok(PackageTemperature {
255                    tj_max: self.tj_max,
256                    offset: 0.0,
257                    temperature: pt.temperature,
258                })
259            } else if let Some(first) = temps.first() {
260                // Fallback: use first temp as package temp
261                Ok(PackageTemperature {
262                    tj_max: self.tj_max,
263                    offset: 0.0,
264                    temperature: first.temperature,
265                })
266            } else {
267                Err(anyhow::anyhow!("No package temperature available"))
268            }
269        } else {
270            Err(anyhow::anyhow!("No hwmon sensor found"))
271        };
272
273        Ok(TemperatureData {
274            tj_max: self.tj_max,
275            core_temps,
276            package_temp,
277        })
278    }
279}