1#[allow(non_upper_case_globals)]
8#[allow(non_camel_case_types)]
9#[allow(non_snake_case)]
10mod bpf_skel;
11
12mod bpf_intf;
13
14#[macro_use]
15mod log;
16mod adaptive;
17mod chaos;
18mod cli;
19mod scheduler;
20mod topology;
21mod tuning;
22mod watchdog;
23
24use std::mem::MaybeUninit;
25use std::sync::atomic::{AtomicBool, Ordering};
26use std::time::Duration;
27
28use anyhow::Result;
29use clap::{Parser, Subcommand};
30
31use scheduler::Scheduler;
32use scx_utils::build_id;
33
34static SHUTDOWN: AtomicBool = AtomicBool::new(false);
35
36#[derive(Parser)]
37#[command(name = "scx_pandemonium")]
38#[command(
39 version,
40 disable_version_flag = true,
41 about = "PANDEMONIUM -- ADAPTIVE LINUX SCHEDULER"
42)]
43struct Cli {
44 #[command(subcommand)]
45 command: Option<SubCmd>,
46
47 #[arg(short, long)]
48 verbose: bool,
49
50 #[arg(long)]
52 version: bool,
53
54 #[arg(long, hide = true)]
56 dump_log: bool,
57
58 #[arg(long, hide = true)]
60 nr_cpus: Option<u64>,
61
62 #[arg(long)]
64 no_adaptive: bool,
65}
66
67#[derive(Subcommand)]
68enum SubCmd {
69 #[command(hide = true)]
71 Probe,
72
73 #[command(hide = true)]
75 StressWorker(StressWorkerArgs),
76}
77
78#[derive(Parser)]
79struct StressWorkerArgs {
80 #[arg(long)]
82 cpu: u32,
83}
84
85fn main() -> Result<()> {
86 let cli = Cli::parse();
87
88 let verbose = cli.verbose;
89 let dump_log = cli.dump_log;
90 let nr_cpus = cli.nr_cpus;
91 let no_adaptive = cli.no_adaptive;
92
93 if cli.version {
94 println!(
95 "scx_pandemonium {}",
96 build_id::full_version(env!("CARGO_PKG_VERSION"))
97 );
98 return Ok(());
99 }
100
101 match cli.command {
102 None => run_scheduler(verbose, dump_log, nr_cpus, no_adaptive),
103 Some(SubCmd::Probe) => {
104 cli::probe::run_probe();
105 Ok(())
106 }
107 Some(SubCmd::StressWorker(args)) => {
108 cli::stress::run_stress_worker(args.cpu);
109 Ok(())
110 }
111 }
112}
113
114fn run_scheduler(
115 verbose: bool,
116 dump_log: bool,
117 nr_cpus: Option<u64>,
118 no_adaptive: bool,
119) -> Result<()> {
120 ctrlc::set_handler(move || {
121 SHUTDOWN.store(true, Ordering::Relaxed);
122 })?;
123
124 watchdog::spawn(&SHUTDOWN, Duration::from_secs(10));
128
129 let nr_cpus_display =
130 nr_cpus.unwrap_or_else(|| libbpf_rs::num_possible_cpus().unwrap_or(1) as u64);
131 let governor = std::fs::read_to_string("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor")
132 .unwrap_or_default()
133 .trim()
134 .to_string();
135
136 let smt_on = std::fs::read_to_string("/sys/devices/system/cpu/smt/active")
137 .map(|s| s.trim() == "1")
138 .unwrap_or(false);
139
140 log_info!(
141 "scx_pandemonium {} SMT {}",
142 build_id::full_version(env!("CARGO_PKG_VERSION")),
143 if smt_on { "on" } else { "off" }
144 );
145 log_info!(
146 "CPUS: {} (governor: {})",
147 nr_cpus_display,
148 if governor.is_empty() {
149 "unknown"
150 } else {
151 &governor
152 }
153 );
154 log_info!("VERBOSE: {}", verbose);
155
156 let mut is_restart = false;
157 loop {
158 if is_restart {
162 std::thread::sleep(Duration::from_secs(2));
163 }
164
165 let mut open_object = MaybeUninit::uninit();
166 let mut sched = Scheduler::init(&mut open_object, nr_cpus)?;
167
168 let mut last_online = topology::CpuTopology::online_cpu_count();
175 if let Err(e) =
176 topology::CpuTopology::detect_and_populate(&mut sched, nr_cpus_display as usize)
177 {
178 log_warn!("CACHE TOPOLOGY DETECT FAILED: {}", e);
179 }
180
181 let should_restart = if no_adaptive {
182 log_info!("PANDEMONIUM IS ACTIVE (BPF ONLY, CTRL+C TO EXIT)");
185 let mut prev = scheduler::PandemoniumStats::default();
186 while !SHUTDOWN.load(Ordering::Relaxed) && !sched.exited() {
187 watchdog::LOOP_HEARTBEAT.fetch_add(1, Ordering::Relaxed);
188 std::thread::sleep(Duration::from_secs(1));
189
190 topology::CpuTopology::poll_hotplug(
193 &mut sched,
194 nr_cpus_display as usize,
195 &mut last_online,
196 );
197
198 let stats = sched.read_stats();
199
200 let delta_d = stats.nr_dispatches.wrapping_sub(prev.nr_dispatches);
201 let delta_idle = stats.nr_idle_hits.wrapping_sub(prev.nr_idle_hits);
202 let delta_shared = stats.nr_shared.wrapping_sub(prev.nr_shared);
203 let delta_preempt = stats.nr_preempt.wrapping_sub(prev.nr_preempt);
204 let delta_keep = stats.nr_keep_running.wrapping_sub(prev.nr_keep_running);
205 let delta_parks = stats.nr_osc_park.wrapping_sub(prev.nr_osc_park);
206 let delta_wake_sum = stats.wake_lat_sum.wrapping_sub(prev.wake_lat_sum);
207 let delta_wake_samples = stats.wake_lat_samples.wrapping_sub(prev.wake_lat_samples);
208 let delta_hard = stats.nr_hard_kicks.wrapping_sub(prev.nr_hard_kicks);
209 let delta_soft = stats.nr_soft_kicks.wrapping_sub(prev.nr_soft_kicks);
210 let delta_steal = stats.nr_steal.wrapping_sub(prev.nr_steal);
211 let delta_enq_wake = stats.nr_enq_wakeup.wrapping_sub(prev.nr_enq_wakeup);
212 let delta_enq_requeue = stats.nr_enq_requeue.wrapping_sub(prev.nr_enq_requeue);
213 let wake_avg_us = if delta_wake_samples > 0 {
214 delta_wake_sum / delta_wake_samples / 1000
215 } else {
216 0
217 };
218
219 let d_idle_sum = stats.wake_lat_idle_sum.wrapping_sub(prev.wake_lat_idle_sum);
220 let d_idle_cnt = stats.wake_lat_idle_cnt.wrapping_sub(prev.wake_lat_idle_cnt);
221 let d_kick_sum = stats.wake_lat_kick_sum.wrapping_sub(prev.wake_lat_kick_sum);
222 let d_kick_cnt = stats.wake_lat_kick_cnt.wrapping_sub(prev.wake_lat_kick_cnt);
223 let lat_idle_us = if d_idle_cnt > 0 {
224 d_idle_sum / d_idle_cnt / 1000
225 } else {
226 0
227 };
228 let lat_kick_us = if d_kick_cnt > 0 {
229 d_kick_sum / d_kick_cnt / 1000
230 } else {
231 0
232 };
233 let delta_reenq = stats.nr_reenqueue.wrapping_sub(prev.nr_reenqueue);
234
235 let dl2_hb = stats.nr_l2_hit_batch.wrapping_sub(prev.nr_l2_hit_batch);
237 let dl2_mb = stats.nr_l2_miss_batch.wrapping_sub(prev.nr_l2_miss_batch);
238 let dl2_hi = stats
239 .nr_l2_hit_interactive
240 .wrapping_sub(prev.nr_l2_hit_interactive);
241 let dl2_mi = stats
242 .nr_l2_miss_interactive
243 .wrapping_sub(prev.nr_l2_miss_interactive);
244 let l2_pct_b = if dl2_hb + dl2_mb > 0 {
245 dl2_hb * 100 / (dl2_hb + dl2_mb)
246 } else {
247 0
248 };
249 let l2_pct_i = if dl2_hi + dl2_mi > 0 {
250 dl2_hi * 100 / (dl2_hi + dl2_mi)
251 } else {
252 0
253 };
254
255 let idle_pct = if delta_d > 0 {
256 delta_idle * 100 / delta_d
257 } else {
258 0
259 };
260
261 let sojourn_ms = stats.batch_sojourn_ns / 1_000_000;
262 let longrun_label = if stats.longrun_mode_active > 0 {
263 " LONGRUN"
264 } else {
265 ""
266 };
267
268 if verbose {
269 println!(
270 "d/s: {:<8} idle: {}% shared: {:<6} preempt: {:<4} keep: {:<4} kick: H={:<4} S={:<4} enq: W={:<4} R={:<4} wake: {}us lat_idle: {}us lat_kick: {}us reenq: {} sjrn: {}ms l2: B={}% I={}% [BPF{}]",
271 delta_d,
272 idle_pct,
273 delta_shared,
274 delta_preempt,
275 delta_keep,
276 delta_hard,
277 delta_soft,
278 delta_enq_wake,
279 delta_enq_requeue,
280 wake_avg_us,
281 lat_idle_us,
282 lat_kick_us,
283 delta_reenq,
284 sojourn_ms,
285 l2_pct_b,
286 l2_pct_i,
287 longrun_label,
288 );
289 }
290
291 sched.log.snapshot(
292 delta_d,
293 delta_idle,
294 delta_shared,
295 delta_preempt,
296 delta_keep,
297 delta_parks,
298 wake_avg_us,
299 delta_hard,
300 delta_soft,
301 lat_idle_us,
302 lat_kick_us,
303 delta_steal,
304 );
305
306 prev = stats;
307 }
308
309 let knobs = sched.read_tuning_knobs();
311 let final_stats = sched.read_stats();
312 let l2_total_b = final_stats.nr_l2_hit_batch + final_stats.nr_l2_miss_batch;
313 let l2_total_i = final_stats.nr_l2_hit_interactive + final_stats.nr_l2_miss_interactive;
314 let l2_cum_b = if l2_total_b > 0 {
315 final_stats.nr_l2_hit_batch * 100 / l2_total_b
316 } else {
317 0
318 };
319 let l2_cum_i = if l2_total_i > 0 {
320 final_stats.nr_l2_hit_interactive * 100 / l2_total_i
321 } else {
322 0
323 };
324 let x = &final_stats.nr_cross_domain;
329 let x_scatter: u64 = x[0..6].iter().sum();
330 let x_scatter_pct = if final_stats.nr_dispatches > 0 {
331 x_scatter * 100 / final_stats.nr_dispatches
332 } else {
333 0
334 };
335 println!(
336 "[KNOBS] regime=BPF slice_ns={} batch_ns={} preempt_ns={} l2_hit=B:{}%/I:{}% cross_domain_scatter_pct={} cross_domain_sel_tight={} cross_domain_sel_sync={} cross_domain_sel_normal={} cross_domain_sel_dfl={} cross_domain_enq_t1={} cross_domain_enq_t2={} cross_domain_steal={} cross_domain_step5={} steal={} spill={} kick_declined={} stay_cost_held={} stay_move_taken={} dispatches={}",
337 knobs.slice_ns,
338 knobs.batch_slice_ns,
339 knobs.preempt_thresh_ns,
340 l2_cum_b,
341 l2_cum_i,
342 x_scatter_pct,
343 x[0],
344 x[1],
345 x[2],
346 x[3],
347 x[4],
348 x[5],
349 x[6],
350 x[7],
351 final_stats.nr_steal,
352 final_stats.nr_spill_kick_preempt,
353 final_stats.nr_kick_declined,
354 final_stats.nr_stay_cost_held,
355 final_stats.nr_stay_move_taken,
356 final_stats.nr_dispatches,
357 );
358
359 sched.read_exit_info()
360 } else {
361 log_info!("PANDEMONIUM IS ACTIVE (CTRL+C TO EXIT)");
363 adaptive::monitor_loop(&mut sched, &SHUTDOWN, verbose, nr_cpus_display)?
364 };
365
366 log_info!("PANDEMONIUM IS SHUTTING DOWN");
367
368 if dump_log {
369 sched.log.dump();
370 }
371 sched.log.summary();
372
373 if !should_restart || SHUTDOWN.load(Ordering::Relaxed) {
374 break;
375 }
376
377 SHUTDOWN.store(false, Ordering::Relaxed);
379 log_info!("RESTARTING PANDEMONIUM...");
380 is_restart = true;
381 }
382
383 log_info!("Shutdown complete");
384 Ok(())
385}