Skip to content

Commit c2b273f

Browse files
committed
feat: add cpu isolation for system, perf and benchmark
1 parent 9eb9fc8 commit c2b273f

3 files changed

Lines changed: 161 additions & 1 deletion

File tree

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
use super::setup::run_with_sudo;
2+
use crate::prelude::*;
3+
4+
pub struct CpuIsolation;
5+
6+
impl CpuIsolation {
7+
pub fn new() -> Self {
8+
Self {}
9+
}
10+
11+
fn set_allowed_cpus(scope: &str, allowed_cpus: &[u32]) -> anyhow::Result<()> {
12+
let cpus = allowed_cpus
13+
.iter()
14+
.map(|cpu| cpu.to_string())
15+
.collect::<Vec<_>>()
16+
.join(",");
17+
18+
run_with_sudo(&[
19+
"systemctl",
20+
"set-property",
21+
scope,
22+
&format!("AllowedCPUs={}", cpus),
23+
])?;
24+
25+
Ok(())
26+
}
27+
28+
pub fn isolate(&self) -> anyhow::Result<()> {
29+
let (instrument_cpu, bench_cpu, system_cpus) = Self::cpu_bindings();
30+
Self::set_allowed_cpus("system.slice", &system_cpus)?;
31+
Self::set_allowed_cpus("init.scope", &system_cpus)?;
32+
33+
let user_cpus = vec![instrument_cpu, bench_cpu];
34+
Self::set_allowed_cpus("user.slice", &user_cpus)?;
35+
36+
Ok(())
37+
}
38+
39+
pub fn reset_isolation(&self) -> anyhow::Result<()> {
40+
let online_cpus = Self::online_cpus()?;
41+
Self::set_allowed_cpus("system.slice", &online_cpus)?;
42+
Self::set_allowed_cpus("user.slice", &online_cpus)?;
43+
Self::set_allowed_cpus("init.scope", &online_cpus)?;
44+
45+
Ok(())
46+
}
47+
48+
/// Find the CPU cores to use for the isolated tasks:
49+
/// 1. System processes
50+
/// 2. Perf process / other instruments
51+
/// 3. Benchmarks
52+
///
53+
/// If isolated CPUs are available, they will be used for the perf and benchmark processes. The
54+
/// remaining online CPUs will be used for system processes.
55+
pub fn cpu_bindings() -> (u32, u32, Vec<u32>) {
56+
let online = Self::online_cpus().unwrap_or_default();
57+
debug!("Online CPUs: {online:?}");
58+
59+
// Use system isolated cpus (done via boot parameter) which we can use for the benchmark
60+
// and perf process. If not isolated CPUs exist, we will use the default ones.
61+
let isolated = Self::isolated_cpus().unwrap_or_default();
62+
debug!("Isolated CPUs: {isolated:?}");
63+
64+
assert!(
65+
online.len() + isolated.len() >= 3,
66+
"At least 3 CPUs are required"
67+
);
68+
let (perf_cpu, bench_cpu, system_cpus) = match isolated.len() {
69+
0 => (online[0], online[1], online[2..].to_vec()),
70+
1 => (online[0], isolated[0], online[1..].to_vec()),
71+
_ => (isolated[0], isolated[1], online),
72+
};
73+
74+
(perf_cpu, bench_cpu, system_cpus)
75+
}
76+
77+
fn parse_cpu_str(content: &str) -> Vec<u32> {
78+
content
79+
.trim()
80+
.split(',')
81+
.filter_map(|cpu| {
82+
if cpu.contains('-') {
83+
let (left, right) = cpu.split_once('-')?;
84+
let left = left.parse::<u32>().ok()?;
85+
let right = right.parse::<u32>().ok()?;
86+
87+
if left > right {
88+
return None;
89+
}
90+
91+
Some((left..=right).collect::<Vec<_>>())
92+
} else {
93+
Some(vec![cpu.parse::<u32>().ok()?])
94+
}
95+
})
96+
.flatten()
97+
.collect::<Vec<_>>()
98+
}
99+
100+
fn online_cpus() -> anyhow::Result<Vec<u32>> {
101+
let content = std::fs::read_to_string("/sys/devices/system/cpu/online")?;
102+
info!("{content}");
103+
let cpus = Self::parse_cpu_str(&content);
104+
if cpus.is_empty() {
105+
return Err(anyhow::anyhow!("No online CPUs found"));
106+
}
107+
Ok(cpus)
108+
}
109+
110+
fn isolated_cpus() -> anyhow::Result<Vec<u32>> {
111+
let content = std::fs::read_to_string("/sys/devices/system/cpu/isolated")?;
112+
let cpus = Self::parse_cpu_str(&content);
113+
if cpus.is_empty() {
114+
return Err(anyhow::anyhow!("No isolated CPUs found"));
115+
}
116+
Ok(cpus)
117+
}
118+
}
119+
120+
impl Drop for CpuIsolation {
121+
fn drop(&mut self) {
122+
self.reset_isolation()
123+
.unwrap_or_else(|e| eprintln!("Failed to reset CPU isolation: {}", e));
124+
}
125+
}
126+
127+
#[cfg(test)]
128+
mod tests {
129+
use super::*;
130+
131+
#[test]
132+
fn test_parse_cpu_str() {
133+
assert_eq!(CpuIsolation::parse_cpu_str("").len(), 0);
134+
assert_eq!(CpuIsolation::parse_cpu_str("0,1,2,3"), vec![0, 1, 2, 3]);
135+
assert_eq!(CpuIsolation::parse_cpu_str("0-1,2-3"), vec![0, 1, 2, 3]);
136+
assert_eq!(
137+
CpuIsolation::parse_cpu_str("0,2,4-7"),
138+
vec![0, 2, 4, 5, 6, 7]
139+
);
140+
}
141+
142+
#[test]
143+
fn test_cpu_bindings() {
144+
let online_cpus = CpuIsolation::online_cpus().unwrap_or_default();
145+
assert!(!online_cpus.is_empty());
146+
147+
let (perf_cpu, bench_cpu, system_cpus) = CpuIsolation::cpu_bindings();
148+
assert!(perf_cpu != bench_cpu);
149+
assert!(!system_cpus.is_empty());
150+
assert!(!system_cpus.contains(&perf_cpu));
151+
assert!(!system_cpus.contains(&bench_cpu));
152+
}
153+
}

