scx_utils/
energy_model.rs

1// SPDX-License-Identifier: GPL-2.0
2//
3// Copyright (c) 2025 Valve Corporation.
4// Author: Changwoo Min <changwoo@igalia.com>
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
14use crate::Cpumask;
15use crate::compat;
16use crate::misc::read_from_file;
17use anyhow::Result;
18use anyhow::bail;
19use glob::glob;
20use std::collections::BTreeMap;
21use std::fmt;
22use std::path::Path;
23use std::sync::Arc;
24
25#[derive(Debug)]
26pub 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)]
35pub 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)]
45pub struct EnergyModel {
46    /// Performance domains indexed by domain id
47    pub perf_doms: BTreeMap<usize, Arc<PerfDomain>>,
48}
49
50impl 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    pub fn get_pd(&self, cpu_id: usize) -> Option<&PerfDomain> {
70        for (_, pd) in self.perf_doms.iter() {
71            if pd.span.test_cpu(cpu_id) {
72                return Some(&pd);
73            }
74        }
75        None
76    }
77}
78
79impl PerfDomain {
80    /// Build a PerfDomain
81    pub fn new(id: usize, root: String) -> Result<PerfDomain> {
82        let mut perf_table = BTreeMap::new();
83        let cpulist = std::fs::read_to_string(root.clone() + "/cpus")?;
84        let span = Cpumask::from_cpulist(&cpulist)?;
85
86        for ps_path in get_ps_paths(root).unwrap() {
87            let ps = PerfState::new(ps_path).unwrap();
88            perf_table.insert(ps.performance, ps.into());
89        }
90
91        Ok(PerfDomain {
92            id,
93            span,
94            perf_table,
95        })
96    }
97}
98
99impl PerfState {
100    /// Build a PerfState
101    pub fn new(root: String) -> Result<PerfState> {
102        let cost = read_from_file(Path::new(&(root.clone() + "/cost")))?;
103        let frequency = read_from_file(Path::new(&(root.clone() + "/frequency")))?;
104        let inefficient = read_from_file(Path::new(&(root.clone() + "/inefficient")))?;
105        let performance = read_from_file(Path::new(&(root.clone() + "/performance")))?;
106        let power = read_from_file(Path::new(&(root.clone() + "/power")))?;
107
108        Ok(PerfState {
109            cost,
110            frequency,
111            inefficient,
112            performance,
113            power,
114        })
115    }
116}
117
118impl fmt::Display for EnergyModel {
119    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
120        for (_, pd) in self.perf_doms.iter() {
121            writeln!(f, "{:#}", pd)?;
122        }
123        Ok(())
124    }
125}
126
127impl fmt::Display for PerfDomain {
128    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
129        writeln!(f, "# perf domain: {:#}, cpus: {:#}", self.id, self.span)?;
130        writeln!(f, "cost, frequency, inefficient, performance, power")?;
131        for (_, ps) in self.perf_table.iter() {
132            writeln!(f, "{:#}", ps)?;
133        }
134        Ok(())
135    }
136}
137
138impl fmt::Display for PerfState {
139    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
140        write!(
141            f,
142            "{}, {}, {}, {}, {}",
143            self.cost, self.frequency, self.inefficient, self.performance, self.power
144        )?;
145        Ok(())
146    }
147}
148
149/*********************************************************
150 * Helper structs/functions for creating the EnergyModel *
151 *********************************************************/
152fn get_ps_paths(root: String) -> Result<Vec<String>> {
153    let ps_paths = glob(&(root.clone() + "/ps:[0-9]*"))?;
154    let mut ps_vec = vec![];
155    for ps_path in ps_paths.filter_map(Result::ok) {
156        let ps_str = ps_path.into_os_string().into_string().unwrap();
157        ps_vec.push(ps_str);
158    }
159
160    Ok(ps_vec)
161}
162
163fn get_pd_paths() -> Result<Vec<(usize, String)>> {
164    let prefix = get_em_root().unwrap() + "/cpu";
165    let pd_paths = glob(&(prefix.clone() + "[0-9]*"))?;
166
167    let mut pd_vec = vec![];
168    for pd_path in pd_paths.filter_map(Result::ok) {
169        let pd_str = pd_path.into_os_string().into_string().unwrap();
170        let pd_id: usize = pd_str[prefix.len()..].parse().unwrap();
171        pd_vec.push((pd_id, pd_str));
172    }
173    if pd_vec.is_empty() {
174        bail!("There is no performance domain.");
175    }
176    pd_vec.sort();
177
178    let mut pd_vec2 = vec![];
179    for (id, (_, pd_str)) in pd_vec.into_iter().enumerate() {
180        pd_vec2.push((id, pd_str));
181    }
182
183    Ok(pd_vec2)
184}
185
186fn get_em_root() -> Result<String> {
187    let root = compat::debugfs_mount().unwrap().join("energy_model");
188    Ok(root.display().to_string())
189}