1mod bpf_skel;
10pub use bpf_skel::*;
11pub mod bpf_intf;
12mod bpf_streams;
13pub use bpf_intf::*;
14
15mod cpu_order;
16use scx_utils::init_libbpf_logging;
17mod stats;
18use std::ffi::CStr;
19use std::ffi::CString;
20use std::ffi::c_int;
21use std::mem;
22use std::mem::MaybeUninit;
23use std::os::unix::ffi::OsStrExt;
24use std::os::unix::fs::MetadataExt;
25use std::path::Path;
26use std::path::PathBuf;
27use std::str;
28use std::sync::Arc;
29use std::sync::atomic::AtomicBool;
30use std::sync::atomic::Ordering;
31use std::thread::ThreadId;
32use std::time::Duration;
33
34use anyhow::Context;
35use anyhow::Result;
36use clap::Parser;
37use clap_num::number_range;
38use cpu_order::CpuOrder;
39use cpu_order::PerfCpuOrder;
40use crossbeam::channel;
41use crossbeam::channel::RecvTimeoutError;
42use crossbeam::channel::Sender;
43use crossbeam::channel::TrySendError;
44use libbpf_rs::AsRawLibbpf;
45use libbpf_rs::OpenObject;
46use libbpf_rs::PrintLevel;
47use libbpf_rs::ProgramInput;
48use libbpf_rs::skel::OpenSkel;
49use libbpf_rs::skel::Skel;
50use libc::c_char;
51use plain::Plain;
52use scx_arena::ArenaLib;
53use scx_stats::prelude::*;
54use scx_utils::EnergyModel;
55use scx_utils::NR_CPU_IDS;
56use scx_utils::TopologyArgs;
57use scx_utils::UserExitInfo;
58use scx_utils::autopower::{PowerProfile, fetch_power_profile};
59use scx_utils::build_id;
60use scx_utils::compat;
61use scx_utils::libbpf_clap_opts::LibbpfOpts;
62use scx_utils::scx_ops_attach;
63use scx_utils::scx_ops_cid_load;
64use scx_utils::scx_ops_cid_open;
65use scx_utils::try_set_rlimit_infinity;
66use scx_utils::uei_exited;
67use scx_utils::uei_report;
68use stats::SchedSample;
69use stats::SchedSamples;
70use stats::StatsReq;
71use stats::StatsRes;
72use stats::SysStats;
73use tracing::{debug, info, warn};
74use tracing_subscriber::filter::EnvFilter;
75
76const SCHEDULER_NAME: &str = "scx_mavd";
77#[derive(Debug, Parser)]
83struct Opts {
84 #[clap(short = 'v', long, action = clap::ArgAction::Count)]
86 verbose: u8,
87
88 #[clap(long = "autopilot", action = clap::ArgAction::SetTrue)]
95 autopilot: bool,
96
97 #[clap(long = "autopower", action = clap::ArgAction::SetTrue)]
104 autopower: bool,
105
106 #[clap(long = "performance", action = clap::ArgAction::SetTrue)]
111 performance: bool,
112
113 #[clap(long = "powersave", action = clap::ArgAction::SetTrue)]
118 powersave: bool,
119
120 #[clap(long = "balanced", action = clap::ArgAction::SetTrue)]
125 balanced: bool,
126
127 #[clap(long = "slice-max-us", default_value = "5000")]
129 slice_max_us: u64,
130
131 #[clap(long = "slice-min-us", default_value = "500")]
133 slice_min_us: u64,
134
135 #[clap(long = "lat-load-target-pct", default_value = "100", value_parser=Opts::lat_load_target_pct_range)]
141 lat_load_target_pct: u16,
142
143 #[clap(long = "mig-delta-pct", default_value = "0", value_parser=Opts::mig_delta_pct_range)]
150 mig_delta_pct: u8,
151
152 #[clap(long = "warm-cpu-us", default_value = "0")]
158 warm_cpu_us: u64,
159
160 #[clap(long = "lb-low-util-pct", default_value = "25", value_parser=Opts::lb_low_util_pct_range)]
166 lb_low_util_pct: u8,
167
168 #[clap(long = "lb-local-dsq-util-pct", default_value = "10", value_parser=Opts::lb_local_dsq_util_pct_range)]
175 lb_local_dsq_util_pct: u8,
176
177 #[clap(long = "pinned-slice-us", default_value = "5000")]
185 pinned_slice_us: Option<u64>,
186
187 #[clap(long = "preempt-shift", default_value = "6", value_parser=Opts::preempt_shift_range)]
192 preempt_shift: u8,
193
194 #[clap(long = "cpu-pref-order", default_value = "")]
200 cpu_pref_order: String,
201
202 #[clap(long = "no-use-em", action = clap::ArgAction::SetTrue)]
204 no_use_em: bool,
205
206 #[clap(long = "no-futex-boost", action = clap::ArgAction::SetTrue)]
208 no_futex_boost: bool,
209
210 #[clap(long = "no-fast-lb", action = clap::ArgAction::SetTrue)]
214 no_fast_lb: bool,
215
216 #[clap(long = "no-ovrflw-extend", action = clap::ArgAction::SetTrue)]
223 no_ovrflw_extend: bool,
224
225 #[clap(long = "no-preemption", action = clap::ArgAction::SetTrue)]
227 no_preemption: bool,
228
229 #[clap(long = "no-wake-sync", action = clap::ArgAction::SetTrue)]
231 no_wake_sync: bool,
232
233 #[clap(long = "no-slice-boost", action = clap::ArgAction::SetTrue)]
235 no_slice_boost: bool,
236
237 #[clap(long = "per-cpu-dsq", action = clap::ArgAction::SetTrue)]
243 per_cpu_dsq: bool,
244
245 #[clap(long = "enable-cpu-bw", action = clap::ArgAction::SetTrue)]
248 enable_cpu_bw: bool,
249
250 #[clap(long = "cgroup", value_name = "PATH")]
253 cgroup: Option<String>,
254
255 #[clap(long = "cpu-bw-max-cgroups", default_value = "2048", value_parser = Opts::cpu_bw_max_cgroups_range)]
259 cpu_bw_max_cgroups: u32,
260
261 #[clap(long = "cpu-bw-max-tree-height", default_value = "32", value_parser = Opts::cpu_bw_max_tree_height_range)]
265 cpu_bw_max_tree_height: u32,
266
267 #[clap(long = "partial", action = clap::ArgAction::SetTrue)]
271 partial: bool,
272
273 #[clap(long = "no-core-compaction", action = clap::ArgAction::SetTrue)]
283 no_core_compaction: bool,
284
285 #[clap(long = "no-freq-scaling", action = clap::ArgAction::SetTrue)]
287 no_freq_scaling: bool,
288
289 #[clap(long)]
291 stats: Option<f64>,
292
293 #[clap(long)]
296 monitor: Option<f64>,
297
298 #[clap(long)]
301 monitor_sched_samples: Option<u64>,
302
303 #[clap(long, default_value = "info")]
306 log_level: String,
307
308 #[clap(long, default_value = "262144")]
313 exit_dump_len: u32,
314
315 #[clap(short = 'V', long, action = clap::ArgAction::SetTrue)]
317 version: bool,
318
319 #[clap(long)]
321 run_id: Option<u64>,
322
323 #[clap(long)]
325 help_stats: bool,
326
327 #[clap(flatten, next_help_heading = "Libbpf Options")]
328 pub libbpf: LibbpfOpts,
329
330 #[clap(flatten)]
332 topology: Option<TopologyArgs>,
333}
334
335impl Opts {
336 fn can_autopilot(&self) -> bool {
337 !self.autopower
338 && !self.performance
339 && !self.powersave
340 && !self.balanced
341 && !self.no_core_compaction
342 }
343
344 fn can_autopower(&self) -> bool {
345 !self.autopilot
346 && !self.performance
347 && !self.powersave
348 && !self.balanced
349 && !self.no_core_compaction
350 }
351
352 fn can_performance(&self) -> bool {
353 !self.autopilot && !self.autopower && !self.powersave && !self.balanced
354 }
355
356 fn can_balanced(&self) -> bool {
357 !self.autopilot
358 && !self.autopower
359 && !self.performance
360 && !self.powersave
361 && !self.no_core_compaction
362 }
363
364 fn can_powersave(&self) -> bool {
365 !self.autopilot
366 && !self.autopower
367 && !self.performance
368 && !self.balanced
369 && !self.no_core_compaction
370 }
371
372 fn proc(&mut self) -> Option<&mut Self> {
373 if !self.autopilot {
374 self.autopilot = self.can_autopilot();
375 }
376
377 if self.autopilot {
378 if !self.can_autopilot() {
379 info!("Autopilot mode cannot be used with conflicting options.");
380 return None;
381 }
382 info!("Autopilot mode is enabled.");
383 }
384
385 if self.autopower {
386 if !self.can_autopower() {
387 info!("Autopower mode cannot be used with conflicting options.");
388 return None;
389 }
390 info!("Autopower mode is enabled.");
391 }
392
393 if self.performance {
394 if !self.can_performance() {
395 info!("Performance mode cannot be used with conflicting options.");
396 return None;
397 }
398 info!("Performance mode is enabled.");
399 self.no_core_compaction = true;
400 self.no_ovrflw_extend = true;
403 }
404
405 if self.powersave {
406 if !self.can_powersave() {
407 info!("Powersave mode cannot be used with conflicting options.");
408 return None;
409 }
410 info!("Powersave mode is enabled.");
411 self.no_core_compaction = false;
412 }
413
414 if self.balanced {
415 if !self.can_balanced() {
416 info!("Balanced mode cannot be used with conflicting options.");
417 return None;
418 }
419 info!("Balanced mode is enabled.");
420 self.no_core_compaction = false;
421 }
422
423 if !EnergyModel::has_energy_model() || !self.cpu_pref_order.is_empty() {
424 self.no_use_em = true;
425 }
426 if self.no_use_em {
427 info!("Energy model won't be used for CPU preference order.");
428 }
429
430 if let Some(pinned_slice) = self.pinned_slice_us {
431 if pinned_slice == 0 {
432 info!("Pinned task slice mode is disabled. Pinned tasks will use per-domain DSQs.");
433 } else if pinned_slice < self.slice_min_us || pinned_slice > self.slice_max_us {
434 info!(
435 "pinned-slice-us ({}) must be between slice-min-us ({}) and slice-max-us ({})",
436 pinned_slice, self.slice_min_us, self.slice_max_us
437 );
438 return None;
439 } else {
440 info!(
441 "Pinned task slice mode is enabled ({} us). Pinned tasks will use per-CPU DSQs.",
442 pinned_slice
443 );
444 }
445 }
446
447 Some(self)
448 }
449
450 fn preempt_shift_range(s: &str) -> Result<u8, String> {
451 number_range(s, 0, 10)
452 }
453
454 fn cpu_bw_max_cgroups_range(s: &str) -> Result<u32, String> {
455 number_range(s, 1, u32::MAX)
456 }
457
458 fn cpu_bw_max_tree_height_range(s: &str) -> Result<u32, String> {
459 number_range(s, 1, u32::MAX)
460 }
461
462 fn lat_load_target_pct_range(s: &str) -> Result<u16, String> {
463 number_range(s, 0, 200)
464 }
465
466 fn mig_delta_pct_range(s: &str) -> Result<u8, String> {
467 number_range(s, 0, 100)
468 }
469
470 fn lb_low_util_pct_range(s: &str) -> Result<u8, String> {
471 number_range(s, 0, 100)
472 }
473
474 fn lb_local_dsq_util_pct_range(s: &str) -> Result<u8, String> {
475 number_range(s, 0, 100)
476 }
477}
478
479unsafe impl Plain for msg_task_ctx {}
480
481impl msg_task_ctx {
482 fn from_bytes(buf: &[u8]) -> &msg_task_ctx {
483 plain::from_bytes(buf).expect("The buffer is either too short or not aligned!")
484 }
485}
486
487impl introspec {
488 fn new() -> Self {
489 unsafe { mem::MaybeUninit::<introspec>::zeroed().assume_init() }
490 }
491}
492
493struct Scheduler<'a> {
494 _arenalib: ArenaLib,
495 skel: BpfSkel<'a>,
496 struct_ops: Option<libbpf_rs::Link>,
497 intrspc: introspec,
498 monitor_tid: Option<ThreadId>,
499 stats_server: StatsServer<StatsReq, StatsRes>,
500 mseq_id: u64,
501}
502
503impl<'a> Scheduler<'a> {
504 fn init(
505 opts: &'a Opts,
506 open_object: &'a mut MaybeUninit<OpenObject>,
507 sub: Option<(u64, PathBuf)>,
508 ) -> Result<Self> {
509 if *NR_CPU_IDS > LAVD_CPU_ID_MAX as usize {
510 panic!(
511 "Num possible CPU IDs ({}) exceeds maximum of ({})",
512 *NR_CPU_IDS, LAVD_CPU_ID_MAX
513 );
514 }
515
516 try_set_rlimit_infinity();
517
518 let debug_level = if opts.log_level.contains("trace") {
520 2
521 } else if opts.log_level.contains("debug") {
522 1
523 } else {
524 0
525 };
526 let mut skel_builder = BpfSkelBuilder::default();
527 skel_builder.obj_builder.debug(debug_level > 1);
528 init_libbpf_logging(Some(PrintLevel::Debug));
529
530 let open_opts = opts.libbpf.clone().into_bpf_open_opts();
531 let mut skel = scx_ops_cid_open!(skel_builder, open_object, lavd_ops, open_opts)?;
532
533 if !opts.no_futex_boost && !Self::attach_futex_ftraces(&mut skel)? {
536 info!("Fail to attach futex ftraces. Try with tracepoints.");
537 if !Self::attach_futex_tracepoints(&mut skel)? {
538 info!("Fail to attach futex tracepoints.");
539 }
540 }
541
542 let order = CpuOrder::new(opts.topology.as_ref(), opts.no_use_em).unwrap();
544 Self::init_cpus(&mut skel, &order);
545
546 if order.cpdom_map.len() > 1 {
549 Self::attach_execve_tracepoints(&mut skel)?;
550 }
551
552 Self::init_globals(
554 &mut skel,
555 opts,
556 &order,
557 debug_level,
558 sub.as_ref().map(|s| s.0),
559 )?;
560
561 if let Some(rodata) = skel.maps.rodata_data.as_mut() {
564 rodata.nr_cgrp_max = opts.cpu_bw_max_cgroups;
565 rodata.tree_height_max = opts.cpu_bw_max_tree_height;
566 }
567
568 scx_utils::resize_cgroup_bw(
571 skel.open_object_mut(),
572 opts.cpu_bw_max_cgroups,
573 opts.cpu_bw_max_tree_height,
574 order.nr_llcs,
575 )?;
576
577 let mut skel = scx_ops_cid_load!(skel, lavd_ops, uei)?;
579 let task_size = std::mem::size_of::<types::task_ctx>();
580 let arenalib = ArenaLib::setup(skel.object_mut(), task_size, 0, *NR_CPU_IDS)?;
581 Self::init_arena_globals(&mut skel, &order, opts)?;
583
584 let mut stats_server = StatsServer::new(stats::server_data(*NR_CPU_IDS as u64));
588 if let Some((_, sched_path)) = &sub {
589 stats_server = stats_server.set_sched_path(sched_path);
590 }
591 let stats_server = stats_server.launch()?;
592
593 let struct_ops = if sub.is_some() {
598 if !compat::is_sched_ext_enabled().context("Failed to read the sched_ext state")? {
599 anyhow::bail!("--cgroup needs a running root scheduler");
600 }
601 skel.attach()
602 .context("Failed to attach non-struct_ops BPF programs")?;
603 match skel.maps.lavd_ops.attach_struct_ops() {
604 Ok(link) => Some(link),
605 Err(e) => {
606 let why = match e.kind() {
607 libbpf_rs::ErrorKind::Unsupported => {
608 "the root scheduler has no sub-scheduler support"
609 }
610 libbpf_rs::ErrorKind::NotFound => {
611 "the cgroup is outside this cgroup namespace"
612 }
613 _ => "a sub-scheduler may already be attached at or above the cgroup",
614 };
615 return Err(e)
616 .with_context(|| format!("Failed to attach as a sub-scheduler: {why}"));
617 }
618 }
619 } else {
620 Some(scx_ops_attach!(skel, lavd_ops)?)
621 };
622
623 Ok(Self {
624 _arenalib: arenalib,
625 skel,
626 struct_ops,
627 intrspc: introspec::new(),
628 monitor_tid: None,
629 stats_server,
630 mseq_id: 0,
631 })
632 }
633
634 fn attach_futex_ftraces(skel: &mut OpenBpfSkel) -> Result<bool> {
635 let ftraces = vec![
636 ("__futex_wait", &skel.progs.fexit___futex_wait),
637 ("futex_wait_multiple", &skel.progs.fexit_futex_wait_multiple),
638 (
639 "futex_wait_requeue_pi",
640 &skel.progs.fexit_futex_wait_requeue_pi,
641 ),
642 ("futex_wake", &skel.progs.fexit_futex_wake),
643 ("futex_wake_op", &skel.progs.fexit_futex_wake_op),
644 ("futex_lock_pi", &skel.progs.fexit_futex_lock_pi),
645 ("futex_unlock_pi", &skel.progs.fexit_futex_unlock_pi),
646 ];
647
648 if !compat::tracer_available("function")? {
649 info!("Ftrace is not enabled in the kernel.");
650 return Ok(false);
651 }
652
653 compat::cond_kprobes_enable(ftraces)
654 }
655
656 fn attach_futex_tracepoints(skel: &mut OpenBpfSkel) -> Result<bool> {
657 let tracepoints = vec![
658 ("syscalls:sys_enter_futex", &skel.progs.rtp_sys_enter_futex),
659 ("syscalls:sys_exit_futex", &skel.progs.rtp_sys_exit_futex),
660 (
661 "syscalls:sys_exit_futex_wait",
662 &skel.progs.rtp_sys_exit_futex_wait,
663 ),
664 (
665 "syscalls:sys_exit_futex_waitv",
666 &skel.progs.rtp_sys_exit_futex_waitv,
667 ),
668 (
669 "syscalls:sys_exit_futex_wake",
670 &skel.progs.rtp_sys_exit_futex_wake,
671 ),
672 ];
673
674 compat::cond_tracepoints_enable(tracepoints)
675 }
676
677 fn attach_execve_tracepoints(skel: &mut OpenBpfSkel) -> Result<bool> {
678 let tracepoints = vec![
679 (
680 "syscalls:sys_enter_execve",
681 &skel.progs.cond_hook_sys_enter_execve,
682 ),
683 (
684 "syscalls:sys_enter_execveat",
685 &skel.progs.cond_hook_sys_enter_execveat,
686 ),
687 ];
688
689 compat::cond_tracepoints_enable(tracepoints)
690 }
691
692 fn init_cpus(skel: &mut OpenBpfSkel, order: &CpuOrder) {
693 debug!("{:#?}", order);
694
695 let nr_pco_states: u8 = order.perf_cpu_order.len() as u8;
697 if nr_pco_states > LAVD_PCO_STATE_MAX as u8 {
698 panic!(
699 "Generated performance vs. CPU order stats are too complex ({nr_pco_states}) to handle"
700 );
701 }
702
703 skel.maps.rodata_data.as_mut().unwrap().nr_pco_states = nr_pco_states;
704 for (_, pco) in order.perf_cpu_order.iter() {
705 info!("{:#}", pco);
706 }
707 }
708
709 fn write_cpu_tables(uptrs: &types::mavd_uptrs, order: &CpuOrder) {
710 let cpu_capacity =
711 unsafe { &mut *(uptrs.cpu_capacity as *mut [u16; LAVD_CPU_ID_MAX as usize]) };
712 let cpu_big = unsafe { &mut *(uptrs.cpu_big as *mut [u8; LAVD_CPU_ID_MAX as usize]) };
713 let cpu_turbo = unsafe { &mut *(uptrs.cpu_turbo as *mut [u8; LAVD_CPU_ID_MAX as usize]) };
714
715 for cpu in order.cpuids.iter() {
716 cpu_capacity[cpu.cpu_adx] = cpu.cpu_cap as u16;
717 cpu_big[cpu.cpu_adx] = cpu.big_core as u8;
718 cpu_turbo[cpu.cpu_adx] = cpu.turbo_core as u8;
719 }
720 }
721
722 fn write_pco_order(uptrs: &types::mavd_uptrs, i: usize, pco: &PerfCpuOrder) {
723 let cpus_perf = pco.cpus_perf.borrow();
724 let cpus_ovflw = pco.cpus_ovflw.borrow();
725 let pco_nr_primary = cpus_perf.len();
726 let bounds = unsafe { &mut *(uptrs.pco_bounds as *mut [u32; LAVD_PCO_STATE_MAX as usize]) };
727 let nr_primary =
728 unsafe { &mut *(uptrs.pco_nr_primary as *mut [u16; LAVD_PCO_STATE_MAX as usize]) };
729 let table = uptrs.pco_table as *mut [u16; LAVD_CPU_ID_MAX as usize];
730 let cpu_order = unsafe { &mut *table.add(i) };
731
732 bounds[i] = pco.perf_cap as u32;
733 nr_primary[i] = pco_nr_primary as u16;
734
735 for (j, &cpu_adx) in cpus_perf.iter().enumerate() {
736 cpu_order[j] = cpu_adx as u16;
737 }
738
739 for (j, &cpu_adx) in cpus_ovflw.iter().enumerate() {
740 cpu_order[j + pco_nr_primary] = cpu_adx as u16;
741 }
742 }
743
744 fn init_cpdoms(uptrs: &types::mavd_uptrs, order: &CpuOrder) {
745 let cpdom_ctxs = uptrs.cpdom_ctxs as *mut types::cpdom_ctx;
747 if order.cpdom_map.len() > LAVD_CPDOM_MAX_NR as usize {
748 panic!("The processor topology is too complex to handle in BPF.");
749 }
750 for (k, v) in order.cpdom_map.iter() {
751 let cpdomc = unsafe { &mut *cpdom_ctxs.add(v.cpdom_id) };
752 cpdomc.id = v.cpdom_id as u64;
753 cpdomc.alt_id = v.cpdom_alt_id.get() as u64;
754 cpdomc.numa_id = k.numa_adx as u8;
755 cpdomc.llc_id = k.llc_adx as u8;
756 cpdomc.is_big = k.is_big as u8;
757 cpdomc.is_valid = 1;
758 for cpu_id in v.cpu_ids.iter() {
759 let i = cpu_id / 64;
760 let j = cpu_id % 64;
761 cpdomc.__cpumask[i] |= 0x01 << j;
762 }
763
764 if v.neighbor_map.borrow().iter().len() > LAVD_CPDOM_MAX_DIST as usize {
765 panic!("The processor topology is too complex to handle in BPF.");
766 }
767
768 for (k, (_d, neighbors)) in v.neighbor_map.borrow().iter().enumerate() {
769 let nr_neighbors = neighbors.borrow().len() as u8;
770 if nr_neighbors > LAVD_CPDOM_MAX_NR as u8 {
771 panic!("The processor topology is too complex to handle in BPF.");
772 }
773 cpdomc.nr_neighbors[k] = nr_neighbors;
774 for (i, &id) in neighbors.borrow().iter().enumerate() {
775 let idx = (k * LAVD_CPDOM_MAX_NR as usize) + i;
776 cpdomc.neighbor_ids[idx] = id as u8;
777 }
778 }
779 }
780 }
781
782 fn fetch_uptrs(skel: &mut BpfSkel) -> Result<types::mavd_uptrs> {
783 let ret = skel
784 .progs
785 .mavd_publish_uptrs
786 .test_run(ProgramInput::default())?;
787 if ret.return_value != 0 {
788 anyhow::bail!("fetching arena addresses: {}", ret.return_value as i32);
789 }
790 Ok(skel.maps.bss_data.as_ref().unwrap().mavd_uptrs)
791 }
792
793 fn init_arena_globals(skel: &mut BpfSkel, order: &CpuOrder, opts: &Opts) -> Result<()> {
796 let uptrs = Self::fetch_uptrs(skel)?;
797
798 Self::write_cpu_tables(&uptrs, order);
799
800 let nr_pco_states = order.perf_cpu_order.len();
801 for (i, (_, pco)) in order.perf_cpu_order.iter().enumerate() {
802 Self::write_pco_order(&uptrs, i, pco);
803 }
804
805 let (_, last_pco) = order.perf_cpu_order.last_key_value().unwrap();
806 for i in nr_pco_states..LAVD_PCO_STATE_MAX as usize {
807 Self::write_pco_order(&uptrs, i, last_pco);
808 }
809
810 Self::init_cpdoms(&uptrs, order);
811
812 unsafe {
813 std::ptr::write_volatile(uptrs.no_preemption as *mut bool, opts.no_preemption);
814 std::ptr::write_volatile(
815 uptrs.no_core_compaction as *mut bool,
816 opts.no_core_compaction,
817 );
818 std::ptr::write_volatile(uptrs.no_freq_scaling as *mut bool, opts.no_freq_scaling);
819 std::ptr::write_volatile(uptrs.is_powersave_mode as *mut bool, opts.powersave);
820 }
821 Ok(())
822 }
823
824 fn init_globals(
825 skel: &mut OpenBpfSkel,
826 opts: &Opts,
827 order: &CpuOrder,
828 debug_level: u8,
829 sub_cgid: Option<u64>,
830 ) -> Result<()> {
831 let rodata = skel.maps.rodata_data.as_mut().unwrap();
832 rodata.nr_llcs = order.nr_llcs as u64;
833 rodata.nr_cpu_ids = *NR_CPU_IDS as u32;
834 rodata.is_smt_active = order.smt_enabled;
835 rodata.is_autopilot_on = opts.autopilot;
836 rodata.verbose = debug_level;
837 rodata.slice_max_ns = opts.slice_max_us * 1000;
838 rodata.slice_min_ns = opts.slice_min_us * 1000;
839 rodata.pinned_slice_ns = opts.pinned_slice_us.map(|v| v * 1000).unwrap_or(0);
840 rodata.preempt_shift = opts.preempt_shift;
841 rodata.lat_load_target_pct = opts.lat_load_target_pct;
842 rodata.mig_delta_pct = opts.mig_delta_pct;
843 rodata.warm_cpu_ns = opts.warm_cpu_us * 1000;
844 rodata.lb_low_util_wall = ((opts.lb_low_util_pct as u64) << 10) / 100;
845 rodata.lb_local_dsq_util_wall = ((opts.lb_local_dsq_util_pct as u64) << 10) / 100;
846 rodata.no_use_em = opts.no_use_em as u8;
847 rodata.no_fast_lb = opts.no_fast_lb as u8;
848 rodata.no_ovrflw_extend = opts.no_ovrflw_extend as u8;
849
850 rodata.no_wake_sync = opts.no_wake_sync;
851 rodata.no_slice_boost = opts.no_slice_boost;
852 rodata.per_cpu_dsq = opts.per_cpu_dsq;
853 rodata.enable_cpu_bw = opts.enable_cpu_bw;
854
855 if let Some(cgid) = sub_cgid {
856 rodata.sub_mode = true;
857 rodata.sub_cgroup_id = cgid;
858 skel.struct_ops.lavd_ops_mut().sub_cgroup_id = cgid;
859 }
860
861 scx_utils::setup_cgroup_bw(skel.open_object_mut(), "lavd_cgroup_set_bandwidth")?;
862
863 unsafe {
865 libbpf_rs::libbpf_sys::bpf_program__set_autoload(
866 skel.progs.lavd_sched_switch.as_libbpf_object().as_ptr(),
867 true,
868 );
869 }
870
871 skel.struct_ops.lavd_ops_mut().flags = *compat::SCX_OPS_ENQ_EXITING
872 | *compat::SCX_OPS_ENQ_LAST
873 | *compat::SCX_OPS_ENQ_MIGRATION_DISABLED;
874
875 if opts.partial {
876 skel.struct_ops.lavd_ops_mut().flags |= *compat::SCX_OPS_SWITCH_PARTIAL;
877 }
878
879 skel.struct_ops.lavd_ops_mut().exit_dump_len = opts.exit_dump_len;
880
881 Ok(())
882 }
883
884 fn get_msg_seq_id() -> u64 {
885 static mut MSEQ: u64 = 0;
886 unsafe {
887 MSEQ += 1;
888 MSEQ
889 }
890 }
891
892 fn relay_introspec(data: &[u8], intrspc_tx: &Sender<SchedSample>) -> i32 {
893 let mt = msg_task_ctx::from_bytes(data);
894 let tx = mt.taskc_x;
895
896 if mt.hdr.kind != LAVD_MSG_TASKC {
898 return 0;
899 }
900
901 let mseq = Scheduler::get_msg_seq_id();
902
903 let c_tx_cm: *const c_char = (&tx.comm as *const [c_char; 17]) as *const c_char;
904 let c_tx_cm_str: &CStr = unsafe { CStr::from_ptr(c_tx_cm) };
905 let tx_comm: &str = c_tx_cm_str.to_str().unwrap();
906
907 let c_waker_cm: *const c_char = (&tx.waker_comm as *const [c_char; 17]) as *const c_char;
908 let c_waker_cm_str: &CStr = unsafe { CStr::from_ptr(c_waker_cm) };
909 let waker_comm: &str = c_waker_cm_str.to_str().unwrap();
910
911 let c_tx_st: *const c_char = (&tx.stat as *const [c_char; 5]) as *const c_char;
912 let c_tx_st_str: &CStr = unsafe { CStr::from_ptr(c_tx_st) };
913 let tx_stat: &str = c_tx_st_str.to_str().unwrap();
914
915 match intrspc_tx.try_send(SchedSample {
916 mseq,
917 pid: tx.pid,
918 comm: tx_comm.into(),
919 stat: tx_stat.into(),
920 cpu_id: tx.cpu_id,
921 prev_cpu_id: tx.prev_cpu_id,
922 suggested_cpu_id: tx.suggested_cpu_id,
923 waker_pid: tx.waker_pid,
924 waker_comm: waker_comm.into(),
925 slice_wall: tx.slice_wall,
926 lat_cri: tx.lat_cri,
927 avg_lat_cri: tx.avg_lat_cri,
928 static_prio: tx.static_prio,
929 rerunnable_interval_wall: tx.rerunnable_interval_wall,
930 resched_interval_wall: tx.resched_interval_wall,
931 run_freq: tx.run_freq,
932 avg_runtime_wall: tx.avg_runtime_wall,
933 wait_freq: tx.wait_freq,
934 wake_freq: tx.wake_freq,
935 perf_cri: tx.perf_cri,
936 thr_perf_cri: tx.thr_perf_cri,
937 cpuperf_cur: tx.cpuperf_cur,
938 cpu_util_wall: tx.cpu_util_wall,
939 cpu_util_invr: tx.cpu_util_invr,
940 steal_util_wall: tx.steal_util_wall,
941 steal_util_invr: tx.steal_util_invr,
942 dom_pinned_util_wall: tx.dom_pinned_util_wall,
943 dom_pinned_util_invr: tx.dom_pinned_util_invr,
944 nr_active: tx.nr_active,
945 dsq_id: tx.dsq_id,
946 dsq_consume_lat: tx.dsq_consume_lat,
947 lat_headroom: tx.lat_headroom,
948 vuln_thresh: tx.vuln_thresh,
949 task_util_est: tx.task_util_est,
950 norm_lat_cri: tx.norm_lat_cri,
951 cpu_heat: tx.cpu_heat,
952 warm_cpu_id: tx.warm_cpu_id,
953 slice_used_wall: tx.last_slice_used_wall,
954 }) {
955 Ok(()) | Err(TrySendError::Full(_)) => 0,
956 Err(e) => panic!("failed to send on intrspc_tx ({})", e),
957 }
958 }
959
960 fn prep_introspec(&mut self) {
961 if !self.skel.maps.bss_data.as_ref().unwrap().is_monitored {
962 self.skel.maps.bss_data.as_mut().unwrap().is_monitored = true;
963 }
964 self.skel.maps.bss_data.as_mut().unwrap().intrspc.cmd = self.intrspc.cmd;
965 self.skel.maps.bss_data.as_mut().unwrap().intrspc.arg = self.intrspc.arg;
966 }
967
968 fn cleanup_introspec(&mut self) {
969 self.skel.maps.bss_data.as_mut().unwrap().intrspc.cmd = LAVD_CMD_NOP;
970 }
971
972 fn get_pc(x: u64, y: u64) -> f64 {
973 100. * x as f64 / y as f64
974 }
975
976 fn get_power_mode(power_mode: i32) -> &'static str {
977 match power_mode as u32 {
978 LAVD_PM_PERFORMANCE => "performance",
979 LAVD_PM_BALANCED => "balanced",
980 LAVD_PM_POWERSAVE => "powersave",
981 _ => "unknown",
982 }
983 }
984
985 fn drain_sched_samples(&mut self, timeout: Option<Duration>) -> Result<Vec<SchedSample>> {
992 let (intrspc_tx, intrspc_rx) = channel::unbounded();
993
994 {
995 let mut builder = libbpf_rs::RingBufferBuilder::new();
996 builder.add(&self.skel.maps.introspec_msg, move |data| {
997 Scheduler::relay_introspec(data, &intrspc_tx)
998 })?;
999 let rb_mgr = builder.build()?;
1000
1001 match timeout {
1002 Some(timeout) => rb_mgr.poll(timeout)?,
1003 None => rb_mgr.consume()?,
1004 }
1005 }
1006
1007 Ok(intrspc_rx.try_iter().collect())
1008 }
1009
1010 fn stats_req_to_res(&mut self, req: &StatsReq) -> Result<StatsRes> {
1011 Ok(match req {
1012 StatsReq::NewSampler(tid) => {
1013 self.drain_sched_samples(None)?;
1015 self.monitor_tid = Some(*tid);
1016 StatsRes::Ack
1017 }
1018 StatsReq::SysStatsReq { tid } => {
1019 if Some(*tid) != self.monitor_tid {
1020 return Ok(StatsRes::Bye);
1021 }
1022 self.mseq_id += 1;
1023
1024 let uptrs = &self.skel.maps.bss_data.as_ref().unwrap().mavd_uptrs;
1025 let st =
1028 unsafe { std::ptr::read_volatile(uptrs.sys_stat as *const types::sys_stat) };
1029 let mode = unsafe { std::ptr::read_volatile(uptrs.power_mode as *const i32) };
1030 let performance_mode_ns =
1031 unsafe { std::ptr::read_volatile(uptrs.performance_mode_ns as *const u64) };
1032 let balanced_mode_ns =
1033 unsafe { std::ptr::read_volatile(uptrs.balanced_mode_ns as *const u64) };
1034 let powersave_mode_ns =
1035 unsafe { std::ptr::read_volatile(uptrs.powersave_mode_ns as *const u64) };
1036
1037 let mseq = self.mseq_id;
1038 let nr_queued_task = st.nr_queued_task;
1039 let nr_active = st.nr_active;
1040 let nr_sched = st.nr_sched;
1041 let nr_preempt = st.nr_preempt;
1042 let nr_reenq_kfunc = st.nr_reenq_kfunc;
1043 let pc_pc = Self::get_pc(st.nr_perf_cri, nr_sched);
1044 let pc_lc = Self::get_pc(st.nr_lat_cri, nr_sched);
1045 let pc_x_migration = Self::get_pc(st.nr_x_migration, nr_sched);
1046 let nr_stealee = st.nr_stealee;
1047 let nr_big = st.nr_big;
1048 let pc_big = Self::get_pc(nr_big, nr_sched);
1049 let pc_pc_on_big = Self::get_pc(st.nr_pc_on_big, nr_big);
1050 let pc_lc_on_big = Self::get_pc(st.nr_lc_on_big, nr_big);
1051 let power_mode = Self::get_power_mode(mode);
1052 let total_time = performance_mode_ns + balanced_mode_ns + powersave_mode_ns;
1053 let pc_performance = Self::get_pc(performance_mode_ns, total_time);
1054 let pc_balanced = Self::get_pc(balanced_mode_ns, total_time);
1055 let pc_powersave = Self::get_pc(powersave_mode_ns, total_time);
1056
1057 StatsRes::SysStats(SysStats {
1058 mseq,
1059 nr_queued_task,
1060 nr_active,
1061 nr_sched,
1062 nr_preempt,
1063 nr_reenq_kfunc,
1064 pc_pc,
1065 pc_lc,
1066 pc_x_migration,
1067 nr_stealee,
1068 pc_big,
1069 pc_pc_on_big,
1070 pc_lc_on_big,
1071 power_mode: power_mode.to_string(),
1072 pc_performance,
1073 pc_balanced,
1074 pc_powersave,
1075 })
1076 }
1077 StatsReq::SchedSamplesNr {
1078 tid,
1079 nr_samples,
1080 interval_ms,
1081 } => {
1082 if Some(*tid) != self.monitor_tid {
1083 return Ok(StatsRes::Bye);
1084 }
1085
1086 self.intrspc.cmd = LAVD_CMD_SCHED_N;
1087 self.intrspc.arg = *nr_samples;
1088 self.prep_introspec();
1089 std::thread::sleep(Duration::from_millis(*interval_ms));
1090
1091 let samples = self.drain_sched_samples(Some(Duration::from_millis(100)))?;
1092
1093 self.cleanup_introspec();
1094
1095 StatsRes::SchedSamples(SchedSamples { samples })
1096 }
1097 })
1098 }
1099
1100 fn stop_monitoring(&mut self) {
1101 if self.skel.maps.bss_data.as_ref().unwrap().is_monitored {
1102 self.skel.maps.bss_data.as_mut().unwrap().is_monitored = false;
1103 }
1104 }
1105
1106 pub fn exited(&mut self) -> bool {
1107 uei_exited!(&self.skel, uei)
1108 }
1109
1110 fn set_power_profile(&mut self, mode: u32) -> Result<(), u32> {
1111 let prog = &mut self.skel.progs.set_power_profile;
1112 let mut args = power_arg {
1113 power_mode: mode as c_int,
1114 };
1115 let input = ProgramInput {
1116 context_in: Some(unsafe {
1117 std::slice::from_raw_parts_mut(
1118 &mut args as *mut _ as *mut u8,
1119 std::mem::size_of_val(&args),
1120 )
1121 }),
1122 ..Default::default()
1123 };
1124 let out = prog.test_run(input).unwrap();
1125 if out.return_value != 0 {
1126 return Err(out.return_value);
1127 }
1128
1129 Ok(())
1130 }
1131
1132 fn update_power_profile(&mut self, prev_profile: PowerProfile) -> (bool, PowerProfile) {
1133 let profile = fetch_power_profile(false);
1134 if profile == prev_profile {
1135 return (true, profile);
1137 }
1138
1139 let _ = match profile {
1140 PowerProfile::Performance => self.set_power_profile(LAVD_PM_PERFORMANCE),
1141 PowerProfile::Balanced { .. } => self.set_power_profile(LAVD_PM_BALANCED),
1142 PowerProfile::Powersave => self.set_power_profile(LAVD_PM_POWERSAVE),
1143 PowerProfile::Unknown => {
1144 return (false, profile);
1147 }
1148 };
1149
1150 info!("Set the scheduler's power profile to {profile} mode.");
1151 (true, profile)
1152 }
1153
1154 fn run(&mut self, opts: &Opts, shutdown: Arc<AtomicBool>) -> Result<UserExitInfo> {
1155 let (res_ch, req_ch) = self.stats_server.channels();
1156 let mut autopower = opts.autopower;
1157 let mut profile = PowerProfile::Unknown;
1158 let mut immed_warned = false;
1159
1160 if opts.performance {
1161 let _ = self.set_power_profile(LAVD_PM_PERFORMANCE);
1162 } else if opts.powersave {
1163 let _ = self.set_power_profile(LAVD_PM_POWERSAVE);
1164 } else {
1165 let _ = self.set_power_profile(LAVD_PM_BALANCED);
1166 }
1167
1168 while !shutdown.load(Ordering::Relaxed) && !self.exited() {
1169 if autopower {
1170 (autopower, profile) = self.update_power_profile(profile);
1171 }
1172 if !immed_warned
1173 && self
1174 .skel
1175 .maps
1176 .bss_data
1177 .as_ref()
1178 .unwrap()
1179 .sub_immed_only_seen
1180 {
1181 immed_warned = true;
1182 warn!(
1183 "A cid granted ENQ_IMMED alone is left unused: borrowing is not implemented."
1184 );
1185 }
1186
1187 match req_ch.recv_timeout(Duration::from_secs(1)) {
1188 Ok(req) => {
1189 let res = self.stats_req_to_res(&req)?;
1190 res_ch.send(res)?;
1191 }
1192 Err(RecvTimeoutError::Timeout) => {
1193 self.stop_monitoring();
1194 }
1195 Err(e) => {
1196 self.stop_monitoring();
1197 Err(e)?
1198 }
1199 }
1200 self.cleanup_introspec();
1201 }
1202
1203 bpf_streams::dump_bpf_streams(&mut self.skel);
1204 let _ = self.struct_ops.take();
1205 uei_report!(&self.skel, uei)
1206 }
1207}
1208
1209impl Drop for Scheduler<'_> {
1210 fn drop(&mut self) {
1211 info!("Unregister {SCHEDULER_NAME} scheduler");
1212
1213 if let Some(struct_ops) = self.struct_ops.take() {
1214 drop(struct_ops);
1215 }
1216 }
1217}
1218
1219const SUB_SCHED_PATH_MAX: usize = 107 - "/var/run/scx/".len() - "/stats".len();
1222
1223fn sub_cgroup(path: &str) -> Result<(u64, PathBuf)> {
1229 let path = std::fs::canonicalize(path)
1230 .with_context(|| format!("Failed to resolve cgroup path {path}"))?;
1231 let cpath = CString::new(path.as_os_str().as_bytes())?;
1232 let mut st: libc::statfs = unsafe { mem::zeroed() };
1233 if unsafe { libc::statfs(cpath.as_ptr(), &mut st) } != 0 {
1234 return Err(std::io::Error::last_os_error())
1235 .with_context(|| format!("Failed to statfs {}", path.display()));
1236 }
1237 if st.f_type as u64 != libc::CGROUP2_SUPER_MAGIC as u64 || !path.is_dir() {
1238 anyhow::bail!("{} is not a cgroup", path.display());
1239 }
1240 let cgid = std::fs::metadata(&path)
1241 .with_context(|| format!("Failed to stat cgroup {}", path.display()))?
1242 .ino();
1243 if cgid <= 1 {
1244 anyhow::bail!("{} is the cgroup root", path.display());
1245 }
1246 let mirrored = path
1247 .strip_prefix("/sys/fs/cgroup")
1248 .ok()
1249 .filter(|rel| !rel.as_os_str().is_empty())
1250 .map(|rel| Path::new("sub").join(rel))
1251 .filter(|p| p.as_os_str().len() <= SUB_SCHED_PATH_MAX);
1252 let sched_path = mirrored.unwrap_or_else(|| PathBuf::from(format!("sub/{cgid}")));
1253 Ok((cgid, sched_path))
1254}
1255
1256fn trim_heap_after_init() {
1265 #[cfg(target_env = "gnu")]
1266 unsafe {
1267 libc::malloc_trim(0);
1268 }
1269}
1270
1271fn init_log(opts: &Opts) {
1272 let env_filter = EnvFilter::try_from_default_env()
1273 .or_else(|_| match EnvFilter::try_new(&opts.log_level) {
1274 Ok(filter) => Ok(filter),
1275 Err(e) => {
1276 eprintln!(
1277 "invalid log envvar: {}, using info, err is: {}",
1278 opts.log_level, e
1279 );
1280 EnvFilter::try_new("info")
1281 }
1282 })
1283 .unwrap_or_else(|_| EnvFilter::new("info"));
1284
1285 match tracing_subscriber::fmt()
1286 .with_env_filter(env_filter)
1287 .with_target(true)
1288 .with_thread_ids(true)
1289 .with_file(true)
1290 .with_line_number(true)
1291 .try_init()
1292 {
1293 Ok(()) => {}
1294 Err(e) => eprintln!("failed to init logger: {}", e),
1295 }
1296}
1297
1298#[clap_main::clap_main]
1299fn main(mut opts: Opts) -> Result<()> {
1300 if opts.version {
1301 println!(
1302 "scx_mavd {}",
1303 build_id::full_version(env!("CARGO_PKG_VERSION"))
1304 );
1305 return Ok(());
1306 }
1307
1308 if opts.help_stats {
1309 let sys_stats_meta_name = SysStats::meta().name;
1310 let sched_sample_meta_name = SchedSample::meta().name;
1311 let stats_meta_names: &[&str] = &[
1312 sys_stats_meta_name.as_str(),
1313 sched_sample_meta_name.as_str(),
1314 ];
1315 stats::server_data(0).describe_meta(&mut std::io::stdout(), Some(stats_meta_names))?;
1316 return Ok(());
1317 }
1318
1319 init_log(&opts);
1320
1321 if opts.verbose > 0 {
1322 warn!("Setting verbose via -v is deprecated and will be an error in future releases.");
1323 }
1324
1325 if let Some(run_id) = opts.run_id {
1326 info!("scx_mavd run_id: {}", run_id);
1327 }
1328
1329 if opts.monitor.is_none() && opts.monitor_sched_samples.is_none() {
1330 opts.proc().unwrap();
1331 info!("{:#?}", opts);
1332 }
1333
1334 let shutdown = Arc::new(AtomicBool::new(false));
1335 let shutdown_clone = shutdown.clone();
1336 ctrlc::set_handler(move || {
1337 shutdown_clone.store(true, Ordering::Relaxed);
1338 })
1339 .context("Error setting Ctrl-C handler")?;
1340
1341 let sub = opts.cgroup.as_deref().map(sub_cgroup).transpose()?;
1343 let sched_path = sub.as_ref().map(|s| s.1.clone());
1344
1345 if let Some(nr_samples) = opts.monitor_sched_samples {
1346 let shutdown_copy = shutdown.clone();
1347 let jh = std::thread::spawn(move || {
1348 stats::monitor_sched_samples(nr_samples, shutdown_copy, sched_path).unwrap()
1349 });
1350 let _ = jh.join();
1351 return Ok(());
1352 }
1353
1354 if let Some(intv) = opts.monitor.or(opts.stats) {
1355 let shutdown_copy = shutdown.clone();
1356 let jh = std::thread::spawn(move || {
1357 stats::monitor(Duration::from_secs_f64(intv), shutdown_copy, sched_path).unwrap()
1358 });
1359 if opts.monitor.is_some() {
1360 let _ = jh.join();
1361 return Ok(());
1362 }
1363 }
1364
1365 let mut open_object = MaybeUninit::uninit();
1366 loop {
1367 let mut sched = Scheduler::init(&opts, &mut open_object, sub.clone())?;
1368 info!(
1369 "scx_mavd scheduler is initialized (build ID: {})",
1370 build_id::full_version(env!("CARGO_PKG_VERSION"))
1371 );
1372 info!("scx_mavd scheduler starts running.");
1373 trim_heap_after_init();
1374 if !sched.run(&opts, shutdown.clone())?.should_restart() {
1375 break;
1376 }
1377 }
1378
1379 Ok(())
1380}