src/run/runner/helpers/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod env;
22
pub mod get_bench_command;
3+
pub mod isolation;
34
pub mod profile_folder;
45
pub mod run_command_with_log_pipe;
56
pub mod setup;

src/run/runner/wall_time/perf/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#![cfg_attr(not(unix), allow(dead_code, unused_mut))]
22

33
use crate::prelude::*;
4+
use crate::run::runner::helpers::isolation::CpuIsolation;
45
use crate::run::runner::helpers::run_command_with_log_pipe::run_command_with_log_pipe_and_callback;
56
use crate::run::runner::helpers::setup::run_with_sudo;
67
use crate::run::runner::valgrind::helpers::ignored_objects_path::get_objects_path_to_ignore;
@@ -96,10 +97,15 @@ impl PerfRunner {
9697
};
9798
debug!("Using call graph mode: {}", cg_mode);
9899

100+
let (perf_cpu, bench_cpu, _) = CpuIsolation::cpu_bindings();
101+
let cpu_isolation = CpuIsolation::new();
102+
cpu_isolation.isolate()?;
103+
99104
cmd.args([
100105
"-c",
101106
&format!(
102-
"perf record --user-callchains --freq=999 --switch-output --control=fifo:{},{} --delay=-1 -g --call-graph={cg_mode} --output={} -- {bench_cmd}",
107+
"taskset -c {perf_cpu} perf record --user-callchains --freq=999 --switch-output --control=fifo:{},{} --delay=-1 -g --call-graph={cg_mode} --output={} -- \
108+
taskset -c {bench_cpu} {bench_cmd}",
103109
perf_fifo.ctl_fifo_path.to_string_lossy(),
104110
perf_fifo.ack_fifo_path.to_string_lossy(),
105111
perf_file.path().to_string_lossy()

0 commit comments

Comments
 (0)