pub fn select_optimal_cpu() -> Option<usize> {
use std::fs;
let allowed_cpus = get_allowed_cpus();
if allowed_cpus.is_empty() {
log::warn!("Could not detect allowed CPUs via sched_getaffinity. Using total fallback.");
}
let cpu_dir = fs::read_dir("/sys/devices/system/cpu").ok()?;
let mut cpus: Vec<usize> = cpu_dir
.filter_map(|entry| {
let name = entry.ok()?.file_name();
let name = name.to_str()?;
let cpu_idx = name.strip_prefix("cpu")?.parse::<usize>().ok()?;
if !allowed_cpus.is_empty() && !allowed_cpus.contains(&cpu_idx) {
return None;
}
Some(cpu_idx)
})
.collect();
if cpus.is_empty() {
return None;
}
cpus.sort_unstable();
let capacities: Vec<(usize, u64)> = cpus
.iter()
.map(|&cpu| {
let path = format!("/sys/devices/system/cpu/cpu{cpu}/cpu_capacity");
let cap = fs::read_to_string(path)
.ok()
.and_then(|s| s.trim().parse::<u64>().ok())
.unwrap_or(1024); (cpu, cap)
})
.collect();
let irq_totals = parse_interrupts_per_cpu(cpus.len());
capacities
.iter()
.map(|&(cpu, cap)| {
let irqs = irq_totals.get(cpu).copied().unwrap_or(u64::MAX);
(cpu, cap, irqs)
})
.max_by(|a, b| {
a.1.cmp(&b.1)
.then_with(|| b.2.cmp(&a.2))
.then_with(|| a.0.cmp(&b.0))
})
.map(|(cpu, _, _)| cpu)
}
pub fn get_allowed_cpus() -> Vec<usize> {
let mut allowed = Vec::new();
unsafe {
let mut cpuset: libc::cpu_set_t = std::mem::zeroed();
libc::CPU_ZERO(&mut cpuset);
if libc::sched_getaffinity(0, std::mem::size_of::<libc::cpu_set_t>(), &mut cpuset) == 0 {
for i in 0..libc::CPU_SETSIZE as usize {
if libc::CPU_ISSET(i, &cpuset) {
allowed.push(i);
}
}
}
}
allowed
}
pub fn parse_interrupts_per_cpu(num_cpus: usize) -> Vec<u64> {
use std::fs::File;
use std::io::{BufRead, BufReader};
let mut totals = vec![0u64; num_cpus];
let file = match File::open("/proc/interrupts") {
Ok(f) => f,
Err(_) => return totals,
};
let reader = BufReader::new(file);
for line_result in reader.lines().skip(1) {
let line = match line_result {
Ok(l) => l,
Err(_) => break,
};
let trimmed = line.trim_start();
let irq_end = trimmed.find(':').unwrap_or(0);
if irq_end == 0 {
continue;
}
if !trimmed[..irq_end]
.trim()
.bytes()
.all(|b| b.is_ascii_digit())
{
continue;
}
let after_colon = match trimmed.get(irq_end + 1..) {
Some(s) => s,
None => continue,
};
for (cpu_idx, token) in after_colon.split_whitespace().enumerate() {
if cpu_idx >= num_cpus {
break;
}
if let Ok(count) = token.parse::<u64>() {
totals[cpu_idx] += count;
} else {
break;
}
}
}
totals
}