kratart/
power.rs

1use anyhow::Result;
2use indexmap::IndexMap;
3use log::info;
4use xencall::sys::{CpuId, SysctlCputopo};
5
6use crate::RuntimeContext;
7
8#[derive(Clone)]
9pub struct PowerManagementContext {
10    pub context: RuntimeContext,
11}
12
13#[derive(Clone, Copy, Debug)]
14pub enum CpuClass {
15    Standard,
16    Performance,
17    Efficiency,
18}
19
20#[derive(Clone, Copy, Debug)]
21pub struct CpuTopologyInfo {
22    pub core: u32,
23    pub socket: u32,
24    pub node: u32,
25    pub thread: u32,
26    pub class: CpuClass,
27}
28
29fn labeled_topology(input: &[SysctlCputopo]) -> Vec<CpuTopologyInfo> {
30    let mut cores: IndexMap<(u32, u32, u32), Vec<CpuTopologyInfo>> = IndexMap::new();
31    let mut pe_cores = false;
32    let mut last: Option<SysctlCputopo> = None;
33
34    for item in input {
35        if cores.is_empty() {
36            cores.insert(
37                (item.core, item.socket, item.node),
38                vec![CpuTopologyInfo {
39                    core: item.core,
40                    socket: item.socket,
41                    thread: 0,
42                    node: item.node,
43                    class: CpuClass::Standard,
44                }],
45            );
46            last = Some(*item);
47            continue;
48        }
49
50        if last
51            .map(|last| {
52                item.core
53                    .checked_sub(last.core)
54                    .map(|diff| diff >= 3)
55                    .unwrap_or(false)
56            })
57            .unwrap_or(false)
58        {
59            // detect if performance cores seem to be kicking in.
60            if let Some(last) = last {
61                if let Some(list) = cores.get_mut(&(last.core, last.socket, last.node)) {
62                    for other in list {
63                        other.class = CpuClass::Performance;
64                    }
65                }
66            }
67            let list = cores
68                .entry((item.core, item.socket, item.node))
69                .or_default();
70            for old in &mut *list {
71                old.class = CpuClass::Performance;
72            }
73            list.push(CpuTopologyInfo {
74                core: item.core,
75                socket: item.socket,
76                thread: 0,
77                node: item.node,
78                class: CpuClass::Performance,
79            });
80            pe_cores = true;
81        } else if pe_cores && last.map(|last| item.core == last.core + 1).unwrap_or(false) {
82            // detect efficiency cores if P/E cores are in use.
83            if let Some(last) = last {
84                if let Some(list) = cores.get_mut(&(last.core, last.socket, last.node)) {
85                    for other in list {
86                        other.class = CpuClass::Efficiency;
87                    }
88                }
89            }
90            let list = cores
91                .entry((item.core, item.socket, item.node))
92                .or_default();
93            list.push(CpuTopologyInfo {
94                core: item.core,
95                socket: item.socket,
96                thread: 0,
97                node: item.node,
98                class: CpuClass::Efficiency,
99            });
100        } else {
101            let list = cores
102                .entry((item.core, item.socket, item.node))
103                .or_default();
104            if list.is_empty() {
105                list.push(CpuTopologyInfo {
106                    core: item.core,
107                    socket: item.socket,
108                    thread: 0,
109                    node: item.node,
110                    class: CpuClass::Standard,
111                });
112            } else {
113                list.push(CpuTopologyInfo {
114                    core: item.core,
115                    socket: item.socket,
116                    thread: 0,
117                    node: item.node,
118                    class: list
119                        .first()
120                        .map(|first| first.class)
121                        .unwrap_or(CpuClass::Standard),
122                });
123            }
124        }
125        last = Some(*item);
126    }
127
128    for threads in cores.values_mut() {
129        for (index, thread) in threads.iter_mut().enumerate() {
130            thread.thread = index as u32;
131        }
132    }
133
134    cores.into_values().flatten().collect::<Vec<_>>()
135}
136
137impl PowerManagementContext {
138    /// Get the CPU topology, with SMT awareness.
139    /// Also translates Intel p-core/e-core nonsense: non-sequential core identifiers
140    /// are treated as p-cores, while e-cores behave as standard cores.
141    /// If there is a p-core/e-core split, then CPU class will be defined as
142    /// `CpuClass::Performance` or `CpuClass::Efficiency`, else `CpuClass::Standard`.
143    pub async fn cpu_topology(&self) -> Result<Vec<CpuTopologyInfo>> {
144        let xen_topology = self.context.xen.call.cpu_topology().await?;
145        let logical_topology = labeled_topology(&xen_topology);
146        Ok(logical_topology)
147    }
148
149    /// Enable or disable SMT awareness in the scheduler.
150    pub async fn set_smt_policy(&self, enable: bool) -> Result<()> {
151        self.context
152            .xen
153            .call
154            .set_turbo_mode(CpuId::All, enable)
155            .await
156            .unwrap_or_else(|error| {
157                info!("non-fatal error while setting SMT policy: {:?}", error);
158            });
159        Ok(())
160    }
161
162    /// Set scheduler policy name.
163    pub async fn set_scheduler_policy(&self, policy: impl AsRef<str>) -> Result<()> {
164        self.context
165            .xen
166            .call
167            .set_cpufreq_gov(CpuId::All, policy)
168            .await
169            .unwrap_or_else(|error| {
170                info!(
171                    "non-fatal error while setting scheduler policy: {:?}",
172                    error
173                );
174            });
175        Ok(())
176    }
177}