1const AMD_TIME_UNIT_MASK: u64 = 0xF0000;
2const AMD_ENERGY_UNIT_MASK: u64 = 0x1F00;
3const AMD_POWER_UNIT_MASK: u64 = 0xF;
4
5const MAX_CPUS: u32 = 1024;
6
7use std::fs::{File, OpenOptions};
8use std::io::{Read, Seek, SeekFrom};
9use std::mem::size_of;
10use std::str;
11use std::sync::Mutex;
12use std::thread::sleep;
13use std::time::Duration;
14use thiserror::Error;
15
16#[repr(u64)]
17enum MsrValue {
18 PowerUnit = 0xC0010299,
19 CoreEnergy = 0xC001029A,
20 PackageEnergy = 0xC001029B,
21}
22
23#[derive(Error, Debug)]
24#[non_exhaustive]
25pub enum Error {
26 #[error("Permission denied when trying to open msr, are you running as root?")]
27 PermissionDenied,
28 #[error("Core with id not found")]
29 CoreNotFound,
30 #[error("IO error when trying to open msr: {0}")]
31 IO(#[source] std::io::Error),
32 #[error("No cores detected")]
33 NoCores,
34 #[error("Invalid package data")]
35 InvalidPackage,
36}
37
38impl From<std::io::Error> for Error {
39 fn from(err: std::io::Error) -> Self {
40 match err.kind() {
41 std::io::ErrorKind::PermissionDenied => Self::PermissionDenied,
42 std::io::ErrorKind::NotFound => Self::CoreNotFound,
43 _ => Error::IO(err),
44 }
45 }
46}
47
48struct Core {
49 handle: Mutex<File>,
50 package: u32,
51}
52
53impl Core {
54 pub fn open(cpu_id: u32) -> Result<Self, Error> {
55 let mut data = [0; 4];
56 let mut package_handle = OpenOptions::new().read(true).open(&format!(
57 "/sys/devices/system/cpu/cpu{}/topology/physical_package_id",
58 cpu_id
59 ))?;
60 package_handle.read(&mut data)?;
61 let package: u32 = str::from_utf8(&data)
62 .map_err(|_| Error::InvalidPackage)?
63 .trim_end_matches('\u{0}')
64 .trim()
65 .parse()
66 .map_err(|_| Error::InvalidPackage)?;
67
68 let handle = OpenOptions::new()
69 .read(true)
70 .write(false)
71 .open(&format!("/dev/cpu/{}/msr", cpu_id))?;
72
73 Ok(Core {
74 handle: Mutex::new(handle),
75 package,
76 })
77 }
78
79 pub fn read(&self, value: MsrValue) -> Result<u64, Error> {
80 let mut handle = self.handle.lock().unwrap();
81 handle.seek(SeekFrom::Start(value as u64))?;
82
83 let mut data = [0; size_of::<u64>()];
84 handle.read(&mut data)?;
85 Ok(u64::from_le_bytes(data))
86 }
87}
88
89#[derive(Debug, Clone)]
90struct CorePower {
91 core_power: f64,
92 package_power: f64,
93 package: u32,
94}
95
96#[derive(Debug, Clone)]
97pub struct CpuPower {
98 cores: Vec<CorePower>,
99}
100
101impl CpuPower {
102 pub fn cores<'a>(&'a self) -> impl Iterator<Item = f64> + 'a {
104 self.cores.iter().map(|core| core.core_power)
105 }
106
107 pub fn packages<'a>(&'a self) -> impl Iterator<Item = f64> + 'a {
109 let mut last_package = u32::max_value();
110
111 let mut packages = Vec::new();
112
113 for core in self.cores.iter() {
114 if core.package != last_package {
115 last_package = core.package;
116 packages.push(core.package_power)
117 }
118 }
119
120 packages.into_iter()
121 }
122}
123
124#[derive(Debug)]
125struct PowerUnits {
126 time_unit: f64,
127 energy_unit: f64,
128 power_unit: f64,
129}
130
131pub struct CpuInfo {
132 cores: Vec<Core>,
133 units: PowerUnits,
134}
135
136impl CpuInfo {
159 pub fn new() -> Result<Self, Error> {
160 let mut cores = Vec::with_capacity(8);
161
162 for i in 0..MAX_CPUS {
163 match Core::open(i) {
164 Ok(core) => cores.push(core),
165 Err(Error::CoreNotFound) => break,
166 Err(e) => return Err(e),
167 }
168 }
169
170 if cores.is_empty() {
171 return Err(Error::NoCores);
172 }
173
174 let units = cores[0].read(MsrValue::PowerUnit)?;
175 let time_unit = (units & AMD_TIME_UNIT_MASK) >> 16;
176 let energy_unit = (units & AMD_ENERGY_UNIT_MASK) >> 8;
177 let power_unit = units & AMD_POWER_UNIT_MASK;
178
179 let time_unit = 0.5f64.powi(time_unit as i32);
180 let energy_unit = 0.5f64.powi(energy_unit as i32);
181 let power_unit = 0.5f64.powi(power_unit as i32);
182
183 let units = PowerUnits {
184 time_unit,
185 energy_unit,
186 power_unit,
187 };
188
189 Ok(CpuInfo { cores, units })
190 }
191
192 pub fn read(&self) -> Result<CpuPower, Error> {
196 let start = self.read_raw()?;
197 sleep(Duration::from_millis(10));
198 let end = self.read_raw()?;
199
200 let cores = start
201 .into_iter()
202 .zip(end.into_iter())
203 .map(|(start, end)| CorePower {
204 core_power: (end.core_power - start.core_power) * 100.0,
205 package_power: (end.package_power - start.package_power) * 100.0,
206 package: start.package,
207 })
208 .collect();
209
210 Ok(CpuPower { cores })
211 }
212
213 fn read_raw(&self) -> Result<Vec<CorePower>, Error> {
214 self.cores
215 .iter()
216 .map(|core| {
217 let core_power = core.read(MsrValue::CoreEnergy)? as f64 * self.units.energy_unit;
218 let package_power =
219 core.read(MsrValue::PackageEnergy)? as f64 * self.units.energy_unit;
220 Ok(CorePower {
221 core_power,
222 package_power,
223 package: core.package,
224 })
225 })
226 .collect()
227 }
228}