1use crate::Cpumask;
15use crate::compat;
16use crate::compat::ROOT_PREFIX;
17use crate::misc::read_from_file;
18use anyhow::Result;
19use anyhow::bail;
20use glob::glob;
21use num::clamp;
22use std::collections::BTreeMap;
23use std::fmt;
24use std::path::Path;
25use std::sync::Arc;
26
27#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
28pub struct PerfState {
29 pub cost: usize,
30 pub frequency: usize,
31 pub inefficient: usize,
32 pub performance: usize,
33 pub power: usize,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
37pub struct PerfDomain {
38 pub id: usize,
40 pub span: Cpumask,
42 pub perf_table: BTreeMap<usize, Arc<PerfState>>,
44}
45
46#[derive(Debug, Clone)]
57pub struct EqPerfDomain {
58 pub id: usize,
60 pub perf_doms: Vec<Arc<PerfDomain>>,
62 pub span: Cpumask,
64 pub perf_table: BTreeMap<usize, Arc<PerfState>>,
67}
68
69#[derive(Debug)]
70pub struct EnergyModel {
71 pub perf_doms: BTreeMap<usize, Arc<PerfDomain>>,
73 pub eq_perf_doms: BTreeMap<usize, Arc<EqPerfDomain>>,
75}
76
77impl EnergyModel {
78 pub fn has_energy_model() -> bool {
79 get_pd_paths().is_ok()
80 }
81
82 pub fn new() -> Result<EnergyModel> {
84 let mut perf_doms = BTreeMap::new();
85 let pd_paths = match get_pd_paths() {
86 Ok(pd_paths) => pd_paths,
87 Err(_) => {
88 bail!("Fail to locate the energy model directory");
89 }
90 };
91
92 for (pd_id, pd_path) in pd_paths {
93 let pd = PerfDomain::new(pd_id, pd_path)?;
94 perf_doms.insert(pd.id, pd.into());
95 }
96 let eq_perf_doms = Self::group_perf_doms(&perf_doms);
97
98 Ok(EnergyModel {
99 perf_doms,
100 eq_perf_doms,
101 })
102 }
103
104 fn group_perf_doms(
110 perf_doms: &BTreeMap<usize, Arc<PerfDomain>>,
111 ) -> BTreeMap<usize, Arc<EqPerfDomain>> {
112 let mut eq_perf_doms: Vec<EqPerfDomain> = vec![];
113
114 for pd in perf_doms.values() {
115 match eq_perf_doms
116 .iter_mut()
117 .find(|eq_pd| eq_pd.perf_table == pd.perf_table)
118 {
119 Some(eq_pd) => {
120 eq_pd.span = eq_pd.span.or(&pd.span);
121 eq_pd.perf_doms.push(pd.clone());
122 }
123 None => {
124 eq_perf_doms.push(EqPerfDomain {
125 id: eq_perf_doms.len(),
126 perf_doms: vec![pd.clone()],
127 span: pd.span.clone(),
128 perf_table: pd.perf_table.clone(),
129 });
130 }
131 }
132 }
133
134 eq_perf_doms
135 .into_iter()
136 .map(|eq_pd| (eq_pd.id, eq_pd.into()))
137 .collect()
138 }
139
140 pub fn get_pd_by_cpu_id(&self, cpu_id: usize) -> Option<&PerfDomain> {
141 self.perf_doms
142 .values()
143 .find(|&pd| pd.span.test_cpu(cpu_id))
144 .map(|c| c as _)
145 }
146
147 pub fn perf_total(&self) -> usize {
148 let mut total = 0;
149
150 for pd in self.perf_doms.values() {
151 total += pd.perf_total();
152 }
153
154 total
155 }
156}
157
158impl PerfDomain {
159 pub fn new(id: usize, root: String) -> Result<PerfDomain> {
161 let mut perf_table = BTreeMap::new();
162 let cpulist = std::fs::read_to_string(root.clone() + "/cpus")?;
163 let span = Cpumask::from_cpulist(&cpulist)?;
164
165 for ps_path in get_ps_paths(root)? {
166 let ps = PerfState::new(ps_path)?;
167 perf_table.insert(ps.performance, ps.into());
168 }
169
170 Ok(PerfDomain {
171 id,
172 span,
173 perf_table,
174 })
175 }
176
177 pub fn select_perf_state(&self, util: f32) -> Option<&Arc<PerfState>> {
180 let util = clamp(util, 0.0, 100.0);
181 let (perf_max, _) = self.perf_table.last_key_value()?;
182 let perf_max = *perf_max as f32;
183 let req_perf = (perf_max * (util / 100.0)) as usize;
184 for (perf, ps) in self.perf_table.iter() {
185 if *perf >= req_perf {
186 return Some(ps);
187 }
188 }
189 None
190 }
191
192 pub fn perf_total(&self) -> usize {
193 let (_, ps) = self.perf_table.last_key_value().unwrap();
194 ps.performance * self.span.weight()
195 }
196}
197
198impl PerfState {
199 pub fn new(root: String) -> Result<PerfState> {
201 let cost = read_from_file(Path::new(&(root.clone() + "/cost")))?;
202 let frequency = read_from_file(Path::new(&(root.clone() + "/frequency")))?;
203 let inefficient = read_from_file(Path::new(&(root.clone() + "/inefficient")))?;
204 let performance = read_from_file(Path::new(&(root.clone() + "/performance")))?;
205 let power = read_from_file(Path::new(&(root.clone() + "/power")))?;
206
207 Ok(PerfState {
208 cost,
209 frequency,
210 inefficient,
211 performance,
212 power,
213 })
214 }
215}
216
217impl fmt::Display for EnergyModel {
218 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
219 for pd in self.perf_doms.values() {
220 writeln!(f, "{pd:#}")?;
221 }
222 for eq_pd in self.eq_perf_doms.values() {
223 writeln!(f, "{eq_pd:#}")?;
224 }
225 Ok(())
226 }
227}
228
229impl fmt::Display for PerfDomain {
230 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
231 writeln!(f, "# perf domain: {:#}, cpus: {:#}", self.id, self.span)?;
232 writeln!(f, "cost, frequency, inefficient, performance, power")?;
233 for ps in self.perf_table.values() {
234 writeln!(f, "{ps:#}")?;
235 }
236 Ok(())
237 }
238}
239
240impl fmt::Display for EqPerfDomain {
241 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
242 let pd_ids: Vec<String> = self.perf_doms.iter().map(|pd| pd.id.to_string()).collect();
243 writeln!(
244 f,
245 "# eq perf domain: {:#}, cpus: {:#}, perf domains: {}",
246 self.id,
247 self.span,
248 pd_ids.join(",")
249 )?;
250 writeln!(f, "cost, frequency, inefficient, performance, power")?;
251 for ps in self.perf_table.values() {
252 writeln!(f, "{ps:#}")?;
253 }
254 Ok(())
255 }
256}
257
258impl fmt::Display for PerfState {
259 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
260 write!(
261 f,
262 "{}, {}, {}, {}, {}",
263 self.cost, self.frequency, self.inefficient, self.performance, self.power
264 )?;
265 Ok(())
266 }
267}
268
269fn get_ps_paths(root: String) -> Result<Vec<String>> {
273 let ps_paths = glob(&(root.clone() + "/ps:[0-9]*"))?;
274 let mut ps_vec = vec![];
275 for ps_path in ps_paths.filter_map(Result::ok) {
276 let ps_str = ps_path.to_string_lossy().into_owned();
277 ps_vec.push(ps_str);
278 }
279
280 Ok(ps_vec)
281}
282
283fn get_pd_paths() -> Result<Vec<(usize, String)>> {
284 let prefix = get_em_root()? + "/cpu";
285 let pd_paths = glob(&(prefix.clone() + "[0-9]*"))?;
286
287 let mut pd_vec = vec![];
288 for pd_path in pd_paths.filter_map(Result::ok) {
289 let pd_str = pd_path.to_string_lossy().into_owned();
290 let pd_id: usize = pd_str[prefix.len()..].parse()?;
291 pd_vec.push((pd_id, pd_str));
292 }
293 if pd_vec.is_empty() {
294 bail!("There is no performance domain.");
295 }
296 pd_vec.sort();
297
298 let mut pd_vec2 = vec![];
299 for (id, (_, pd_str)) in pd_vec.into_iter().enumerate() {
300 pd_vec2.push((id, pd_str));
301 }
302
303 Ok(pd_vec2)
304}
305
306fn get_em_root() -> Result<String> {
307 if ROOT_PREFIX.is_empty() {
308 let root = compat::debugfs_mount()?.join("energy_model");
309 Ok(root.display().to_string())
310 } else {
311 let root = format!("{}/sys/kernel/debug/energy_model", *ROOT_PREFIX);
312 Ok(root)
313 }
314}
315
316#[cfg(test)]
320mod tests {
321 use super::*;
322
323 fn perf_table(states: &[(usize, usize)]) -> BTreeMap<usize, Arc<PerfState>> {
324 states
325 .iter()
326 .map(|&(performance, power)| {
327 let ps = PerfState {
328 cost: performance,
329 frequency: performance,
330 inefficient: 0,
331 performance,
332 power,
333 };
334 (performance, ps.into())
335 })
336 .collect()
337 }
338
339 fn perf_dom(id: usize, perf_table: BTreeMap<usize, Arc<PerfState>>) -> Arc<PerfDomain> {
342 PerfDomain {
343 id,
344 span: Cpumask::from_vec(vec![1u64 << id]),
345 perf_table,
346 }
347 .into()
348 }
349
350 #[test]
355 fn test_group_hybrid_perf_doms() {
356 let p_core = perf_table(&[(100, 50)]);
357 let e_core = perf_table(&[(60, 20)]);
358 let lpe_core = perf_table(&[(30, 5)]);
359
360 let mut perf_doms = BTreeMap::new();
361 for id in 0..28 {
362 let table = if id < 8 {
363 p_core.clone()
364 } else if id < 24 {
365 e_core.clone()
366 } else {
367 lpe_core.clone()
368 };
369 perf_doms.insert(id, perf_dom(id, table));
370 }
371
372 let eq_perf_doms = EnergyModel::group_perf_doms(&perf_doms);
373
374 let weights: Vec<usize> = eq_perf_doms.values().map(|e| e.span.weight()).collect();
375 assert_eq!(weights, vec![8, 16, 4]);
376
377 for (&id, eq_pd) in eq_perf_doms.iter() {
378 assert_eq!(eq_pd.id, id);
379 assert_eq!(eq_pd.perf_doms.len(), eq_pd.span.weight());
380
381 let member_ids: Vec<usize> = eq_pd.perf_doms.iter().map(|pd| pd.id).collect();
382 assert!(member_ids.windows(2).all(|ids| ids[0] < ids[1]));
383
384 for pd in eq_pd.perf_doms.iter() {
385 assert_eq!(pd.perf_table, eq_pd.perf_table);
386 assert!(eq_pd.span.test_cpu(pd.id));
387 }
388 }
389 }
390
391 #[test]
394 fn test_group_distinct_perf_doms() {
395 let mut perf_doms = BTreeMap::new();
396 for id in 0..24 {
397 perf_doms.insert(id, perf_dom(id, perf_table(&[(id + 1, id + 1)])));
398 }
399
400 let eq_perf_doms = EnergyModel::group_perf_doms(&perf_doms);
401
402 assert_eq!(eq_perf_doms.len(), 24);
403 for eq_pd in eq_perf_doms.values() {
404 assert_eq!(eq_pd.perf_doms.len(), 1);
405 assert_eq!(eq_pd.span.weight(), 1);
406 }
407 }
408
409 #[test]
412 fn test_group_uniform_perf_doms() {
413 let table = perf_table(&[(100, 50)]);
414 let mut perf_doms = BTreeMap::new();
415 for id in 0..8 {
416 perf_doms.insert(id, perf_dom(id, table.clone()));
417 }
418
419 let eq_perf_doms = EnergyModel::group_perf_doms(&perf_doms);
420
421 assert_eq!(eq_perf_doms.len(), 1);
422 let eq_pd = eq_perf_doms.get(&0).unwrap();
423 assert_eq!(eq_pd.perf_doms.len(), 8);
424 assert_eq!(eq_pd.span.weight(), 8);
425 }
426}