#[cfg(target_os = "linux")]
use std::fs;
#[cfg(target_os = "linux")]
use std::path::Path;
use crate::buffer::allocation::allocate_aligned_buffer;
#[cfg(target_os = "linux")]
pub fn allocate_numa_buffer(size: usize, numa_node: Option<usize>) -> Box<[u8]> {
match numa_node {
Some(node) => {
if is_numa_available() {
match try_numa_allocation(size, node) {
Ok(buffer) => buffer,
Err(_) => {
allocate_aligned_buffer(size)
}
}
} else {
allocate_aligned_buffer(size)
}
}
None => allocate_aligned_buffer(size),
}
}
#[cfg(target_os = "linux")]
fn try_numa_allocation(size: usize, node: usize) -> Result<Box<[u8]>, std::io::Error> {
let original_affinity = get_current_affinity()?;
set_numa_affinity(node)?;
let result = allocate_aligned_buffer(size);
let _ = set_cpu_affinity(&original_affinity);
Ok(result)
}
#[cfg(target_os = "linux")]
fn get_current_affinity() -> Result<libc::cpu_set_t, std::io::Error> {
use std::mem;
unsafe {
let mut cpu_set: libc::cpu_set_t = mem::zeroed();
if libc::sched_getaffinity(0, mem::size_of::<libc::cpu_set_t>(), &mut cpu_set) == 0 {
Ok(cpu_set)
} else {
Err(std::io::Error::last_os_error())
}
}
}
#[cfg(target_os = "linux")]
fn set_numa_affinity(node: usize) -> Result<(), std::io::Error> {
use std::mem;
let cpus = get_numa_node_cpus(node)?;
if cpus.is_empty() {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("No CPUs found for NUMA node {node}"),
));
}
unsafe {
let mut cpu_set: libc::cpu_set_t = mem::zeroed();
libc::CPU_ZERO(&mut cpu_set);
for cpu in cpus {
if cpu < 1024 {
libc::CPU_SET(cpu, &mut cpu_set);
}
}
if libc::sched_setaffinity(0, mem::size_of::<libc::cpu_set_t>(), &cpu_set) == 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error())
}
}
}
#[cfg(target_os = "linux")]
fn set_cpu_affinity(cpu_set: &libc::cpu_set_t) -> Result<(), std::io::Error> {
use std::mem;
unsafe {
if libc::sched_setaffinity(0, mem::size_of::<libc::cpu_set_t>(), cpu_set) == 0 {
Ok(())
} else {
Err(std::io::Error::last_os_error())
}
}
}
#[cfg(target_os = "linux")]
fn get_numa_node_cpus(node: usize) -> Result<Vec<usize>, std::io::Error> {
let path = format!("/sys/devices/system/node/node{node}/cpulist");
if !Path::new(&path).exists() {
return Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("NUMA node {node} not found"),
));
}
let cpulist = fs::read_to_string(&path)?;
parse_cpu_list(cpulist.trim())
}
#[allow(dead_code)]
fn parse_cpu_list(cpulist: &str) -> Result<Vec<usize>, std::io::Error> {
let mut cpus = Vec::new();
for part in cpulist.split(',') {
if part.contains('-') {
let range: Vec<&str> = part.split('-').collect();
if range.len() == 2 {
let start: usize = range[0].parse().map_err(|_| {
std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid CPU range")
})?;
let end: usize = range[1].parse().map_err(|_| {
std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid CPU range")
})?;
for cpu in start..=end {
cpus.push(cpu);
}
}
} else {
let cpu: usize = part.parse().map_err(|_| {
std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid CPU number")
})?;
cpus.push(cpu);
}
}
Ok(cpus)
}
#[cfg(target_os = "linux")]
pub fn is_numa_available() -> bool {
Path::new("/sys/devices/system/node").exists()
&& fs::read_dir("/sys/devices/system/node")
.map(|entries| entries.count() > 1) .unwrap_or(false)
}
#[cfg(not(target_os = "linux"))]
pub fn allocate_numa_buffer(size: usize, _numa_node: Option<usize>) -> Box<[u8]> {
allocate_aligned_buffer(size)
}
#[cfg(target_os = "linux")]
pub fn current_numa_node() -> Option<usize> {
if let Ok(stat) = fs::read_to_string("/proc/self/stat") {
let fields: Vec<&str> = stat.split_whitespace().collect();
if let Some(cpu_str) = fields.get(38) {
if let Ok(cpu) = cpu_str.parse::<usize>() {
if let Ok(node) = get_cpu_numa_node(cpu) {
return Some(node);
}
}
}
}
#[cfg(target_arch = "x86_64")]
{
unsafe {
let mut cpu: libc::c_uint = 0;
let mut node: libc::c_uint = 0;
if libc::syscall(
libc::SYS_getcpu,
&mut cpu,
&mut node,
std::ptr::null_mut::<libc::c_void>(),
) == 0
{
return Some(node as usize);
}
}
}
None
}
#[cfg(target_os = "linux")]
fn get_cpu_numa_node(cpu: usize) -> Result<usize, std::io::Error> {
let path = format!("/sys/devices/system/cpu/cpu{cpu}/node");
if Path::new(&path).exists() {
let node_str = fs::read_to_string(&path)?;
node_str
.trim()
.parse()
.map_err(|_| std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid NUMA node"))
} else {
Ok(cpu / 8)
}
}
#[cfg(target_os = "linux")]
pub fn numa_node_count() -> usize {
if let Ok(entries) = fs::read_dir("/sys/devices/system/node") {
entries
.filter_map(|entry| entry.ok())
.filter(|entry| {
entry.file_name().to_string_lossy().starts_with("node")
&& entry
.file_name()
.to_string_lossy()
.chars()
.skip(4)
.all(|c| c.is_ascii_digit())
})
.count()
} else {
1 }
}
#[cfg(not(target_os = "linux"))]
pub fn numa_node_count() -> usize {
1
}
#[cfg(not(target_os = "linux"))]
pub fn is_numa_available() -> bool {
false
}
#[cfg(not(target_os = "linux"))]
pub fn current_numa_node() -> Option<usize> {
None
}