Skip to content

Commit c016769

Browse files
author
Changwoo Min
committed
scx_utils: Add EnergyModel (energy_model.rs).
Schedulers need to know the energy model of a system to make energy-aware scheduling decisions. The EnergyModel (energy_model.rs) represents the energy model of a system. It loads the energy model data from debugfs (typically at /sys/kernel/debug/energy_model), so the kernel should support not only the energy model (CONFIG_ENERGY_MODEL) but also debugfs. A system's energy model (EnergyModel) consists of one or more performance domains (PerfDomain). A group of CPUs belonging to the same performance domain is frequency-scaled altogether (i.e., the same frequency domain), so it has the same performance-power characteristics. The performance-power attributes of a performance domain are represented by a collection of performance states (PerfState). Each performance state includes 1) CPU frequency (kHz), 2) the cost of the transition, 3) performance scaled by 1024 against the highest performance of the fastest CPU, 4) power consumption (mW), and 5) whether this state is power-inefficient or not. Signed-off-by: Changwoo Min <[email protected]>
1 parent 3e64e1e commit c016769

File tree

2 files changed

+206
-0
lines changed

2 files changed

+206
-0
lines changed

rust/scx_utils/src/energy_model.rs

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
//
3+
// Copyright (c) 2025 Valve Corporation.
4+
// Author: Changwoo Min <[email protected]>
5+
6+
// This software may be used and distributed according to the terms of the
7+
// GNU General Public License version 2.
8+
9+
//! # SCX Energy Model
10+
//!
11+
//! A crate that allows schedulers to inspect and model the host's energy model,
12+
//! which is loaded from debugfs.
13+
14+
use crate::compat;
15+
use crate::misc::read_file_usize;
16+
use crate::Cpumask;
17+
use anyhow::bail;
18+
use anyhow::Result;
19+
use glob::glob;
20+
use sscanf::sscanf;
21+
use std::collections::BTreeMap;
22+
use std::fmt;
23+
use std::path::Path;
24+
use std::sync::Arc;
25+
26+
#[derive(Debug)]
27+
pub struct PerfState {
28+
pub cost: usize,
29+
pub frequency: usize,
30+
pub inefficient: usize,
31+
pub performance: usize,
32+
pub power: usize,
33+
}
34+
35+
#[derive(Debug)]
36+
pub struct PerfDomain {
37+
/// Monotonically increasing unique id.
38+
pub id: usize,
39+
/// Cpumask of all CPUs in this performance domain.
40+
pub span: Cpumask,
41+
/// Table of performance states indexed by performance.
42+
pub perf_table: BTreeMap<usize, Arc<PerfState>>,
43+
}
44+
45+
#[derive(Debug)]
46+
pub struct EnergyModel {
47+
/// Performance domains indexed by domain id
48+
pub perf_doms: BTreeMap<usize, Arc<PerfDomain>>,
49+
}
50+
51+
impl EnergyModel {
52+
/// Build a complete EnergyModel
53+
pub fn new() -> Result<EnergyModel> {
54+
let mut perf_doms = BTreeMap::new();
55+
let pd_paths = match get_pd_paths() {
56+
Ok(pd_paths) => pd_paths,
57+
Err(_) => {
58+
bail!("Fail to locate the energy model directory");
59+
}
60+
};
61+
62+
for (pd_id, pd_path) in pd_paths {
63+
let pd = PerfDomain::new(pd_id, pd_path).unwrap();
64+
perf_doms.insert(pd.id, pd.into());
65+
}
66+
67+
Ok(EnergyModel { perf_doms })
68+
}
69+
}
70+
71+
impl PerfDomain {
72+
/// Build a PerfDomain
73+
pub fn new(id: usize, root: String) -> Result<PerfDomain> {
74+
let mut perf_table = BTreeMap::new();
75+
let span = read_cpumask(root.clone() + "/cpus").unwrap();
76+
for ps_path in get_ps_paths(root).unwrap() {
77+
let ps = PerfState::new(ps_path).unwrap();
78+
perf_table.insert(ps.performance, ps.into());
79+
}
80+
81+
Ok(PerfDomain {
82+
id,
83+
span,
84+
perf_table,
85+
})
86+
}
87+
}
88+
89+
impl PerfState {
90+
/// Build a PerfState
91+
pub fn new(root: String) -> Result<PerfState> {
92+
let cost = read_file_usize(Path::new(&(root.clone() + "/cost"))).unwrap();
93+
let frequency = read_file_usize(Path::new(&(root.clone() + "/frequency"))).unwrap();
94+
let inefficient = read_file_usize(Path::new(&(root.clone() + "/inefficient"))).unwrap();
95+
let performance = read_file_usize(Path::new(&(root.clone() + "/performance"))).unwrap();
96+
let power = read_file_usize(Path::new(&(root.clone() + "/power"))).unwrap();
97+
98+
Ok(PerfState {
99+
cost,
100+
frequency,
101+
inefficient,
102+
performance,
103+
power,
104+
})
105+
}
106+
}
107+
108+
impl fmt::Display for EnergyModel {
109+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
110+
for (_, pd) in self.perf_doms.iter() {
111+
write!(f, "{:#}\n", pd)?;
112+
}
113+
Ok(())
114+
}
115+
}
116+
117+
impl fmt::Display for PerfDomain {
118+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
119+
write!(f, "# perf domain: {:#}, cpus: {:#}\n", self.id, self.span)?;
120+
write!(f, "cost, frequency, inefficient, performance, power\n")?;
121+
for (_, ps) in self.perf_table.iter() {
122+
write!(f, "{:#}\n", ps)?;
123+
}
124+
Ok(())
125+
}
126+
}
127+
128+
impl fmt::Display for PerfState {
129+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
130+
write!(
131+
f,
132+
"{}, {}, {}, {}, {}",
133+
self.cost, self.frequency, self.inefficient, self.performance, self.power
134+
)?;
135+
Ok(())
136+
}
137+
}
138+
139+
/*********************************************************
140+
* Helper structs/functions for creating the EnergyModel *
141+
*********************************************************/
142+
fn get_ps_paths(root: String) -> Result<Vec<String>> {
143+
let ps_paths = glob(&(root.clone() + "/ps:[0-9]*"))?;
144+
let mut ps_vec = vec![];
145+
for ps_path in ps_paths.filter_map(Result::ok) {
146+
let ps_str = ps_path.into_os_string().into_string().unwrap();
147+
ps_vec.push(ps_str);
148+
}
149+
150+
Ok(ps_vec)
151+
}
152+
153+
fn read_cpumask(path: String) -> Result<Cpumask> {
154+
let cpus = std::fs::read_to_string(path)?;
155+
let cpu_groups: Vec<&str> = cpus.split(',').collect();
156+
let mut mask = Cpumask::new();
157+
for group in cpu_groups.iter() {
158+
let (min, max) = match sscanf!(group.trim(), "{usize}-{usize}") {
159+
Ok((x, y)) => (x, y),
160+
Err(_) => match sscanf!(group.trim(), "{usize}") {
161+
Ok(x) => (x, x),
162+
Err(_) => {
163+
bail!("Failed to parse online cpus {}", group.trim());
164+
}
165+
},
166+
};
167+
for i in min..(max + 1) {
168+
mask.set_cpu(i)?;
169+
}
170+
}
171+
172+
Ok(mask)
173+
}
174+
175+
fn get_pd_paths() -> Result<Vec<(usize, String)>> {
176+
let prefix = get_em_root().unwrap() + "/cpu";
177+
let pd_paths = glob(&(prefix.clone() + "[0-9]*"))?;
178+
179+
let mut pd_vec = vec![];
180+
for pd_path in pd_paths.filter_map(Result::ok) {
181+
let pd_str = pd_path.into_os_string().into_string().unwrap();
182+
let pd_id: usize = pd_str[prefix.len()..].parse().unwrap();
183+
pd_vec.push((pd_id, pd_str));
184+
}
185+
if pd_vec.len() == 0 {
186+
bail!("There is no performance domain.");
187+
}
188+
pd_vec.sort();
189+
190+
let mut pd_vec2 = vec![];
191+
for (id, (_, pd_str)) in pd_vec.into_iter().enumerate() {
192+
pd_vec2.push((id, pd_str));
193+
}
194+
195+
Ok(pd_vec2)
196+
}
197+
198+
fn get_em_root() -> Result<String> {
199+
let root = compat::debugfs_mount().unwrap().join("energy_model");
200+
Ok(root.display().to_string())
201+
}

rust/scx_utils/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ pub use topology::Topology;
7070
pub use topology::NR_CPUS_POSSIBLE;
7171
pub use topology::NR_CPU_IDS;
7272

73+
mod energy_model;
74+
pub use energy_model::EnergyModel;
75+
pub use energy_model::PerfDomain;
76+
pub use energy_model::PerfState;
77+
7378
mod cpumask;
7479
pub use cpumask::Cpumask;
7580

0 commit comments

Comments
 (0)