fix: correctly parse multiple cache instances via sysfs - #64
Conversation
The raw_cpuid parser only queried the core it was running on, leading to incorrect topology maps on asymmetric processors like the Ryzen 9 9950X3D where one CCD has 96MB of L3 and the other has 32MB. This implements a sysfs-based cache reader similar to lscpu that correctly aggregates the topology across all distinct cache instances.
There was a problem hiding this comment.
⚠️ Not ready to approve
The current diff introduces a non-x86 build break (cfg-gated CacheLevel import) and likely regresses L1/L2 reporting by summing per-core caches into totals, plus it lacks test coverage for the new parsing/aggregation logic.
Pull request overview
This PR updates CPU cache detection to correctly handle systems with multiple distinct cache instances (e.g., asymmetric NUMA / multi-CCD parts) by preferring a sysfs-based cache inventory over single-core CPUID results.
Changes:
- Prefer a new sysfs cache reader (
/sys/devices/system/cpu/cpu*/cache/index*) over CPUID for cache topology/size reporting. - Aggregate cache instances using
shared_cpu_listto better reflect multi-instance L3 layouts. - Extend collector imports to support the new aggregation logic.
File summaries
| File | Description |
|---|---|
src/collectors/cpu.rs |
Switches cache collection to a sysfs-based enumerator to detect and aggregate multiple cache instances (notably L3) across CPUs/CCDs. |
Review details
Comments suppressed due to low confidence (2)
src/collectors/cpu.rs:557
- The
if !ways_map.contains_key(&key)guard prevents later cache instances from filling in missing metadata if the first instance returnsNonefor one of these sysfs files. Each metadata field should be populated independently when absent, rather than all being gated onways_map.
if !ways_map.contains_key(&key) {
if let Some(ways) = sysfs::read_u32_optional(&dir.join("ways_of_associativity")) {
ways_map.insert(key.clone(), ways);
}
if let Some(line) = sysfs::read_u32_optional(&dir.join("coherency_line_size")) {
src/collectors/cpu.rs:566
- If sysfs globbing finds directories but all entries get skipped (missing/invalid fields),
cache_mapstays empty and this function returnsSome(CpuCache { ..None }), which then prevents falling back to CPUID. ReturningNonewhen no cache entries were successfully parsed makes the fallback path reliable.
}
- Files reviewed: 1/1 changed files
- Comments generated: 3
- Review effort level: Low
Note
Your feedback helps us improve the quality of this feature.
Please use 👍 or 👎 to tell us whether this assessment is correct.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -1,4 +1,4 @@ | |||
| use std::collections::{BTreeMap, BTreeSet, HashMap}; | |||
| use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; | |||
| fn gather_cache_sysfs() -> Option<CpuCache> { | ||
| let mut cache_map: BTreeMap<(u8, String), HashSet<String>> = BTreeMap::new(); | ||
| let mut size_map: BTreeMap<(u8, String), u64> = BTreeMap::new(); | ||
| let mut ways_map: BTreeMap<(u8, String), u32> = BTreeMap::new(); | ||
| let mut line_map: BTreeMap<(u8, String), u32> = BTreeMap::new(); | ||
| let mut sets_map: BTreeMap<(u8, String), u32> = BTreeMap::new(); | ||
| let mut shared_map: BTreeMap<(u8, String), u32> = BTreeMap::new(); | ||
|
|
| size_str.parse::<u64>().unwrap_or(0) | ||
| }; | ||
|
|
||
| *size_map.entry(key.clone()).or_default() += size_bytes; |
Summary
raw_cpuidonly queried the core it was running on, leading to incomplete L3 cache reporting on asymmetric NUMA processors (like the Ryzen 9 9950X3D)./sys/devices/system/cpu/cpu*/cache/index*) that correctly aggregates topology across all distinct cache instances.Description
While testing
siomonon an ASUS X870E ProArt motherboard with a Ryzen 9 9950X3D, we noticed that it was only detecting 96MB of L3 cache instead of the full 128MB.The original
gather_cachefunction utilized theraw_cpuidcrate, which only reads the CPU instruction data for the thread the program starts on. On a 9950X3D, if it lands on the 3D V-Cache CCD, it reports 96MB and assumes it applies across the whole CPU, completely missing the 32MB standard L3 cache on the second CCD.This PR introduces a new
gather_cache_sysfsfunction that acts similarly tolscpu. It iterates through the Linux kernel's sysfs cache directories and maps out every distinct instance using theshared_cpu_list. This correctly aggregates the total L3 cache size and identifies the distinct instances across multiple NUMA nodes.Disclaimer: This Pull Request was built with AI assistance to help diagnose the issue and formulate the sysfs parsing logic.