use std::collections::BTreeSet;
use std::io;
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NodeAffinity {
All,
Some,
None,
}
pub fn node_affinity(cpuset: &BTreeSet<usize>, node: u32) -> NodeAffinity {
let Ok(node_cpus) = numa_node_cpus(node) else {
return NodeAffinity::None;
};
let total = node_cpus.len();
if total == 0 {
return NodeAffinity::None;
}
let matching = node_cpus.iter().filter(|cpu| cpuset.contains(cpu)).count();
if matching == 0 {
NodeAffinity::None
} else if matching == total {
NodeAffinity::All
} else {
NodeAffinity::Some
}
}
pub fn numa_online_nodes() -> io::Result<BTreeSet<u32>> {
let path = Path::new("/sys/devices/system/node/online");
let content = std::fs::read_to_string(path)?;
Ok(crate::model::status::parse_cpuset_list(&content)?
.into_iter()
.map(|n| n as u32)
.collect())
}
fn online_cpus() -> io::Result<BTreeSet<usize>> {
let path = Path::new("/sys/devices/system/cpu/online");
let content = std::fs::read_to_string(path)?;
crate::model::status::parse_cpuset_list(&content)
}
fn numa_node_cpus(node: u32) -> io::Result<BTreeSet<usize>> {
let path_str = format!("/sys/devices/system/node/node{node}/cpulist");
let path = Path::new(&path_str);
let content = std::fs::read_to_string(path)?;
let node_cpus = crate::model::status::parse_cpuset_list(&content)?;
let online = online_cpus()?;
Ok(node_cpus.intersection(&online).copied().collect())
}
pub fn cpu_to_node(cpu: u32) -> Option<u32> {
let nodes = numa_online_nodes().ok()?;
for &node in &nodes {
if let Ok(cpus) = numa_node_cpus(node) {
if cpus.contains(&(cpu as usize)) {
return Some(node);
}
}
}
None
}
#[cfg(test)]
mod tests {
use crate::model::status::parse_cpuset_list;
#[test]
fn parse_cpuset_list_single() {
let set = parse_cpuset_list("5").unwrap();
assert!(set.contains(&5));
assert!(!set.contains(&4));
}
#[test]
fn parse_cpuset_list_range() {
let set = parse_cpuset_list("0-3").unwrap();
assert_eq!(set.iter().copied().collect::<Vec<_>>(), vec![0, 1, 2, 3]);
}
#[test]
fn parse_cpuset_list_mixed() {
let set = parse_cpuset_list("0-2,5,7-8").unwrap();
assert_eq!(
set.iter().copied().collect::<Vec<_>>(),
vec![0, 1, 2, 5, 7, 8]
);
}
#[test]
fn parse_cpuset_list_empty() {
let set = parse_cpuset_list("").unwrap();
assert!(set.is_empty());
}
#[test]
fn parse_cpuset_list_deduplicates() {
let set = parse_cpuset_list("1,1,2").unwrap();
assert_eq!(set.iter().copied().collect::<Vec<_>>(), vec![1, 2]);
}
#[test]
fn parse_cpuset_list_invalid_range() {
assert!(parse_cpuset_list("5-2").is_err());
}
}