scx_utils/
energy_model.rs1use 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)]
47pub struct EnergyModel {
48 pub perf_doms: BTreeMap<usize, Arc<PerfDomain>>,
50}
51
52impl EnergyModel {
53 pub fn has_energy_model() -> bool {
54 get_pd_paths().is_ok()
55 }
56
57 pub fn new() -> Result<EnergyModel> {
59 let mut perf_doms = BTreeMap::new();
60 let pd_paths = match get_pd_paths() {
61 Ok(pd_paths) => pd_paths,
62 Err(_) => {
63 bail!("Fail to locate the energy model directory");
64 }
65 };
66
67 for (pd_id, pd_path) in pd_paths {
68 let pd = PerfDomain::new(pd_id, pd_path).unwrap();
69 perf_doms.insert(pd.id, pd.into());
70 }
71
72 Ok(EnergyModel { perf_doms })
73 }
74
75 pub fn get_pd_by_cpu_id(&self, cpu_id: usize) -> Option<&PerfDomain> {
76 self.perf_doms
77 .values()
78 .find(|&pd| pd.span.test_cpu(cpu_id))
79 .map(|c| c as _)
80 }
81
82 pub fn perf_total(&self) -> usize {
83 let mut total = 0;
84
85 for (_, pd) in self.perf_doms.iter() {
86 total += pd.perf_total();
87 }
88
89 total
90 }
91}
92
93impl PerfDomain {
94 pub fn new(id: usize, root: String) -> Result<PerfDomain> {
96 let mut perf_table = BTreeMap::new();
97 let cpulist = std::fs::read_to_string(root.clone() + "/cpus")?;
98 let span = Cpumask::from_cpulist(&cpulist)?;
99
100 for ps_path in get_ps_paths(root).unwrap() {
101 let ps = PerfState::new(ps_path).unwrap();
102 perf_table.insert(ps.performance, ps.into());
103 }
104
105 Ok(PerfDomain {
106 id,
107 span,
108 perf_table,
109 })
110 }
111
112 pub fn select_perf_state(&self, util: f32) -> Option<&Arc<PerfState>> {
115 let util = clamp(util, 0.0, 100.0);
116 let (perf_max, _) = self.perf_table.last_key_value().unwrap();
117 let perf_max = *perf_max as f32;
118 let req_perf = (perf_max * (util / 100.0)) as usize;
119 for (perf, ps) in self.perf_table.iter() {
120 if *perf >= req_perf {
121 return Some(ps);
122 }
123 }
124 None
125 }
126
127 pub fn perf_total(&self) -> usize {
128 let (_, ps) = self.perf_table.last_key_value().unwrap();
129 ps.performance * self.span.weight()
130 }
131}
132
133impl PartialEq for PerfDomain {
134 fn eq(&self, other: &Self) -> bool {
135 self.id == other.id && self.span == other.span && self.perf_table == other.perf_table
136 }
137}
138
139impl PerfState {
140 pub fn new(root: String) -> Result<PerfState> {
142 let cost = read_from_file(Path::new(&(root.clone() + "/cost")))?;
143 let frequency = read_from_file(Path::new(&(root.clone() + "/frequency")))?;
144 let inefficient = read_from_file(Path::new(&(root.clone() + "/inefficient")))?;
145 let performance = read_from_file(Path::new(&(root.clone() + "/performance")))?;
146 let power = read_from_file(Path::new(&(root.clone() + "/power")))?;
147
148 Ok(PerfState {
149 cost,
150 frequency,
151 inefficient,
152 performance,
153 power,
154 })
155 }
156}
157
158impl PartialEq for PerfState {
159 fn eq(&self, other: &Self) -> bool {
160 self.cost == other.cost
161 && self.frequency == other.frequency
162 && self.performance == other.performance
163 && self.power == other.power
164 }
165}
166
167impl fmt::Display for EnergyModel {
168 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
169 for (_, pd) in self.perf_doms.iter() {
170 writeln!(f, "{pd:#}")?;
171 }
172 Ok(())
173 }
174}
175
176impl fmt::Display for PerfDomain {
177 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
178 writeln!(f, "# perf domain: {:#}, cpus: {:#}", self.id, self.span)?;
179 writeln!(f, "cost, frequency, inefficient, performance, power")?;
180 for (_, ps) in self.perf_table.iter() {
181 writeln!(f, "{ps:#}")?;
182 }
183 Ok(())
184 }
185}
186
187impl fmt::Display for PerfState {
188 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
189 write!(
190 f,
191 "{}, {}, {}, {}, {}",
192 self.cost, self.frequency, self.inefficient, self.performance, self.power
193 )?;
194 Ok(())
195 }
196}
197
198fn get_ps_paths(root: String) -> Result<Vec<String>> {
202 let ps_paths = glob(&(root.clone() + "/ps:[0-9]*"))?;
203 let mut ps_vec = vec![];
204 for ps_path in ps_paths.filter_map(Result::ok) {
205 let ps_str = ps_path.into_os_string().into_string().unwrap();
206 ps_vec.push(ps_str);
207 }
208
209 Ok(ps_vec)
210}
211
212fn get_pd_paths() -> Result<Vec<(usize, String)>> {
213 let prefix = get_em_root().unwrap() + "/cpu";
214 let pd_paths = glob(&(prefix.clone() + "[0-9]*"))?;
215
216 let mut pd_vec = vec![];
217 for pd_path in pd_paths.filter_map(Result::ok) {
218 let pd_str = pd_path.into_os_string().into_string().unwrap();
219 let pd_id: usize = pd_str[prefix.len()..].parse().unwrap();
220 pd_vec.push((pd_id, pd_str));
221 }
222 if pd_vec.is_empty() {
223 bail!("There is no performance domain.");
224 }
225 pd_vec.sort();
226
227 let mut pd_vec2 = vec![];
228 for (id, (_, pd_str)) in pd_vec.into_iter().enumerate() {
229 pd_vec2.push((id, pd_str));
230 }
231
232 Ok(pd_vec2)
233}
234
235fn get_em_root() -> Result<String> {
236 if ROOT_PREFIX.is_empty() {
237 let root = compat::debugfs_mount().unwrap().join("energy_model");
238 Ok(root.display().to_string())
239 } else {
240 let root = format!("{}/sys/kernel/debug/energy_model", *ROOT_PREFIX);
241 Ok(root)
242 }
243}