scx_arena_selftests/
main.rs

1// Copyright (c) Meta Platforms, Inc. and affiliates.
2
3// This software may be used and distributed according to the terms of the
4// GNU General Public License version 2.
5mod bpf_skel;
6pub use bpf_skel::*;
7
8use std::mem::MaybeUninit;
9
10use anyhow::bail;
11use anyhow::Context;
12use anyhow::Result;
13
14use std::ffi::c_ulong;
15use std::ffi::c_void;
16
17use std::os::fd::AsFd;
18use std::os::fd::AsRawFd;
19use std::sync::Arc;
20
21use scx_utils::init_libbpf_logging;
22use scx_utils::Core;
23use scx_utils::Llc;
24use scx_utils::Topology;
25use scx_utils::NR_CPU_IDS;
26
27use simplelog::{ColorChoice, Config as SimplelogConfig, TermLogger, TerminalMode};
28
29use libbpf_rs::libbpf_sys;
30
31use libbpf_rs::skel::OpenSkel;
32use libbpf_rs::skel::SkelBuilder;
33use libbpf_rs::PrintLevel;
34use libbpf_rs::ProgramInput;
35
36const BPF_STDOUT: u32 = 1;
37const BPF_STDERR: u32 = 2;
38
39fn setup_arenas(skel: &mut BpfSkel<'_>) -> Result<()> {
40    const STATIC_ALLOC_PAGES_GRANULARITY: c_ulong = 512;
41    const TASK_SIZE: c_ulong = 42;
42
43    // Allocate the arena memory from the BPF side so userspace initializes it before starting
44    // the scheduler. Despite the function call's name this is neither a test nor a test run,
45    // it's the recommended way of executing SEC("syscall") probes.
46    let mut args = types::arena_init_args {
47        static_pages: STATIC_ALLOC_PAGES_GRANULARITY,
48        task_ctx_size: TASK_SIZE,
49    };
50
51    let input = ProgramInput {
52        context_in: Some(unsafe {
53            std::slice::from_raw_parts_mut(
54                &mut args as *mut _ as *mut u8,
55                std::mem::size_of_val(&args),
56            )
57        }),
58        ..Default::default()
59    };
60
61    let output = skel.progs.arena_init.test_run(input)?;
62    if output.return_value != 0 {
63        bail!(
64            "Could not initialize arenas, arena_init returned {}",
65            output.return_value as i32
66        );
67    }
68
69    Ok(())
70}
71
72fn setup_topology_node(skel: &mut BpfSkel<'_>, mask: &[u64]) -> Result<()> {
73    let mut args = types::arena_alloc_mask_args {
74        bitmap: 0 as c_ulong,
75    };
76
77    let input = ProgramInput {
78        context_in: Some(unsafe {
79            std::slice::from_raw_parts_mut(
80                &mut args as *mut _ as *mut u8,
81                std::mem::size_of_val(&args),
82            )
83        }),
84        ..Default::default()
85    };
86
87    let output = skel.progs.arena_alloc_mask.test_run(input)?;
88    if output.return_value != 0 {
89        bail!(
90            "Could not initialize arenas, setup_topology_node returned {}",
91            output.return_value as i32
92        );
93    }
94
95    let ptr = unsafe { std::mem::transmute::<u64, &mut [u64; 10]>(args.bitmap) };
96
97    let (valid_mask, _) = ptr.split_at_mut(mask.len());
98    valid_mask.clone_from_slice(mask);
99
100    let mut args = types::arena_topology_node_init_args {
101        bitmap: args.bitmap as c_ulong,
102        data_size: 0 as c_ulong,
103        id: 0 as c_ulong,
104    };
105
106    let input = ProgramInput {
107        context_in: Some(unsafe {
108            std::slice::from_raw_parts_mut(
109                &mut args as *mut _ as *mut u8,
110                std::mem::size_of_val(&args),
111            )
112        }),
113        ..Default::default()
114    };
115
116    let output = skel.progs.arena_topology_node_init.test_run(input)?;
117    if output.return_value != 0 {
118        bail!(
119            "arena_topology_node_init returned {}",
120            output.return_value as i32
121        );
122    }
123
124    Ok(())
125}
126
127fn setup_topology(skel: &mut BpfSkel<'_>) -> Result<()> {
128    let topo = Topology::new().expect("Failed to build host topology");
129
130    setup_topology_node(skel, topo.span.as_raw_slice())?;
131
132    for (_, node) in topo.nodes {
133        setup_topology_node(skel, node.span.as_raw_slice())?;
134    }
135
136    for (_, llc) in topo.all_llcs {
137        setup_topology_node(
138            skel,
139            Arc::<Llc>::into_inner(llc)
140                .expect("missing llc")
141                .span
142                .as_raw_slice(),
143        )?;
144    }
145
146    for (_, core) in topo.all_cores {
147        setup_topology_node(
148            skel,
149            Arc::<Core>::into_inner(core)
150                .expect("missing core")
151                .span
152                .as_raw_slice(),
153        )?;
154    }
155    for (_, cpu) in topo.all_cpus {
156        let mut mask = [0; 9];
157        mask[cpu.id.checked_shr(64).unwrap_or(0)] |= 1 << (cpu.id % 64);
158        setup_topology_node(skel, &mask)?;
159    }
160
161    Ok(())
162}
163
164fn print_stream(skel: &mut BpfSkel<'_>, stream_id: u32) -> () {
165    let mut buf = vec![0u8; 4096];
166    let name = if stream_id == 1 { "OUTPUT" } else { "ERROR" };
167    let mut started = false;
168
169    loop {
170        let ret = unsafe {
171            libbpf_sys::bpf_prog_stream_read(
172                skel.progs.arena_selftest.as_fd().as_raw_fd(),
173                stream_id,
174                buf.as_mut_ptr() as *mut c_void,
175                buf.len() as u32,
176                std::ptr::null_mut(),
177            )
178        };
179        if ret < 0 {
180            eprintln!("STREAM {} UNAVAILABLE (REQUIRES >= v6.17)", name);
181            return;
182        }
183
184        if !started {
185            println!("===BEGIN STREAM {}===", name);
186            started = true;
187        }
188
189        if ret == 0 {
190            break;
191        }
192
193        print!("{}", String::from_utf8_lossy(&buf[..ret as usize]));
194    }
195
196    println!("\n====END STREAM  {}====", name);
197}
198
199fn main() {
200    TermLogger::init(
201        simplelog::LevelFilter::Info,
202        SimplelogConfig::default(),
203        TerminalMode::Mixed,
204        ColorChoice::Auto,
205    )
206    .unwrap();
207
208    let mut open_object = MaybeUninit::uninit();
209    let mut builder = BpfSkelBuilder::default();
210
211    builder.obj_builder.debug(true);
212    init_libbpf_logging(Some(PrintLevel::Debug));
213
214    let mut skel = builder
215        .open(&mut open_object)
216        .context("Failed to open BPF program")
217        .unwrap();
218
219    skel.maps.rodata_data.as_mut().unwrap().nr_cpu_ids = *NR_CPU_IDS as u32;
220
221    let mut skel = skel.load().context("Failed to load BPF program").unwrap();
222
223    setup_arenas(&mut skel).unwrap();
224    setup_topology(&mut skel).unwrap();
225
226    let input = ProgramInput {
227        ..Default::default()
228    };
229
230    let output = skel.progs.arena_selftest.test_run(input).unwrap();
231    if output.return_value != 0 {
232        eprintln!(
233            "Selftest returned {}, please check bpf tracelog for more details.",
234            output.return_value as i32
235        );
236    } else {
237        println!("Selftest successful.");
238    }
239
240    print_stream(&mut skel, BPF_STDOUT);
241    print_stream(&mut skel, BPF_STDERR);
242}