Skip to main content

scx_flow/
carriage.rs

1// SPDX-License-Identifier: GPL-2.0
2// Copyright (c) 2026 Galih Tama <galpt@v.recipes>
3//
4// Topology discovery: reads per-CPU max frequencies, LLC IDs, and SMT
5// status from sysfs and writes them into BPF BSS arrays.
6
7use std::path::Path;
8
9use anyhow::{Context, Result};
10use log::info;
11
12use scx_utils::Topology;
13
14pub fn init_topology(skel: &mut crate::BpfSkel<'_>) -> Result<()> {
15    let topo = Topology::new().context("Failed to read CPU topology")?;
16    let nr_cpus = topo.all_cpus.len();
17    let bss = skel
18        .maps
19        .bss_data
20        .as_mut()
21        .context("bss_data missing — BPF object has no .bss section")?;
22
23    info!(
24        "Discovered {} CPUs — reading max frequencies, LLC topology, SMT",
25        nr_cpus
26    );
27
28    let mut core_to_cpus: Vec<Vec<usize>> = vec![Vec::new(); 1024];
29    let mut system_total_khz: u64 = 0;
30
31    for (id, cpu) in &topo.all_cpus {
32        if *id >= 1024 {
33            log::warn!("CPU {} exceeds max supported (1024), skipping", id);
34            continue;
35        }
36        let freq_khz = cpu.max_freq as u64;
37        let llc_id = read_cpu_llc_id(*id);
38        let core_id = read_cpu_core_id(*id);
39
40        bss.per_cpu_max_freq_khz[*id] = freq_khz;
41        bss.per_cpu_llc_id[*id] = llc_id;
42
43        if core_id < 1024 {
44            core_to_cpus[core_id].push(*id);
45        }
46
47        info!(
48            "  CPU {:3}: max_freq = {:>6} MHz, LLC = {}, core_id = {}",
49            id,
50            freq_khz / 1000,
51            llc_id,
52            core_id,
53        );
54    }
55
56    /* Set per_cpu_sibling_count for all CPUs and mark SMT secondaries.
57     * The first CPU per core_id is primary; subsequent ones are secondary.
58     * sibling_count is set for ALL CPUs (including non-SMT, where it's 1)
59     * so the bandwidth model works correctly for every CPU. */
60    for (core_id, cpus) in core_to_cpus.iter().enumerate() {
61        let count = cpus.len();
62        if count > 1 {
63            for (j, &cpu_id) in cpus.iter().enumerate() {
64                bss.per_cpu_sibling_count[cpu_id] = count as u64;
65                if j == 0 {
66                    info!(
67                        "  CPU {:3}: primary (core {}: {} CPUs)",
68                        cpu_id, core_id, count
69                    );
70                } else {
71                    bss.per_cpu_is_smt[cpu_id] = 1;
72                    info!("  CPU {:3}: SMT sibling (core {})", cpu_id, core_id);
73                }
74            }
75        } else if count == 1 {
76            bss.per_cpu_sibling_count[cpus[0]] = 1;
77        }
78    }
79
80    for (id, cpu) in &topo.all_cpus {
81        let freq_khz = cpu.max_freq as u64;
82        let core_id = read_cpu_core_id(*id);
83        let siblings = if core_id < 1024 {
84            core_to_cpus[core_id].len()
85        } else {
86            1
87        };
88        let thread_bw = if siblings > 1 {
89            freq_khz / siblings as u64
90        } else {
91            freq_khz
92        };
93        system_total_khz += thread_bw;
94    }
95    bss.system_total_khz = system_total_khz;
96
97    info!(
98        "  System total effective bandwidth: {} MHz ({} cores, SMT halved)",
99        system_total_khz / 1000,
100        nr_cpus,
101    );
102
103    Ok(())
104}
105
106fn read_cpu_llc_id(cpu: usize) -> u64 {
107    let p = format!("/sys/devices/system/cpu/cpu{}/cache/index3/id", cpu);
108    let path = Path::new(&p);
109    if path.exists() {
110        return std::fs::read_to_string(path)
111            .ok()
112            .and_then(|s| s.trim().parse::<u64>().ok())
113            .unwrap_or(0);
114    }
115    let fb = format!("/sys/devices/system/cpu/cpu{}/topology/llc_id", cpu);
116    let fb_path = Path::new(&fb);
117    if fb_path.exists() {
118        return std::fs::read_to_string(fb_path)
119            .ok()
120            .and_then(|s| s.trim().parse::<u64>().ok())
121            .unwrap_or(0);
122    }
123    0
124}
125
126fn read_cpu_core_id(cpu: usize) -> usize {
127    let p = format!("/sys/devices/system/cpu/cpu{}/topology/core_id", cpu);
128    let path = Path::new(&p);
129    if !path.exists() {
130        log::warn!("CPU {}: topology/core_id not found", cpu);
131        return 0;
132    }
133    std::fs::read_to_string(path)
134        .ok()
135        .and_then(|s| s.trim().parse::<usize>().ok())
136        .unwrap_or(0)
137}