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
12static 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#[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 next_uj + (max_energy_range_uj - prev_uj)
262 };
263 energy_uj as f32 / 1e6
264}