Skip to content

Commit 9c9fd2b

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 f2b85e9 commit 9c9fd2b

File tree

4 files changed

+217
-18
lines changed

4 files changed

+217
-18
lines changed

rust/scx_utils/src/cpumask.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ use anyhow::bail;
6060
use anyhow::Context;
6161
use anyhow::Result;
6262
use bitvec::prelude::*;
63+
use sscanf::sscanf;
6364
use std::fmt;
6465
use std::ops::BitAndAssign;
6566
use std::ops::BitOrAssign;
@@ -137,6 +138,15 @@ impl Cpumask {
137138
Ok(Self { mask })
138139
}
139140

141+
pub fn from_cpulist(cpulist: &str) -> Result<Cpumask> {
142+
let mut mask = Cpumask::new();
143+
for cpu_id in read_cpulist(cpulist).unwrap() {
144+
mask.set_cpu(cpu_id);
145+
}
146+
147+
Ok(mask)
148+
}
149+
140150
pub fn from_vec(vec: Vec<u64>) -> Self {
141151
Self {
142152
mask: BitVec::from_vec(vec),
@@ -322,6 +332,27 @@ impl Cpumask {
322332
}
323333
}
324334

335+
pub fn read_cpulist(cpulist: &str) -> Result<Vec<usize>> {
336+
let cpu_groups: Vec<&str> = cpulist.split(',').collect();
337+
let mut cpu_ids = vec![];
338+
for group in cpu_groups.iter() {
339+
let (min, max) = match sscanf!(group.trim(), "{usize}-{usize}") {
340+
Ok((x, y)) => (x, y),
341+
Err(_) => match sscanf!(group.trim(), "{usize}") {
342+
Ok(x) => (x, x),
343+
Err(_) => {
344+
bail!("Failed to parse cpulist {}", group.trim());
345+
}
346+
},
347+
};
348+
for i in min..(max + 1) {
349+
cpu_ids.push(i);
350+
}
351+
}
352+
353+
Ok(cpu_ids)
354+
}
355+
325356
pub struct CpumaskIterator<'a> {
326357
mask: &'a Cpumask,
327358
index: usize,

rust/scx_utils/src/energy_model.rs

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
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_from_file;
16+
use crate::Cpumask;
17+
use anyhow::bail;
18+
use anyhow::Result;
19+
use glob::glob;
20+
use std::collections::BTreeMap;
21+
use std::fmt;
22+
use std::path::Path;
23+
use std::sync::Arc;
24+
25+
#[derive(Debug)]
26+
pub struct PerfState {
27+
pub cost: usize,
28+
pub frequency: usize,
29+
pub inefficient: usize,
30+
pub performance: usize,
31+
pub power: usize,
32+
}
33+
34+
#[derive(Debug)]
35+
pub struct PerfDomain {
36+
/// Monotonically increasing unique id.
37+
pub id: usize,
38+
/// Cpumask of all CPUs in this performance domain.
39+
pub span: Cpumask,
40+
/// Table of performance states indexed by performance.
41+
pub perf_table: BTreeMap<usize, Arc<PerfState>>,
42+
}
43+
44+
#[derive(Debug)]
45+
pub struct EnergyModel {
46+
/// Performance domains indexed by domain id
47+
pub perf_doms: BTreeMap<usize, Arc<PerfDomain>>,
48+
}
49+
50+
impl EnergyModel {
51+
/// Build a complete EnergyModel
52+
pub fn new() -> Result<EnergyModel> {
53+
let mut perf_doms = BTreeMap::new();
54+
let pd_paths = match get_pd_paths() {
55+
Ok(pd_paths) => pd_paths,
56+
Err(_) => {
57+
bail!("Fail to locate the energy model directory");
58+
}
59+
};
60+
61+
for (pd_id, pd_path) in pd_paths {
62+
let pd = PerfDomain::new(pd_id, pd_path).unwrap();
63+
perf_doms.insert(pd.id, pd.into());
64+
}
65+
66+
Ok(EnergyModel { perf_doms })
67+
}
68+
}
69+
70+
impl PerfDomain {
71+
/// Build a PerfDomain
72+
pub fn new(id: usize, root: String) -> Result<PerfDomain> {
73+
let mut perf_table = BTreeMap::new();
74+
let cpulist = std::fs::read_to_string(root.clone() + "/cpus")?;
75+
let span = Cpumask::from_cpulist(&cpulist)?;
76+
77+
for ps_path in get_ps_paths(root).unwrap() {
78+
let ps = PerfState::new(ps_path).unwrap();
79+
perf_table.insert(ps.performance, ps.into());
80+
}
81+
82+
Ok(PerfDomain {
83+
id,
84+
span,
85+
perf_table,
86+
})
87+
}
88+
}
89+
90+
impl PerfState {
91+
/// Build a PerfState
92+
pub fn new(root: String) -> Result<PerfState> {
93+
let cost = read_from_file(Path::new(&(root.clone() + "/cost")))?;
94+
let frequency = read_from_file(Path::new(&(root.clone() + "/frequency")))?;
95+
let inefficient = read_from_file(Path::new(&(root.clone() + "/inefficient")))?;
96+
let performance = read_from_file(Path::new(&(root.clone() + "/performance")))?;
97+
let power = read_from_file(Path::new(&(root.clone() + "/power")))?;
98+
99+
Ok(PerfState {
100+
cost,
101+
frequency,
102+
inefficient,
103+
performance,
104+
power,
105+
})
106+
}
107+
}
108+
109+
impl fmt::Display for EnergyModel {
110+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
111+
for (_, pd) in self.perf_doms.iter() {
112+
write!(f, "{:#}\n", pd)?;
113+
}
114+
Ok(())
115+
}
116+
}
117+
118+
impl fmt::Display for PerfDomain {
119+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
120+
write!(f, "# perf domain: {:#}, cpus: {:#}\n", self.id, self.span)?;
121+
write!(f, "cost, frequency, inefficient, performance, power\n")?;
122+
for (_, ps) in self.perf_table.iter() {
123+
write!(f, "{:#}\n", ps)?;
124+
}
125+
Ok(())
126+
}
127+
}
128+
129+
impl fmt::Display for PerfState {
130+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
131+
write!(
132+
f,
133+
"{}, {}, {}, {}, {}",
134+
self.cost, self.frequency, self.inefficient, self.performance, self.power
135+
)?;
136+
Ok(())
137+
}
138+
}
139+
140+
/*********************************************************
141+
* Helper structs/functions for creating the EnergyModel *
142+
*********************************************************/
143+
fn get_ps_paths(root: String) -> Result<Vec<String>> {
144+
let ps_paths = glob(&(root.clone() + "/ps:[0-9]*"))?;
145+
let mut ps_vec = vec![];
146+
for ps_path in ps_paths.filter_map(Result::ok) {
147+
let ps_str = ps_path.into_os_string().into_string().unwrap();
148+
ps_vec.push(ps_str);
149+
}
150+
151+
Ok(ps_vec)
152+
}
153+
154+
fn get_pd_paths() -> Result<Vec<(usize, String)>> {
155+
let prefix = get_em_root().unwrap() + "/cpu";
156+
let pd_paths = glob(&(prefix.clone() + "[0-9]*"))?;
157+
158+
let mut pd_vec = vec![];
159+
for pd_path in pd_paths.filter_map(Result::ok) {
160+
let pd_str = pd_path.into_os_string().into_string().unwrap();
161+
let pd_id: usize = pd_str[prefix.len()..].parse().unwrap();
162+
pd_vec.push((pd_id, pd_str));
163+
}
164+
if pd_vec.len() == 0 {
165+
bail!("There is no performance domain.");
166+
}
167+
pd_vec.sort();
168+
169+
let mut pd_vec2 = vec![];
170+
for (id, (_, pd_str)) in pd_vec.into_iter().enumerate() {
171+
pd_vec2.push((id, pd_str));
172+
}
173+
174+
Ok(pd_vec2)
175+
}
176+
177+
fn get_em_root() -> Result<String> {
178+
let root = compat::debugfs_mount().unwrap().join("energy_model");
179+
Ok(root.display().to_string())
180+
}

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

rust/scx_utils/src/topology.rs

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -353,24 +353,7 @@ impl TopoCtx {
353353
fn cpus_online() -> Result<Cpumask> {
354354
let path = "/sys/devices/system/cpu/online";
355355
let online = std::fs::read_to_string(path)?;
356-
let online_groups: Vec<&str> = online.split(',').collect();
357-
let mut mask = Cpumask::new();
358-
for group in online_groups.iter() {
359-
let (min, max) = match sscanf!(group.trim(), "{usize}-{usize}") {
360-
Ok((x, y)) => (x, y),
361-
Err(_) => match sscanf!(group.trim(), "{usize}") {
362-
Ok(x) => (x, x),
363-
Err(_) => {
364-
bail!("Failed to parse online cpus {}", group.trim());
365-
}
366-
},
367-
};
368-
for i in min..(max + 1) {
369-
mask.set_cpu(i)?;
370-
}
371-
}
372-
373-
Ok(mask)
356+
Cpumask::from_cpulist(&online)
374357
}
375358

376359
fn get_cache_id(topo_ctx: &mut TopoCtx, cache_level_path: &PathBuf, cache_level: usize) -> usize {

0 commit comments

Comments
 (0)