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::Topology;
17use scx_utils::init_libbpf_logging;
18mod stats;
19use std::ffi::CStr;
20use std::ffi::c_int;
21use std::mem;
22use std::mem::MaybeUninit;
23use std::str;
24use std::sync::Arc;
25use std::sync::atomic::AtomicBool;
26use std::sync::atomic::Ordering;
27use std::thread::ThreadId;
28use std::time::Duration;
29
30use anyhow::Context;
31use anyhow::Result;
32use clap::Parser;
33use clap_num::number_range;
34use cpu_order::CpuOrder;
35use cpu_order::PerfCpuOrder;
36use crossbeam::channel;
37use crossbeam::channel::RecvTimeoutError;
38use crossbeam::channel::Sender;
39use crossbeam::channel::TrySendError;
40use libbpf_rs::AsRawLibbpf;
41use libbpf_rs::OpenObject;
42use libbpf_rs::PrintLevel;
43use libbpf_rs::ProgramInput;
44use libbpf_rs::skel::OpenSkel;
45use libbpf_rs::skel::Skel;
46use libc::c_char;
47use plain::Plain;
48use scx_arena::ArenaLib;
49use scx_stats::prelude::*;
50use scx_utils::EnergyModel;
51use scx_utils::NR_CPU_IDS;
52use scx_utils::TopologyArgs;
53use scx_utils::UserExitInfo;
54use scx_utils::autopower::{PowerProfile, fetch_power_profile};
55use scx_utils::build_id;
56use scx_utils::compat;
57use scx_utils::ksym_exists;
58use scx_utils::libbpf_clap_opts::LibbpfOpts;
59use scx_utils::scx_ops_attach;
60use scx_utils::scx_ops_load;
61use scx_utils::scx_ops_open;
62use scx_utils::try_set_rlimit_infinity;
63use scx_utils::uei_exited;
64use scx_utils::uei_report;
65use stats::SchedSample;
66use stats::SchedSamples;
67use stats::StatsReq;
68use stats::StatsRes;
69use stats::SysStats;
70use tracing::{debug, info, warn};
71use tracing_subscriber::filter::EnvFilter;
72
73const SCHEDULER_NAME: &str = "scx_lavd";
74#[derive(Debug, Parser)]
80struct Opts {
81 #[clap(short = 'v', long, action = clap::ArgAction::Count)]
83 verbose: u8,
84
85 #[clap(long = "autopilot", action = clap::ArgAction::SetTrue)]
92 autopilot: bool,
93
94 #[clap(long = "autopower", action = clap::ArgAction::SetTrue)]
101 autopower: bool,
102
103 #[clap(long = "performance", action = clap::ArgAction::SetTrue)]
108 performance: bool,
109
110 #[clap(long = "powersave", action = clap::ArgAction::SetTrue)]
115 powersave: bool,
116
117 #[clap(long = "balanced", action = clap::ArgAction::SetTrue)]
122 balanced: bool,
123
124 #[clap(long = "slice-max-us", default_value = "5000")]
126 slice_max_us: u64,
127
128 #[clap(long = "slice-min-us", default_value = "500")]
130 slice_min_us: u64,
131
132 #[clap(long = "lat-load-target-pct", default_value = "100", value_parser=Opts::lat_load_target_pct_range)]
138 lat_load_target_pct: u16,
139
140 #[clap(long = "mig-delta-pct", default_value = "0", value_parser=Opts::mig_delta_pct_range)]
147 mig_delta_pct: u8,
148
149 #[clap(long = "warm-cpu-us", default_value = "0")]
155 warm_cpu_us: u64,
156
157 #[clap(long = "lb-low-util-pct", default_value = "25", value_parser=Opts::lb_low_util_pct_range)]
163 lb_low_util_pct: u8,
164
165 #[clap(long = "lb-local-dsq-util-pct", default_value = "10", value_parser=Opts::lb_local_dsq_util_pct_range)]
172 lb_local_dsq_util_pct: u8,
173
174 #[clap(long = "xmig-min-gain-us", default_value = "350", value_parser=Opts::xmig_min_gain_us_range)]
189 xmig_min_gain_us: u64,
190
191 #[clap(long = "pinned-slice-us", default_value = "5000")]
199 pinned_slice_us: Option<u64>,
200
201 #[clap(long = "preempt-shift", default_value = "6", value_parser=Opts::preempt_shift_range)]
206 preempt_shift: u8,
207
208 #[clap(long = "cpu-pref-order", default_value = "")]
214 cpu_pref_order: String,
215
216 #[clap(long = "no-use-em", action = clap::ArgAction::SetTrue)]
218 no_use_em: bool,
219
220 #[clap(long = "no-futex-boost", action = clap::ArgAction::SetTrue)]
222 no_futex_boost: bool,
223
224 #[clap(long = "no-fast-lb", action = clap::ArgAction::SetTrue)]
228 no_fast_lb: bool,
229
230 #[clap(long = "no-ovrflw-extend", action = clap::ArgAction::SetTrue)]
237 no_ovrflw_extend: bool,
238
239 #[clap(long = "no-preemption", action = clap::ArgAction::SetTrue)]
241 no_preemption: bool,
242
243 #[clap(long = "no-wake-sync", action = clap::ArgAction::SetTrue)]
245 no_wake_sync: bool,
246
247 #[clap(long = "no-slice-boost", action = clap::ArgAction::SetTrue)]
249 no_slice_boost: bool,
250
251 #[clap(long = "per-cpu-dsq", action = clap::ArgAction::SetTrue)]
257 per_cpu_dsq: bool,
258
259 #[clap(long = "enable-cpu-bw", action = clap::ArgAction::SetTrue)]
262 enable_cpu_bw: bool,
263
264 #[clap(long = "cpu-bw-max-cgroups", default_value = "2048", value_parser = Opts::cpu_bw_max_cgroups_range)]
268 cpu_bw_max_cgroups: u32,
269
270 #[clap(long = "cpu-bw-max-tree-height", default_value = "32", value_parser = Opts::cpu_bw_max_tree_height_range)]
274 cpu_bw_max_tree_height: u32,
275
276 #[clap(long = "partial", action = clap::ArgAction::SetTrue)]
280 partial: bool,
281
282 #[clap(long = "no-core-compaction", action = clap::ArgAction::SetTrue)]
292 no_core_compaction: bool,
293
294 #[clap(long = "no-freq-scaling", action = clap::ArgAction::SetTrue)]
296 no_freq_scaling: bool,
297
298 #[clap(long)]
300 stats: Option<f64>,
301
302 #[clap(long)]
304 monitor: Option<f64>,
305
306 #[clap(long)]
309 monitor_sched_samples: Option<u64>,
310
311 #[clap(long, default_value = "info")]
314 log_level: String,
315
316 #[clap(long, default_value = "262144")]
321 exit_dump_len: u32,
322
323 #[clap(short = 'V', long, action = clap::ArgAction::SetTrue)]
325 version: bool,
326
327 #[clap(long)]
329 run_id: Option<u64>,
330
331 #[clap(long)]
333 help_stats: bool,
334
335 #[clap(flatten, next_help_heading = "Libbpf Options")]
336 pub libbpf: LibbpfOpts,
337
338 #[clap(flatten)]
340 topology: Option<TopologyArgs>,
341}
342
343impl Opts {
344 fn can_autopilot(&self) -> bool {
345 !self.autopower
346 && !self.performance
347 && !self.powersave
348 && !self.balanced
349 && !self.no_core_compaction
350 }
351
352 fn can_autopower(&self) -> bool {
353 !self.autopilot
354 && !self.performance
355 && !self.powersave
356 && !self.balanced
357 && !self.no_core_compaction
358 }
359
360 fn can_performance(&self) -> bool {
361 !self.autopilot && !self.autopower && !self.powersave && !self.balanced
362 }
363
364 fn can_balanced(&self) -> bool {
365 !self.autopilot
366 && !self.autopower
367 && !self.performance
368 && !self.powersave
369 && !self.no_core_compaction
370 }
371
372 fn can_powersave(&self) -> bool {
373 !self.autopilot
374 && !self.autopower
375 && !self.performance
376 && !self.balanced
377 && !self.no_core_compaction
378 }
379
380 fn proc(&mut self) -> Option<&mut Self> {
381 if !self.autopilot {
382 self.autopilot = self.can_autopilot();
383 }
384
385 if self.autopilot {
386 if !self.can_autopilot() {
387 info!("Autopilot mode cannot be used with conflicting options.");
388 return None;
389 }
390 info!("Autopilot mode is enabled.");
391 }
392
393 if self.autopower {
394 if !self.can_autopower() {
395 info!("Autopower mode cannot be used with conflicting options.");
396 return None;
397 }
398 info!("Autopower mode is enabled.");
399 }
400
401 if self.performance {
402 if !self.can_performance() {
403 info!("Performance mode cannot be used with conflicting options.");
404 return None;
405 }
406 info!("Performance mode is enabled.");
407 self.no_core_compaction = true;
408 self.no_ovrflw_extend = true;
411 }
412
413 if self.powersave {
414 if !self.can_powersave() {
415 info!("Powersave mode cannot be used with conflicting options.");
416 return None;
417 }
418 info!("Powersave mode is enabled.");
419 self.no_core_compaction = false;
420 }
421
422 if self.balanced {
423 if !self.can_balanced() {
424 info!("Balanced mode cannot be used with conflicting options.");
425 return None;
426 }
427 info!("Balanced mode is enabled.");
428 self.no_core_compaction = false;
429 }
430
431 if self.xmig_min_gain_us > 0 {
434 let topo = match self.topology.as_ref() {
435 Some(args) => Topology::with_args(args),
436 None => Topology::new(),
437 };
438 match topo {
439 Ok(topo) if !topo.has_little_cores() => {
440 info!("Completion-time migration is disabled on a homogeneous machine.");
441 self.xmig_min_gain_us = 0;
442 }
443 Ok(_) => {}
444 Err(e) => warn!(
445 "Could not read the topology to classify the machine; \
446 leaving --xmig-min-gain-us as set: {}",
447 e
448 ),
449 }
450 }
451
452 if !EnergyModel::has_energy_model() || !self.cpu_pref_order.is_empty() {
453 self.no_use_em = true;
454 }
455 if self.no_use_em {
456 info!("Energy model won't be used for CPU preference order.");
457 }
458
459 if let Some(pinned_slice) = self.pinned_slice_us {
460 if pinned_slice == 0 {
461 info!("Pinned task slice mode is disabled. Pinned tasks will use per-domain DSQs.");
462 } else if pinned_slice < self.slice_min_us || pinned_slice > self.slice_max_us {
463 info!(
464 "pinned-slice-us ({}) must be between slice-min-us ({}) and slice-max-us ({})",
465 pinned_slice, self.slice_min_us, self.slice_max_us
466 );
467 return None;
468 } else {
469 info!(
470 "Pinned task slice mode is enabled ({} us). Pinned tasks will use per-CPU DSQs.",
471 pinned_slice
472 );
473 }
474 }
475
476 Some(self)
477 }
478
479 fn preempt_shift_range(s: &str) -> Result<u8, String> {
480 number_range(s, 0, 10)
481 }
482
483 fn cpu_bw_max_cgroups_range(s: &str) -> Result<u32, String> {
484 number_range(s, 1, u32::MAX)
485 }
486
487 fn cpu_bw_max_tree_height_range(s: &str) -> Result<u32, String> {
488 number_range(s, 1, u32::MAX)
489 }
490
491 fn lat_load_target_pct_range(s: &str) -> Result<u16, String> {
492 number_range(s, 0, 200)
493 }
494
495 fn mig_delta_pct_range(s: &str) -> Result<u8, String> {
496 number_range(s, 0, 100)
497 }
498
499 fn lb_low_util_pct_range(s: &str) -> Result<u8, String> {
500 number_range(s, 0, 100)
501 }
502
503 fn lb_local_dsq_util_pct_range(s: &str) -> Result<u8, String> {
504 number_range(s, 0, 100)
505 }
506
507 fn xmig_min_gain_us_range(s: &str) -> Result<u64, String> {
508 number_range(s, 0, 100_000)
509 }
510}
511
512unsafe impl Plain for msg_task_ctx {}
513
514impl msg_task_ctx {
515 fn from_bytes(buf: &[u8]) -> &msg_task_ctx {
516 plain::from_bytes(buf).expect("The buffer is either too short or not aligned!")
517 }
518}
519
520impl introspec {
521 fn new() -> Self {
522 unsafe { mem::MaybeUninit::<introspec>::zeroed().assume_init() }
523 }
524}
525
526struct Scheduler<'a> {
527 _arenalib: ArenaLib,
528 skel: BpfSkel<'a>,
529 struct_ops: Option<libbpf_rs::Link>,
530 intrspc: introspec,
531 monitor_tid: Option<ThreadId>,
532 stats_server: StatsServer<StatsReq, StatsRes>,
533 mseq_id: u64,
534}
535
536impl<'a> Scheduler<'a> {
537 fn init(opts: &'a Opts, open_object: &'a mut MaybeUninit<OpenObject>) -> Result<Self> {
538 if *NR_CPU_IDS > LAVD_CPU_ID_MAX as usize {
539 panic!(
540 "Num possible CPU IDs ({}) exceeds maximum of ({})",
541 *NR_CPU_IDS, LAVD_CPU_ID_MAX
542 );
543 }
544
545 try_set_rlimit_infinity();
546
547 let debug_level = if opts.log_level.contains("trace") {
549 2
550 } else if opts.log_level.contains("debug") {
551 1
552 } else {
553 0
554 };
555 let mut skel_builder = BpfSkelBuilder::default();
556 skel_builder.obj_builder.debug(debug_level > 1);
557 init_libbpf_logging(Some(PrintLevel::Debug));
558
559 let open_opts = opts.libbpf.clone().into_bpf_open_opts();
560 let mut skel = scx_ops_open!(skel_builder, open_object, lavd_ops, open_opts)?;
561
562 if !opts.no_futex_boost && !Self::attach_futex_ftraces(&mut skel)? {
565 info!("Fail to attach futex ftraces. Try with tracepoints.");
566 if !Self::attach_futex_tracepoints(&mut skel)? {
567 info!("Fail to attach futex tracepoints.");
568 }
569 }
570
571 let order = CpuOrder::new(opts.topology.as_ref(), opts.no_use_em).unwrap();
573 Self::init_cpus(&mut skel, &order);
574 Self::init_cpdoms(&mut skel, &order);
575
576 if order.cpdom_map.len() > 1 {
579 Self::attach_execve_tracepoints(&mut skel)?;
580 }
581
582 Self::init_globals(&mut skel, opts, &order, debug_level)?;
584
585 if let Some(rodata) = skel.maps.rodata_data.as_mut() {
588 rodata.nr_cgrp_max = opts.cpu_bw_max_cgroups;
589 rodata.tree_height_max = opts.cpu_bw_max_tree_height;
590 }
591
592 scx_utils::resize_cgroup_bw(
595 skel.open_object_mut(),
596 opts.cpu_bw_max_cgroups,
597 opts.cpu_bw_max_tree_height,
598 order.nr_llcs,
599 )?;
600
601 let mut skel = scx_ops_load!(skel, lavd_ops, uei)?;
603 let task_size = std::mem::size_of::<types::task_ctx>();
604 let arenalib = ArenaLib::setup(skel.object_mut(), task_size, 0, *NR_CPU_IDS)?;
605
606 let struct_ops = Some(scx_ops_attach!(skel, lavd_ops)?);
608 let stats_server = StatsServer::new(stats::server_data(*NR_CPU_IDS as u64)).launch()?;
609
610 Ok(Self {
611 _arenalib: arenalib,
612 skel,
613 struct_ops,
614 intrspc: introspec::new(),
615 monitor_tid: None,
616 stats_server,
617 mseq_id: 0,
618 })
619 }
620
621 fn attach_futex_ftraces(skel: &mut OpenBpfSkel) -> Result<bool> {
622 let ftraces = vec![
623 ("__futex_wait", &skel.progs.fexit___futex_wait),
624 ("futex_wait_multiple", &skel.progs.fexit_futex_wait_multiple),
625 (
626 "futex_wait_requeue_pi",
627 &skel.progs.fexit_futex_wait_requeue_pi,
628 ),
629 ("futex_wake", &skel.progs.fexit_futex_wake),
630 ("futex_wake_op", &skel.progs.fexit_futex_wake_op),
631 ("futex_lock_pi", &skel.progs.fexit_futex_lock_pi),
632 ("futex_unlock_pi", &skel.progs.fexit_futex_unlock_pi),
633 ];
634
635 if !compat::tracer_available("function")? {
636 info!("Ftrace is not enabled in the kernel.");
637 return Ok(false);
638 }
639
640 compat::cond_kprobes_enable(ftraces)
641 }
642
643 fn attach_futex_tracepoints(skel: &mut OpenBpfSkel) -> Result<bool> {
644 let tracepoints = vec![
645 ("syscalls:sys_enter_futex", &skel.progs.rtp_sys_enter_futex),
646 ("syscalls:sys_exit_futex", &skel.progs.rtp_sys_exit_futex),
647 (
648 "syscalls:sys_exit_futex_wait",
649 &skel.progs.rtp_sys_exit_futex_wait,
650 ),
651 (
652 "syscalls:sys_exit_futex_waitv",
653 &skel.progs.rtp_sys_exit_futex_waitv,
654 ),
655 (
656 "syscalls:sys_exit_futex_wake",
657 &skel.progs.rtp_sys_exit_futex_wake,
658 ),
659 ];
660
661 compat::cond_tracepoints_enable(tracepoints)
662 }
663
664 fn attach_execve_tracepoints(skel: &mut OpenBpfSkel) -> Result<bool> {
665 let tracepoints = vec![
666 (
667 "syscalls:sys_enter_execve",
668 &skel.progs.cond_hook_sys_enter_execve,
669 ),
670 (
671 "syscalls:sys_enter_execveat",
672 &skel.progs.cond_hook_sys_enter_execveat,
673 ),
674 ];
675
676 compat::cond_tracepoints_enable(tracepoints)
677 }
678
679 fn init_cpus(skel: &mut OpenBpfSkel, order: &CpuOrder) {
680 debug!("{:#?}", order);
681
682 for cpu in order.cpuids.iter() {
684 skel.maps.rodata_data.as_mut().unwrap().cpu_capacity[cpu.cpu_adx] = cpu.cpu_cap as u16;
685 skel.maps.rodata_data.as_mut().unwrap().cpu_big[cpu.cpu_adx] = cpu.big_core as u8;
686 skel.maps.rodata_data.as_mut().unwrap().cpu_turbo[cpu.cpu_adx] = cpu.turbo_core as u8;
687 skel.maps.rodata_data.as_mut().unwrap().cpu_sibling[cpu.cpu_adx] =
688 cpu.cpu_sibling as u32;
689 }
690
691 let nr_pco_states: u8 = order.perf_cpu_order.len() as u8;
693 if nr_pco_states > LAVD_PCO_STATE_MAX as u8 {
694 panic!(
695 "Generated performance vs. CPU order stats are too complex ({nr_pco_states}) to handle"
696 );
697 }
698
699 skel.maps.rodata_data.as_mut().unwrap().nr_pco_states = nr_pco_states;
700 for (i, (_, pco)) in order.perf_cpu_order.iter().enumerate() {
701 Self::init_pco_tuple(skel, i, pco);
702 info!("{:#}", pco);
703 }
704
705 let (_, last_pco) = order.perf_cpu_order.last_key_value().unwrap();
706 for i in nr_pco_states..LAVD_PCO_STATE_MAX as u8 {
707 Self::init_pco_tuple(skel, i as usize, last_pco);
708 }
709 }
710
711 fn init_pco_tuple(skel: &mut OpenBpfSkel, i: usize, pco: &PerfCpuOrder) {
712 let cpus_perf = pco.cpus_perf.borrow();
713 let cpus_ovflw = pco.cpus_ovflw.borrow();
714 let pco_nr_primary = cpus_perf.len();
715
716 skel.maps.rodata_data.as_mut().unwrap().pco_bounds[i] = pco.perf_cap as u32;
717 skel.maps.rodata_data.as_mut().unwrap().pco_nr_primary[i] = pco_nr_primary as u16;
718
719 for (j, &cpu_adx) in cpus_perf.iter().enumerate() {
720 skel.maps.rodata_data.as_mut().unwrap().pco_table[i][j] = cpu_adx as u16;
721 }
722
723 for (j, &cpu_adx) in cpus_ovflw.iter().enumerate() {
724 let k = j + pco_nr_primary;
725 skel.maps.rodata_data.as_mut().unwrap().pco_table[i][k] = cpu_adx as u16;
726 }
727 }
728
729 fn init_cpdoms(skel: &mut OpenBpfSkel, order: &CpuOrder) {
730 for (k, v) in order.cpdom_map.iter() {
732 skel.maps.bss_data.as_mut().unwrap().cpdom_ctxs[v.cpdom_id].id = v.cpdom_id as u64;
733 skel.maps.bss_data.as_mut().unwrap().cpdom_ctxs[v.cpdom_id].alt_id =
734 v.cpdom_alt_id.get() as u64;
735 skel.maps.bss_data.as_mut().unwrap().cpdom_ctxs[v.cpdom_id].numa_id = k.numa_adx as u8;
736 skel.maps.bss_data.as_mut().unwrap().cpdom_ctxs[v.cpdom_id].llc_id = k.llc_adx as u8;
737 skel.maps.bss_data.as_mut().unwrap().cpdom_ctxs[v.cpdom_id].is_big = k.is_big as u8;
738 skel.maps.bss_data.as_mut().unwrap().cpdom_ctxs[v.cpdom_id].is_valid = 1;
739 for cpu_id in v.cpu_ids.iter() {
740 let i = cpu_id / 64;
741 let j = cpu_id % 64;
742 skel.maps.bss_data.as_mut().unwrap().cpdom_ctxs[v.cpdom_id].__cpumask[i] |=
743 0x01 << j;
744 }
745
746 if v.neighbor_map.borrow().iter().len() > LAVD_CPDOM_MAX_DIST as usize {
747 panic!("The processor topology is too complex to handle in BPF.");
748 }
749
750 for (k, (_d, neighbors)) in v.neighbor_map.borrow().iter().enumerate() {
751 let nr_neighbors = neighbors.borrow().len() as u8;
752 if nr_neighbors > LAVD_CPDOM_MAX_NR as u8 {
753 panic!("The processor topology is too complex to handle in BPF.");
754 }
755 skel.maps.bss_data.as_mut().unwrap().cpdom_ctxs[v.cpdom_id].nr_neighbors[k] =
756 nr_neighbors;
757 for (i, &id) in neighbors.borrow().iter().enumerate() {
758 let idx = (k * LAVD_CPDOM_MAX_NR as usize) + i;
759 skel.maps.bss_data.as_mut().unwrap().cpdom_ctxs[v.cpdom_id].neighbor_ids[idx] =
760 id as u8;
761 }
762 }
763 }
764 }
765
766 fn init_globals(
767 skel: &mut OpenBpfSkel,
768 opts: &Opts,
769 order: &CpuOrder,
770 debug_level: u8,
771 ) -> Result<()> {
772 let bss_data = skel.maps.bss_data.as_mut().unwrap();
773 bss_data.no_preemption = opts.no_preemption;
774 bss_data.no_core_compaction = opts.no_core_compaction;
775 bss_data.no_freq_scaling = opts.no_freq_scaling;
776 bss_data.is_powersave_mode = opts.powersave;
777 let rodata = skel.maps.rodata_data.as_mut().unwrap();
778 rodata.nr_llcs = order.nr_llcs as u64;
779 rodata.nr_cpu_ids = *NR_CPU_IDS as u32;
780 rodata.is_smt_active = order.smt_enabled;
781 rodata.is_autopilot_on = opts.autopilot;
782 rodata.verbose = debug_level;
783 rodata.slice_max_ns = opts.slice_max_us * 1000;
784 rodata.slice_min_ns = opts.slice_min_us * 1000;
785 rodata.pinned_slice_ns = opts.pinned_slice_us.map(|v| v * 1000).unwrap_or(0);
786 rodata.preempt_shift = opts.preempt_shift;
787 rodata.lat_load_target_pct = opts.lat_load_target_pct;
788 rodata.mig_delta_pct = opts.mig_delta_pct;
789 rodata.warm_cpu_ns = opts.warm_cpu_us * 1000;
790 rodata.lb_low_util_wall = ((opts.lb_low_util_pct as u64) << 10) / 100;
791 rodata.lb_local_dsq_util_wall = ((opts.lb_local_dsq_util_pct as u64) << 10) / 100;
792 rodata.xmig_min_gain_ns = opts.xmig_min_gain_us * 1000;
793 rodata.no_use_em = opts.no_use_em as u8;
794 rodata.no_fast_lb = opts.no_fast_lb as u8;
795 rodata.no_ovrflw_extend = opts.no_ovrflw_extend as u8;
796
797 rodata.no_wake_sync = opts.no_wake_sync;
798 rodata.no_slice_boost = opts.no_slice_boost;
799 rodata.per_cpu_dsq = opts.per_cpu_dsq;
800 rodata.enable_cpu_bw = opts.enable_cpu_bw;
801 rodata.bw_kick_builtin_idle = true;
803
804 let cgroup_bw =
808 scx_utils::setup_cgroup_bw(skel.open_object_mut(), "lavd_cgroup_set_bandwidth")?;
809 if opts.enable_cpu_bw && cgroup_bw == scx_utils::CgroupBwSupport::Unsupported {
810 anyhow::bail!(
811 "--enable-cpu-bw requires kernel cpu.max support (ops.cgroup_set_bandwidth), \
812 which this kernel lacks"
813 );
814 }
815
816 if ksym_exists("scx_bpf_reenqueue_local___v2").unwrap() {
827 skel.struct_ops.lavd_ops_mut().cpu_release = std::ptr::null_mut();
828 unsafe {
829 libbpf_rs::libbpf_sys::bpf_program__set_autoload(
830 skel.progs.lavd_sched_switch.as_libbpf_object().as_ptr(),
831 true,
832 );
833 }
834 }
835
836 skel.struct_ops.lavd_ops_mut().flags = *compat::SCX_OPS_ENQ_EXITING
837 | *compat::SCX_OPS_ENQ_LAST
838 | *compat::SCX_OPS_ENQ_MIGRATION_DISABLED
839 | *compat::SCX_OPS_KEEP_BUILTIN_IDLE;
840
841 if opts.partial {
842 skel.struct_ops.lavd_ops_mut().flags |= *compat::SCX_OPS_SWITCH_PARTIAL;
843 }
844
845 skel.struct_ops.lavd_ops_mut().exit_dump_len = opts.exit_dump_len;
846
847 Ok(())
848 }
849
850 fn get_msg_seq_id() -> u64 {
851 static mut MSEQ: u64 = 0;
852 unsafe {
853 MSEQ += 1;
854 MSEQ
855 }
856 }
857
858 fn relay_introspec(data: &[u8], intrspc_tx: &Sender<SchedSample>) -> i32 {
859 let mt = msg_task_ctx::from_bytes(data);
860 let tx = mt.taskc_x;
861
862 if mt.hdr.kind != LAVD_MSG_TASKC {
864 return 0;
865 }
866
867 let mseq = Scheduler::get_msg_seq_id();
868
869 let c_tx_cm: *const c_char = (&tx.comm as *const [c_char; 17]) as *const c_char;
870 let c_tx_cm_str: &CStr = unsafe { CStr::from_ptr(c_tx_cm) };
871 let tx_comm: &str = c_tx_cm_str.to_str().unwrap();
872
873 let c_waker_cm: *const c_char = (&tx.waker_comm as *const [c_char; 17]) as *const c_char;
874 let c_waker_cm_str: &CStr = unsafe { CStr::from_ptr(c_waker_cm) };
875 let waker_comm: &str = c_waker_cm_str.to_str().unwrap();
876
877 let c_tx_st: *const c_char = (&tx.stat as *const [c_char; 5]) as *const c_char;
878 let c_tx_st_str: &CStr = unsafe { CStr::from_ptr(c_tx_st) };
879 let tx_stat: &str = c_tx_st_str.to_str().unwrap();
880
881 match intrspc_tx.try_send(SchedSample {
882 mseq,
883 pid: tx.pid,
884 comm: tx_comm.into(),
885 stat: tx_stat.into(),
886 cpu_id: tx.cpu_id,
887 prev_cpu_id: tx.prev_cpu_id,
888 suggested_cpu_id: tx.suggested_cpu_id,
889 waker_pid: tx.waker_pid,
890 waker_comm: waker_comm.into(),
891 slice_wall: tx.slice_wall,
892 lat_cri: tx.lat_cri,
893 avg_lat_cri: tx.avg_lat_cri,
894 static_prio: tx.static_prio,
895 rerunnable_interval_wall: tx.rerunnable_interval_wall,
896 resched_interval_wall: tx.resched_interval_wall,
897 run_freq: tx.run_freq,
898 avg_runtime_wall: tx.avg_runtime_wall,
899 wait_freq: tx.wait_freq,
900 wake_freq: tx.wake_freq,
901 perf_cri: tx.perf_cri,
902 thr_perf_cri: tx.thr_perf_cri,
903 cpuperf_cur: tx.cpuperf_cur,
904 cpu_util_wall: tx.cpu_util_wall,
905 cpu_util_invr: tx.cpu_util_invr,
906 steal_util_wall: tx.steal_util_wall,
907 steal_util_invr: tx.steal_util_invr,
908 dom_pinned_util_wall: tx.dom_pinned_util_wall,
909 dom_pinned_util_invr: tx.dom_pinned_util_invr,
910 nr_active: tx.nr_active,
911 dsq_id: tx.dsq_id,
912 dsq_consume_lat: tx.dsq_consume_lat,
913 lat_headroom: tx.lat_headroom,
914 vuln_thresh: tx.vuln_thresh,
915 task_util_est: tx.task_util_est,
916 norm_lat_cri: tx.norm_lat_cri,
917 cpu_heat: tx.cpu_heat,
918 warm_cpu_id: tx.warm_cpu_id,
919 slice_used_wall: tx.last_slice_used_wall,
920 }) {
921 Ok(()) | Err(TrySendError::Full(_)) => 0,
922 Err(e) => panic!("failed to send on intrspc_tx ({})", e),
923 }
924 }
925
926 fn prep_introspec(&mut self) {
927 if !self.skel.maps.bss_data.as_ref().unwrap().is_monitored {
928 self.skel.maps.bss_data.as_mut().unwrap().is_monitored = true;
929 }
930 self.skel.maps.bss_data.as_mut().unwrap().intrspc.cmd = self.intrspc.cmd;
931 self.skel.maps.bss_data.as_mut().unwrap().intrspc.arg = self.intrspc.arg;
932 }
933
934 fn cleanup_introspec(&mut self) {
935 self.skel.maps.bss_data.as_mut().unwrap().intrspc.cmd = LAVD_CMD_NOP;
936 }
937
938 fn get_pc(x: u64, y: u64) -> f64 {
939 100. * x as f64 / y as f64
940 }
941
942 fn get_power_mode(power_mode: i32) -> &'static str {
943 match power_mode as u32 {
944 LAVD_PM_PERFORMANCE => "performance",
945 LAVD_PM_BALANCED => "balanced",
946 LAVD_PM_POWERSAVE => "powersave",
947 _ => "unknown",
948 }
949 }
950
951 fn drain_sched_samples(&mut self, timeout: Option<Duration>) -> Result<Vec<SchedSample>> {
958 let (intrspc_tx, intrspc_rx) = channel::unbounded();
959
960 {
961 let mut builder = libbpf_rs::RingBufferBuilder::new();
962 builder.add(&self.skel.maps.introspec_msg, move |data| {
963 Scheduler::relay_introspec(data, &intrspc_tx)
964 })?;
965 let rb_mgr = builder.build()?;
966
967 match timeout {
968 Some(timeout) => rb_mgr.poll(timeout)?,
969 None => rb_mgr.consume()?,
970 }
971 }
972
973 Ok(intrspc_rx.try_iter().collect())
974 }
975
976 fn stats_req_to_res(&mut self, req: &StatsReq) -> Result<StatsRes> {
977 Ok(match req {
978 StatsReq::NewSampler(tid) => {
979 self.drain_sched_samples(None)?;
981 self.monitor_tid = Some(*tid);
982 StatsRes::Ack
983 }
984 StatsReq::SysStatsReq { tid } => {
985 if Some(*tid) != self.monitor_tid {
986 return Ok(StatsRes::Bye);
987 }
988 self.mseq_id += 1;
989
990 let bss_data = self.skel.maps.bss_data.as_ref().unwrap();
991 let st = bss_data.sys_stat;
992
993 let mseq = self.mseq_id;
994 let nr_queued_task = st.nr_queued_task;
995 let nr_active = st.nr_active;
996 let nr_sched = st.nr_sched;
997 let nr_preempt = st.nr_preempt;
998 let pc_pc = Self::get_pc(st.nr_perf_cri, nr_sched);
999 let pc_lc = Self::get_pc(st.nr_lat_cri, nr_sched);
1000 let pc_x_migration = Self::get_pc(st.nr_x_migration, nr_sched);
1001 let nr_stealee = st.nr_stealee;
1002 let nr_big = st.nr_big;
1003 let pc_big = Self::get_pc(nr_big, nr_sched);
1004 let pc_pc_on_big = Self::get_pc(st.nr_pc_on_big, nr_big);
1005 let pc_lc_on_big = Self::get_pc(st.nr_lc_on_big, nr_big);
1006 let power_mode = Self::get_power_mode(bss_data.power_mode);
1007 let total_time = bss_data.performance_mode_ns
1008 + bss_data.balanced_mode_ns
1009 + bss_data.powersave_mode_ns;
1010 let pc_performance = Self::get_pc(bss_data.performance_mode_ns, total_time);
1011 let pc_balanced = Self::get_pc(bss_data.balanced_mode_ns, total_time);
1012 let pc_powersave = Self::get_pc(bss_data.powersave_mode_ns, total_time);
1013
1014 StatsRes::SysStats(SysStats {
1015 mseq,
1016 nr_queued_task,
1017 nr_active,
1018 nr_sched,
1019 nr_preempt,
1020 pc_pc,
1021 pc_lc,
1022 pc_x_migration,
1023 nr_stealee,
1024 pc_big,
1025 pc_pc_on_big,
1026 pc_lc_on_big,
1027 power_mode: power_mode.to_string(),
1028 pc_performance,
1029 pc_balanced,
1030 pc_powersave,
1031 })
1032 }
1033 StatsReq::SchedSamplesNr {
1034 tid,
1035 nr_samples,
1036 interval_ms,
1037 } => {
1038 if Some(*tid) != self.monitor_tid {
1039 return Ok(StatsRes::Bye);
1040 }
1041
1042 self.intrspc.cmd = LAVD_CMD_SCHED_N;
1043 self.intrspc.arg = *nr_samples;
1044 self.prep_introspec();
1045 std::thread::sleep(Duration::from_millis(*interval_ms));
1046
1047 let samples = self.drain_sched_samples(Some(Duration::from_millis(100)))?;
1048
1049 self.cleanup_introspec();
1050
1051 StatsRes::SchedSamples(SchedSamples { samples })
1052 }
1053 })
1054 }
1055
1056 fn stop_monitoring(&mut self) {
1057 if self.skel.maps.bss_data.as_ref().unwrap().is_monitored {
1058 self.skel.maps.bss_data.as_mut().unwrap().is_monitored = false;
1059 }
1060 }
1061
1062 pub fn exited(&mut self) -> bool {
1063 uei_exited!(&self.skel, uei)
1064 }
1065
1066 fn set_power_profile(&mut self, mode: u32) -> Result<(), u32> {
1067 let prog = &mut self.skel.progs.set_power_profile;
1068 let mut args = power_arg {
1069 power_mode: mode as c_int,
1070 };
1071 let input = ProgramInput {
1072 context_in: Some(unsafe {
1073 std::slice::from_raw_parts_mut(
1074 &mut args as *mut _ as *mut u8,
1075 std::mem::size_of_val(&args),
1076 )
1077 }),
1078 ..Default::default()
1079 };
1080 let out = prog.test_run(input).unwrap();
1081 if out.return_value != 0 {
1082 return Err(out.return_value);
1083 }
1084
1085 Ok(())
1086 }
1087
1088 fn update_power_profile(&mut self, prev_profile: PowerProfile) -> (bool, PowerProfile) {
1089 let profile = fetch_power_profile(false);
1090 if profile == prev_profile {
1091 return (true, profile);
1093 }
1094
1095 let _ = match profile {
1096 PowerProfile::Performance => self.set_power_profile(LAVD_PM_PERFORMANCE),
1097 PowerProfile::Balanced { .. } => self.set_power_profile(LAVD_PM_BALANCED),
1098 PowerProfile::Powersave => self.set_power_profile(LAVD_PM_POWERSAVE),
1099 PowerProfile::Unknown => {
1100 return (false, profile);
1103 }
1104 };
1105
1106 info!("Set the scheduler's power profile to {profile} mode.");
1107 (true, profile)
1108 }
1109
1110 fn run(&mut self, opts: &Opts, shutdown: Arc<AtomicBool>) -> Result<UserExitInfo> {
1111 let (res_ch, req_ch) = self.stats_server.channels();
1112 let mut autopower = opts.autopower;
1113 let mut profile = PowerProfile::Unknown;
1114
1115 if opts.performance {
1116 let _ = self.set_power_profile(LAVD_PM_PERFORMANCE);
1117 } else if opts.powersave {
1118 let _ = self.set_power_profile(LAVD_PM_POWERSAVE);
1119 } else {
1120 let _ = self.set_power_profile(LAVD_PM_BALANCED);
1121 }
1122
1123 while !shutdown.load(Ordering::Relaxed) && !self.exited() {
1124 if autopower {
1125 (autopower, profile) = self.update_power_profile(profile);
1126 }
1127
1128 match req_ch.recv_timeout(Duration::from_secs(1)) {
1129 Ok(req) => {
1130 let res = self.stats_req_to_res(&req)?;
1131 res_ch.send(res)?;
1132 }
1133 Err(RecvTimeoutError::Timeout) => {
1134 self.stop_monitoring();
1135 }
1136 Err(e) => {
1137 self.stop_monitoring();
1138 Err(e)?
1139 }
1140 }
1141 self.cleanup_introspec();
1142 }
1143
1144 bpf_streams::dump_bpf_streams(&mut self.skel);
1145 let _ = self.struct_ops.take();
1146 uei_report!(&self.skel, uei)
1147 }
1148}
1149
1150impl Drop for Scheduler<'_> {
1151 fn drop(&mut self) {
1152 info!("Unregister {SCHEDULER_NAME} scheduler");
1153
1154 if let Some(struct_ops) = self.struct_ops.take() {
1155 drop(struct_ops);
1156 }
1157 }
1158}
1159
1160fn trim_heap_after_init() {
1169 #[cfg(target_env = "gnu")]
1170 unsafe {
1171 libc::malloc_trim(0);
1172 }
1173}
1174
1175fn init_log(opts: &Opts) {
1176 let env_filter = EnvFilter::try_from_default_env()
1177 .or_else(|_| match EnvFilter::try_new(&opts.log_level) {
1178 Ok(filter) => Ok(filter),
1179 Err(e) => {
1180 eprintln!(
1181 "invalid log envvar: {}, using info, err is: {}",
1182 opts.log_level, e
1183 );
1184 EnvFilter::try_new("info")
1185 }
1186 })
1187 .unwrap_or_else(|_| EnvFilter::new("info"));
1188
1189 match tracing_subscriber::fmt()
1190 .with_env_filter(env_filter)
1191 .with_target(true)
1192 .with_thread_ids(true)
1193 .with_file(true)
1194 .with_line_number(true)
1195 .try_init()
1196 {
1197 Ok(()) => {}
1198 Err(e) => eprintln!("failed to init logger: {}", e),
1199 }
1200}
1201
1202#[clap_main::clap_main]
1203fn main(mut opts: Opts) -> Result<()> {
1204 if opts.version {
1205 println!(
1206 "scx_lavd {}",
1207 build_id::full_version(env!("CARGO_PKG_VERSION"))
1208 );
1209 return Ok(());
1210 }
1211
1212 if opts.help_stats {
1213 let sys_stats_meta_name = SysStats::meta().name;
1214 let sched_sample_meta_name = SchedSample::meta().name;
1215 let stats_meta_names: &[&str] = &[
1216 sys_stats_meta_name.as_str(),
1217 sched_sample_meta_name.as_str(),
1218 ];
1219 stats::server_data(0).describe_meta(&mut std::io::stdout(), Some(stats_meta_names))?;
1220 return Ok(());
1221 }
1222
1223 init_log(&opts);
1224
1225 if opts.verbose > 0 {
1226 warn!("Setting verbose via -v is deprecated and will be an error in future releases.");
1227 }
1228
1229 if let Some(run_id) = opts.run_id {
1230 info!("scx_lavd run_id: {}", run_id);
1231 }
1232
1233 if opts.monitor.is_none() && opts.monitor_sched_samples.is_none() {
1234 opts.proc().unwrap();
1235 info!("{:#?}", opts);
1236 }
1237
1238 let shutdown = Arc::new(AtomicBool::new(false));
1239 let shutdown_clone = shutdown.clone();
1240 ctrlc::set_handler(move || {
1241 shutdown_clone.store(true, Ordering::Relaxed);
1242 })
1243 .context("Error setting Ctrl-C handler")?;
1244
1245 if let Some(nr_samples) = opts.monitor_sched_samples {
1246 let shutdown_copy = shutdown.clone();
1247 let jh = std::thread::spawn(move || {
1248 stats::monitor_sched_samples(nr_samples, shutdown_copy).unwrap()
1249 });
1250 let _ = jh.join();
1251 return Ok(());
1252 }
1253
1254 if let Some(intv) = opts.monitor.or(opts.stats) {
1255 let shutdown_copy = shutdown.clone();
1256 let jh = std::thread::spawn(move || {
1257 stats::monitor(Duration::from_secs_f64(intv), shutdown_copy).unwrap()
1258 });
1259 if opts.monitor.is_some() {
1260 let _ = jh.join();
1261 return Ok(());
1262 }
1263 }
1264
1265 let mut open_object = MaybeUninit::uninit();
1266 loop {
1267 let mut sched = Scheduler::init(&opts, &mut open_object)?;
1268 info!(
1269 "scx_lavd scheduler is initialized (build ID: {})",
1270 build_id::full_version(env!("CARGO_PKG_VERSION"))
1271 );
1272 info!("scx_lavd scheduler starts running.");
1273 trim_heap_after_init();
1274 if !sched.run(&opts, shutdown.clone())?.should_restart() {
1275 break;
1276 }
1277 }
1278
1279 Ok(())
1280}