Skip to content

fix: correctly parse multiple cache instances via sysfs - #64

Open
dadealus wants to merge 1 commit into
level1techs:mainfrom
dadealus:fix-cpu-cache-topology
Open

fix: correctly parse multiple cache instances via sysfs#64
dadealus wants to merge 1 commit into
level1techs:mainfrom
dadealus:fix-cpu-cache-topology

Conversation

@dadealus

@dadealus dadealus commented Apr 9, 2026

Copy link
Copy Markdown

Summary

  • Fixes an issue where raw_cpuid only queried the core it was running on, leading to incomplete L3 cache reporting on asymmetric NUMA processors (like the Ryzen 9 9950X3D).
  • Replaces the single-threaded CPUID cache parser with a robust sysfs-based cache reader (/sys/devices/system/cpu/cpu*/cache/index*) that correctly aggregates topology across all distinct cache instances.

Description

While testing siomon on 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_cache function utilized the raw_cpuid crate, 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_sysfs function that acts similarly to lscpu. It iterates through the Linux kernel's sysfs cache directories and maps out every distinct instance using the shared_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.

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.
@wendelltron wendelltron self-assigned this Apr 24, 2026
@eous
eous requested review from Copilot and eous July 25, 2026 00:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ 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_list to 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 returns None for one of these sysfs files. Each metadata field should be populated independently when absent, rather than all being gated on ways_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_map stays empty and this function returns Some(CpuCache { ..None }), which then prevents falling back to CPUID. Returning None when 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.

Comment thread src/collectors/cpu.rs
@@ -1,4 +1,4 @@
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
Comment thread src/collectors/cpu.rs
Comment on lines +513 to +520
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();

Comment thread src/collectors/cpu.rs
size_str.parse::<u64>().unwrap_or(0)
};

*size_map.entry(key.clone()).or_default() += size_bytes;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants