1mod 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;
16use std::io::IsTerminal;
17
18use std::os::fd::AsFd;
19use std::os::fd::AsRawFd;
20use std::sync::Arc;
21
22use clap::Parser;
23
24use scx_utils::init_libbpf_logging;
25use scx_utils::Core;
26use scx_utils::Llc;
27use scx_utils::Topology;
28use scx_utils::NR_CPU_IDS;
29
30use simplelog::{ColorChoice, Config as SimplelogConfig, TermLogger, TerminalMode};
31
32use libbpf_rs::libbpf_sys;
33
34use libbpf_rs::skel::OpenSkel;
35use libbpf_rs::skel::SkelBuilder;
36use libbpf_rs::PrintLevel;
37use libbpf_rs::ProgramInput;
38
39const BPF_STDOUT: u32 = 1;
40const BPF_STDERR: u32 = 2;
41
42const COLOR_BRIGHT_GREEN: &str = "\x1b[92m";
43const COLOR_BRIGHT_RED: &str = "\x1b[91m";
44const COLOR_RESET: &str = "\x1b[0m";
45
46fn colorize(text: &str, color: &str, is_tty: bool) -> String {
47 if is_tty {
48 format!("{}{}{}", color, text, COLOR_RESET)
49 } else {
50 text.to_string()
51 }
52}
53
54#[repr(u32)]
58#[allow(non_camel_case_types)]
59enum SelfTestId {
60 #[allow(dead_code)]
61 SCX_SELFTEST_ID_ALL = 0,
62 SCX_SELFTEST_ID_ATQ = 1,
63 SCX_SELFTEST_ID_BTREE = 2,
64 SCX_SELFTEST_ID_LVQUEUE = 3,
65 SCX_SELFTEST_ID_MINHEAP = 4,
66 SCX_SELFTEST_ID_RBTREE = 5,
67 SCX_SELFTEST_ID_TOPOLOGY = 6,
68}
69
70fn available_tests() -> String {
71 TEST_CASES
72 .iter()
73 .map(|(name, _)| format!(" {}", name))
74 .collect::<Vec<_>>()
75 .join("\n")
76}
77
78const TEST_CASES: &[(&str, u32)] = &[
79 ("atq", SelfTestId::SCX_SELFTEST_ID_ATQ as u32),
80 ("btree", SelfTestId::SCX_SELFTEST_ID_BTREE as u32),
81 ("lvqueue", SelfTestId::SCX_SELFTEST_ID_LVQUEUE as u32),
82 ("minheap", SelfTestId::SCX_SELFTEST_ID_MINHEAP as u32),
83 ("rbtree", SelfTestId::SCX_SELFTEST_ID_RBTREE as u32),
84 ("topology", SelfTestId::SCX_SELFTEST_ID_TOPOLOGY as u32),
85];
86
87#[derive(Debug, Parser)]
88#[clap(about = "scx_arena library selftests")]
89struct Opts {
90 #[clap(long)]
92 list: bool,
93
94 #[clap(long = "test", value_name = "NAME", num_args(1..))]
98 tests: Vec<String>,
99}
100
101fn setup_arenas(skel: &mut BpfSkel<'_>) -> Result<()> {
102 const STATIC_ALLOC_PAGES_GRANULARITY: c_ulong = 512;
103 const TASK_SIZE: c_ulong = 42;
104
105 let mut args = types::arena_init_args {
109 static_pages: STATIC_ALLOC_PAGES_GRANULARITY,
110 task_ctx_size: TASK_SIZE,
111 };
112
113 let input = ProgramInput {
114 context_in: Some(unsafe {
115 std::slice::from_raw_parts_mut(
116 &mut args as *mut _ as *mut u8,
117 std::mem::size_of_val(&args),
118 )
119 }),
120 ..Default::default()
121 };
122
123 let output = skel.progs.arena_init.test_run(input)?;
124 if output.return_value != 0 {
125 bail!(
126 "Could not initialize arenas, arena_init returned {}",
127 output.return_value as i32
128 );
129 }
130
131 Ok(())
132}
133
134fn setup_topology_node(skel: &mut BpfSkel<'_>, mask: &[u64]) -> Result<()> {
135 let mut args = types::arena_alloc_mask_args {
136 bitmap: 0 as c_ulong,
137 };
138
139 let input = ProgramInput {
140 context_in: Some(unsafe {
141 std::slice::from_raw_parts_mut(
142 &mut args as *mut _ as *mut u8,
143 std::mem::size_of_val(&args),
144 )
145 }),
146 ..Default::default()
147 };
148
149 let output = skel.progs.arena_alloc_mask.test_run(input)?;
150 if output.return_value != 0 {
151 bail!(
152 "Could not initialize arenas, setup_topology_node returned {}",
153 output.return_value as i32
154 );
155 }
156
157 let ptr = unsafe {
158 &mut *std::ptr::with_exposed_provenance_mut::<[u64; 10]>(args.bitmap.try_into().unwrap())
159 };
160
161 let (valid_mask, _) = ptr.split_at_mut(mask.len());
162 valid_mask.clone_from_slice(mask);
163
164 let mut args = types::arena_topology_node_init_args {
165 bitmap: args.bitmap as c_ulong,
166 data_size: 0 as c_ulong,
167 id: 0 as c_ulong,
168 };
169
170 let input = ProgramInput {
171 context_in: Some(unsafe {
172 std::slice::from_raw_parts_mut(
173 &mut args as *mut _ as *mut u8,
174 std::mem::size_of_val(&args),
175 )
176 }),
177 ..Default::default()
178 };
179
180 let output = skel.progs.arena_topology_node_init.test_run(input)?;
181 if output.return_value != 0 {
182 bail!(
183 "arena_topology_node_init returned {}",
184 output.return_value as i32
185 );
186 }
187
188 Ok(())
189}
190
191fn setup_topology(skel: &mut BpfSkel<'_>) -> Result<()> {
192 let topo = Topology::new().expect("Failed to build host topology");
193
194 setup_topology_node(skel, topo.span.as_raw_slice())?;
195
196 for (_, node) in topo.nodes {
197 setup_topology_node(skel, node.span.as_raw_slice())?;
198 }
199
200 for (_, llc) in topo.all_llcs {
201 setup_topology_node(
202 skel,
203 Arc::<Llc>::into_inner(llc)
204 .expect("missing llc")
205 .span
206 .as_raw_slice(),
207 )?;
208 }
209
210 for (_, core) in topo.all_cores {
211 setup_topology_node(
212 skel,
213 Arc::<Core>::into_inner(core)
214 .expect("missing core")
215 .span
216 .as_raw_slice(),
217 )?;
218 }
219 for (_, cpu) in topo.all_cpus {
220 let mut mask = [0; 9];
221 mask[cpu.id.checked_shr(64).unwrap_or(0)] |= 1 << (cpu.id % 64);
222 setup_topology_node(skel, &mask)?;
223 }
224
225 Ok(())
226}
227
228fn print_stream(skel: &mut BpfSkel<'_>, stream_id: u32) -> () {
229 let prog_fd = skel.progs.arena_selftest.as_fd().as_raw_fd();
230 let mut buf = vec![0u8; 4096];
231 let name = if stream_id == 1 { "OUTPUT" } else { "ERROR" };
232 let mut started = false;
233
234 loop {
235 let ret = unsafe {
236 libbpf_sys::bpf_prog_stream_read(
237 prog_fd,
238 stream_id,
239 buf.as_mut_ptr() as *mut c_void,
240 buf.len() as u32,
241 std::ptr::null_mut(),
242 )
243 };
244 if ret < 0 {
245 eprintln!("STREAM {} UNAVAILABLE (REQUIRES >= v6.17)", name);
246 return;
247 }
248
249 if !started {
250 println!("===BEGIN STREAM {}===", name);
251 started = true;
252 }
253
254 if ret == 0 {
255 break;
256 }
257
258 print!("{}", String::from_utf8_lossy(&buf[..ret as usize]));
259 }
260
261 println!("\n====END STREAM {}====", name);
262}
263
264fn run_test_by_name(skel: &mut BpfSkel<'_>, name: &str) -> Result<i32> {
267 let id = TEST_CASES
268 .iter()
269 .find(|(n, _)| *n == name)
270 .map(|(_, id)| *id)
271 .ok_or_else(|| {
272 anyhow::anyhow!(
273 "Unknown test: '{}'. Use --list to see available tests.",
274 name
275 )
276 })?;
277
278 skel.maps.bss_data.as_mut().unwrap().selftest_run_id = id;
279
280 let input = ProgramInput {
281 ..Default::default()
282 };
283 let output = skel.progs.arena_selftest.test_run(input)?;
284
285 Ok(output.return_value as i32)
286}
287
288fn main() {
289 TermLogger::init(
290 simplelog::LevelFilter::Info,
291 SimplelogConfig::default(),
292 TerminalMode::Mixed,
293 ColorChoice::Auto,
294 )
295 .unwrap();
296
297 let opts = Opts::parse();
298
299 if opts.list {
300 println!("Available test cases:\n{}", available_tests());
301 return;
302 }
303
304 for name in &opts.tests {
306 if !TEST_CASES.iter().any(|(n, _)| *n == name.as_str()) {
307 eprintln!(
308 "Unknown test: '{}'.\nAvailable tests:\n{}",
309 name,
310 available_tests()
311 );
312 std::process::exit(1);
313 }
314 }
315
316 let mut open_object = MaybeUninit::uninit();
317 let mut builder = BpfSkelBuilder::default();
318
319 builder.obj_builder.debug(true);
320 init_libbpf_logging(Some(PrintLevel::Debug));
321
322 let mut skel = builder
323 .open(&mut open_object)
324 .context("Failed to open BPF program")
325 .unwrap();
326
327 skel.maps.rodata_data.as_mut().unwrap().nr_cpu_ids = *NR_CPU_IDS as u32;
328
329 let mut skel = skel.load().context("Failed to load BPF program").unwrap();
330
331 setup_arenas(&mut skel).unwrap();
332 setup_topology(&mut skel).unwrap();
333
334 let to_run: Vec<&str> = if opts.tests.is_empty() {
335 TEST_CASES.iter().map(|(n, _)| *n).collect()
336 } else {
337 opts.tests.iter().map(String::as_str).collect()
338 };
339
340 let stdout_tty = std::io::stdout().is_terminal();
341 let stderr_tty = std::io::stderr().is_terminal();
342 let pass_label = colorize("[ PASS ]", COLOR_BRIGHT_GREEN, stdout_tty);
343 let fail_label = colorize("[ FAIL ]", COLOR_BRIGHT_RED, stderr_tty);
344
345 let mut any_failed = false;
346 for &name in &to_run {
347 match run_test_by_name(&mut skel, name) {
348 Ok(0) => println!("{} {}", pass_label, name),
349 Ok(ret) => {
350 eprintln!("{} {} (returned {})", fail_label, name, ret);
351 any_failed = true;
352 }
353 Err(e) => {
354 eprintln!("{} {} (error: {})", fail_label, name, e);
355 any_failed = true;
356 }
357 }
358
359 print_stream(&mut skel, BPF_STDOUT);
360 print_stream(&mut skel, BPF_STDERR);
361 }
362
363 if any_failed {
364 eprintln!(
365 "{}",
366 colorize(
367 "One or more selftests failed.",
368 COLOR_BRIGHT_RED,
369 stderr_tty
370 )
371 );
372 std::process::exit(1);
373 } else {
374 println!(
375 "{}",
376 colorize("All selftests passed.", COLOR_BRIGHT_GREEN, stdout_tty)
377 );
378 }
379}