numaperf_core/
capability.rs1use std::fs;
4use std::path::Path;
5
6#[derive(Debug, Clone)]
30pub struct Capabilities {
31 pub strict_memory_binding: bool,
33 pub strict_cpu_affinity: bool,
35 pub memory_locking: bool,
37 pub numa_balancing_disabled: bool,
39 pub numa_node_count: usize,
41}
42
43impl Capabilities {
44 pub fn detect() -> Self {
49 Self {
50 strict_memory_binding: has_capability(CAP_SYS_ADMIN),
51 strict_cpu_affinity: has_capability(CAP_SYS_NICE),
52 memory_locking: has_capability(CAP_IPC_LOCK),
53 numa_balancing_disabled: check_numa_balancing_disabled(),
54 numa_node_count: count_numa_nodes(),
55 }
56 }
57
58 pub fn supports_hard_mode(&self) -> bool {
65 self.strict_memory_binding
66 && self.strict_cpu_affinity
67 && self.numa_balancing_disabled
68 }
69
70 pub fn missing_for_hard_mode(&self) -> Vec<&'static str> {
74 let mut missing = Vec::new();
75 if !self.strict_memory_binding {
76 missing.push("CAP_SYS_ADMIN (for strict memory binding)");
77 }
78 if !self.strict_cpu_affinity {
79 missing.push("CAP_SYS_NICE (for strict CPU affinity)");
80 }
81 if !self.numa_balancing_disabled {
82 missing.push("kernel.numa_balancing=0 (to prevent automatic migration)");
83 }
84 missing
85 }
86
87 pub fn is_numa_system(&self) -> bool {
89 self.numa_node_count > 1
90 }
91
92 pub fn summary(&self) -> String {
94 let mut s = String::new();
95 s.push_str("NUMA System Capabilities\n");
96 s.push_str("========================\n");
97 s.push_str(&format!("NUMA nodes detected: {}\n", self.numa_node_count));
98 s.push_str(&format!(
99 "CAP_SYS_ADMIN (strict memory binding): {}\n",
100 if self.strict_memory_binding { "yes" } else { "no" }
101 ));
102 s.push_str(&format!(
103 "CAP_SYS_NICE (strict CPU affinity): {}\n",
104 if self.strict_cpu_affinity { "yes" } else { "no" }
105 ));
106 s.push_str(&format!(
107 "CAP_IPC_LOCK (memory locking): {}\n",
108 if self.memory_locking { "yes" } else { "no" }
109 ));
110 s.push_str(&format!(
111 "NUMA balancing disabled: {}\n",
112 if self.numa_balancing_disabled { "yes" } else { "no" }
113 ));
114 s.push_str(&format!(
115 "\nHard mode supported: {}\n",
116 if self.supports_hard_mode() { "YES" } else { "NO" }
117 ));
118
119 if !self.supports_hard_mode() {
120 s.push_str("\nMissing for hard mode:\n");
121 for cap in self.missing_for_hard_mode() {
122 s.push_str(&format!(" - {}\n", cap));
123 }
124 }
125 s
126 }
127}
128
129impl std::fmt::Display for Capabilities {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 write!(
132 f,
133 "Capabilities(nodes={}, hard_mode={})",
134 self.numa_node_count,
135 if self.supports_hard_mode() { "supported" } else { "unavailable" }
136 )
137 }
138}
139
140const CAP_IPC_LOCK: u8 = 14;
142const CAP_SYS_ADMIN: u8 = 21;
143const CAP_SYS_NICE: u8 = 23;
144
145fn has_capability(cap: u8) -> bool {
149 let status = match fs::read_to_string("/proc/self/status") {
150 Ok(s) => s,
151 Err(_) => return false,
152 };
153
154 for line in status.lines() {
155 if let Some(hex) = line.strip_prefix("CapEff:\t") {
156 return check_capability_bit(hex.trim(), cap);
157 }
158 }
159 false
160}
161
162fn check_capability_bit(hex: &str, cap: u8) -> bool {
164 let cap_bit = cap as u64;
167
168 match u64::from_str_radix(hex, 16) {
169 Ok(caps) => (caps & (1 << cap_bit)) != 0,
170 Err(_) => false,
171 }
172}
173
174fn check_numa_balancing_disabled() -> bool {
176 let path = Path::new("/proc/sys/kernel/numa_balancing");
177 if !path.exists() {
178 return true;
181 }
182
183 match fs::read_to_string(path) {
184 Ok(content) => {
185 content.trim() == "0"
187 }
188 Err(_) => false,
189 }
190}
191
192fn count_numa_nodes() -> usize {
194 let node_dir = Path::new("/sys/devices/system/node");
195 if !node_dir.exists() {
196 return 1;
198 }
199
200 match fs::read_dir(node_dir) {
201 Ok(entries) => {
202 let count = entries
203 .filter_map(|e| e.ok())
204 .filter(|e| {
205 e.file_name()
206 .to_string_lossy()
207 .starts_with("node")
208 })
209 .count();
210 std::cmp::max(count, 1)
212 }
213 Err(_) => 1,
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn test_capability_detection() {
223 let caps = Capabilities::detect();
224 assert!(caps.numa_node_count >= 1);
226 }
227
228 #[test]
229 fn test_missing_capabilities() {
230 let caps = Capabilities::detect();
231 let missing = caps.missing_for_hard_mode();
232
233 if caps.supports_hard_mode() {
235 assert!(missing.is_empty());
236 } else {
237 assert!(!missing.is_empty());
239 }
240 }
241
242 #[test]
243 fn test_capability_bit_check() {
244 assert!(check_capability_bit("0000000000200000", CAP_SYS_ADMIN)); assert!(check_capability_bit("0000000000800000", CAP_SYS_NICE)); assert!(check_capability_bit("0000000000004000", CAP_IPC_LOCK)); assert!(check_capability_bit("ffffffffffffffff", CAP_SYS_ADMIN));
251 assert!(check_capability_bit("ffffffffffffffff", CAP_SYS_NICE));
252 assert!(check_capability_bit("ffffffffffffffff", CAP_IPC_LOCK));
253
254 assert!(!check_capability_bit("0000000000000000", CAP_SYS_ADMIN));
256 assert!(!check_capability_bit("0000000000000000", CAP_SYS_NICE));
257 assert!(!check_capability_bit("0000000000000000", CAP_IPC_LOCK));
258 }
259
260 #[test]
261 fn test_summary_format() {
262 let caps = Capabilities::detect();
263 let summary = caps.summary();
264
265 assert!(summary.contains("NUMA System Capabilities"));
266 assert!(summary.contains("NUMA nodes detected:"));
267 assert!(summary.contains("CAP_SYS_ADMIN"));
268 assert!(summary.contains("CAP_SYS_NICE"));
269 assert!(summary.contains("Hard mode supported:"));
270 }
271
272 #[test]
273 fn test_display() {
274 let caps = Capabilities::detect();
275 let display = format!("{}", caps);
276
277 assert!(display.contains("Capabilities"));
278 assert!(display.contains("nodes="));
279 }
280
281 #[test]
282 fn test_is_numa_system() {
283 let caps = Capabilities::detect();
284
285 if caps.numa_node_count > 1 {
286 assert!(caps.is_numa_system());
287 } else {
288 assert!(!caps.is_numa_system());
289 }
290 }
291
292 #[test]
293 fn test_capability_struct_fields() {
294 let caps = Capabilities {
296 strict_memory_binding: true,
297 strict_cpu_affinity: true,
298 memory_locking: true,
299 numa_balancing_disabled: true,
300 numa_node_count: 2,
301 };
302
303 assert!(caps.supports_hard_mode());
304 assert!(caps.missing_for_hard_mode().is_empty());
305 assert!(caps.is_numa_system());
306 }
307
308 #[test]
309 fn test_missing_some_capabilities() {
310 let caps = Capabilities {
311 strict_memory_binding: false,
312 strict_cpu_affinity: true,
313 memory_locking: true,
314 numa_balancing_disabled: false,
315 numa_node_count: 1,
316 };
317
318 assert!(!caps.supports_hard_mode());
319 let missing = caps.missing_for_hard_mode();
320 assert_eq!(missing.len(), 2);
321 assert!(missing.iter().any(|s| s.contains("CAP_SYS_ADMIN")));
322 assert!(missing.iter().any(|s| s.contains("numa_balancing")));
323 }
324}