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 #[arg(long)]
71 phi_scale: Option<u64>,
72}
73
74#[derive(Subcommand)]
75enum SubCmd {
76 #[command(hide = true)]
78 Probe,
79
80 #[command(hide = true)]
82 StressWorker(StressWorkerArgs),
83}
84
85#[derive(Parser)]
86struct StressWorkerArgs {
87 #[arg(long)]
89 cpu: u32,
90}
91
92fn main() -> Result<()> {
93 let cli = Cli::parse();
94
95 let verbose = cli.verbose;
96 let dump_log = cli.dump_log;
97 let nr_cpus = cli.nr_cpus;
98 let no_adaptive = cli.no_adaptive;
99 let phi_scale = cli.phi_scale;
100
101 if cli.version {
102 println!(
103 "scx_pandemonium {}",
104 build_id::full_version(env!("CARGO_PKG_VERSION"))
105 );
106 return Ok(());
107 }
108
109 match cli.command {
110 None => run_scheduler(verbose, dump_log, nr_cpus, no_adaptive, phi_scale),
111 Some(SubCmd::Probe) => {
112 cli::probe::run_probe();
113 Ok(())
114 }
115 Some(SubCmd::StressWorker(args)) => {
116 cli::stress::run_stress_worker(args.cpu);
117 Ok(())
118 }
119 }
120}
121
122fn run_scheduler(
123 verbose: bool,
124 dump_log: bool,
125 nr_cpus: Option<u64>,
126 no_adaptive: bool,
127 phi_scale: Option<u64>,
128) -> Result<()> {
129 ctrlc::set_handler(move || {
130 SHUTDOWN.store(true, Ordering::Relaxed);
131 })?;
132
133 watchdog::spawn(&SHUTDOWN, Duration::from_secs(10));
137
138 let nr_cpus_display =
139 nr_cpus.unwrap_or_else(|| libbpf_rs::num_possible_cpus().unwrap_or(1) as u64);
140 let governor = std::fs::read_to_string("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor")
141 .unwrap_or_default()
142 .trim()
143 .to_string();
144
145 let smt_on = std::fs::read_to_string("/sys/devices/system/cpu/smt/active")
146 .map(|s| s.trim() == "1")
147 .unwrap_or(false);
148
149 log_info!(
150 "scx_pandemonium {} SMT {}",
151 build_id::full_version(env!("CARGO_PKG_VERSION")),
152 if smt_on { "on" } else { "off" }
153 );
154 log_info!(
155 "CPUS: {} (governor: {})",
156 nr_cpus_display,
157 if governor.is_empty() {
158 "unknown"
159 } else {
160 &governor
161 }
162 );
163 log_info!("VERBOSE: {}", verbose);
164
165 let mut is_restart = false;
166 loop {
167 if is_restart {
171 std::thread::sleep(Duration::from_secs(2));
172 }
173
174 let mut open_object = MaybeUninit::uninit();
175 let mut sched = Scheduler::init(&mut open_object, nr_cpus)?;
176
177 let mut last_online = topology::CpuTopology::online_cpu_count();
184 if let Err(e) = topology::CpuTopology::detect_and_populate(
185 &mut sched,
186 nr_cpus_display as usize,
187 phi_scale,
188 ) {
189 log_warn!("CACHE TOPOLOGY DETECT FAILED: {}", e);
190 }
191
192 let should_restart = if no_adaptive {
193 log_info!("PANDEMONIUM IS ACTIVE (BPF ONLY, CTRL+C TO EXIT)");
196 let mut prev = scheduler::PandemoniumStats::default();
197 while !SHUTDOWN.load(Ordering::Relaxed) && !sched.exited() {
198 watchdog::LOOP_HEARTBEAT.fetch_add(1, Ordering::Relaxed);
199 std::thread::sleep(Duration::from_secs(1));
200
201 topology::CpuTopology::poll_hotplug(
204 &mut sched,
205 nr_cpus_display as usize,
206 phi_scale,
207 &mut last_online,
208 );
209
210 let stats = sched.read_stats();
211
212 let delta_d = stats.nr_dispatches.wrapping_sub(prev.nr_dispatches);
213 let delta_idle = stats.nr_idle_hits.wrapping_sub(prev.nr_idle_hits);
214 let delta_shared = stats.nr_shared.wrapping_sub(prev.nr_shared);
215 let delta_preempt = stats.nr_preempt.wrapping_sub(prev.nr_preempt);
216 let delta_keep = stats.nr_keep_running.wrapping_sub(prev.nr_keep_running);
217 let delta_parks = stats.nr_osc_park.wrapping_sub(prev.nr_osc_park);
218 let delta_wake_sum = stats.wake_lat_sum.wrapping_sub(prev.wake_lat_sum);
219 let delta_wake_samples = stats.wake_lat_samples.wrapping_sub(prev.wake_lat_samples);
220 let delta_hard = stats.nr_hard_kicks.wrapping_sub(prev.nr_hard_kicks);
221 let delta_soft = stats.nr_soft_kicks.wrapping_sub(prev.nr_soft_kicks);
222 let delta_steal = stats.nr_steal.wrapping_sub(prev.nr_steal);
223 let delta_enq_wake = stats.nr_enq_wakeup.wrapping_sub(prev.nr_enq_wakeup);
224 let delta_enq_requeue = stats.nr_enq_requeue.wrapping_sub(prev.nr_enq_requeue);
225 let wake_avg_us = if delta_wake_samples > 0 {
226 delta_wake_sum / delta_wake_samples / 1000
227 } else {
228 0
229 };
230
231 let d_idle_sum = stats.wake_lat_idle_sum.wrapping_sub(prev.wake_lat_idle_sum);
232 let d_idle_cnt = stats.wake_lat_idle_cnt.wrapping_sub(prev.wake_lat_idle_cnt);
233 let d_kick_sum = stats.wake_lat_kick_sum.wrapping_sub(prev.wake_lat_kick_sum);
234 let d_kick_cnt = stats.wake_lat_kick_cnt.wrapping_sub(prev.wake_lat_kick_cnt);
235 let lat_idle_us = if d_idle_cnt > 0 {
236 d_idle_sum / d_idle_cnt / 1000
237 } else {
238 0
239 };
240 let lat_kick_us = if d_kick_cnt > 0 {
241 d_kick_sum / d_kick_cnt / 1000
242 } else {
243 0
244 };
245 let delta_reenq = stats.nr_reenqueue.wrapping_sub(prev.nr_reenqueue);
246
247 let dl2_hb = stats.nr_l2_hit_batch.wrapping_sub(prev.nr_l2_hit_batch);
249 let dl2_mb = stats.nr_l2_miss_batch.wrapping_sub(prev.nr_l2_miss_batch);
250 let dl2_hi = stats
251 .nr_l2_hit_interactive
252 .wrapping_sub(prev.nr_l2_hit_interactive);
253 let dl2_mi = stats
254 .nr_l2_miss_interactive
255 .wrapping_sub(prev.nr_l2_miss_interactive);
256 let l2_pct_b = if dl2_hb + dl2_mb > 0 {
257 dl2_hb * 100 / (dl2_hb + dl2_mb)
258 } else {
259 0
260 };
261 let l2_pct_i = if dl2_hi + dl2_mi > 0 {
262 dl2_hi * 100 / (dl2_hi + dl2_mi)
263 } else {
264 0
265 };
266
267 let idle_pct = if delta_d > 0 {
268 delta_idle * 100 / delta_d
269 } else {
270 0
271 };
272
273 let sojourn_ms = stats.batch_sojourn_ns / 1_000_000;
274 let longrun_label = if stats.longrun_mode_active > 0 {
275 " LONGRUN"
276 } else {
277 ""
278 };
279
280 if verbose {
281 println!(
282 "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{}]",
283 delta_d, idle_pct, delta_shared, delta_preempt, delta_keep,
284 delta_hard, delta_soft, delta_enq_wake, delta_enq_requeue,
285 wake_avg_us, lat_idle_us, lat_kick_us,
286 delta_reenq, sojourn_ms, l2_pct_b, 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={}",
337 knobs.slice_ns, knobs.batch_slice_ns,
338 knobs.preempt_thresh_ns,
339 l2_cum_b, l2_cum_i,
340 x_scatter_pct, x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7],
341 );
342
343 sched.read_exit_info()
344 } else {
345 log_info!("PANDEMONIUM IS ACTIVE (CTRL+C TO EXIT)");
347 adaptive::monitor_loop(&mut sched, &SHUTDOWN, verbose, nr_cpus_display, phi_scale)?
348 };
349
350 log_info!("PANDEMONIUM IS SHUTTING DOWN");
351
352 if dump_log {
353 sched.log.dump();
354 }
355 sched.log.summary();
356
357 if !should_restart || SHUTDOWN.load(Ordering::Relaxed) {
358 break;
359 }
360
361 SHUTDOWN.store(false, Ordering::Relaxed);
363 log_info!("RESTARTING PANDEMONIUM...");
364 is_restart = true;
365 }
366
367 log_info!("Shutdown complete");
368 Ok(())
369}