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 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 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 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 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 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}