1mod bpf_skel;
6pub use bpf_skel::*;
7
8use std::mem::MaybeUninit;
9
10use anyhow::Context;
11use anyhow::Result;
12use anyhow::bail;
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::Core;
25use scx_utils::Llc;
26use scx_utils::NR_CPU_IDS;
27use scx_utils::Topology;
28use scx_utils::init_libbpf_logging;
29
30use simplelog::{ColorChoice, Config as SimplelogConfig, TermLogger, TerminalMode};
31
32use libbpf_rs::libbpf_sys;
33
34use libbpf_rs::PrintLevel;
35use libbpf_rs::ProgramInput;
36use libbpf_rs::skel::OpenSkel;
37use libbpf_rs::skel::SkelBuilder;
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 task_ctx_align: 0,
112 };
113
114 let input = ProgramInput {
115 context_in: Some(unsafe {
116 std::slice::from_raw_parts_mut(
117 &mut args as *mut _ as *mut u8,
118 std::mem::size_of_val(&args),
119 )
120 }),
121 ..Default::default()
122 };
123
124 let output = skel.progs.arena_init.test_run(input)?;
125 if output.return_value != 0 {
126 bail!(
127 "Could not initialize arenas, arena_init returned {}",
128 output.return_value as i32
129 );
130 }
131
132 Ok(())
133}
134
135fn setup_topology_node(skel: &mut BpfSkel<'_>, mask: &[u64]) -> Result<()> {
136 let mut args = types::arena_alloc_mask_args {
137 bitmap: 0 as c_ulong,
138 };
139
140 let input = ProgramInput {
141 context_in: Some(unsafe {
142 std::slice::from_raw_parts_mut(
143 &mut args as *mut _ as *mut u8,
144 std::mem::size_of_val(&args),
145 )
146 }),
147 ..Default::default()
148 };
149
150 let output = skel.progs.arena_alloc_mask.test_run(input)?;
151 if output.return_value != 0 {
152 bail!(
153 "Could not initialize arenas, setup_topology_node returned {}",
154 output.return_value as i32
155 );
156 }
157
158 let ptr = unsafe {
159 &mut *std::ptr::with_exposed_provenance_mut::<[u64; 10]>(args.bitmap.try_into().unwrap())
160 };
161
162 let (valid_mask, _) = ptr.split_at_mut(mask.len());
163 valid_mask.clone_from_slice(mask);
164
165 let mut args = types::arena_topology_node_init_args {
166 bitmap: args.bitmap as c_ulong,
167 data_size: 0 as c_ulong,
168 id: 0 as c_ulong,
169 };
170
171 let input = ProgramInput {
172 context_in: Some(unsafe {
173 std::slice::from_raw_parts_mut(
174 &mut args as *mut _ as *mut u8,
175 std::mem::size_of_val(&args),
176 )
177 }),
178 ..Default::default()
179 };
180
181 let output = skel.progs.arena_topology_node_init.test_run(input)?;
182 if output.return_value != 0 {
183 bail!(
184 "arena_topology_node_init returned {}",
185 output.return_value as i32
186 );
187 }
188
189 Ok(())
190}
191
192fn setup_topology(skel: &mut BpfSkel<'_>) -> Result<()> {
193 let topo = Topology::new().expect("Failed to build host topology");
194
195 let max_children: [u32; 5] = [
199 topo.nodes.len() as u32,
200 topo.nodes.values().map(|n| n.llcs.len()).max().unwrap_or(0) as u32,
201 topo.all_llcs
202 .values()
203 .map(|l| l.cores.len())
204 .max()
205 .unwrap_or(0) as u32,
206 topo.all_cores
207 .values()
208 .map(|c| c.cpus.len())
209 .max()
210 .unwrap_or(0) as u32,
211 0,
212 ];
213 let mut init_args = types::arena_topology_init_args { max_children };
214 let init_input = ProgramInput {
215 context_in: Some(unsafe {
216 std::slice::from_raw_parts_mut(
217 &mut init_args as *mut _ as *mut u8,
218 std::mem::size_of_val(&init_args),
219 )
220 }),
221 ..Default::default()
222 };
223 let output = skel.progs.arena_topology_init.test_run(init_input)?;
224 if output.return_value != 0 {
225 bail!(
226 "arena_topology_init returned {}",
227 output.return_value as i32
228 );
229 }
230
231 setup_topology_node(skel, topo.span.as_raw_slice())?;
232
233 for (_, node) in topo.nodes {
234 setup_topology_node(skel, node.span.as_raw_slice())?;
235 }
236
237 for (_, llc) in topo.all_llcs {
238 setup_topology_node(
239 skel,
240 Arc::<Llc>::into_inner(llc)
241 .expect("missing llc")
242 .span
243 .as_raw_slice(),
244 )?;
245 }
246
247 for (_, core) in topo.all_cores {
248 setup_topology_node(
249 skel,
250 Arc::<Core>::into_inner(core)
251 .expect("missing core")
252 .span
253 .as_raw_slice(),
254 )?;
255 }
256 let topolen = topo.span.as_raw_slice().len();
257 for (_, cpu) in topo.all_cpus {
258 let mut mask = vec![0u64; topolen];
259 mask[cpu.id / 64] |= 1 << (cpu.id % 64);
260 setup_topology_node(skel, &mask)?;
261 }
262
263 Ok(())
264}
265
266fn print_stream(skel: &mut BpfSkel<'_>, stream_id: u32) {
267 let prog_fd = skel.progs.arena_selftest.as_fd().as_raw_fd();
268 let mut buf = vec![0u8; 4096];
269 let name = if stream_id == 1 { "OUTPUT" } else { "ERROR" };
270 let mut started = false;
271
272 loop {
273 let ret = unsafe {
274 libbpf_sys::bpf_prog_stream_read(
275 prog_fd,
276 stream_id,
277 buf.as_mut_ptr() as *mut c_void,
278 buf.len() as u32,
279 std::ptr::null_mut(),
280 )
281 };
282 if ret < 0 {
283 eprintln!("STREAM {} UNAVAILABLE (REQUIRES >= v6.17)", name);
284 return;
285 }
286
287 if !started {
288 println!("===BEGIN STREAM {}===", name);
289 started = true;
290 }
291
292 if ret == 0 {
293 break;
294 }
295
296 print!("{}", String::from_utf8_lossy(&buf[..ret as usize]));
297 }
298
299 println!("\n====END STREAM {}====", name);
300}
301
302fn run_test_by_name(skel: &mut BpfSkel<'_>, name: &str) -> Result<i32> {
305 let id = TEST_CASES
306 .iter()
307 .find(|(n, _)| *n == name)
308 .map(|(_, id)| *id)
309 .ok_or_else(|| {
310 anyhow::anyhow!(
311 "Unknown test: '{}'. Use --list to see available tests.",
312 name
313 )
314 })?;
315
316 skel.maps.bss_data.as_mut().unwrap().selftest_run_id = id;
317
318 let input = ProgramInput {
319 ..Default::default()
320 };
321 let output = skel.progs.arena_selftest.test_run(input)?;
322
323 Ok(output.return_value as i32)
324}
325
326fn main() {
327 TermLogger::init(
328 simplelog::LevelFilter::Info,
329 SimplelogConfig::default(),
330 TerminalMode::Mixed,
331 ColorChoice::Auto,
332 )
333 .unwrap();
334
335 let opts = Opts::parse();
336
337 if opts.list {
338 println!("Available test cases:\n{}", available_tests());
339 return;
340 }
341
342 for name in &opts.tests {
344 if !TEST_CASES.iter().any(|(n, _)| *n == name.as_str()) {
345 eprintln!(
346 "Unknown test: '{}'.\nAvailable tests:\n{}",
347 name,
348 available_tests()
349 );
350 std::process::exit(1);
351 }
352 }
353
354 let mut open_object = MaybeUninit::uninit();
355 let mut builder = BpfSkelBuilder::default();
356
357 builder.obj_builder.debug(true);
358 init_libbpf_logging(Some(PrintLevel::Debug));
359
360 let mut skel = builder
361 .open(&mut open_object)
362 .context("Failed to open BPF program")
363 .unwrap();
364
365 skel.maps.rodata_data.as_mut().unwrap().nr_cpu_ids = *NR_CPU_IDS as u32;
366
367 let mut skel = skel.load().context("Failed to load BPF program").unwrap();
368
369 setup_arenas(&mut skel).unwrap();
370 setup_topology(&mut skel).unwrap();
371
372 let to_run: Vec<&str> = if opts.tests.is_empty() {
373 TEST_CASES.iter().map(|(n, _)| *n).collect()
374 } else {
375 opts.tests.iter().map(String::as_str).collect()
376 };
377
378 let stdout_tty = std::io::stdout().is_terminal();
379 let stderr_tty = std::io::stderr().is_terminal();
380 let pass_label = colorize("[ PASS ]", COLOR_BRIGHT_GREEN, stdout_tty);
381 let fail_label = colorize("[ FAIL ]", COLOR_BRIGHT_RED, stderr_tty);
382
383 let mut any_failed = false;
384 for &name in &to_run {
385 match run_test_by_name(&mut skel, name) {
386 Ok(0) => println!("{} {}", pass_label, name),
387 Ok(ret) => {
388 eprintln!("{} {} (returned {})", fail_label, name, ret);
389 any_failed = true;
390 }
391 Err(e) => {
392 eprintln!("{} {} (error: {})", fail_label, name, e);
393 any_failed = true;
394 }
395 }
396
397 print_stream(&mut skel, BPF_STDOUT);
398 print_stream(&mut skel, BPF_STDERR);
399 }
400
401 if any_failed {
402 eprintln!(
403 "{}",
404 colorize(
405 "One or more selftests failed.",
406 COLOR_BRIGHT_RED,
407 stderr_tty
408 )
409 );
410 std::process::exit(1);
411 } else {
412 println!(
413 "{}",
414 colorize("All selftests passed.", COLOR_BRIGHT_GREEN, stdout_tty)
415 );
416 }
417}