1mod bpf_skel;
9pub use bpf_skel::*;
10pub mod bpf_intf;
11pub use bpf_intf::*;
12
13use std::collections::BTreeMap;
14use std::mem::MaybeUninit;
15use std::sync::atomic::AtomicBool;
16use std::sync::atomic::Ordering;
17use std::sync::Arc;
18use std::time::Duration;
19
20use anyhow::Context;
21use anyhow::Result;
22use clap::Parser;
23use libbpf_rs::MapCore;
24use libbpf_rs::MapFlags;
25use libbpf_rs::OpenObject;
26use log::info;
27use log::warn;
28use scx_utils::build_id;
29use scx_utils::compat;
30use scx_utils::scx_ops_attach;
31use scx_utils::scx_ops_load;
32use scx_utils::scx_ops_open;
33use scx_utils::try_set_rlimit_infinity;
34use scx_utils::uei_exited;
35use scx_utils::uei_report;
36use scx_utils::Topology;
37use scx_utils::UserExitInfo;
38use scx_utils::NR_CPU_IDS;
39
40const SCHEDULER_NAME: &str = "scx_cake";
41
42#[derive(Debug, Parser)]
50struct Opts {
51 #[clap(short = 'v', long, action = clap::ArgAction::SetTrue)]
54 verbose: bool,
55
56 #[clap(short = 'V', long, action = clap::ArgAction::SetTrue)]
58 version: bool,
59
60 #[clap(long = "toggle", value_name = "NAME=0|1")]
65 toggle: Vec<String>,
66}
67
68struct Scheduler<'a> {
69 skel: BpfSkel<'a>,
70 struct_ops: Option<libbpf_rs::Link>,
71 _irq_links: Vec<libbpf_rs::Link>,
73 probe_on: bool,
75 frame_bucket: Option<u32>,
77 frame_period: u64,
79 slow_polls: u32,
82 frame_floor: u64,
85 verbose: bool,
88 sinks: SinkMonitor,
90 sinks_logged: bool,
93}
94
95impl<'a> Scheduler<'a> {
96 fn init(opts: &Opts, open_object: &'a mut MaybeUninit<OpenObject>) -> Result<Self> {
97 try_set_rlimit_infinity();
98
99 let topo = Topology::new().context("failed to read topology")?;
105 let physical = topo.all_cores.len();
106 let total = topo.all_cpus.len();
107 let smt = total.saturating_sub(physical);
108
109 let slice_us = bpf_intf::consts_SLICE_NS as u64 / bpf_intf::consts_NSEC_PER_USEC as u64;
110 let queued_wakeup = *compat::SCX_OPS_ALLOW_QUEUED_WAKEUP != 0;
111 let dsq_peek = compat::ksym_exists("scx_bpf_dsq_peek").unwrap_or(false);
112
113 info!(
114 "🍰 {} {}",
115 SCHEDULER_NAME,
116 build_id::full_version(env!("CARGO_PKG_VERSION"))
117 );
118 info!(" cores {physical} physical + {smt} SMT = {total} CPUs");
119 info!(" slice {slice_us}µs · queues {total} per-CPU vtime + 1 global wake");
120 info!(
121 " kernel queued_wakeup {} · dsq_peek {}",
122 if queued_wakeup { "on" } else { "UNSUPPORTED" },
123 if dsq_peek { "native" } else { "MISSING" },
128 );
129
130 let mut skel_builder = BpfSkelBuilder::default();
132 skel_builder.obj_builder.debug(opts.verbose);
133 let mut skel = scx_ops_open!(skel_builder, open_object, cake_ops, None)?;
134
135 let rodata = skel
140 .maps
141 .rodata_data
142 .as_mut()
143 .context("BPF rodata unavailable for CPU topology")?;
144
145 anyhow::ensure!(
151 *NR_CPU_IDS <= bpf_intf::consts_MAX_CPUS as usize,
152 "host nr_cpu_ids {} exceeds Cake's compiled MAX_CPUS {}",
153 *NR_CPU_IDS,
154 bpf_intf::consts_MAX_CPUS
155 );
156 rodata.nr_cpu_span = *NR_CPU_IDS as u32;
157 rodata.cake_span_mask = (*NR_CPU_IDS as u32).next_power_of_two() - 1;
158
159 for spec in &opts.toggle {
163 let (name, val) = spec
164 .split_once('=')
165 .with_context(|| format!("--toggle {spec}: expected NAME=0|1"))?;
166 let on = match val {
167 "0" => 0u8,
168 "1" => 1u8,
169 _ => anyhow::bail!("--toggle {spec}: value must be 0 or 1"),
170 };
171 match name {
172 "g39b" => rodata.cake_tog_g39b = on,
173 "g46" => rodata.cake_tog_g46 = on,
174 "m6" => rodata.cake_tog_m6 = on,
175 "g51" => rodata.cake_tog_g51 = on,
176 "g52" => rodata.cake_tog_g52 = on,
177 "g56" => rodata.cake_tog_g56 = on,
178 "g57" => rodata.cake_tog_g57 = on,
179 "g58" => rodata.cake_tog_g58 = on,
180 "g59" => rodata.cake_tog_g59 = on,
181 "g60" => rodata.cake_tog_g60 = on,
182 "g61" => rodata.cake_tog_g61 = on,
183 "g62" => rodata.cake_tog_g62 = on,
184 "g63" => rodata.cake_tog_g63 = on,
185 "g64" => rodata.cake_tog_g64 = on,
186 "g65" => rodata.cake_tog_g65 = on,
187 "g66" => rodata.cake_tog_g66 = on,
188 "g67" => rodata.cake_tog_g67 = on,
189 "g68" => rodata.cake_tog_g68 = on,
190 "g69" => rodata.cake_tog_g69 = on,
191 "g70" => rodata.cake_tog_g70 = on,
192 "g72" => rodata.cake_tog_g72 = on,
193 "g71" => rodata.cake_tog_g71 = on,
194 "g73" => rodata.cake_tog_g73 = on,
195 "g74" => rodata.cake_tog_g74 = on,
196 "g75" => rodata.cake_tog_g75 = on,
197 "g77" => rodata.cake_tog_g77 = on,
198 "g78" => rodata.cake_tog_g78 = on,
199 "g79" => rodata.cake_tog_g79 = on,
200 "g81" => rodata.cake_tog_g81 = on,
201 "m7" => rodata.cake_tog_m7 = on,
202 "probe" => rodata.cake_tog_probe = on,
203 _ => anyhow::bail!("--toggle {spec}: unknown name {name}"),
204 }
205 }
206 if rodata.cake_tog_g68 == 1 || rodata.cake_tog_g70 == 1 {
208 let mut vip = 0u32;
210 if let Ok(rd) = std::fs::read_dir("/proc") {
211 for e in rd.flatten() {
212 if let Ok(comm) = std::fs::read_to_string(e.path().join("comm")) {
213 if comm.starts_with("FPSAimTrainer") {
214 if let Ok(pid) = e.file_name().to_string_lossy().parse::<u32>() {
215 vip = pid;
216 break;
217 }
218 }
219 }
220 }
221 }
222 rodata.cake_vip_tgid = vip;
223 info!(" g68 VIP process tgid {vip} (0 = not found, VIP inert)");
224 }
225 if rodata.cake_tog_g59 == 1 {
226 rodata.cake_tog_g51 = 1;
227 }
228 let probe_on = rodata.cake_tog_probe == 1;
229 info!(
230 " toggle g39b={} g46={} g51={} g52={} g56={} g57={} g58={} g59={} g60={} g61={} g62={} g63={} g64={} g65={} g66={} g67={} g68={} g69={} g70={} g72={} g71={} g73={} g74={} g75={} g77={} g78={} g79={} g81={} m6={} m7={}",
231 rodata.cake_tog_g39b,
232 rodata.cake_tog_g46,
233 rodata.cake_tog_g51,
234 rodata.cake_tog_g52,
235 rodata.cake_tog_g56,
236 rodata.cake_tog_g57,
237 rodata.cake_tog_g58,
238 rodata.cake_tog_g59,
239 rodata.cake_tog_g60,
240 rodata.cake_tog_g61,
241 rodata.cake_tog_g62,
242 rodata.cake_tog_g63,
243 rodata.cake_tog_g64,
244 rodata.cake_tog_g65,
245 rodata.cake_tog_g66,
246 rodata.cake_tog_g67,
247 rodata.cake_tog_g68,
248 rodata.cake_tog_g69,
249 rodata.cake_tog_g70,
250 rodata.cake_tog_g72,
251 rodata.cake_tog_g71,
252 rodata.cake_tog_g73,
253 rodata.cake_tog_g74,
254 rodata.cake_tog_g75,
255 rodata.cake_tog_g77,
256 rodata.cake_tog_g78,
257 rodata.cake_tog_g79,
258 rodata.cake_tog_g81,
259 rodata.cake_tog_m6,
260 rodata.cake_tog_m7
261 );
262
263 let mut deepest_us = 0u32;
266 for i in 0..bpf_intf::consts_CAKE_CSTATE_TABLE as usize {
267 let path = format!("/sys/devices/system/cpu/cpu0/cpuidle/state{i}/latency");
268 if let Ok(s) = std::fs::read_to_string(&path) {
269 let us: u32 = s.trim().parse().unwrap_or(0);
270 rodata.cake_cstate_exit_us[i] = us;
271 deepest_us = deepest_us.max(us);
272 }
273 }
274 if rodata.cake_tog_g51 == 1 && deepest_us == 0 {
275 info!(" g51 no cpuidle driver: depth model inert");
276 }
277 rodata.cake_prewake_lead_ns = if deepest_us > 0 {
280 u64::from(deepest_us) * 2 * 1000
281 } else {
282 bpf_intf::consts_PREWAKE_LEAD_DEFAULT_NS as u64
283 };
284 if rodata.cake_tog_g58 == 1 {
285 info!(
286 " g58 pre-wake lead {} us ({})",
287 rodata.cake_prewake_lead_ns / 1000,
288 if deepest_us > 0 {
289 "2x deepest exit"
290 } else {
291 "default, no cpuidle"
292 }
293 );
294 }
295 if rodata.cake_tog_g59 == 1 && deepest_us == 0 {
296 info!(" g59 no cpuidle table: depth pick is first fit");
297 }
298 let mut perf_seen = false;
299 for c in 0..*NR_CPU_IDS {
300 let path = format!("/sys/devices/system/cpu/cpu{c}/acpi_cppc/highest_perf");
301 if let Ok(s) = std::fs::read_to_string(&path) {
302 let v: u64 = s.trim().parse().unwrap_or(0);
303 rodata.cpu_perf_rank[c] = v.min(255) as u8;
304 perf_seen = perf_seen || v != 0;
305 }
306 }
307 if rodata.cake_tog_g52 == 1 && !perf_seen {
308 info!(" g52 no CPPC highest_perf: rank tiebreak inert");
309 }
310
311 let nr_llcs = topo.all_llcs.len().min(bpf_intf::consts_MAX_LLCS as usize);
318 let mut llc_rank = [0u8; bpf_intf::consts_MAX_LLCS as usize];
319 for (idx, llc) in topo.all_llcs.values().take(nr_llcs).enumerate() {
320 let mut word = 0u64;
321 for cpu in llc.all_cpus.keys().copied() {
322 if cpu < rodata.cake_cpu_llc.len() {
323 rodata.cake_cpu_llc[cpu] = idx as u8;
324 }
325 if cpu < 64 {
326 word |= 1u64 << cpu;
327 }
328 if cpu < *NR_CPU_IDS {
329 llc_rank[idx] = llc_rank[idx].max(rodata.cpu_perf_rank[cpu]);
330 }
331 }
332 rodata.cake_llc_qword[idx] = word;
333 }
334 rodata.cake_nr_llcs = nr_llcs.max(1) as u32;
335 for home in 0..nr_llcs {
336 let mut foreign: Vec<usize> = (0..nr_llcs).filter(|&l| l != home).collect();
337 if rodata.cake_tog_g52 == 1 {
338 foreign.sort_by_key(|&l| std::cmp::Reverse(llc_rank[l]));
339 }
340 rodata.cake_llc_order[home][0] = home as u8;
341 for (b, l) in foreign.iter().enumerate() {
342 rodata.cake_llc_order[home][b + 1] = *l as u8;
343 }
344 }
345 if rodata.cake_tog_g56 == 1 {
346 info!(
347 " g56 banded steal: {nr_llcs} LLC band(s), order {}",
348 if rodata.cake_tog_g52 == 1 {
349 "rank"
350 } else {
351 "id"
352 }
353 );
354 }
355
356 match probe_handoff_hop_ns() {
360 Some(probe) => {
361 let (med, p99) = (probe.median, probe.p99);
382 let hm = rodata.cake_handoff_max_ns;
383 rodata.cake_wake_hop_ns = p99;
387 if opts.verbose {
388 info!(" class starvation = mean wait > mean burst (no threshold)");
389 info!(
390 " probe hop median {med}ns p99 {p99}ns (diagnostic) · handoff_max {hm}ns"
391 );
392 }
393 }
394 None => {
395 if opts.verbose {
396 log::warn!(" probe handoff probe failed (diagnostic only)");
397 }
398 }
399 }
400
401 info!(" irq interrupt sinks tracked live by handler-time share");
409
410 let siblings = &mut rodata.cpu_sibling;
411 siblings.fill(-1);
412 for core in topo.all_cores.values() {
413 let cpu_ids: Vec<usize> = core.cpus.keys().copied().collect();
414
415 if cpu_ids.len() < 2 {
416 continue;
417 }
418 for (idx, cpu) in cpu_ids.iter().copied().enumerate() {
419 let sibling = cpu_ids[(idx + 1) % cpu_ids.len()];
420
421 anyhow::ensure!(
422 cpu < siblings.len() && sibling <= i32::MAX as usize,
423 "CPU topology id exceeds Cake's compiled sibling map"
424 );
425 siblings[cpu] = sibling as i32;
426 }
427 }
428
429 if let Some(bss) = skel.maps.bss_data.as_mut() {
436 bss.cake_frame_ns = bpf_intf::consts_FRAME_PERIOD_MAX_NS as u64;
437 bss.cake_frame_floor_ns = bpf_intf::consts_FRAME_FLOOR_BOOT_NS as u64;
438 bss.cake_frame_slice_ns = bpf_intf::consts_SLICE_NS as u64;
439 }
440
441 {
445 let rodata = skel
446 .maps
447 .rodata_data
448 .as_mut()
449 .context("BPF rodata unavailable for cache topology")?;
450 let order = &mut rodata.cpu_steal_order;
451 let span = bpf_intf::consts_STEAL_SPAN as usize;
452 let fits = topo
453 .all_cpus
454 .keys()
455 .next_back()
456 .is_none_or(|cpu| *cpu < span);
457 let multi_ccd = topo.all_llcs.len() > 1;
458 rodata.steal_order_live = u8::from(multi_ccd && fits);
459 order.fill(0);
460
461 if multi_ccd && !fits {
462 warn!(" ccd host wider than steal matrix ({span} CPUs); ring steal only");
463 }
464 if multi_ccd && fits {
465 let llc_cache: BTreeMap<usize, usize> = topo
466 .all_llcs
467 .iter()
468 .map(|(id, llc)| {
469 (
470 *id,
471 llc.all_cpus
472 .values()
473 .map(|cpu| cpu.cache_size)
474 .max()
475 .unwrap_or(0),
476 )
477 })
478 .collect();
479 let policy = bpf_intf::consts_CCD_STEAL_POLICY;
480 let nr_ids = *NR_CPU_IDS;
481
482 for src in topo.all_cpus.values() {
483 let mut candidates: Vec<_> = topo
484 .all_cpus
485 .values()
486 .filter(|dst| dst.id != src.id)
487 .collect();
488 candidates.sort_by_key(|dst| {
489 let class = if dst.llc_id == src.llc_id {
490 0
491 } else if policy > 1 && llc_cache[&dst.llc_id] == llc_cache[&src.llc_id] {
492 1
493 } else {
494 2
495 };
496 (class, (dst.id + nr_ids - src.id) % nr_ids)
497 });
498 let base = src.id * span;
499 for (slot, dst) in candidates.into_iter().enumerate() {
500 order[base + slot] = dst.id as u16;
501 }
502 }
503 }
504 }
505
506 let want_cpu_idle = skel
510 .maps
511 .rodata_data
512 .as_ref()
513 .map(|r| r.cake_tog_g51 == 1)
514 .unwrap_or(false);
515 skel.progs.cake_cpu_idle.set_autoload(want_cpu_idle);
516 let mut skel = scx_ops_load!(skel, cake_ops, uei)?;
517 let struct_ops = Some(scx_ops_attach!(skel, cake_ops)?);
518
519 let mut irq_links = Vec::with_capacity(4);
523 for (name, res) in [
524 ("irq_enter", skel.progs.cake_irq_enter.attach()),
525 ("irq_leave", skel.progs.cake_irq_leave.attach()),
526 ("softirq_enter", skel.progs.cake_softirq_enter.attach()),
527 ("softirq_leave", skel.progs.cake_softirq_leave.attach()),
528 ] {
529 match res {
530 Ok(link) => irq_links.push(link),
531 Err(e) => warn!(" irq {name} hook failed ({e}); chronic steering only"),
532 }
533 }
534 if want_cpu_idle {
535 match skel.progs.cake_cpu_idle.attach() {
536 Ok(link) => irq_links.push(link),
537 Err(e) => warn!(" g51 cpu_idle hook failed ({e}); depth mirror off"),
538 }
539 }
540
541 info!("🍰 attached — wakeups queue globally, continuations locally");
542
543 drop_all_capabilities();
551
552 Ok(Self {
553 skel,
554 struct_ops,
555 _irq_links: irq_links,
556 probe_on,
557 frame_bucket: None,
558 frame_period: 0,
559 slow_polls: 0,
560 frame_floor: 0,
561 verbose: opts.verbose,
562 sinks: SinkMonitor::new(*NR_CPU_IDS),
563 sinks_logged: false,
564 })
565 }
566
567 fn exited(&mut self) -> bool {
568 uei_exited!(&self.skel, uei)
569 }
570
571 fn run(&mut self, shutdown: Arc<AtomicBool>) -> Result<UserExitInfo> {
572 let mut shown: u64 = 0;
575 while !shutdown.load(Ordering::Relaxed) && !self.exited() {
576 std::thread::sleep(Duration::from_secs(1));
577
578 if let Some(set) = self.sinks.tick(*NR_CPU_IDS) {
579 self.publish_sinks(&set);
580 }
581
582 let observed = self.publish_frame_clock();
583 if self.verbose && observed != 0 && observed.abs_diff(shown) > shown / 16 {
584 shown = observed;
585 info!(
586 " frame observed period {}us ({:.1} Hz)",
587 observed / 1000,
588 1e9 / observed as f64
589 );
590 }
591 }
592
593 if let (true, Some(bss)) = (self.probe_on, self.skel.maps.bss_data.as_ref()) {
595 let n = bss.cake_blackbox_n;
596 for i in 0..n.min(4) {
597 let b = &bss.cake_blackbox[i as usize];
598 let comm = String::from_utf8_lossy(
599 &b.comm
600 .iter()
601 .map(|c| *c as u8)
602 .take_while(|c| *c != 0)
603 .collect::<Vec<u8>>(),
604 )
605 .to_string();
606 info!(
607 " BLACKBOX wait {:.2} ms {} pid {} kind {} target cpu{} caller cpu{} waker {} ran_on cpu{} seats {:#018x} core_free {:#018x} thread_free {:#018x} idle {:#018x}",
608 b.wait_ns as f64 / 1e6, comm, b.pid, b.kind, b.target, b.caller, b.waker_pid, b.ran_on, b.seats, b.core_free, b.thread_free, b.idle_word
609 );
610 }
611 }
612 if self.probe_on {
613 const NAMES: [&str; 47] = [
614 "select_calls",
615 "serial",
616 "home_warm",
617 "park_reached",
618 "park_prev",
619 "park_mbox",
620 "opt_reached",
621 "opt_hit",
622 "ranked",
623 "wp_attempt",
624 "wp_tiny",
625 "wp_small",
626 "wp_protect",
627 "wp_vtime",
628 "wp_starved",
629 "wp_fired",
630 "free_pick",
631 "prewake_fire",
632 "reserved_take",
633 "pl_local",
634 "pl_local_on",
635 "pl_cpuq_wake",
636 "pl_cpuq_cont",
637 "pl_global",
638 "h300_local",
639 "h300_local_on",
640 "h300_cpuq_wake",
641 "h300_cpuq_cont",
642 "h300_global",
643 "h1ms_local",
644 "h1ms_local_on",
645 "h1ms_cpuq_wake",
646 "h1ms_cpuq_cont",
647 "h1ms_global",
648 "pl_self",
649 "h300_self",
650 "h1ms_self",
651 "hd_skip",
652 "hd_sync",
653 "hd_starved",
654 "hd_irq",
655 "hd_aff",
656 "hd_contended",
657 "hd_notidle",
658 "home_busy",
659 "home_localq",
660 "h300_home_busy",
661 ];
662 let mut tot = [0u64; NAMES.len()];
663 for (i, t) in tot.iter_mut().enumerate() {
664 let key = (i as u32).to_ne_bytes();
665 if let Ok(Some(percpu)) =
666 self.skel.maps.cake_stats.lookup_percpu(&key, MapFlags::ANY)
667 {
668 for cpu in &percpu {
669 if cpu.len() >= 8 {
670 *t += u64::from_ne_bytes(cpu[..8].try_into().unwrap());
671 }
672 }
673 }
674 }
675 let sel = tot[0].max(1) as f64;
676 for (i, name) in NAMES.iter().enumerate() {
677 info!(
678 " arms {name:<13} {:>12} {:>6.2}% of select",
679 tot[i],
680 tot[i] as f64 * 100.0 / sel
681 );
682 }
683 }
684
685 if let Some(bss) = self.skel.maps.bss_data.as_mut() {
686 let ev = &bss.cake_events;
687 info!(
688 " events select_fallback {} keep_last {} enq_skip_exiting {}",
689 ev.SCX_EV_SELECT_CPU_FALLBACK,
690 ev.SCX_EV_DISPATCH_KEEP_LAST,
691 ev.SCX_EV_ENQ_SKIP_EXITING
692 );
693 }
694
695 self.struct_ops.take();
696 info!("🍰 {SCHEDULER_NAME} detached — default scheduler restored");
697 uei_report!(&self.skel, uei)
698 }
699
700 fn publish_sinks(&mut self, set: &[bool]) {
705 let Some(bss) = self.skel.maps.bss_data.as_mut() else {
706 return;
707 };
708 for (cpu, hot) in set.iter().enumerate() {
709 if cpu < bss.cpu_irq_hot.len() {
710 bss.cpu_irq_hot[cpu] = u8::from(*hot);
711 }
712 }
713 bss.cake_sink_gen = bss.cake_sink_gen.wrapping_add(1);
714
715 if self.verbose || !self.sinks_logged {
716 self.sinks_logged = true;
717 let named: Vec<usize> = set
718 .iter()
719 .enumerate()
720 .filter_map(|(cpu, hot)| hot.then_some(cpu))
721 .collect();
722 info!(" irq interrupt-sink CPUs {named:?} — steered around");
723 }
724 }
725
726 fn publish_frame_clock(&mut self) -> u64 {
737 const WIDTH: usize = std::mem::size_of::<u64>() * 2;
738 const QUALIFY: u64 = 2;
740 const FADE: u64 = 4;
742
743 let hist = &self.skel.maps.cake_frame_hist;
744 let mut crowds: Vec<(u32, u64, u64)> = Vec::new();
745
746 for idx in 0..bpf_intf::consts_FRAME_BUCKETS {
747 let key = idx.to_ne_bytes();
748 let Ok(Some(percpu)) = hist.lookup_percpu(&key, MapFlags::ANY) else {
749 continue;
750 };
751 let (mut count, mut sum) = (0u64, 0u64);
752 for cpu in &percpu {
753 if cpu.len() < WIDTH {
754 continue;
755 }
756 count += u64::from_ne_bytes(cpu[..8].try_into().unwrap());
757 sum += u64::from_ne_bytes(cpu[8..16].try_into().unwrap());
758 }
759 if count == 0 {
760 continue;
761 }
762 crowds.push((idx, count, sum));
763 let zeroed = vec![vec![0u8; WIDTH]; percpu.len()];
764 let _ = hist.update_percpu(&key, &zeroed, MapFlags::ANY);
765 }
766
767 let Some(max_count) = crowds.iter().map(|c| c.1).max() else {
768 return 0;
769 };
770 let Some((qi, qc, qs)) = crowds
772 .iter()
773 .copied()
774 .filter(|c| c.1 * QUALIFY >= max_count)
775 .min_by_key(|c| c.0)
776 else {
777 return 0;
778 };
779
780 let held = self
781 .frame_bucket
782 .and_then(|hb| crowds.iter().copied().find(|c| c.0 == hb));
783 let (bucket, count, sum) = match held {
784 Some((hi, hc, hs)) if hi <= qi && hc * FADE >= max_count => (hi, hc, hs),
787 _ => (qi, qc, qs),
788 };
789 let cand = sum / count;
790
791 const SLOW_POLLS: u32 = 3;
796 let (period, bucket) = if self.frame_period == 0 || cand <= self.frame_period {
797 self.slow_polls = 0;
798 (cand, Some(bucket))
799 } else {
800 self.slow_polls += 1;
801 if self.slow_polls >= SLOW_POLLS {
802 self.slow_polls = 0;
803 (cand, Some(bucket))
804 } else {
805 (self.frame_period, self.frame_bucket)
806 }
807 };
808 self.frame_period = period;
809
810 self.frame_floor = match self.frame_floor {
814 0 => period,
815 f if period < f => period,
816 f => f + (period - f) / 16,
817 };
818 self.frame_bucket = bucket;
819 if let Some(bss) = self.skel.maps.bss_data.as_mut() {
820 bss.cake_frame_ns = period;
821 bss.cake_frame_floor_ns = self.frame_floor;
822 bss.cake_frame_slice_ns = ((self.frame_floor >> 1) + (self.frame_floor >> 2))
825 .min(bpf_intf::consts_SLICE_NS as u64);
826 }
827 period
828 }
829}
830
831struct HandoffProbe {
845 median: u64,
847 p99: u64,
855}
856
857fn read_irq_ticks(nr_cpus: usize) -> Option<Vec<u64>> {
868 let text = std::fs::read_to_string("/proc/stat").ok()?;
869 let mut ticks = vec![0u64; nr_cpus];
870 let mut seen = false;
871 for line in text.lines() {
872 let mut fields = line.split_whitespace();
873 let Some(cpu) = fields
874 .next()
875 .and_then(|l| l.strip_prefix("cpu"))
876 .and_then(|n| n.parse::<usize>().ok())
877 else {
878 continue;
879 };
880 if cpu >= nr_cpus {
881 continue;
882 }
883 let vals: Vec<u64> = fields.filter_map(|v| v.parse().ok()).collect();
885 if vals.len() < 7 {
886 continue;
887 }
888 ticks[cpu] = vals[5] + vals[6];
889 seen = true;
890 }
891 seen.then_some(ticks)
892}
893
894fn sinks_by_widest_gap(deltas: &[u64]) -> Option<Vec<bool>> {
908 let mut ranked: Vec<(f64, usize)> = deltas
909 .iter()
910 .enumerate()
911 .map(|(cpu, &d)| ((d as f64).max(0.5), cpu))
912 .collect();
913 ranked.sort_by(|a, b| b.0.total_cmp(&a.0));
914
915 let mut cut = 0;
916 let mut widest = 1.0f64;
917 for i in 1..ranked.len() {
918 let ratio = ranked[i - 1].0 / ranked[i].0;
919 if ratio > widest {
920 widest = ratio;
921 cut = i;
922 }
923 }
924 if cut == 0 {
925 return Some(vec![false; deltas.len()]);
926 }
927 if cut * 2 >= deltas.len() {
928 return None;
929 }
930
931 let mut hot = vec![false; deltas.len()];
932 for &(_, cpu) in &ranked[..cut] {
933 hot[cpu] = true;
934 }
935 Some(hot)
936}
937
938struct SinkMonitor {
951 prev: Option<Vec<u64>>,
954 published: Vec<bool>,
956 hot_streak: Vec<u32>,
958 quiet: Vec<u32>,
960 stable: u32,
962 interval: u32,
964 ticks: u32,
965}
966
967impl SinkMonitor {
968 const STABLE_POLLS: u32 = 8;
970 const INTERVAL_MAX: u32 = 16;
972 const FLAG_POLLS: u32 = 2;
974 const UNFLAG_POLLS: u32 = 3;
976
977 fn new(nr_cpus: usize) -> Self {
978 Self {
979 prev: None,
980 published: vec![false; nr_cpus],
981 hot_streak: vec![0; nr_cpus],
982 quiet: vec![0; nr_cpus],
983 stable: 0,
984 interval: 1,
985 ticks: 0,
986 }
987 }
988
989 fn tick(&mut self, nr_cpus: usize) -> Option<Vec<bool>> {
991 self.ticks = self.ticks.wrapping_add(1);
992 if !self.ticks.is_multiple_of(self.interval) {
993 return None;
994 }
995 let sample = read_irq_ticks(nr_cpus)?;
998 let before = self.prev.replace(sample)?;
999 let current = self.prev.as_ref()?;
1000 let deltas: Vec<u64> = current
1001 .iter()
1002 .zip(before.iter())
1003 .map(|(c, b)| c.saturating_sub(*b))
1004 .collect();
1005 let hot = sinks_by_widest_gap(&deltas)?;
1006
1007 let mut changed = false;
1008 for (cpu, &is_hot) in hot.iter().enumerate().take(self.published.len()) {
1009 if is_hot {
1010 self.quiet[cpu] = 0;
1011 self.hot_streak[cpu] += 1;
1012 if !self.published[cpu]
1017 && self.hot_streak[cpu] >= Self::FLAG_POLLS
1018 && (self.published.iter().filter(|h| **h).count() + 1) * 2
1019 < self.published.len()
1020 {
1021 self.published[cpu] = true;
1022 changed = true;
1023 }
1024 } else {
1025 self.hot_streak[cpu] = 0;
1026 if self.published[cpu] {
1027 self.quiet[cpu] += 1;
1028 if self.quiet[cpu] >= Self::UNFLAG_POLLS {
1029 self.published[cpu] = false;
1030 self.quiet[cpu] = 0;
1031 changed = true;
1032 }
1033 }
1034 }
1035 }
1036
1037 if changed {
1038 self.stable = 0;
1039 self.interval = 1;
1040 return Some(self.published.clone());
1041 }
1042 self.stable += 1;
1043 if self.stable >= Self::STABLE_POLLS {
1044 self.stable = 0;
1045 self.interval = (self.interval * 2).min(Self::INTERVAL_MAX);
1046 }
1047 None
1048 }
1049}
1050
1051fn probe_handoff_hop_ns() -> Option<HandoffProbe> {
1065 const WARMUP: u32 = 200;
1066 const ITERS: u32 = 2_000;
1067 let total = WARMUP + ITERS;
1068
1069 let pair = Arc::new((std::sync::Mutex::new(0u32), std::sync::Condvar::new()));
1070 let peer = Arc::clone(&pair);
1071
1072 let responder = std::thread::Builder::new()
1073 .name("cake-probe".into())
1074 .spawn(move || {
1075 let (lock, cv) = &*peer;
1076 let mut turn = lock.lock().ok()?;
1077 for _ in 0..total {
1078 while *turn % 2 == 0 {
1079 turn = cv.wait(turn).ok()?;
1080 }
1081 *turn = turn.wrapping_add(1);
1082 cv.notify_one();
1083 }
1084 Some(())
1085 })
1086 .ok()?;
1087
1088 let mut hops: Vec<u64> = Vec::with_capacity(ITERS as usize);
1089 {
1090 let (lock, cv) = &*pair;
1091 let mut turn = lock.lock().ok()?;
1092 for i in 0..total {
1093 let t0 = std::time::Instant::now();
1094 *turn = turn.wrapping_add(1);
1095 cv.notify_one();
1096 while *turn % 2 == 1 {
1097 turn = cv.wait(turn).ok()?;
1098 }
1099 if i >= WARMUP {
1100 hops.push(t0.elapsed().as_nanos() as u64 / 2);
1102 }
1103 }
1104 }
1105 responder.join().ok()?;
1106
1107 if hops.len() < ITERS as usize / 2 {
1108 return None;
1109 }
1110 hops.sort_unstable();
1111 let median = hops[hops.len() / 2];
1112 let p99 = hops[hops.len() * 99 / 100];
1113 if median == 0 || p99 == 0 {
1114 return None;
1115 }
1116 Some(HandoffProbe { median, p99 })
1117}
1118
1119fn reexec_self() -> Result<()> {
1120 use std::os::unix::process::CommandExt;
1121
1122 let exe = std::fs::read_link("/proc/self/exe")
1123 .context("failed to resolve /proc/self/exe for restart")?;
1124 let err = std::process::Command::new(exe)
1126 .args(std::env::args_os().skip(1))
1127 .exec();
1128
1129 Err(anyhow::Error::new(err).context("re-exec after kernel restart request failed"))
1130}
1131
1132fn drop_all_capabilities() {
1134 #[repr(C)]
1135 struct CapHeader {
1136 version: u32,
1137 pid: i32,
1138 }
1139 #[repr(C)]
1140 #[derive(Clone, Copy)]
1141 struct CapData {
1142 effective: u32,
1143 permitted: u32,
1144 inheritable: u32,
1145 }
1146 const LINUX_CAPABILITY_VERSION_3: u32 = 0x2008_0522;
1147 let hdr = CapHeader {
1148 version: LINUX_CAPABILITY_VERSION_3,
1149 pid: 0,
1150 };
1151 let data = [CapData {
1152 effective: 0,
1153 permitted: 0,
1154 inheritable: 0,
1155 }; 2];
1156 let rc = unsafe { libc::syscall(libc::SYS_capset, &hdr, data.as_ptr()) };
1157 if rc != 0 {
1158 log::warn!("capset drop failed ({})", std::io::Error::last_os_error());
1159 }
1160}
1161
1162fn main() -> Result<()> {
1163 unsafe {
1168 libc::prctl(libc::PR_SET_DUMPABLE, 1, 0, 0, 0);
1169 }
1170
1171 let opts = Opts::parse();
1172
1173 if opts.version {
1174 println!(
1175 "{} {}",
1176 SCHEDULER_NAME,
1177 build_id::full_version(env!("CARGO_PKG_VERSION"))
1178 );
1179 return Ok(());
1180 }
1181
1182 let mut lcfg = simplelog::ConfigBuilder::new();
1183 lcfg.set_time_level(simplelog::LevelFilter::Error)
1184 .set_location_level(simplelog::LevelFilter::Off)
1185 .set_target_level(simplelog::LevelFilter::Off)
1186 .set_thread_level(simplelog::LevelFilter::Off);
1187 simplelog::TermLogger::init(
1188 simplelog::LevelFilter::Info,
1189 lcfg.build(),
1190 simplelog::TerminalMode::Stderr,
1191 simplelog::ColorChoice::Auto,
1192 )?;
1193
1194 let shutdown = Arc::new(AtomicBool::new(false));
1195 let shutdown_clone = shutdown.clone();
1196 ctrlc::set_handler(move || {
1197 shutdown_clone.store(true, Ordering::Relaxed);
1198 })
1199 .context("Error setting Ctrl-C handler")?;
1200
1201 let mut open_object = MaybeUninit::uninit();
1202 let mut sched = Scheduler::init(&opts, &mut open_object)?;
1203
1204 if sched.run(shutdown.clone())?.should_restart() {
1205 info!("🍰 restart requested by the kernel — re-executing");
1206 reexec_self()?;
1207 }
1208
1209 Ok(())
1210}
1211
1212#[cfg(test)]
1213mod tests {
1214 use super::sinks_by_widest_gap;
1215
1216 fn flagged(hot: &[bool]) -> Vec<usize> {
1217 hot.iter()
1218 .enumerate()
1219 .filter_map(|(cpu, h)| h.then_some(cpu))
1220 .collect()
1221 }
1222
1223 #[test]
1224 fn bimodal_host_cuts_above_the_gap() {
1225 let deltas = [0, 1, 0, 0, 0, 20, 0, 1, 0, 0, 0, 0, 0, 32, 0, 0];
1228 let hot = sinks_by_widest_gap(&deltas).unwrap();
1229 assert_eq!(flagged(&hot), vec![5, 13]);
1230 }
1231
1232 #[test]
1233 fn flat_distribution_has_no_sinks() {
1234 assert_eq!(sinks_by_widest_gap(&[3; 16]).unwrap(), vec![false; 16]);
1235 assert_eq!(sinks_by_widest_gap(&[0; 16]).unwrap(), vec![false; 16]);
1236 }
1237
1238 #[test]
1239 fn half_machine_cut_is_untrusted() {
1240 let deltas = [50, 50, 50, 50, 50, 50, 50, 50, 0, 0, 0, 0, 0, 0, 0, 0];
1242 assert!(sinks_by_widest_gap(&deltas).is_none());
1243 }
1244
1245 #[test]
1246 fn lone_loud_cpu_separates_from_quiet_machine() {
1247 let mut deltas = [0u64; 16];
1248 deltas[13] = 4;
1249 let hot = sinks_by_widest_gap(&deltas).unwrap();
1250 assert_eq!(flagged(&hot), vec![13]);
1251 }
1252}