use std::os::unix::process::CommandExt;
use std::process::{Child, Command};
use crate::platform::SwitchCounters;
use crate::topology::LogicalId;
use corescout_core::cpuset::CpuSet;
use corescout_core::error::{Error, Result};
fn words_for(max_cpu: u32) -> usize {
(max_cpu as usize / 64) + 1
}
fn to_mask(cpus: &CpuSet) -> Result<Vec<u64>> {
let max = cpus
.max()
.ok_or_else(|| Error::invalid("cannot set an empty CPU affinity mask"))?;
let mut mask = vec![0u64; words_for(max)];
for cpu in cpus.iter() {
mask[cpu as usize / 64] |= 1u64 << (cpu % 64);
}
Ok(mask)
}
fn from_mask(mask: &[u64]) -> CpuSet {
let mut set = CpuSet::new();
for (word_index, word) in mask.iter().enumerate() {
let mut bits = *word;
while bits != 0 {
let bit = bits.trailing_zeros() as usize;
set.insert((word_index * 64 + bit) as LogicalId);
bits &= bits - 1;
}
}
set
}
fn set_affinity(pid: libc::pid_t, cpus: &CpuSet) -> Result<()> {
let mask = to_mask(cpus)?;
let len = std::mem::size_of_val(&mask[..]);
let rc = unsafe {
libc::syscall(
libc::SYS_sched_setaffinity,
pid as libc::c_long,
len as libc::c_long,
mask.as_ptr(),
)
};
if rc < 0 {
return Err(Error::Syscall {
call: "sched_setaffinity",
errno: errno(),
});
}
Ok(())
}
fn get_affinity(pid: libc::pid_t) -> Result<CpuSet> {
let mut words = 16; loop {
let mut mask = vec![0u64; words];
let len = std::mem::size_of_val(&mask[..]);
let rc = unsafe {
libc::syscall(
libc::SYS_sched_getaffinity,
pid as libc::c_long,
len as libc::c_long,
mask.as_mut_ptr(),
)
};
if rc >= 0 {
return Ok(from_mask(&mask));
}
let err = errno();
if err == libc::EINVAL && words < 1024 {
words *= 2;
continue;
}
return Err(Error::Syscall {
call: "sched_getaffinity",
errno: err,
});
}
}
pub fn pin_current_thread(cpu: LogicalId) -> Result<()> {
let mut set = CpuSet::new();
set.insert(cpu);
set_affinity(0, &set)
}
pub fn set_current_thread_affinity(cpus: &CpuSet) -> Result<()> {
set_affinity(0, cpus)
}
pub fn current_thread_affinity() -> Result<CpuSet> {
get_affinity(0)
}
pub fn process_affinity() -> Result<CpuSet> {
let pid = unsafe { libc::getpid() };
get_affinity(pid)
}
pub fn thread_switch_counters() -> Option<SwitchCounters> {
let mut usage = std::mem::MaybeUninit::<libc::rusage>::uninit();
let rc = unsafe { libc::getrusage(libc::RUSAGE_THREAD, usage.as_mut_ptr()) };
if rc != 0 {
return None;
}
let usage = unsafe { usage.assume_init() };
Some(SwitchCounters {
voluntary: usage.ru_nvcsw.max(0) as u64,
involuntary: usage.ru_nivcsw.max(0) as u64,
})
}
pub fn spawn_with_affinity(command: &mut Command, cpus: &CpuSet) -> Result<Child> {
let mask = to_mask(cpus)?;
let len = std::mem::size_of_val(&mask[..]);
unsafe {
command.pre_exec(move || {
let rc = libc::syscall(
libc::SYS_sched_setaffinity,
0 as libc::c_long,
len as libc::c_long,
mask.as_ptr(),
);
if rc < 0 {
return Err(std::io::Error::last_os_error());
}
Ok(())
});
}
command
.spawn()
.map_err(|e| Error::io(command.get_program(), e))
}
fn errno() -> i32 {
std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mask_round_trips() {
let set: CpuSet = [0u32, 5, 63, 64, 130].into_iter().collect();
let mask = to_mask(&set).unwrap();
assert_eq!(mask.len(), 3, "cpu 130 needs a third 64-bit word");
assert_eq!(from_mask(&mask), set);
}
#[test]
fn empty_mask_is_rejected() {
assert!(to_mask(&CpuSet::new()).is_err());
}
#[test]
fn word_sizing() {
assert_eq!(words_for(0), 1);
assert_eq!(words_for(63), 1);
assert_eq!(words_for(64), 2);
assert_eq!(words_for(255), 4);
}
#[test]
fn reads_back_a_pin() {
let original = current_thread_affinity().expect("get affinity");
let first = original.iter().next().expect("at least one CPU");
pin_current_thread(first).expect("pin");
let now = current_thread_affinity().expect("get affinity");
assert_eq!(now.to_vec(), vec![first]);
set_current_thread_affinity(&original).expect("restore");
}
#[test]
fn switch_counters_are_monotonic() {
let a = thread_switch_counters().expect("RUSAGE_THREAD");
let b = thread_switch_counters().expect("RUSAGE_THREAD");
assert!(b.voluntary >= a.voluntary);
assert!(b.involuntary >= a.involuntary);
assert_eq!(b.delta(b), SwitchCounters::default());
}
}