libcontainer/process/
cpu_affinity.rs1use nix::sched::{CpuSet, sched_getaffinity, sched_setaffinity};
2use nix::unistd::Pid;
3use tracing::{Level, enabled};
4
5#[derive(Debug, thiserror::Error)]
6pub enum CPUAffinityError {
7 #[error("invalid CPU string: {0}")]
8 ParseError(String),
9 #[error("values larger than {max} are not supported")]
10 CpuOutOfRange { cpu: usize, max: usize },
11 #[error("failed to set CPU for CPU {cpu}: {source}")]
12 CpuSet {
13 cpu: usize,
14 #[source]
15 source: nix::Error,
16 },
17 #[error("failed to setaffinity")]
18 SetAffinity(#[source] nix::Error),
19 #[error("failed to getaffinity")]
20 GetAffinity(#[source] nix::Error),
21}
22
23type Result<T> = std::result::Result<T, CPUAffinityError>;
24
25pub fn to_cpuset(cpuset_str: &str) -> Result<CpuSet> {
26 let mut cpuset = CpuSet::new();
27 let max_cpu = CpuSet::count();
28
29 for part in cpuset_str
30 .trim()
31 .split(',')
32 .map(str::trim)
33 .filter(|s| !s.is_empty())
34 {
35 match part.split_once('-') {
36 Some((start_str, end_str)) => {
37 let start = parse_cpu_index(start_str, max_cpu)?;
38 let end = parse_cpu_index(end_str, max_cpu)?;
39 if start > end {
40 return Err(CPUAffinityError::ParseError(format!(
41 "invalid range: {}-{}",
42 start, end
43 )));
44 }
45 for cpu in start..=end {
46 cpuset
47 .set(cpu)
48 .map_err(|e| CPUAffinityError::CpuSet { cpu, source: e })?;
49 }
50 }
51 None => {
52 let cpu = parse_cpu_index(part, max_cpu)?;
53 cpuset
54 .set(cpu)
55 .map_err(|e| CPUAffinityError::CpuSet { cpu, source: e })?;
56 }
57 }
58 }
59 Ok(cpuset)
60}
61
62fn parse_cpu_index(s: &str, max_cpu: usize) -> Result<usize> {
63 let cpu: usize = s
64 .parse()
65 .map_err(|_| CPUAffinityError::ParseError(s.to_string()))?;
66 if cpu >= max_cpu {
67 return Err(CPUAffinityError::CpuOutOfRange {
68 cpu,
69 max: max_cpu - 1,
70 });
71 }
72 Ok(cpu)
73}
74
75pub fn set_cpuset_affinity_from_string(pid: Pid, cpuset_str: &str) -> Result<()> {
76 tracing::debug!(?cpuset_str, "setting CPU affinity for tenant container");
77 sched_setaffinity(pid, &to_cpuset(cpuset_str)?).map_err(CPUAffinityError::SetAffinity)
78}
79
80pub fn log_cpu_affinity() -> Result<()> {
84 if !enabled!(Level::DEBUG) {
85 return Ok(());
86 }
87 let cpuset = sched_getaffinity(Pid::this()).map_err(CPUAffinityError::GetAffinity)?;
88 let mask = (0..usize::BITS as usize)
89 .filter(|&i| cpuset.is_set(i).unwrap_or(false))
90 .fold(0usize, |mask, i| mask | (1usize << i));
91 tracing::debug!("affinity: 0x{:x}", mask);
92 Ok(())
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
100 fn test_to_cpuset_single_values() {
101 let cpuset = to_cpuset("0,1,2").unwrap();
102 for cpu in [0, 1, 2] {
103 assert!(cpuset.is_set(cpu).unwrap());
104 }
105 }
106
107 #[test]
108 fn test_to_cpuset_range() {
109 let cpuset = to_cpuset("3-5").unwrap();
110 for cpu in [3, 4, 5] {
111 assert!(cpuset.is_set(cpu).unwrap());
112 }
113 }
114
115 #[test]
116 fn test_to_cpuset_mixed() {
117 let cpuset = to_cpuset("0, 2-4, 6").unwrap();
118 for cpu in [0, 2, 3, 4, 6] {
119 assert!(cpuset.is_set(cpu).unwrap());
120 }
121 for cpu in [1, 5, 7] {
122 assert!(!cpuset.is_set(cpu).unwrap_or(false));
123 }
124 }
125
126 #[test]
127 fn test_to_cpuset_spaces_and_empty() {
128 let cpuset = to_cpuset(" , 1 , 3 , 5-7 , ").unwrap();
129 for cpu in [1, 3, 5, 6, 7] {
130 assert!(cpuset.is_set(cpu).unwrap());
131 }
132 }
133
134 #[test]
135 fn test_to_cpuset_invalid_range() {
136 let err = to_cpuset("5-3").unwrap_err();
137 matches!(err, CPUAffinityError::ParseError(_));
138 }
139
140 #[test]
141 fn test_to_cpuset_invalid_value() {
142 let err = to_cpuset("a,b,c").unwrap_err();
143 matches!(err, CPUAffinityError::ParseError(_));
144 }
145
146 #[test]
147 fn test_to_cpuset_max_allowed_cpu() {
148 let max = CpuSet::count();
149 let highest = max - 1;
150 let cpuset = to_cpuset(&highest.to_string()).unwrap();
151 assert!(cpuset.is_set(highest).unwrap());
152 }
153
154 #[test]
155 fn test_to_cpuset_exceeds_max_cpu() {
156 let max = CpuSet::count();
157 let result = to_cpuset(&max.to_string());
158 assert!(matches!(
159 result,
160 Err(CPUAffinityError::CpuOutOfRange { .. })
161 ));
162 }
163}