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