1#[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#[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#[cfg(target_os = "windows")]
31struct CoreThread {
32 handle: JoinHandle<anyhow::Result<()>>,
33 tx: Sender<CoreThreadCommand>,
34}
35
36#[cfg(target_os = "windows")]
38enum CoreThreadCommand {
39 GetTemperature(Sender<anyhow::Result<CoreTemperatureData>>),
40 Close,
41}
42
43#[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 pub fn get_temperatures(&self, msr: &mut IntelMsr) -> anyhow::Result<TemperatureData> {
132 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 let (tx, rx) = unbounded();
141 let mut core_temps = Vec::new();
142
143 for thread in &self.core_threads {
145 thread
146 .tx
147 .send(CoreThreadCommand::GetTemperature(tx.clone()))
148 .ok();
149 }
150 drop(tx);
151
152 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 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#[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 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 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 for (i, temp) in temps.iter().enumerate() {
227 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, core_temp,
242 });
243 }
244 }
245
246 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 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}