use super::MAX_PROCESSOR_ID;
use crate::topology::types::CoreId;
pub(crate) fn detect_core_ids(logical_processors: usize) -> Option<Box<[CoreId]>> {
#[cfg(target_os = "linux")]
{
linux::detect(logical_processors)
}
#[cfg(windows)]
{
cores_from_processor_records(
&super::efficiency::windows::core_records()?,
logical_processors,
)
}
}
#[cfg(any(test, windows))]
fn cores_from_processor_records(bytes: &[u8], logical_processors: usize) -> Option<Box<[CoreId]>> {
if logical_processors == 0 || logical_processors > MAX_PROCESSOR_ID {
return None;
}
let entries = super::efficiency::records::parse_processor_cores(bytes)?;
let mut raw: Vec<Option<u32>> = vec![None; logical_processors];
for entry in entries {
let slot = raw.get_mut(usize::try_from(entry.processor).ok()?)?;
match slot {
Some(existing) if *existing != entry.core => return None,
_ => *slot = Some(entry.core),
}
}
let raw = raw.into_iter().collect::<Option<Vec<u32>>>()?;
dense_core_ids(&raw)
}
#[cfg(all(feature = "std", any(target_os = "linux", all(test, windows))))]
fn cores_from_sibling_lists(
lists: &[Option<&str>],
logical_processors: usize,
) -> Option<Box<[CoreId]>> {
if logical_processors == 0
|| logical_processors > MAX_PROCESSOR_ID
|| lists.len() != logical_processors
{
return None;
}
let parsed: Vec<Vec<u32>> = lists
.iter()
.map(|list| list.map(super::parse_cpu_list))
.collect::<Option<Vec<_>>>()?;
let mut raw = Vec::with_capacity(logical_processors);
for (processor, siblings) in parsed.iter().enumerate() {
let processor = u32::try_from(processor).ok()?;
if siblings.is_empty() || !siblings.contains(&processor) {
return None;
}
let mut lowest = processor;
for &sibling in siblings {
let sibling_list = parsed.get(usize::try_from(sibling).ok()?)?;
if !sibling_list.contains(&processor) {
return None;
}
lowest = lowest.min(sibling);
}
raw.push(lowest);
}
dense_core_ids(&raw)
}
fn dense_core_ids(raw: &[u32]) -> Option<Box<[CoreId]>> {
let mut dense_of_raw: Vec<Option<u32>> = vec![None; raw.len()];
let mut next = 0u32;
let mut ids = Vec::with_capacity(raw.len());
for &value in raw {
let slot = dense_of_raw.get_mut(usize::try_from(value).ok()?)?;
if slot.is_none() {
*slot = Some(next);
next = next.checked_add(1)?;
}
ids.push(CoreId::new((*slot)?));
}
(!ids.is_empty()).then(|| ids.into_boxed_slice())
}
#[cfg(target_os = "linux")]
mod linux {
use super::{cores_from_sibling_lists, MAX_PROCESSOR_ID};
use crate::topology::types::CoreId;
use std::fs;
const CPU_ROOT: &str = "/sys/devices/system/cpu";
pub(super) fn detect(logical_processors: usize) -> Option<Box<[CoreId]>> {
if logical_processors == 0 || logical_processors > MAX_PROCESSOR_ID {
return None;
}
let lists: Vec<Option<String>> = (0..logical_processors)
.map(|processor| {
fs::read_to_string(format!(
"{CPU_ROOT}/cpu{processor}/topology/thread_siblings_list"
))
.ok()
})
.collect();
let lists: Vec<Option<&str>> = lists.iter().map(|list| list.as_deref()).collect();
cores_from_sibling_lists(&lists, logical_processors)
}
}
#[cfg(test)]
mod tests {
use super::super::efficiency::records::fixtures::{
cache_record, core_record, homogeneous_host_buffer, hybrid_host_buffer,
};
use super::{cores_from_processor_records, dense_core_ids};
use crate::topology::types::CoreId;
fn ids(table: &[CoreId]) -> Vec<u32> {
table.iter().map(|id| id.get()).collect()
}
#[test]
fn two_thread_cores_pair_their_processors() {
let table = cores_from_processor_records(&homogeneous_host_buffer(), 16)
.expect("eight two-thread cores form a table");
assert_eq!(
ids(&table),
vec![0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7]
);
}
#[test]
fn single_thread_cores_are_present_not_absent() {
let table = cores_from_processor_records(&hybrid_host_buffer(), 24)
.expect("24 single-thread cores form a table");
assert_eq!(ids(&table), (0..24).collect::<Vec<u32>>());
}
#[test]
fn core_ids_follow_lowest_processor_not_record_order() {
let records = [
core_record(0, &[(0, 0b1100)]),
cache_record(),
core_record(0, &[(0, 0b0011)]),
]
.concat();
let table = cores_from_processor_records(&records, 4).expect("two cores");
assert_eq!(ids(&table), vec![0, 0, 1, 1]);
}
#[test]
fn a_processor_claimed_by_two_records_is_absent() {
let records = [
core_record(0, &[(0, 0b0011)]),
core_record(0, &[(0, 0b0010)]),
]
.concat();
assert_eq!(cores_from_processor_records(&records, 2), None);
}
#[test]
fn records_that_do_not_cover_every_processor_are_absent() {
let records = core_record(0, &[(0, 0b0011)]);
assert_eq!(cores_from_processor_records(&records, 3), None);
assert_eq!(cores_from_processor_records(&records, 0), None);
let gapped = [core_record(0, &[(0, 0b11)]), core_record(0, &[(1, 0b11)])].concat();
assert_eq!(cores_from_processor_records(&gapped, 66), None);
}
#[test]
fn dense_ids_are_by_first_appearance() {
let table = dense_core_ids(&[3, 3, 1, 1, 3, 5]).expect("three cores");
assert_eq!(ids(&table), vec![0, 0, 1, 1, 0, 2]);
assert_eq!(dense_core_ids(&[]), None);
assert_eq!(dense_core_ids(&[0, 99]), None);
}
#[cfg(any(target_os = "linux", windows))]
mod sibling_lists {
use super::super::cores_from_sibling_lists;
use super::ids;
#[test]
fn symmetric_sibling_lists_pair_their_processors() {
let lists = [Some("0-1"), Some("0,1"), Some("2-3"), Some("2-3")];
let table = cores_from_sibling_lists(&lists, 4).expect("two cores");
assert_eq!(ids(&table), vec![0, 0, 1, 1]);
}
#[test]
fn singleton_lists_are_one_processor_per_core() {
let lists = [Some("0"), Some("1"), Some("2")];
let table = cores_from_sibling_lists(&lists, 3).expect("three cores");
assert_eq!(ids(&table), vec![0, 1, 2]);
}
#[test]
fn an_absent_file_or_asymmetric_list_is_absent() {
assert_eq!(cores_from_sibling_lists(&[Some("0-1"), None], 2), None);
assert_eq!(cores_from_sibling_lists(&[Some("0"), Some("0-1")], 2), None);
assert_eq!(cores_from_sibling_lists(&[Some("1"), Some("1")], 2), None);
}
#[test]
fn members_beyond_the_snapshot_are_absent() {
assert_eq!(cores_from_sibling_lists(&[Some("0,5")], 1), None);
assert_eq!(cores_from_sibling_lists(&[Some("0")], 2), None);
}
}
}