use super::{CacheTopology, Level};
fn read_field(dir: &str, field: &str) -> Option<String> {
std::fs::read_to_string(format!("{dir}/{field}"))
.ok()
.map(|s| s.trim().to_string())
}
fn parse_size(s: &str) -> Option<usize> {
let s = s.trim();
let last = *s.as_bytes().last()?; let (num, mult): (&str, usize) = match last {
b'K' | b'k' => (&s[..s.len() - 1], 1024),
b'M' | b'm' => (&s[..s.len() - 1], 1024 * 1024),
b'G' | b'g' => (&s[..s.len() - 1], 1024 * 1024 * 1024),
_ => (s, 1),
};
num.trim().parse::<usize>().ok()?.checked_mul(mult)
}
fn count_cpu_list(s: &str) -> usize {
let mut n = 0usize;
for part in s.trim().split(',') {
let part = part.trim();
if part.is_empty() {
continue;
}
match part.split_once('-') {
Some((a, b)) => {
if let (Ok(a), Ok(b)) = (a.trim().parse::<usize>(), b.trim().parse::<usize>())
&& b >= a
{
n = n.saturating_add((b - a).saturating_add(1));
}
}
None if part.parse::<usize>().is_ok() => n += 1,
None => {}
}
}
n.max(1)
}
fn read_level(dir: &str) -> Option<(Level, usize)> {
let bytes = parse_size(&read_field(dir, "size")?)?;
let assoc = read_field(dir, "ways_of_associativity")?
.parse::<usize>()
.ok()?;
let line = read_field(dir, "coherency_line_size")?
.parse::<usize>()
.ok()?;
let shared = read_field(dir, "shared_cpu_list")
.map(|s| count_cpu_list(&s))
.unwrap_or(1);
(bytes > 0 && assoc > 0 && line > 0).then_some((
Level {
bytes,
assoc,
line,
shared_by: 1,
},
shared,
))
}
pub fn detect() -> Option<CacheTopology> {
const BASE: &str = "/sys/devices/system/cpu/cpu0/cache";
let mut l1d: Option<(Level, usize)> = None;
let mut l2: Option<(Level, usize)> = None;
let mut l3: Option<(Level, usize)> = None;
for i in 0..64 {
let dir = format!("{BASE}/index{i}");
let Some(level) = read_field(&dir, "level").and_then(|s| s.parse::<usize>().ok()) else {
break;
};
let ty = read_field(&dir, "type").unwrap_or_default();
let Some(entry) = read_level(&dir) else {
continue;
};
match level {
1 if ty == "Data" || ty == "Unified" => {
l1d.get_or_insert(entry);
}
2 => {
l2.get_or_insert(entry);
}
3 => {
l3.get_or_insert(entry);
}
_ => {}
}
}
let (mut l1d, l1d_shared) = l1d?;
let (mut l2, l2_shared) = l2?;
let smt = l1d_shared.max(1);
l1d.shared_by = 1;
l2.shared_by = (l2_shared / smt).max(1);
let l3 = l3.map(|(mut l, _)| {
l.shared_by = 1;
l
});
Some(CacheTopology { l1d, l2, l3 })
}
#[cfg(all(
test,
target_os = "linux",
any(target_arch = "x86", target_arch = "x86_64"),
not(miri)
))]
mod tests {
#[test]
fn sysfs_agrees_with_cpuid() {
let (Some(sf), Some(cp)) = (super::detect(), super::super::cpuid::detect()) else {
eprintln!("skipping: a backend returned None (CPUID masked or /sys hidden)");
return;
};
assert_eq!(sf.l1d.bytes, cp.l1d.bytes, "L1d size mismatch");
assert_eq!(sf.l2.bytes, cp.l2.bytes, "L2 size mismatch");
assert_eq!(sf.l1d.line, cp.l1d.line, "L1d line mismatch");
assert_eq!(sf.l2.line, cp.l2.line, "L2 line mismatch");
assert_eq!(sf.l1d.shared_by, 1, "L1d shared_by must be 1");
assert!(
sf.l2.shared_by >= 1,
"L2 shared_by must be a positive count"
);
if let Some(s3) = sf.l3 {
assert_eq!(s3.shared_by, 1, "L3 shared_by must be 1");
}
}
#[test]
fn parse_size_all_arms() {
assert_eq!(super::parse_size("48K"), Some(48 * 1024));
assert_eq!(super::parse_size("1024k"), Some(1024 * 1024));
assert_eq!(super::parse_size("32M"), Some(32 * 1024 * 1024));
assert_eq!(super::parse_size("8m"), Some(8 * 1024 * 1024));
assert_eq!(super::parse_size("2G"), Some(2 * 1024 * 1024 * 1024));
assert_eq!(super::parse_size("1g"), Some(1024 * 1024 * 1024));
assert_eq!(super::parse_size("123"), Some(123)); assert_eq!(super::parse_size(" 64K "), Some(64 * 1024)); assert_eq!(super::parse_size(""), None); assert_eq!(super::parse_size("K"), None); assert_eq!(super::parse_size("notanumber"), None);
assert_eq!(super::parse_size(&format!("{}G", usize::MAX)), None);
}
#[test]
fn count_cpu_list_all_arms() {
assert_eq!(super::count_cpu_list("0-7,16-23"), 16); assert_eq!(super::count_cpu_list("0-7"), 8); assert_eq!(super::count_cpu_list("0,16"), 2); assert_eq!(super::count_cpu_list("3"), 1); assert_eq!(super::count_cpu_list(""), 1); assert_eq!(super::count_cpu_list(" "), 1); assert_eq!(super::count_cpu_list("5-2"), 1); assert_eq!(super::count_cpu_list("0-3,,8"), 5); }
}