1use crate::compat;
15use crate::compat::ROOT_PREFIX;
16use crate::misc::read_from_file;
17use crate::Cpumask;
18use anyhow::bail;
19use anyhow::Result;
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, 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, 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.iter() {
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 PartialEq for PerfDomain {
199 fn eq(&self, other: &Self) -> bool {
200 self.id == other.id && self.span == other.span && self.perf_table == other.perf_table
201 }
202}
203
204impl PerfState {
205 pub fn new(root: String) -> Result<PerfState> {
207 let cost = read_from_file(Path::new(&(root.clone() + "/cost")))?;
208 let frequency = read_from_file(Path::new(&(root.clone() + "/frequency")))?;
209 let inefficient = read_from_file(Path::new(&(root.clone() + "/inefficient")))?;
210 let performance = read_from_file(Path::new(&(root.clone() + "/performance")))?;
211 let power = read_from_file(Path::new(&(root.clone() + "/power")))?;
212
213 Ok(PerfState {
214 cost,
215 frequency,
216 inefficient,
217 performance,
218 power,
219 })
220 }
221}
222
223impl PartialEq for PerfState {
224 fn eq(&self, other: &Self) -> bool {
225 self.cost == other.cost
226 && self.frequency == other.frequency
227 && self.performance == other.performance
228 && self.power == other.power
229 }
230}
231
232impl fmt::Display for EnergyModel {
233 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
234 for (_, pd) in self.perf_doms.iter() {
235 writeln!(f, "{pd:#}")?;
236 }
237 for (_, eq_pd) in self.eq_perf_doms.iter() {
238 writeln!(f, "{eq_pd:#}")?;
239 }
240 Ok(())
241 }
242}
243
244impl fmt::Display for PerfDomain {
245 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
246 writeln!(f, "# perf domain: {:#}, cpus: {:#}", self.id, self.span)?;
247 writeln!(f, "cost, frequency, inefficient, performance, power")?;
248 for (_, ps) in self.perf_table.iter() {
249 writeln!(f, "{ps:#}")?;
250 }
251 Ok(())
252 }
253}
254
255impl fmt::Display for EqPerfDomain {
256 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
257 let pd_ids: Vec<String> = self.perf_doms.iter().map(|pd| pd.id.to_string()).collect();
258 writeln!(
259 f,
260 "# eq perf domain: {:#}, cpus: {:#}, perf domains: {}",
261 self.id,
262 self.span,
263 pd_ids.join(",")
264 )?;
265 writeln!(f, "cost, frequency, inefficient, performance, power")?;
266 for (_, ps) in self.perf_table.iter() {
267 writeln!(f, "{ps:#}")?;
268 }
269 Ok(())
270 }
271}
272
273impl fmt::Display for PerfState {
274 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
275 write!(
276 f,
277 "{}, {}, {}, {}, {}",
278 self.cost, self.frequency, self.inefficient, self.performance, self.power
279 )?;
280 Ok(())
281 }
282}
283
284fn get_ps_paths(root: String) -> Result<Vec<String>> {
288 let ps_paths = glob(&(root.clone() + "/ps:[0-9]*"))?;
289 let mut ps_vec = vec![];
290 for ps_path in ps_paths.filter_map(Result::ok) {
291 let ps_str = ps_path.to_string_lossy().into_owned();
292 ps_vec.push(ps_str);
293 }
294
295 Ok(ps_vec)
296}
297
298fn get_pd_paths() -> Result<Vec<(usize, String)>> {
299 let prefix = get_em_root()? + "/cpu";
300 let pd_paths = glob(&(prefix.clone() + "[0-9]*"))?;
301
302 let mut pd_vec = vec![];
303 for pd_path in pd_paths.filter_map(Result::ok) {
304 let pd_str = pd_path.to_string_lossy().into_owned();
305 let pd_id: usize = pd_str[prefix.len()..].parse()?;
306 pd_vec.push((pd_id, pd_str));
307 }
308 if pd_vec.is_empty() {
309 bail!("There is no performance domain.");
310 }
311 pd_vec.sort();
312
313 let mut pd_vec2 = vec![];
314 for (id, (_, pd_str)) in pd_vec.into_iter().enumerate() {
315 pd_vec2.push((id, pd_str));
316 }
317
318 Ok(pd_vec2)
319}
320
321fn get_em_root() -> Result<String> {
322 if ROOT_PREFIX.is_empty() {
323 let root = compat::debugfs_mount()?.join("energy_model");
324 Ok(root.display().to_string())
325 } else {
326 let root = format!("{}/sys/kernel/debug/energy_model", *ROOT_PREFIX);
327 Ok(root)
328 }
329}
330
331#[cfg(test)]
335mod tests {
336 use super::*;
337
338 fn perf_table(states: &[(usize, usize)]) -> BTreeMap<usize, Arc<PerfState>> {
339 states
340 .iter()
341 .map(|&(performance, power)| {
342 let ps = PerfState {
343 cost: performance,
344 frequency: performance,
345 inefficient: 0,
346 performance,
347 power,
348 };
349 (performance, ps.into())
350 })
351 .collect()
352 }
353
354 fn perf_dom(id: usize, perf_table: BTreeMap<usize, Arc<PerfState>>) -> Arc<PerfDomain> {
357 PerfDomain {
358 id,
359 span: Cpumask::from_vec(vec![1u64 << id]),
360 perf_table,
361 }
362 .into()
363 }
364
365 #[test]
370 fn test_group_hybrid_perf_doms() {
371 let p_core = perf_table(&[(100, 50)]);
372 let e_core = perf_table(&[(60, 20)]);
373 let lpe_core = perf_table(&[(30, 5)]);
374
375 let mut perf_doms = BTreeMap::new();
376 for id in 0..28 {
377 let table = if id < 8 {
378 p_core.clone()
379 } else if id < 24 {
380 e_core.clone()
381 } else {
382 lpe_core.clone()
383 };
384 perf_doms.insert(id, perf_dom(id, table));
385 }
386
387 let eq_perf_doms = EnergyModel::group_perf_doms(&perf_doms);
388
389 let weights: Vec<usize> = eq_perf_doms.values().map(|e| e.span.weight()).collect();
390 assert_eq!(weights, vec![8, 16, 4]);
391
392 for (&id, eq_pd) in eq_perf_doms.iter() {
393 assert_eq!(eq_pd.id, id);
394 assert_eq!(eq_pd.perf_doms.len(), eq_pd.span.weight());
395
396 let member_ids: Vec<usize> = eq_pd.perf_doms.iter().map(|pd| pd.id).collect();
397 assert!(member_ids.windows(2).all(|ids| ids[0] < ids[1]));
398
399 for pd in eq_pd.perf_doms.iter() {
400 assert_eq!(pd.perf_table, eq_pd.perf_table);
401 assert!(eq_pd.span.test_cpu(pd.id));
402 }
403 }
404 }
405
406 #[test]
409 fn test_group_distinct_perf_doms() {
410 let mut perf_doms = BTreeMap::new();
411 for id in 0..24 {
412 perf_doms.insert(id, perf_dom(id, perf_table(&[(id + 1, id + 1)])));
413 }
414
415 let eq_perf_doms = EnergyModel::group_perf_doms(&perf_doms);
416
417 assert_eq!(eq_perf_doms.len(), 24);
418 for eq_pd in eq_perf_doms.values() {
419 assert_eq!(eq_pd.perf_doms.len(), 1);
420 assert_eq!(eq_pd.span.weight(), 1);
421 }
422 }
423
424 #[test]
427 fn test_group_uniform_perf_doms() {
428 let table = perf_table(&[(100, 50)]);
429 let mut perf_doms = BTreeMap::new();
430 for id in 0..8 {
431 perf_doms.insert(id, perf_dom(id, table.clone()));
432 }
433
434 let eq_perf_doms = EnergyModel::group_perf_doms(&perf_doms);
435
436 assert_eq!(eq_perf_doms.len(), 1);
437 let eq_pd = eq_perf_doms.get(&0).unwrap();
438 assert_eq!(eq_pd.perf_doms.len(), 8);
439 assert_eq!(eq_pd.span.weight(), 8);
440 }
441}