Skip to main content

rapl_energy/
lib.rs

1mod constraint;
2mod file_handle;
3#[cfg(feature = "libc")]
4mod libc;
5
6use std::{io, path::Path, str::FromStr, sync::LazyLock};
7
8use indexmap::IndexMap;
9
10use crate::{constraint::Constraint, file_handle::FileHandle};
11
12/// RAPL can exist in either `/sys/class` or `/sys/devices`. Determine the
13/// correct location, with a preference for `/sys/class` if both exist.
14/// Note the even on AMD processors, the path contains `intel-rapl`.
15static PREFIX: LazyLock<&'static str> = LazyLock::new(|| {
16    if Path::new("/sys/class/powercap/intel-rapl").exists() {
17        "/sys/class/powercap/intel-rapl"
18    } else {
19        "/sys/devices/virtual/powercap/intel-rapl"
20    }
21});
22
23/// https://www.kernel.org/doc/html/latest/power/powercap/powercap.html
24#[derive(Debug)]
25pub struct Rapl {
26    pub packages: Vec<Package>,
27}
28
29#[derive(Debug)]
30pub struct Package {
31    handle: FileHandle,
32    pub name: String,
33    pub package_energy_uj: u64,
34    pub max_energy_range_uj: u64,
35    pub constraints: Vec<Constraint>,
36    pub subzones: Vec<Subzone>,
37}
38
39#[derive(Debug)]
40pub struct Subzone {
41    handle: FileHandle,
42    pub name: String,
43    pub energy_uj: u64,
44    pub max_energy_range_uj: u64,
45    pub constraints: Vec<Constraint>,
46}
47
48impl Rapl {
49    pub fn new(with_subzones: bool) -> Option<Self> {
50        let head = Package::new(0, with_subzones)?;
51        let tail = (1..u8::MAX).map_while(|package_id| Package::new(package_id, with_subzones));
52        let mmio = (0..u8::MAX).map_while(|package_id| Package::new_mmio(package_id, with_subzones));
53        let packages = std::iter::once(head).chain(tail).chain(mmio).collect();
54        Some(Self { packages })
55    }
56
57    pub fn elapsed(&self) -> IndexMap<String, f32> {
58        self.packages.iter().flat_map(Package::elapsed).collect()
59    }
60
61    pub fn reset(&mut self) {
62        self.packages.iter_mut().for_each(Package::reset);
63    }
64
65    pub fn iter_subzones(&self) -> impl Iterator<Item = &Subzone> {
66        self.packages.iter().flat_map(Package::iter_subzones)
67    }
68
69    pub fn iter_packages(&self) -> impl Iterator<Item = &Package> {
70        self.packages.iter()
71    }
72
73    pub fn iter_mut_packages(&mut self) -> impl Iterator<Item = &mut Package> {
74        self.packages.iter_mut()
75    }
76
77    pub fn iter_mut_subzones(&mut self) -> impl Iterator<Item = &mut Subzone> {
78        self.packages.iter_mut().flat_map(Package::iter_mut_subzones)
79    }
80
81    pub fn iter_constraints(&self, with_subzones: bool) -> impl Iterator<Item = &Constraint> {
82        self.packages.iter().flat_map(move |p| p.iter_constraints(with_subzones))
83    }
84
85    pub fn iter_mut_constraints(&mut self, with_subzones: bool) -> impl Iterator<Item = &mut Constraint> {
86        self.packages.iter_mut().flat_map(move |p| p.iter_mut_constraints(with_subzones))
87    }
88
89    pub fn reset_power_limits(&mut self, with_subzones: bool) -> Result<(), io::Error> {
90        self.iter_mut_constraints(with_subzones).try_for_each(|c| c.reset_power_limit(None))
91    }
92
93    pub fn reset_time_windows(&mut self, with_subzones: bool) -> Result<(), io::Error> {
94        self.iter_mut_constraints(with_subzones).try_for_each(|c| c.reset_time_window(None))
95    }
96}
97
98impl Package {
99    pub fn new(package_id: u8, with_subzones: bool) -> Option<Self> {
100        let path = format!("{}/intel-rapl:{}", *PREFIX, package_id);
101        let handle = FileHandle::new(&format!("{}/energy_uj", path), false).ok()?;
102
103        let name = required(&path, "name");
104        let max_energy_range_uj = required(&path, "max_energy_range_uj");
105
106        let package_energy_uj = handle.read();
107
108        let constraints = (0..u8::MAX).map_while(|constraint_id| Constraint::new(&path, constraint_id)).collect();
109
110        let subzones = if with_subzones {
111            (0..u8::MAX).map_while(|subzone_id| Subzone::new(package_id, subzone_id)).collect()
112        } else {
113            Vec::with_capacity(0)
114        };
115
116        Some(Self { handle, name, max_energy_range_uj, package_energy_uj, constraints, subzones })
117    }
118
119    pub fn new_mmio(package_id: u8, with_subzones: bool) -> Option<Self> {
120        let path = format!("{}/intel-rapl-mmio:{}", *PREFIX, package_id);
121        let handle = FileHandle::new(&format!("{}/energy_uj", path), false).ok()?;
122
123        let name = required(&path, "name");
124        let max_energy_range_uj = required(&path, "max_energy_range_uj");
125
126        let package_energy_uj = handle.read();
127
128        let constraints = (0..u8::MAX).map_while(|constraint_id| Constraint::new(&path, constraint_id)).collect();
129
130        let subzones = if with_subzones {
131            (0..u8::MAX).map_while(|subzone_id| Subzone::new(package_id, subzone_id)).collect()
132        } else {
133            Vec::with_capacity(0)
134        };
135
136        Some(Self { handle, name, max_energy_range_uj, package_energy_uj, constraints, subzones })
137    }
138
139    pub fn elapsed(&self) -> IndexMap<String, f32> {
140        let mut res = IndexMap::with_capacity(1 + self.subzones.len());
141
142        let package_energy_next = self.handle.read();
143        let package_energy = diff(self.package_energy_uj, package_energy_next, self.max_energy_range_uj);
144        res.insert(format!("RAPL {} (J)", self.name), package_energy);
145
146        let subzone_energy_uj = self.subzones.iter().map(Subzone::elapsed);
147        res.extend(subzone_energy_uj);
148
149        res
150    }
151
152    pub fn reset(&mut self) {
153        self.package_energy_uj = self.handle.read();
154        self.subzones.iter_mut().for_each(Subzone::reset);
155    }
156
157    pub fn iter_subzones(&self) -> impl Iterator<Item = &Subzone> {
158        self.subzones.iter()
159    }
160
161    pub fn iter_mut_subzones(&mut self) -> impl Iterator<Item = &mut Subzone> {
162        self.subzones.iter_mut()
163    }
164
165    pub fn iter_constraints(&self, with_subzones: bool) -> impl Iterator<Item = &Constraint> {
166        self.constraints.iter().chain(
167            with_subzones
168                .then(|| self.subzones.iter().flat_map(Subzone::iter_constraints))
169                .into_iter()
170                .flatten())
171    }
172
173    pub fn iter_mut_constraints(&mut self, with_subzones: bool) -> impl Iterator<Item = &mut Constraint> {
174        self.constraints.iter_mut().chain(
175            with_subzones
176                .then(|| self.subzones.iter_mut().flat_map(Subzone::iter_mut_constraints))
177                .into_iter()
178                .flatten())
179    }
180
181    pub fn reset_power_limits(&mut self, with_subzones: bool) -> Result<(), io::Error> {
182        self.iter_mut_constraints(with_subzones).try_for_each(|c| c.reset_power_limit(None))
183    }
184
185    pub fn reset_time_windows(&mut self, with_subzones: bool) -> Result<(), io::Error> {
186        self.iter_mut_constraints(with_subzones).try_for_each(|c| c.reset_time_window(None))
187    }
188}
189
190impl Subzone {
191    pub fn new(package_id: u8, subzone_id: u8) -> Option<Self> {
192        let package_path = format!("{}/intel-rapl:{}", *PREFIX, package_id);
193        let subzone_path = format!("{}/intel-rapl:{}:{}", package_path, package_id, subzone_id);
194        let handle = FileHandle::new(&format!("{}/energy_uj", subzone_path), false).ok()?;
195
196        let package_name: String = required(&package_path, "name");
197        let subzone_name: String = required(&subzone_path, "name");
198        let name = format!("{}-{}", package_name, subzone_name);
199
200        let max_energy_range_uj = required(&subzone_path, "max_energy_range_uj");
201
202        let energy_uj = handle.read();
203
204        let constraints = (0..u8::MAX).map_while(|constraint_id| Constraint::new(&subzone_path, constraint_id)).collect();
205
206        Some(Self { handle, name, max_energy_range_uj, energy_uj, constraints })
207    }
208
209    pub fn new_mmio(package_id: u8) -> Option<Self> {
210        let path = format!("{}-mmio/intel-rapl-mmio:{}", *PREFIX, package_id);
211        let handle = FileHandle::new(&format!("{}/energy_uj", path), false).ok()?;
212
213        let package_name: String = required(&path, "name");
214        let name = format!("{}-mmio", package_name);
215
216        let max_energy_range_uj = required(&path, "max_energy_range_uj");
217
218        let energy_uj = handle.read();
219
220        Some(Self { handle, name, max_energy_range_uj, energy_uj, constraints: Vec::new() })
221    }
222
223    pub fn elapsed(&self) -> (String, f32) {
224        let energy_next = self.handle.read();
225        let energy = diff(self.energy_uj, energy_next, self.max_energy_range_uj);
226        (format!("RAPL {} (J)", self.name), energy)
227    }
228
229    pub fn reset(&mut self) {
230        self.energy_uj = self.handle.read();
231    }
232
233    pub fn iter_constraints(&self) -> impl Iterator<Item = &Constraint> {
234        self.constraints.iter()
235    }
236
237    pub fn iter_mut_constraints(&mut self) -> impl Iterator<Item = &mut Constraint> {
238        self.constraints.iter_mut()
239    }
240
241    pub fn reset_power_limits(&mut self) -> Result<(), io::Error> {
242        self.iter_mut_constraints().try_for_each(|c| c.reset_power_limit(None))
243    }
244
245    pub fn reset_time_windows(&mut self) -> Result<(), io::Error> {
246        self.iter_mut_constraints().try_for_each(|c| c.reset_time_window(None))
247    }
248}
249
250fn required<T: FromStr>(path: &str, file: &str) -> T where T::Err: std::fmt::Debug {
251    let path = format!("{}/{}", path, file);
252    let handle = FileHandle::new(&path, false).unwrap();
253    handle.read::<T>()
254}
255
256fn diff(prev_uj: u64, next_uj: u64, max_energy_range_uj: u64) -> f32 {
257    let energy_uj = if next_uj >= prev_uj {
258        next_uj - prev_uj
259    } else {
260        // The accumulator overflowed
261        next_uj + (max_energy_range_uj - prev_uj)
262    };
263    energy_uj as f32 / 1e6
264}