1mod bpf_skel;
6mod stats;
7
8use std::collections::BTreeMap;
9use std::collections::BTreeSet;
10use std::collections::HashMap;
11use std::collections::HashSet;
12use std::ffi::CString;
13use std::fs;
14use std::io::Write;
15use std::mem::MaybeUninit;
16use std::ops::Sub;
17use std::path::Path;
18use std::path::PathBuf;
19use std::sync::Arc;
20use std::sync::atomic::AtomicBool;
21use std::sync::atomic::Ordering;
22use std::thread::ThreadId;
23use std::time::Duration;
24use std::time::Instant;
25
26use inotify::{Inotify, WatchMask};
27use std::os::unix::io::AsRawFd;
28
29use anyhow::Context;
30use anyhow::Result;
31use anyhow::anyhow;
32use anyhow::bail;
33pub use bpf_skel::*;
34use clap::Parser;
35use crossbeam::channel::Receiver;
36use crossbeam::select;
37use lazy_static::lazy_static;
38use libbpf_rs::AsRawLibbpf;
39use libbpf_rs::MapCore as _;
40use libbpf_rs::OpenObject;
41use libbpf_rs::ProgramInput;
42use libbpf_rs::libbpf_sys;
43use nix::sched::CpuSet;
44use nvml_wrapper::Nvml;
45use nvml_wrapper::error::NvmlError;
46use once_cell::sync::OnceCell;
47use regex::Regex;
48use scx_layered::alloc::{LayerAlloc, LayerDemand, unified_alloc};
49use scx_layered::*;
50use scx_raw_pmu::PMUManager;
51use scx_stats::prelude::*;
52use scx_utils::CoreType;
53use scx_utils::Cpumask;
54use scx_utils::Llc;
55use scx_utils::NR_CPU_IDS;
56use scx_utils::NR_CPUS_POSSIBLE;
57use scx_utils::NetDev;
58use scx_utils::Topology;
59use scx_utils::TopologyArgs;
60use scx_utils::UserExitInfo;
61use scx_utils::build_id;
62use scx_utils::compat;
63use scx_utils::init_libbpf_logging;
64use scx_utils::libbpf_clap_opts::LibbpfOpts;
65use scx_utils::perf;
66use scx_utils::pm::{cpu_idle_resume_latency_supported, update_cpu_idle_resume_latency};
67use scx_utils::read_netdevs;
68use scx_utils::scx_enums;
69use scx_utils::scx_ops_attach;
70use scx_utils::scx_ops_load;
71use scx_utils::scx_ops_open;
72use scx_utils::uei_exited;
73use scx_utils::uei_report;
74use stats::LayerStats;
75use stats::StatsReq;
76use stats::StatsRes;
77use stats::SysStats;
78use std::collections::VecDeque;
79use sysinfo::{Pid, ProcessRefreshKind, ProcessesToUpdate, System};
80use tracing::{debug, error, info, trace, warn};
81use tracing_subscriber::filter::EnvFilter;
82use walkdir::WalkDir;
83
84const SCHEDULER_NAME: &str = "scx_layered";
85const MAX_PATH: usize = bpf_intf::consts_MAX_PATH as usize;
86const MAX_COMM: usize = bpf_intf::consts_MAX_COMM as usize;
87const MAX_LAYER_WEIGHT: u32 = bpf_intf::consts_MAX_LAYER_WEIGHT;
88const MIN_LAYER_WEIGHT: u32 = bpf_intf::consts_MIN_LAYER_WEIGHT;
89const MAX_LAYER_MATCH_ORS: usize = bpf_intf::consts_MAX_LAYER_MATCH_ORS as usize;
90const MAX_LAYER_NAME: usize = bpf_intf::consts_MAX_LAYER_NAME as usize;
91const MAX_LAYERS: usize = bpf_intf::consts_MAX_LAYERS as usize;
92const DEFAULT_LAYER_WEIGHT: u32 = bpf_intf::consts_DEFAULT_LAYER_WEIGHT;
93const USAGE_HALF_LIFE: u32 = bpf_intf::consts_USAGE_HALF_LIFE;
94const USAGE_HALF_LIFE_F64: f64 = USAGE_HALF_LIFE as f64 / 1_000_000_000.0;
95
96const LAYER_USAGE_OWNED: usize = bpf_intf::layer_usage_LAYER_USAGE_OWNED as usize;
97const LAYER_USAGE_OPEN: usize = bpf_intf::layer_usage_LAYER_USAGE_OPEN as usize;
98const LAYER_USAGE_SUM_UPTO: usize = bpf_intf::layer_usage_LAYER_USAGE_SUM_UPTO as usize;
99const LAYER_USAGE_PROTECTED: usize = bpf_intf::layer_usage_LAYER_USAGE_PROTECTED as usize;
100const LAYER_USAGE_PROTECTED_PREEMPT: usize =
101 bpf_intf::layer_usage_LAYER_USAGE_PROTECTED_PREEMPT as usize;
102const NR_LAYER_USAGES: usize = bpf_intf::layer_usage_NR_LAYER_USAGES as usize;
103
104const NR_GSTATS: usize = bpf_intf::global_stat_id_NR_GSTATS as usize;
105const NR_LSTATS: usize = bpf_intf::layer_stat_id_NR_LSTATS as usize;
106const NR_LLC_LSTATS: usize = bpf_intf::llc_layer_stat_id_NR_LLC_LSTATS as usize;
107
108const NR_LAYER_MATCH_KINDS: usize = bpf_intf::layer_match_kind_NR_LAYER_MATCH_KINDS as usize;
109
110static NVML: OnceCell<Nvml> = OnceCell::new();
111
112fn nvml() -> Result<&'static Nvml, NvmlError> {
113 NVML.get_or_try_init(Nvml::init)
114}
115
116lazy_static! {
117 static ref USAGE_DECAY: f64 = 0.5f64.powf(1.0 / USAGE_HALF_LIFE_F64);
118 static ref DFL_DISALLOW_OPEN_AFTER_US: u64 = 2 * scx_enums.SCX_SLICE_DFL / 1000;
119 static ref DFL_DISALLOW_PREEMPT_AFTER_US: u64 = 4 * scx_enums.SCX_SLICE_DFL / 1000;
120 static ref EXAMPLE_CONFIG: LayerConfig = serde_json::from_str(
121 r#"[
122 {
123 "name": "batch",
124 "comment": "tasks under system.slice or tasks with nice value > 0",
125 "matches": [[{"CgroupPrefix": "system.slice/"}], [{"NiceAbove": 0}]],
126 "kind": {"Confined": {
127 "util_range": [0.8, 0.9], "cpus_range": [0, 16],
128 "min_exec_us": 1000, "slice_us": 20000, "weight": 100,
129 "xllc_mig_min_us": 1000.0, "perf": 1024
130 }}
131 },
132 {
133 "name": "immediate",
134 "comment": "tasks under workload.slice with nice value < 0",
135 "matches": [[{"CgroupPrefix": "workload.slice/"}, {"NiceBelow": 0}]],
136 "kind": {"Open": {
137 "min_exec_us": 100, "yield_ignore": 0.25, "slice_us": 20000,
138 "preempt": true, "exclusive": true,
139 "prev_over_idle_core": true,
140 "weight": 100, "perf": 1024
141 }}
142 },
143 {
144 "name": "stress-ng",
145 "comment": "stress-ng test layer",
146 "matches": [[{"CommPrefix": "stress-ng"}], [{"PcommPrefix": "stress-ng"}]],
147 "kind": {"Confined": {
148 "util_range": [0.2, 0.8],
149 "min_exec_us": 800, "preempt": true, "slice_us": 800,
150 "weight": 100, "growth_algo": "Topo", "perf": 1024
151 }}
152 },
153 {
154 "name": "normal",
155 "comment": "the rest",
156 "matches": [[]],
157 "kind": {"Grouped": {
158 "util_range": [0.5, 0.6], "util_includes_open_cputime": true,
159 "min_exec_us": 200, "slice_us": 20000, "weight": 100,
160 "xllc_mig_min_us": 100.0, "growth_algo": "Linear", "perf": 1024
161 }}
162 }
163 ]"#,
164 )
165 .unwrap();
166}
167
168#[derive(Debug, Parser)]
470#[command(verbatim_doc_comment)]
471struct Opts {
472 #[clap(short = 'v', long, action = clap::ArgAction::Count)]
474 verbose: u8,
475
476 #[clap(short = 's', long, default_value = "20000")]
478 slice_us: u64,
479
480 #[clap(short = 'M', long, default_value = "0")]
485 max_exec_us: u64,
486
487 #[clap(short = 'i', long, default_value = "0.1")]
489 interval: f64,
490
491 #[clap(short = 'n', long, default_value = "false")]
494 no_load_frac_limit: bool,
495
496 #[clap(long, default_value = "0")]
498 exit_dump_len: u32,
499
500 #[clap(long, default_value = "info")]
503 log_level: String,
504
505 #[arg(short = 't', long, num_args = 0..=1, default_missing_value = "true", require_equals = true)]
509 disable_topology: Option<bool>,
510
511 #[clap(long)]
513 monitor_disable: bool,
514
515 #[clap(short = 'e', long)]
517 example: Option<String>,
518
519 #[clap(long, default_value = "0.0")]
523 layer_preempt_weight_disable: f64,
524
525 #[clap(long, default_value = "0.0")]
529 layer_growth_weight_disable: f64,
530
531 #[clap(long)]
533 stats: Option<f64>,
534
535 #[clap(long)]
538 monitor: Option<f64>,
539
540 #[clap(long, default_value = "95")]
542 stats_columns: usize,
543
544 #[clap(long)]
546 stats_no_llc: bool,
547
548 #[clap(long)]
550 run_example: bool,
551
552 #[clap(long)]
554 allow_partial_core: bool,
555
556 #[clap(long, default_value = "false")]
559 local_llc_iteration: bool,
560
561 #[clap(long, default_value = "10000")]
566 lo_fb_wait_us: u64,
567
568 #[clap(long, default_value = ".05")]
571 lo_fb_share: f64,
572
573 #[clap(long, default_value = "false")]
575 disable_antistall: bool,
576
577 #[clap(long, default_value = "false")]
579 enable_gpu_affinitize: bool,
580
581 #[clap(long, default_value = "900")]
584 gpu_affinitize_secs: u64,
585
586 #[clap(long, default_value = "false")]
591 enable_match_debug: bool,
592
593 #[clap(long, default_value = "3")]
595 antistall_sec: u64,
596
597 #[clap(long, default_value = "false")]
599 enable_gpu_support: bool,
600
601 #[clap(long, default_value = "3")]
609 gpu_kprobe_level: u64,
610
611 #[clap(long, default_value = "false")]
615 util_compensation: bool,
616
617 #[clap(long, default_value = "false")]
619 netdev_irq_balance: bool,
620
621 #[clap(long, default_value = "false")]
623 disable_queued_wakeup: bool,
624
625 #[clap(long, default_value = "false")]
627 disable_percpu_kthread_preempt: bool,
628
629 #[clap(long, default_value = "false")]
633 percpu_kthread_preempt_all: bool,
634
635 #[clap(short = 'V', long, action = clap::ArgAction::SetTrue)]
637 version: bool,
638
639 #[clap(long)]
641 run_id: Option<u64>,
642
643 #[clap(long)]
645 help_stats: bool,
646
647 specs: Vec<String>,
649
650 #[clap(long, default_value = "2000")]
653 layer_refresh_ms_avgruntime: u64,
654
655 #[clap(long, default_value = "")]
657 task_hint_map: String,
658
659 #[clap(long, default_value = "false")]
661 print_and_exit: bool,
662
663 #[clap(long, default_value = "")]
665 hi_fb_thread_name: String,
666
667 #[clap(flatten, next_help_heading = "Topology Options")]
668 topology: TopologyArgs,
669
670 #[clap(flatten, next_help_heading = "Libbpf Options")]
671 pub libbpf: LibbpfOpts,
672}
673
674#[derive(Debug, Clone)]
676enum CgroupEvent {
677 Created {
678 path: String,
679 cgroup_id: u64, match_bitmap: u64, },
682 Removed {
683 path: String,
684 cgroup_id: u64, },
686}
687
688fn read_total_cpu(reader: &fb_procfs::ProcReader) -> Result<fb_procfs::CpuStat> {
689 reader
690 .read_stat()
691 .context("Failed to read procfs")?
692 .total_cpu
693 .ok_or_else(|| anyhow!("Could not read total cpu stat in proc"))
694}
695
696fn calc_util(curr: &fb_procfs::CpuStat, prev: &fb_procfs::CpuStat) -> Result<f64> {
697 match (curr, prev) {
698 (
699 fb_procfs::CpuStat {
700 user_usec: Some(curr_user),
701 nice_usec: Some(curr_nice),
702 system_usec: Some(curr_system),
703 idle_usec: Some(curr_idle),
704 iowait_usec: Some(curr_iowait),
705 irq_usec: Some(curr_irq),
706 softirq_usec: Some(curr_softirq),
707 stolen_usec: Some(curr_stolen),
708 ..
709 },
710 fb_procfs::CpuStat {
711 user_usec: Some(prev_user),
712 nice_usec: Some(prev_nice),
713 system_usec: Some(prev_system),
714 idle_usec: Some(prev_idle),
715 iowait_usec: Some(prev_iowait),
716 irq_usec: Some(prev_irq),
717 softirq_usec: Some(prev_softirq),
718 stolen_usec: Some(prev_stolen),
719 ..
720 },
721 ) => {
722 let idle_usec = curr_idle.saturating_sub(*prev_idle);
723 let iowait_usec = curr_iowait.saturating_sub(*prev_iowait);
724 let user_usec = curr_user.saturating_sub(*prev_user);
725 let system_usec = curr_system.saturating_sub(*prev_system);
726 let nice_usec = curr_nice.saturating_sub(*prev_nice);
727 let irq_usec = curr_irq.saturating_sub(*prev_irq);
728 let softirq_usec = curr_softirq.saturating_sub(*prev_softirq);
729 let stolen_usec = curr_stolen.saturating_sub(*prev_stolen);
730
731 let busy_usec =
732 user_usec + system_usec + nice_usec + irq_usec + softirq_usec + stolen_usec;
733 let total_usec = idle_usec + busy_usec + iowait_usec;
734 if total_usec > 0 {
735 Ok(((busy_usec as f64) / (total_usec as f64)).clamp(0.0, 1.0))
736 } else {
737 Ok(1.0)
738 }
739 }
740 _ => bail!("Missing stats in cpustat"),
741 }
742}
743
744fn copy_into_cstr(dst: &mut [i8], src: &str) {
745 let cstr = CString::new(src).unwrap();
746 let bytes = unsafe { std::mem::transmute::<&[u8], &[i8]>(cstr.as_bytes_with_nul()) };
747 dst[0..bytes.len()].copy_from_slice(bytes);
748}
749
750#[allow(clippy::needless_range_loop)]
751fn read_cpu_ctxs(skel: &BpfSkel) -> Result<Vec<bpf_intf::cpu_ctx>> {
752 let mut cpu_ctxs = vec![];
753 let cpu_ctxs_vec = skel
754 .maps
755 .cpu_ctxs
756 .lookup_percpu(&0u32.to_ne_bytes(), libbpf_rs::MapFlags::ANY)
757 .context("Failed to lookup cpu_ctx")?
758 .unwrap();
759 for cpu in 0..*NR_CPUS_POSSIBLE {
760 cpu_ctxs.push(
761 *plain::from_bytes(cpu_ctxs_vec[cpu].as_slice())
762 .expect("cpu_ctx: short or misaligned buffer"),
763 );
764 }
765 Ok(cpu_ctxs)
766}
767
768#[derive(Clone, Debug)]
769struct BpfStats {
770 gstats: Vec<u64>,
771 lstats: Vec<Vec<u64>>,
772 lstats_sums: Vec<u64>,
773 llc_lstats: Vec<Vec<Vec<u64>>>, }
775
776impl BpfStats {
777 #[allow(clippy::needless_range_loop)]
778 fn read(skel: &BpfSkel, cpu_ctxs: &[bpf_intf::cpu_ctx]) -> Self {
779 let nr_layers = skel.maps.rodata_data.as_ref().unwrap().nr_layers as usize;
780 let nr_llcs = skel.maps.rodata_data.as_ref().unwrap().nr_llcs as usize;
781 let mut gstats = vec![0u64; NR_GSTATS];
782 let mut lstats = vec![vec![0u64; NR_LSTATS]; nr_layers];
783 let mut llc_lstats = vec![vec![vec![0u64; NR_LLC_LSTATS]; nr_llcs]; nr_layers];
784
785 for cpu in 0..*NR_CPUS_POSSIBLE {
786 for (stat, value) in gstats.iter_mut().enumerate() {
787 *value += cpu_ctxs[cpu].gstats[stat];
788 }
789 for (layer, layer_stats) in lstats.iter_mut().enumerate() {
790 for (stat, value) in layer_stats.iter_mut().enumerate() {
791 *value += cpu_ctxs[cpu].lstats[layer][stat];
792 }
793 }
794 }
795
796 let mut lstats_sums = vec![0u64; NR_LSTATS];
797 for layer_stats in lstats.iter() {
798 for (stat, value) in lstats_sums.iter_mut().enumerate() {
799 *value += layer_stats[stat];
800 }
801 }
802
803 for llc_id in 0..nr_llcs {
804 let v = skel
810 .maps
811 .llc_data
812 .lookup(&(llc_id as u32).to_ne_bytes(), libbpf_rs::MapFlags::ANY)
813 .unwrap()
814 .unwrap();
815 let llcc: &bpf_intf::llc_ctx =
816 plain::from_bytes(v.as_slice()).expect("llc_ctx: short or misaligned buffer");
817
818 for (layer_id, layer_llc_stats) in llc_lstats.iter_mut().enumerate() {
819 for (stat_id, stat_value) in layer_llc_stats[llc_id].iter_mut().enumerate() {
820 *stat_value = llcc.lstats[layer_id][stat_id];
821 }
822 }
823 }
824
825 Self {
826 gstats,
827 lstats,
828 lstats_sums,
829 llc_lstats,
830 }
831 }
832}
833
834impl<'b> Sub<&'b BpfStats> for &BpfStats {
835 type Output = BpfStats;
836
837 fn sub(self, rhs: &'b BpfStats) -> BpfStats {
838 let vec_sub = |l: &[u64], r: &[u64]| l.iter().zip(r.iter()).map(|(l, r)| *l - *r).collect();
839 BpfStats {
840 gstats: vec_sub(&self.gstats, &rhs.gstats),
841 lstats: self
842 .lstats
843 .iter()
844 .zip(rhs.lstats.iter())
845 .map(|(l, r)| vec_sub(l, r))
846 .collect(),
847 lstats_sums: vec_sub(&self.lstats_sums, &rhs.lstats_sums),
848 llc_lstats: self
849 .llc_lstats
850 .iter()
851 .zip(rhs.llc_lstats.iter())
852 .map(|(l_layer, r_layer)| {
853 l_layer
854 .iter()
855 .zip(r_layer.iter())
856 .map(|(l_llc, r_llc)| {
857 let (l_llc, mut r_llc) = (l_llc.clone(), r_llc.clone());
858 r_llc[bpf_intf::llc_layer_stat_id_LLC_LSTAT_LAT as usize] = 0;
860 vec_sub(&l_llc, &r_llc)
861 })
862 .collect()
863 })
864 .collect(),
865 }
866 }
867}
868
869#[derive(Clone, Debug)]
870struct Stats {
871 at: Instant,
872 elapsed: Duration,
873 topo: Arc<Topology>,
874 nr_layers: usize,
875 nr_layer_tasks: Vec<usize>,
876 layer_nr_node_pinned_tasks: Vec<Vec<u64>>,
877
878 total_util: f64, layer_utils: Vec<Vec<f64>>,
880 layer_node_pinned_utils: Vec<Vec<f64>>,
881 prev_layer_node_pinned_usages: Vec<Vec<u64>>,
882 layer_node_utils: Vec<Vec<f64>>,
883 prev_layer_node_usages: Vec<Vec<u64>>,
884 layer_node_duty_sums: Vec<Vec<f64>>, prev_layer_node_duty_raw: Vec<Vec<u64>>, layer_membws: Vec<Vec<f64>>, prev_layer_membw_agg: Vec<Vec<u64>>, cpu_busy: f64, prev_total_cpu: fb_procfs::CpuStat,
892 prev_pmu_resctrl_membw: (u64, u64), util_compensation: bool,
895 layer_utils_compensated: Vec<Vec<f64>>, prev_cpu_layer_usages: Vec<u64>, prev_per_cpu_stats: BTreeMap<u32, fb_procfs::CpuStat>,
898
899 system_cpu_util_ewma: f64, layer_dsq_insert_ewma: Vec<f64>, bpf_stats: BpfStats,
903 prev_bpf_stats: BpfStats,
904
905 processing_dur: Duration,
906 prev_processing_dur: Duration,
907
908 layer_slice_us: Vec<u64>,
909
910 gpu_tasks_affinitized: u64,
911 gpu_task_affinitization_ms: u64,
912}
913
914impl Stats {
915 #[allow(clippy::needless_range_loop)]
916 fn read_layer_membw_agg(cpu_ctxs: &[bpf_intf::cpu_ctx], nr_layers: usize) -> Vec<Vec<u64>> {
917 let mut layer_membw_agg = vec![vec![0u64; NR_LAYER_USAGES]; nr_layers];
918
919 for cpu in 0..*NR_CPUS_POSSIBLE {
920 for (layer, layer_membw) in layer_membw_agg.iter_mut().enumerate() {
921 for (usage, value) in layer_membw.iter_mut().enumerate() {
922 *value += cpu_ctxs[cpu].layer_membw_agg[layer][usage];
923 }
924 }
925 }
926
927 layer_membw_agg
928 }
929
930 #[allow(clippy::needless_range_loop)]
931 fn read_layer_node_pinned_usages(
932 cpu_ctxs: &[bpf_intf::cpu_ctx],
933 topo: &Topology,
934 nr_layers: usize,
935 nr_nodes: usize,
936 ) -> Vec<Vec<u64>> {
937 let mut usages = vec![vec![0u64; nr_nodes]; nr_layers];
938
939 for cpu in 0..*NR_CPUS_POSSIBLE {
940 let node = topo.all_cpus.get(&cpu).map_or(0, |c| c.node_id);
941 for (layer, layer_usages) in usages.iter_mut().enumerate().take(nr_layers) {
942 layer_usages[node] += cpu_ctxs[cpu].node_pinned_usage[layer];
943 }
944 }
945
946 usages
947 }
948
949 #[allow(clippy::needless_range_loop)]
950 fn read_layer_node_usages(
951 cpu_ctxs: &[bpf_intf::cpu_ctx],
952 topo: &Topology,
953 nr_layers: usize,
954 nr_nodes: usize,
955 ) -> Vec<Vec<u64>> {
956 let mut usages = vec![vec![0u64; nr_nodes]; nr_layers];
957
958 for cpu in 0..*NR_CPUS_POSSIBLE {
959 let node = topo.all_cpus.get(&cpu).map_or(0, |c| c.node_id);
960 for (layer, layer_usages) in usages.iter_mut().enumerate().take(nr_layers) {
961 for usage in 0..=LAYER_USAGE_SUM_UPTO {
962 layer_usages[node] += cpu_ctxs[cpu].layer_usages[layer][usage];
963 }
964 }
965 }
966
967 usages
968 }
969
970 #[allow(clippy::needless_range_loop)]
971 fn read_layer_node_duty_raw(
972 cpu_ctxs: &[bpf_intf::cpu_ctx],
973 topo: &Topology,
974 nr_layers: usize,
975 nr_nodes: usize,
976 ) -> Vec<Vec<u64>> {
977 let mut sums = vec![vec![0u64; nr_nodes]; nr_layers];
978
979 for cpu in 0..*NR_CPUS_POSSIBLE {
980 let node = topo.all_cpus.get(&cpu).map_or(0, |c| c.node_id);
981 for (layer, layer_sums) in sums.iter_mut().enumerate().take(nr_layers) {
982 layer_sums[node] += cpu_ctxs[cpu].layer_duty_sum[layer];
983 }
984 }
985
986 sums
987 }
988
989 #[allow(clippy::needless_range_loop)]
990 fn read_per_cpu_layer_usages(cpu_ctxs: &[bpf_intf::cpu_ctx], nr_layers: usize) -> Vec<u64> {
991 let stride = nr_layers * NR_LAYER_USAGES;
992 let mut flat = vec![0u64; *NR_CPUS_POSSIBLE * stride];
993
994 for cpu in 0..*NR_CPUS_POSSIBLE {
995 let base = cpu * stride;
996 for layer in 0..nr_layers {
997 for usage in 0..NR_LAYER_USAGES {
998 flat[base + layer * NR_LAYER_USAGES + usage] =
999 cpu_ctxs[cpu].layer_usages[layer][usage];
1000 }
1001 }
1002 }
1003
1004 flat
1005 }
1006
1007 fn resctrl_read_total_membw() -> Result<u64> {
1019 let mut total_membw = 0u64;
1020 for entry in WalkDir::new("/sys/fs/resctrl/mon_data")
1021 .min_depth(1)
1022 .into_iter()
1023 .filter_map(Result::ok)
1024 .filter(|x| x.path().is_dir())
1025 {
1026 let mut path = entry.path().to_path_buf();
1027 path.push("mbm_total_bytes");
1028 total_membw += fs::read_to_string(path)?.trim().parse::<u64>()?;
1029 }
1030
1031 Ok(total_membw)
1032 }
1033
1034 fn new(
1035 skel: &mut BpfSkel,
1036 proc_reader: &fb_procfs::ProcReader,
1037 topo: Arc<Topology>,
1038 gpu_task_affinitizer: &GpuTaskAffinitizer,
1039 util_compensation: bool,
1040 ) -> Result<Self> {
1041 let nr_layers = skel.maps.rodata_data.as_ref().unwrap().nr_layers as usize;
1042 let nr_nodes = topo.nodes.len();
1043 let cpu_ctxs = read_cpu_ctxs(skel)?;
1044 let bpf_stats = BpfStats::read(skel, &cpu_ctxs);
1045 let pmu_membw = Self::read_layer_membw_agg(&cpu_ctxs, nr_layers);
1046
1047 Ok(Self {
1048 at: Instant::now(),
1049 elapsed: Default::default(),
1050
1051 topo: topo.clone(),
1052 nr_layers,
1053 nr_layer_tasks: vec![0; nr_layers],
1054 layer_nr_node_pinned_tasks: vec![vec![0; nr_nodes]; nr_layers],
1055
1056 total_util: 0.0,
1057 layer_utils: vec![vec![0.0; NR_LAYER_USAGES]; nr_layers],
1058 layer_node_pinned_utils: vec![vec![0.0; nr_nodes]; nr_layers],
1059 prev_layer_node_pinned_usages: Self::read_layer_node_pinned_usages(
1060 &cpu_ctxs, &topo, nr_layers, nr_nodes,
1061 ),
1062 layer_node_utils: vec![vec![0.0; nr_nodes]; nr_layers],
1063 prev_layer_node_usages: Self::read_layer_node_usages(
1064 &cpu_ctxs, &topo, nr_layers, nr_nodes,
1065 ),
1066 layer_node_duty_sums: vec![vec![0.0; nr_nodes]; nr_layers],
1067 prev_layer_node_duty_raw: Self::read_layer_node_duty_raw(
1068 &cpu_ctxs, &topo, nr_layers, nr_nodes,
1069 ),
1070 layer_membws: vec![vec![0.0; NR_LAYER_USAGES]; nr_layers],
1071 prev_layer_membw_agg: pmu_membw,
1075 prev_pmu_resctrl_membw: (0, 0),
1076
1077 cpu_busy: 0.0,
1078 prev_total_cpu: read_total_cpu(proc_reader)?,
1079 util_compensation,
1080 layer_utils_compensated: vec![vec![0.0; NR_LAYER_USAGES]; nr_layers],
1081 prev_cpu_layer_usages: Self::read_per_cpu_layer_usages(&cpu_ctxs, nr_layers),
1082 prev_per_cpu_stats: BTreeMap::new(),
1083 system_cpu_util_ewma: 0.0,
1084 layer_dsq_insert_ewma: vec![0.0; nr_layers],
1085
1086 bpf_stats: bpf_stats.clone(),
1087 prev_bpf_stats: bpf_stats,
1088
1089 processing_dur: Default::default(),
1090 prev_processing_dur: Default::default(),
1091
1092 layer_slice_us: vec![0; nr_layers],
1093 gpu_tasks_affinitized: gpu_task_affinitizer.tasks_affinitized,
1094 gpu_task_affinitization_ms: gpu_task_affinitizer.last_task_affinitization_ms,
1095 })
1096 }
1097
1098 fn refresh(
1099 &mut self,
1100 skel: &mut BpfSkel,
1101 proc_reader: &fb_procfs::ProcReader,
1102 now: Instant,
1103 cur_processing_dur: Duration,
1104 gpu_task_affinitizer: &GpuTaskAffinitizer,
1105 ) -> Result<()> {
1106 let elapsed = now.duration_since(self.at);
1107 let elapsed_f64 = elapsed.as_secs_f64();
1108 let cpu_ctxs = read_cpu_ctxs(skel)?;
1109
1110 let layers = &skel.maps.bss_data.as_ref().unwrap().layers;
1111 let nr_layer_tasks: Vec<usize> = layers
1112 .iter()
1113 .take(self.nr_layers)
1114 .map(|layer| layer.nr_tasks as usize)
1115 .collect();
1116 let layer_nr_node_pinned_tasks: Vec<Vec<u64>> = layers
1117 .iter()
1118 .take(self.nr_layers)
1119 .map(|layer| {
1120 layer.node[..self.topo.nodes.len()]
1121 .iter()
1122 .map(|n| n.nr_pinned_tasks)
1123 .collect()
1124 })
1125 .collect();
1126 let layer_slice_us: Vec<u64> = layers
1127 .iter()
1128 .take(self.nr_layers)
1129 .map(|layer| layer.slice_ns / 1000_u64)
1130 .collect();
1131
1132 let cur_layer_node_pinned_usages = Self::read_layer_node_pinned_usages(
1133 &cpu_ctxs,
1134 &self.topo,
1135 self.nr_layers,
1136 self.topo.nodes.len(),
1137 );
1138 let cur_layer_node_usages = Self::read_layer_node_usages(
1139 &cpu_ctxs,
1140 &self.topo,
1141 self.nr_layers,
1142 self.topo.nodes.len(),
1143 );
1144 let cur_layer_node_duty_raw = Self::read_layer_node_duty_raw(
1145 &cpu_ctxs,
1146 &self.topo,
1147 self.nr_layers,
1148 self.topo.nodes.len(),
1149 );
1150 let cur_layer_membw_agg = Self::read_layer_membw_agg(&cpu_ctxs, self.nr_layers);
1151
1152 let (pmu_prev, resctrl_prev) = self.prev_pmu_resctrl_membw;
1160 let pmu_cur: u64 = cur_layer_membw_agg
1161 .iter()
1162 .map(|membw_agg| membw_agg[LAYER_USAGE_OPEN] + membw_agg[LAYER_USAGE_OWNED])
1163 .sum();
1164 let resctrl_cur = Self::resctrl_read_total_membw()?;
1165 let factor = (resctrl_cur - resctrl_prev) as f64 / (pmu_cur - pmu_prev) as f64;
1166
1167 let compute_diff = |cur_agg: &Vec<Vec<u64>>, prev_agg: &Vec<Vec<u64>>| {
1169 cur_agg
1170 .iter()
1171 .zip(prev_agg.iter())
1172 .map(|(cur, prev)| {
1173 cur.iter()
1174 .zip(prev.iter())
1175 .map(|(c, p)| (c - p) as f64 / 1_000_000_000.0 / elapsed_f64)
1176 .collect()
1177 })
1178 .collect()
1179 };
1180
1181 let compute_mem_diff = |cur_agg: &Vec<Vec<u64>>, prev_agg: &Vec<Vec<u64>>| {
1184 cur_agg
1185 .iter()
1186 .zip(prev_agg.iter())
1187 .map(|(cur, prev)| {
1188 cur.iter()
1189 .zip(prev.iter())
1190 .map(|(c, p)| (*c as i64 - *p as i64) as f64 / 1024_f64.powf(3.0))
1191 .collect()
1192 })
1193 .collect()
1194 };
1195
1196 let cur_layer_membw: Vec<Vec<f64>> =
1198 compute_mem_diff(&cur_layer_membw_agg, &self.prev_layer_membw_agg);
1199
1200 let cur_layer_membw: Vec<Vec<f64>> = cur_layer_membw
1201 .iter()
1202 .map(|x| x.iter().map(|x| *x * factor).collect())
1203 .collect();
1204
1205 let metric_decay =
1206 |cur_metric: Vec<Vec<f64>>, prev_metric: &Vec<Vec<f64>>, decay_rate: f64| {
1207 cur_metric
1208 .iter()
1209 .zip(prev_metric.iter())
1210 .map(|(cur, prev)| {
1211 cur.iter()
1212 .zip(prev.iter())
1213 .map(|(c, p)| {
1214 let decay = decay_rate.powf(elapsed_f64);
1215 p * decay + c * (1.0 - decay)
1216 })
1217 .collect()
1218 })
1219 .collect()
1220 };
1221
1222 let cur_node_pinned_utils: Vec<Vec<f64>> = compute_diff(
1223 &cur_layer_node_pinned_usages,
1224 &self.prev_layer_node_pinned_usages,
1225 );
1226 let layer_node_pinned_utils: Vec<Vec<f64>> = metric_decay(
1227 cur_node_pinned_utils,
1228 &self.layer_node_pinned_utils,
1229 *USAGE_DECAY,
1230 );
1231 let cur_node_utils: Vec<Vec<f64>> =
1232 compute_diff(&cur_layer_node_usages, &self.prev_layer_node_usages);
1233 let layer_node_utils: Vec<Vec<f64>> =
1234 metric_decay(cur_node_utils, &self.layer_node_utils, *USAGE_DECAY);
1235 let cur_node_duty: Vec<Vec<f64>> =
1236 compute_diff(&cur_layer_node_duty_raw, &self.prev_layer_node_duty_raw);
1237 let layer_node_duty_sums: Vec<Vec<f64>> =
1238 metric_decay(cur_node_duty, &self.layer_node_duty_sums, *USAGE_DECAY);
1239
1240 let layer_membws: Vec<Vec<f64>> = metric_decay(cur_layer_membw, &self.layer_membws, 0.0);
1241
1242 let proc_stat = proc_reader
1243 .read_stat()
1244 .context("Failed to read /proc/stat")?;
1245 let cur_total_cpu = proc_stat
1246 .total_cpu
1247 .ok_or_else(|| anyhow!("Could not read total cpu stat in proc"))?;
1248 let cpu_busy = calc_util(&cur_total_cpu, &self.prev_total_cpu)?;
1249
1250 const SYS_CPU_UTIL_EWMA_SECS: f64 = 10.0;
1252 let elapsed_f64 = elapsed.as_secs_f64();
1253 let alpha = elapsed_f64 / SYS_CPU_UTIL_EWMA_SECS.max(elapsed_f64);
1254 let system_cpu_util_ewma = alpha * cpu_busy + (1.0 - alpha) * self.system_cpu_util_ewma;
1255
1256 let cur_per_cpu_stats = proc_stat.cpus_map.unwrap_or_default();
1259 let mut cpu_scales = vec![1.0f64; *NR_CPUS_POSSIBLE];
1260 if self.util_compensation {
1261 for (&cpu_id, cur_cpu_stat) in &cur_per_cpu_stats {
1262 let cpu = cpu_id as usize;
1263 if let Some(prev_cpu_stat) = self.prev_per_cpu_stats.get(&cpu_id)
1264 && let (
1265 fb_procfs::CpuStat {
1266 user_usec: Some(cu),
1267 nice_usec: Some(cn),
1268 system_usec: Some(cs),
1269 idle_usec: Some(ci),
1270 iowait_usec: Some(cw),
1271 irq_usec: Some(cq),
1272 softirq_usec: Some(cf),
1273 stolen_usec: Some(ct),
1274 ..
1275 },
1276 fb_procfs::CpuStat {
1277 user_usec: Some(pu),
1278 nice_usec: Some(pn),
1279 system_usec: Some(ps),
1280 idle_usec: Some(pi),
1281 iowait_usec: Some(pw),
1282 irq_usec: Some(pq),
1283 softirq_usec: Some(pf),
1284 stolen_usec: Some(pt),
1285 ..
1286 },
1287 ) = (cur_cpu_stat, prev_cpu_stat)
1288 {
1289 let delta_total = cu.saturating_sub(*pu)
1290 + cn.saturating_sub(*pn)
1291 + cs.saturating_sub(*ps)
1292 + ci.saturating_sub(*pi)
1293 + cw.saturating_sub(*pw)
1294 + cq.saturating_sub(*pq)
1295 + cf.saturating_sub(*pf)
1296 + ct.saturating_sub(*pt);
1297 let overhead =
1298 cq.saturating_sub(*pq) + cf.saturating_sub(*pf) + ct.saturating_sub(*pt);
1299 let available = delta_total.saturating_sub(overhead);
1300 cpu_scales[cpu] = if available > 0 {
1301 (delta_total as f64 / available as f64).clamp(1.0, 20.0)
1302 } else {
1303 1.0
1304 };
1305 }
1306 }
1307 }
1308
1309 let cur_cpu_layer_usages = Self::read_per_cpu_layer_usages(&cpu_ctxs, self.nr_layers);
1313 let stride = self.nr_layers * NR_LAYER_USAGES;
1314 let mut raw_sums = vec![vec![0.0f64; NR_LAYER_USAGES]; self.nr_layers];
1315 let mut scaled_sums = vec![vec![0.0f64; NR_LAYER_USAGES]; self.nr_layers];
1316 #[allow(clippy::needless_range_loop)]
1317 for cpu in 0..*NR_CPUS_POSSIBLE {
1318 let scale = cpu_scales[cpu];
1319 let base = cpu * stride;
1320 for layer in 0..self.nr_layers {
1321 for usage in 0..NR_LAYER_USAGES {
1322 let idx = base + layer * NR_LAYER_USAGES + usage;
1323 let delta =
1324 cur_cpu_layer_usages[idx].saturating_sub(self.prev_cpu_layer_usages[idx]);
1325 if delta > 0 {
1326 let delta_f = delta as f64;
1327 raw_sums[layer][usage] += delta_f;
1328 scaled_sums[layer][usage] += delta_f * scale;
1329 }
1330 }
1331 }
1332 }
1333 let normalize = |sums: Vec<Vec<f64>>| -> Vec<Vec<f64>> {
1334 sums.into_iter()
1335 .map(|layer_sums| {
1336 layer_sums
1337 .into_iter()
1338 .map(|s| s / 1_000_000_000.0 / elapsed_f64)
1339 .collect()
1340 })
1341 .collect()
1342 };
1343 let layer_utils: Vec<Vec<f64>> =
1344 metric_decay(normalize(raw_sums), &self.layer_utils, *USAGE_DECAY);
1345 let layer_utils_compensated: Vec<Vec<f64>> = metric_decay(
1346 normalize(scaled_sums),
1347 &self.layer_utils_compensated,
1348 *USAGE_DECAY,
1349 );
1350
1351 let cur_bpf_stats = BpfStats::read(skel, &cpu_ctxs);
1352 let bpf_stats = &cur_bpf_stats - &self.prev_bpf_stats;
1353
1354 const DSQ_INSERT_EWMA_SECS: f64 = 10.0;
1356 let dsq_alpha = elapsed_f64 / DSQ_INSERT_EWMA_SECS.max(elapsed_f64);
1357 let layer_dsq_insert_ewma: Vec<f64> = (0..self.nr_layers)
1358 .map(|layer_id| {
1359 let sel_local = bpf_stats.lstats[layer_id]
1360 [bpf_intf::layer_stat_id_LSTAT_SEL_LOCAL as usize]
1361 as f64;
1362 let enq_local = bpf_stats.lstats[layer_id]
1363 [bpf_intf::layer_stat_id_LSTAT_ENQ_LOCAL as usize]
1364 as f64;
1365 let enq_dsq = bpf_stats.lstats[layer_id]
1366 [bpf_intf::layer_stat_id_LSTAT_ENQ_DSQ as usize]
1367 as f64;
1368 let total_dispatches = sel_local + enq_local + enq_dsq;
1369
1370 let cur_ratio = if total_dispatches > 0.0 {
1371 enq_dsq / total_dispatches
1372 } else {
1373 0.0
1374 };
1375
1376 dsq_alpha * cur_ratio + (1.0 - dsq_alpha) * self.layer_dsq_insert_ewma[layer_id]
1377 })
1378 .collect();
1379
1380 let processing_dur = cur_processing_dur
1381 .checked_sub(self.prev_processing_dur)
1382 .unwrap();
1383
1384 *self = Self {
1385 at: now,
1386 elapsed,
1387 topo: self.topo.clone(),
1388 nr_layers: self.nr_layers,
1389 nr_layer_tasks,
1390 layer_nr_node_pinned_tasks,
1391
1392 total_util: layer_utils
1393 .iter()
1394 .map(|x| x.iter().take(LAYER_USAGE_SUM_UPTO + 1).sum::<f64>())
1395 .sum(),
1396 layer_utils,
1397 layer_node_pinned_utils,
1398 prev_layer_node_pinned_usages: cur_layer_node_pinned_usages,
1399 layer_node_utils,
1400 prev_layer_node_usages: cur_layer_node_usages,
1401 layer_node_duty_sums,
1402 prev_layer_node_duty_raw: cur_layer_node_duty_raw,
1403
1404 layer_membws,
1405 prev_layer_membw_agg: cur_layer_membw_agg,
1406 prev_pmu_resctrl_membw: (pmu_cur, resctrl_cur),
1408
1409 cpu_busy,
1410 prev_total_cpu: cur_total_cpu,
1411 util_compensation: self.util_compensation,
1412 layer_utils_compensated,
1413 prev_cpu_layer_usages: cur_cpu_layer_usages,
1414 prev_per_cpu_stats: cur_per_cpu_stats,
1415 system_cpu_util_ewma,
1416 layer_dsq_insert_ewma,
1417
1418 bpf_stats,
1419 prev_bpf_stats: cur_bpf_stats,
1420
1421 processing_dur,
1422 prev_processing_dur: cur_processing_dur,
1423
1424 layer_slice_us,
1425 gpu_tasks_affinitized: gpu_task_affinitizer.tasks_affinitized,
1426 gpu_task_affinitization_ms: gpu_task_affinitizer.last_task_affinitization_ms,
1427 };
1428 Ok(())
1429 }
1430}
1431
1432#[derive(Debug)]
1433struct Layer {
1434 name: String,
1435 kind: LayerKind,
1436 growth_algo: LayerGrowthAlgo,
1437 core_order: Vec<Vec<usize>>,
1438
1439 assigned_llcs: Vec<Vec<usize>>,
1440
1441 nr_cpus: usize,
1442 nr_llc_cpus: Vec<usize>,
1443 nr_node_cpus: Vec<usize>,
1444 cpus: Cpumask,
1445 allowed_cpus: Cpumask,
1446
1447 nr_pinned_cpus: Vec<usize>,
1449}
1450
1451fn get_kallsyms_addr(sym_name: &str) -> Result<u64> {
1452 fs::read_to_string("/proc/kallsyms")?
1453 .lines()
1454 .find(|line| line.contains(sym_name))
1455 .and_then(|line| line.split_whitespace().next())
1456 .and_then(|addr| u64::from_str_radix(addr, 16).ok())
1457 .ok_or_else(|| anyhow!("Symbol '{}' not found", sym_name))
1458}
1459
1460fn resolve_cpus_pct_range(
1461 cpus_range: &Option<(usize, usize)>,
1462 cpus_range_frac: &Option<(f64, f64)>,
1463 max_cpus: usize,
1464) -> Result<(usize, usize)> {
1465 match (cpus_range, cpus_range_frac) {
1466 (Some(_x), Some(_y)) => {
1467 bail!("cpus_range cannot be used with cpus_pct.");
1468 }
1469 (Some((cpus_range_min, cpus_range_max)), None) => Ok((*cpus_range_min, *cpus_range_max)),
1470 (None, Some((cpus_frac_min, cpus_frac_max))) => {
1471 if *cpus_frac_min < 0_f64
1472 || *cpus_frac_min > 1_f64
1473 || *cpus_frac_max < 0_f64
1474 || *cpus_frac_max > 1_f64
1475 {
1476 bail!("cpus_range_frac values must be between 0.0 and 1.0");
1477 }
1478 let cpus_min_count = ((max_cpus as f64) * cpus_frac_min).round_ties_even() as usize;
1479 let cpus_max_count = ((max_cpus as f64) * cpus_frac_max).round_ties_even() as usize;
1480 Ok((
1481 std::cmp::max(cpus_min_count, 1),
1482 std::cmp::min(std::cmp::max(cpus_max_count, 1), max_cpus),
1483 ))
1484 }
1485 (None, None) => Ok((0, max_cpus)),
1486 }
1487}
1488
1489fn update_peak_util(prev_peak: f64, cur_util: f64, half_life: Duration, elapsed: Duration) -> f64 {
1490 let decay = 0.5f64.powf(elapsed.as_secs_f64() / half_life.as_secs_f64());
1491 (prev_peak * decay).max(cur_util)
1492}
1493
1494fn calc_peak_aware_target_range(
1495 util: f64,
1496 peak_util: f64,
1497 util_range: (f64, f64),
1498) -> (usize, usize) {
1499 let low = (util / util_range.1).ceil() as usize;
1500 let high = ((util.max(peak_util) / util_range.0).floor() as usize).max(low);
1501 (low, high)
1502}
1503
1504impl Layer {
1505 fn new(spec: &LayerSpec, topo: &Topology, core_order: &Vec<Vec<usize>>) -> Result<Self> {
1506 let name = &spec.name;
1507 let kind = spec.kind.clone();
1508 let mut allowed_cpus = Cpumask::new();
1509 match &kind {
1510 LayerKind::Confined {
1511 cpus_range,
1512 cpus_range_frac,
1513 common: LayerCommon { nodes, llcs, .. },
1514 ..
1515 } => {
1516 let cpus_range =
1517 resolve_cpus_pct_range(cpus_range, cpus_range_frac, topo.all_cpus.len())?;
1518 if cpus_range.0 > cpus_range.1 || cpus_range.1 == 0 {
1519 bail!("invalid cpus_range {:?}", cpus_range);
1520 }
1521 if nodes.is_empty() && llcs.is_empty() {
1522 allowed_cpus.set_all();
1523 } else {
1524 for (node_id, node) in &topo.nodes {
1526 if nodes.contains(node_id) {
1528 for &id in node.all_cpus.keys() {
1529 allowed_cpus.set_cpu(id)?;
1530 }
1531 }
1532 for (llc_id, llc) in &node.llcs {
1534 if llcs.contains(llc_id) {
1535 for &id in llc.all_cpus.keys() {
1536 allowed_cpus.set_cpu(id)?;
1537 }
1538 }
1539 }
1540 }
1541 }
1542 }
1543 LayerKind::Grouped {
1544 common: LayerCommon { nodes, llcs, .. },
1545 ..
1546 }
1547 | LayerKind::Open {
1548 common: LayerCommon { nodes, llcs, .. },
1549 ..
1550 } => {
1551 if nodes.is_empty() && llcs.is_empty() {
1552 allowed_cpus.set_all();
1553 } else {
1554 for (node_id, node) in &topo.nodes {
1556 if nodes.contains(node_id) {
1558 for &id in node.all_cpus.keys() {
1559 allowed_cpus.set_cpu(id)?;
1560 }
1561 }
1562 for (llc_id, llc) in &node.llcs {
1564 if llcs.contains(llc_id) {
1565 for &id in llc.all_cpus.keys() {
1566 allowed_cpus.set_cpu(id)?;
1567 }
1568 }
1569 }
1570 }
1571 }
1572 }
1573 }
1574
1575 if let Some(util_range) = kind.util_range() {
1578 if util_range.0 < 0.0 || util_range.1 < 0.0 || util_range.0 >= util_range.1 {
1579 bail!("invalid util_range {:?}", util_range);
1580 }
1581 } else if kind.common().util_peak_half_life_ms != 0 {
1582 bail!("util_peak_half_life_ms requires util_range");
1583 }
1584
1585 let layer_growth_algo = kind.common().growth_algo.clone();
1586
1587 debug!(
1588 "layer: {} algo: {:?} core order: {:?}",
1589 name, &layer_growth_algo, core_order
1590 );
1591
1592 Ok(Self {
1593 name: name.into(),
1594 kind,
1595 growth_algo: layer_growth_algo,
1596 core_order: core_order.clone(),
1597
1598 assigned_llcs: vec![vec![]; topo.nodes.len()],
1599
1600 nr_cpus: 0,
1601 nr_llc_cpus: vec![0; topo.all_llcs.len()],
1602 nr_node_cpus: vec![0; topo.nodes.len()],
1603 cpus: Cpumask::new(),
1604 allowed_cpus,
1605
1606 nr_pinned_cpus: vec![0; topo.nodes.len()],
1607 })
1608 }
1609}
1610#[derive(Debug, Clone)]
1611struct NodeInfo {
1612 node_mask: nix::sched::CpuSet,
1613 _node_id: usize,
1614}
1615
1616#[derive(Debug)]
1617struct GpuTaskAffinitizer {
1618 gpu_devs_to_node_info: HashMap<u32, NodeInfo>,
1621 gpu_pids_to_devs: HashMap<Pid, u32>,
1622 last_process_time: Option<Instant>,
1623 sys: System,
1624 pid_map: HashMap<Pid, Vec<Pid>>,
1625 poll_interval: Duration,
1626 enable: bool,
1627 tasks_affinitized: u64,
1628 last_task_affinitization_ms: u64,
1629}
1630
1631impl GpuTaskAffinitizer {
1632 pub fn new(poll_interval: u64, enable: bool) -> GpuTaskAffinitizer {
1633 GpuTaskAffinitizer {
1634 gpu_devs_to_node_info: HashMap::new(),
1635 gpu_pids_to_devs: HashMap::new(),
1636 last_process_time: None,
1637 sys: System::default(),
1638 pid_map: HashMap::new(),
1639 poll_interval: Duration::from_secs(poll_interval),
1640 enable,
1641 tasks_affinitized: 0,
1642 last_task_affinitization_ms: 0,
1643 }
1644 }
1645
1646 fn find_one_cpu(&self, affinity: Vec<u64>) -> Result<u32> {
1647 for (chunk, &mask) in affinity.iter().enumerate() {
1648 let mut inner_offset: u64 = 1;
1649 for _ in 0..64 {
1650 if (mask & inner_offset) != 0 {
1651 return Ok((64 * chunk + u64::trailing_zeros(inner_offset) as usize) as u32);
1652 }
1653 inner_offset <<= 1;
1654 }
1655 }
1656 anyhow::bail!("unable to get CPU from NVML bitmask");
1657 }
1658
1659 fn node_to_cpuset(&self, node: &scx_utils::Node) -> Result<CpuSet> {
1660 let mut cpuset = CpuSet::new();
1661 for cpu_id in node.all_cpus.keys() {
1662 cpuset.set(*cpu_id)?;
1663 }
1664 Ok(cpuset)
1665 }
1666
1667 fn init_dev_node_map(&mut self, topo: Arc<Topology>) -> Result<()> {
1668 let nvml = nvml()?;
1669 let device_count = nvml.device_count()?;
1670
1671 for idx in 0..device_count {
1672 let dev = nvml.device_by_index(idx)?;
1673 let cpu = dev.cpu_affinity(16)?;
1675 let ideal_cpu = self.find_one_cpu(cpu)?;
1676 if let Some(cpu) = topo.all_cpus.get(&(ideal_cpu as usize)) {
1677 self.gpu_devs_to_node_info.insert(
1678 idx,
1679 NodeInfo {
1680 node_mask: self.node_to_cpuset(
1681 topo.nodes.get(&cpu.node_id).expect("topo missing node"),
1682 )?,
1683 _node_id: cpu.node_id,
1684 },
1685 );
1686 }
1687 }
1688 Ok(())
1689 }
1690
1691 fn update_gpu_pids(&mut self) -> Result<()> {
1692 let nvml = nvml()?;
1693 for i in 0..nvml.device_count()? {
1694 let device = nvml.device_by_index(i)?;
1695 for proc in device
1696 .running_compute_processes()?
1697 .into_iter()
1698 .chain(device.running_graphics_processes()?)
1699 {
1700 self.gpu_pids_to_devs.insert(Pid::from_u32(proc.pid), i);
1701 }
1702 }
1703 Ok(())
1704 }
1705
1706 fn update_process_info(&mut self) -> Result<()> {
1707 self.sys.refresh_processes_specifics(
1708 ProcessesToUpdate::All,
1709 true,
1710 ProcessRefreshKind::nothing(),
1711 );
1712 self.pid_map.clear();
1713 for (pid, proc_) in self.sys.processes() {
1714 if let Some(ppid) = proc_.parent() {
1715 self.pid_map.entry(ppid).or_default().push(*pid);
1716 }
1717 }
1718 Ok(())
1719 }
1720
1721 fn get_child_pids_and_tids(&self, root_pid: Pid) -> HashSet<Pid> {
1722 let mut work = VecDeque::from([root_pid]);
1723 let mut pids_and_tids: HashSet<Pid> = HashSet::new();
1724
1725 while let Some(pid) = work.pop_front() {
1726 if pids_and_tids.insert(pid) {
1727 if let Some(kids) = self.pid_map.get(&pid) {
1728 work.extend(kids);
1729 }
1730 if let Some(proc_) = self.sys.process(pid)
1731 && let Some(tasks) = proc_.tasks()
1732 {
1733 pids_and_tids.extend(tasks.iter().copied());
1734 }
1735 }
1736 }
1737 pids_and_tids
1738 }
1739
1740 fn affinitize_gpu_pids(&mut self) -> Result<()> {
1741 if !self.enable {
1742 return Ok(());
1743 }
1744 for (pid, dev) in &self.gpu_pids_to_devs {
1745 let node_info = self
1746 .gpu_devs_to_node_info
1747 .get(dev)
1748 .expect("Unable to get gpu pid node mask");
1749 for child in self.get_child_pids_and_tids(*pid) {
1750 match nix::sched::sched_setaffinity(
1751 nix::unistd::Pid::from_raw(child.as_u32() as i32),
1752 &node_info.node_mask,
1753 ) {
1754 Ok(_) => {
1755 self.tasks_affinitized += 1;
1757 }
1758 Err(_) => {
1759 debug!(
1760 "Error affinitizing gpu pid {} to node {:#?}",
1761 child.as_u32(),
1762 node_info
1763 );
1764 }
1765 };
1766 }
1767 }
1768 Ok(())
1769 }
1770
1771 pub fn maybe_affinitize(&mut self) {
1772 if !self.enable {
1773 return;
1774 }
1775 let now = Instant::now();
1776
1777 if let Some(last_process_time) = self.last_process_time
1778 && (now - last_process_time) < self.poll_interval
1779 {
1780 return;
1781 }
1782
1783 match self.update_gpu_pids() {
1784 Ok(_) => {}
1785 Err(e) => {
1786 error!("Error updating GPU PIDs: {}", e);
1787 }
1788 };
1789 match self.update_process_info() {
1790 Ok(_) => {}
1791 Err(e) => {
1792 error!("Error updating process info to affinitize GPU PIDs: {}", e);
1793 }
1794 };
1795 match self.affinitize_gpu_pids() {
1796 Ok(_) => {}
1797 Err(e) => {
1798 error!("Error updating GPU PIDs: {}", e);
1799 }
1800 };
1801 self.last_process_time = Some(now);
1802 self.last_task_affinitization_ms = (Instant::now() - now).as_millis() as u64;
1803 }
1804
1805 pub fn init(&mut self, topo: Arc<Topology>) {
1806 if !self.enable || self.last_process_time.is_some() {
1807 return;
1808 }
1809
1810 match self.init_dev_node_map(topo) {
1811 Ok(_) => {}
1812 Err(e) => {
1813 error!("Error initializing gpu node dev map: {}", e);
1814 }
1815 };
1816 self.sys = System::new_all();
1817 }
1818}
1819
1820struct Scheduler<'a> {
1821 skel: BpfSkel<'a>,
1822 struct_ops: Option<libbpf_rs::Link>,
1823 layer_specs: Vec<LayerSpec>,
1824
1825 sched_intv: Duration,
1826 layer_refresh_intv: Duration,
1827
1828 cpu_pool: CpuPool,
1829 layers: Vec<Layer>,
1830 idle_qos_enabled: bool,
1831
1832 proc_reader: fb_procfs::ProcReader,
1833 sched_stats: Stats,
1834 layer_peak_utils: Vec<f64>,
1835
1836 cgroup_regexes: Option<HashMap<u32, Regex>>,
1837
1838 nr_layer_cpus_ranges: Vec<(usize, usize)>,
1839 xnuma_mig_src: Vec<Vec<bool>>,
1840 growth_denied: Vec<Vec<bool>>,
1841 processing_dur: Duration,
1842
1843 topo: Arc<Topology>,
1844 netdevs: BTreeMap<String, NetDev>,
1845 stats_server: StatsServer<StatsReq, StatsRes>,
1846 gpu_task_handler: GpuTaskAffinitizer,
1847}
1848
1849const DUTY_CYCLE_SCALE: f64 = (1u64 << 20) as f64;
1850const XNUMA_RATE_DAMPEN: f64 = 0.5;
1851
1852struct XnumaRates {
1854 rates: Vec<Vec<u64>>,
1856}
1857
1858fn xnuma_check_active(
1871 duty_sums: &[f64],
1872 allocs: &[usize],
1873 threshold: (f64, f64),
1874 threshold_delta: (f64, f64),
1875 growth_denied: &[bool],
1876 currently_active: &[bool],
1877) -> Vec<bool> {
1878 let nr_nodes = duty_sums.len();
1879 let total_duty: f64 = duty_sums.iter().sum();
1880 let total_alloc: f64 = allocs.iter().map(|&a| a as f64).sum();
1881 let eq_ratio = if total_alloc > 0.0 {
1882 total_duty / total_alloc
1883 } else {
1884 0.0
1885 };
1886
1887 let (thresh_lo, thresh_hi) = threshold;
1888 let (delta_lo, delta_hi) = threshold_delta;
1889
1890 let mut result = vec![false; nr_nodes];
1891 for nid in 0..nr_nodes {
1892 let alloc = allocs[nid] as f64;
1893 if alloc <= 0.0 {
1894 if duty_sums[nid] > 0.0 && growth_denied[nid] {
1895 result[nid] = true;
1896 }
1897 continue;
1898 }
1899
1900 let load_ratio = duty_sums[nid] / alloc;
1901 let surplus = duty_sums[nid] - eq_ratio * alloc;
1902 let surplus_ratio = surplus / alloc;
1903
1904 let should_activate =
1905 load_ratio > thresh_hi && surplus_ratio > delta_hi && growth_denied[nid];
1906 let should_deactivate =
1907 load_ratio < thresh_lo || surplus_ratio < delta_lo || !growth_denied[nid];
1908
1909 if should_activate {
1910 result[nid] = true;
1911 } else if should_deactivate {
1912 result[nid] = false;
1913 } else {
1914 result[nid] = currently_active[nid];
1915 }
1916 }
1917 result
1918}
1919
1920fn xnuma_compute_rates(duty_sums: &[f64], allocs: &[usize]) -> XnumaRates {
1926 let nr_nodes = duty_sums.len();
1927 let total_duty: f64 = duty_sums.iter().sum();
1928 let total_alloc: f64 = allocs.iter().map(|&a| a as f64).sum();
1929
1930 if total_alloc <= 0.0 {
1931 return XnumaRates {
1932 rates: vec![vec![0u64; nr_nodes]; nr_nodes],
1933 };
1934 }
1935
1936 let eq_ratio = total_duty / total_alloc;
1937
1938 let mut surpluses = vec![0.0f64; nr_nodes];
1939 let mut deficits = vec![0.0f64; nr_nodes];
1940 for nid in 0..nr_nodes {
1941 let expected = eq_ratio * allocs[nid] as f64;
1942 let delta = duty_sums[nid] - expected;
1943 if delta > 0.0 {
1944 surpluses[nid] = delta;
1945 } else {
1946 deficits[nid] = -delta;
1947 }
1948 }
1949
1950 let total_deficit: f64 = deficits.iter().sum();
1951
1952 let mut rates = vec![vec![0u64; nr_nodes]; nr_nodes];
1953 for src in 0..nr_nodes {
1954 for dst in 0..nr_nodes {
1955 if src == dst || total_deficit <= 0.0 || surpluses[src] <= 0.0 {
1956 continue;
1957 }
1958 let migration = surpluses[src] * deficits[dst] / total_deficit * XNUMA_RATE_DAMPEN;
1961 rates[src][dst] = (migration * DUTY_CYCLE_SCALE) as u64;
1962 }
1963 }
1964
1965 XnumaRates { rates }
1966}
1967
1968impl<'a> Scheduler<'a> {
1969 fn init_layers(
1970 skel: &mut OpenBpfSkel,
1971 specs: &[LayerSpec],
1972 topo: &Topology,
1973 ) -> Result<HashMap<u32, Regex>> {
1974 skel.maps.rodata_data.as_mut().unwrap().nr_layers = specs.len() as u32;
1975 let mut perf_set = false;
1976
1977 let mut layer_iteration_order = (0..specs.len()).collect::<Vec<_>>();
1978 let mut layer_weights: Vec<usize> = vec![];
1979 let mut cgroup_regex_id = 0;
1980 let mut cgroup_regexes = HashMap::new();
1981
1982 for (spec_i, spec) in specs.iter().enumerate() {
1983 let layer = &mut skel.maps.bss_data.as_mut().unwrap().layers[spec_i];
1984
1985 for (or_i, or) in spec.matches.iter().enumerate() {
1986 for (and_i, and) in or.iter().enumerate() {
1987 let mt = &mut layer.matches[or_i].matches[and_i];
1988
1989 mt.exclude.write(false);
1991
1992 match and {
1993 LayerMatch::CgroupPrefix(prefix) => {
1994 mt.kind = bpf_intf::layer_match_kind_MATCH_CGROUP_PREFIX as i32;
1995 copy_into_cstr(&mut mt.cgroup_prefix, prefix.as_str());
1996 }
1997 LayerMatch::CgroupSuffix(suffix) => {
1998 mt.kind = bpf_intf::layer_match_kind_MATCH_CGROUP_SUFFIX as i32;
1999 copy_into_cstr(&mut mt.cgroup_suffix, suffix.as_str());
2000 }
2001 LayerMatch::CgroupRegex(regex_str) => {
2002 if cgroup_regex_id >= bpf_intf::consts_MAX_CGROUP_REGEXES {
2003 bail!(
2004 "Too many cgroup regex rules. Maximum allowed: {}",
2005 bpf_intf::consts_MAX_CGROUP_REGEXES
2006 );
2007 }
2008
2009 mt.kind = bpf_intf::layer_match_kind_MATCH_CGROUP_REGEX as i32;
2011 mt.cgroup_regex_id = cgroup_regex_id;
2012
2013 let regex = Regex::new(regex_str).with_context(|| {
2014 format!("Invalid regex '{}' in layer '{}'", regex_str, spec.name)
2015 })?;
2016 cgroup_regexes.insert(cgroup_regex_id, regex);
2017 cgroup_regex_id += 1;
2018 }
2019 LayerMatch::CgroupContains(substr) => {
2020 mt.kind = bpf_intf::layer_match_kind_MATCH_CGROUP_CONTAINS as i32;
2021 copy_into_cstr(&mut mt.cgroup_substr, substr.as_str());
2022 }
2023 LayerMatch::CommPrefix(prefix) => {
2024 mt.kind = bpf_intf::layer_match_kind_MATCH_COMM_PREFIX as i32;
2025 copy_into_cstr(&mut mt.comm_prefix, prefix.as_str());
2026 }
2027 LayerMatch::CommPrefixExclude(prefix) => {
2028 mt.kind = bpf_intf::layer_match_kind_MATCH_COMM_PREFIX as i32;
2029 mt.exclude.write(true);
2030 copy_into_cstr(&mut mt.comm_prefix, prefix.as_str());
2031 }
2032 LayerMatch::PcommPrefix(prefix) => {
2033 mt.kind = bpf_intf::layer_match_kind_MATCH_PCOMM_PREFIX as i32;
2034 copy_into_cstr(&mut mt.pcomm_prefix, prefix.as_str());
2035 }
2036 LayerMatch::PcommPrefixExclude(prefix) => {
2037 mt.kind = bpf_intf::layer_match_kind_MATCH_PCOMM_PREFIX as i32;
2038 mt.exclude.write(true);
2039 copy_into_cstr(&mut mt.pcomm_prefix, prefix.as_str());
2040 }
2041 LayerMatch::NiceAbove(nice) => {
2042 mt.kind = bpf_intf::layer_match_kind_MATCH_NICE_ABOVE as i32;
2043 mt.nice = *nice;
2044 }
2045 LayerMatch::NiceBelow(nice) => {
2046 mt.kind = bpf_intf::layer_match_kind_MATCH_NICE_BELOW as i32;
2047 mt.nice = *nice;
2048 }
2049 LayerMatch::NiceEquals(nice) => {
2050 mt.kind = bpf_intf::layer_match_kind_MATCH_NICE_EQUALS as i32;
2051 mt.nice = *nice;
2052 }
2053 LayerMatch::UIDEquals(user_id) => {
2054 mt.kind = bpf_intf::layer_match_kind_MATCH_USER_ID_EQUALS as i32;
2055 mt.user_id = *user_id;
2056 }
2057 LayerMatch::GIDEquals(group_id) => {
2058 mt.kind = bpf_intf::layer_match_kind_MATCH_GROUP_ID_EQUALS as i32;
2059 mt.group_id = *group_id;
2060 }
2061 LayerMatch::PIDEquals(pid) => {
2062 mt.kind = bpf_intf::layer_match_kind_MATCH_PID_EQUALS as i32;
2063 mt.pid = *pid;
2064 }
2065 LayerMatch::PPIDEquals(ppid) => {
2066 mt.kind = bpf_intf::layer_match_kind_MATCH_PPID_EQUALS as i32;
2067 mt.ppid = *ppid;
2068 }
2069 LayerMatch::TGIDEquals(tgid) => {
2070 mt.kind = bpf_intf::layer_match_kind_MATCH_TGID_EQUALS as i32;
2071 mt.tgid = *tgid;
2072 }
2073 LayerMatch::NSPIDEquals(nsid, pid) => {
2074 mt.kind = bpf_intf::layer_match_kind_MATCH_NSPID_EQUALS as i32;
2075 mt.nsid = *nsid;
2076 mt.pid = *pid;
2077 }
2078 LayerMatch::NSEquals(nsid) => {
2079 mt.kind = bpf_intf::layer_match_kind_MATCH_NS_EQUALS as i32;
2080 mt.nsid = *nsid as u64;
2081 }
2082 LayerMatch::CmdJoin(joincmd) => {
2083 mt.kind = bpf_intf::layer_match_kind_MATCH_SCXCMD_JOIN as i32;
2084 copy_into_cstr(&mut mt.comm_prefix, joincmd);
2085 }
2086 LayerMatch::IsGroupLeader(polarity) => {
2087 mt.kind = bpf_intf::layer_match_kind_MATCH_IS_GROUP_LEADER as i32;
2088 mt.is_group_leader.write(*polarity);
2089 }
2090 LayerMatch::IsKthread(polarity) => {
2091 mt.kind = bpf_intf::layer_match_kind_MATCH_IS_KTHREAD as i32;
2092 mt.is_kthread.write(*polarity);
2093 }
2094 LayerMatch::UsedGpuTid(polarity) => {
2095 mt.kind = bpf_intf::layer_match_kind_MATCH_USED_GPU_TID as i32;
2096 mt.used_gpu_tid.write(*polarity);
2097 }
2098 LayerMatch::UsedGpuPid(polarity) => {
2099 mt.kind = bpf_intf::layer_match_kind_MATCH_USED_GPU_PID as i32;
2100 mt.used_gpu_pid.write(*polarity);
2101 }
2102 LayerMatch::AvgRuntime(min, max) => {
2103 mt.kind = bpf_intf::layer_match_kind_MATCH_AVG_RUNTIME as i32;
2104 mt.min_avg_runtime_us = *min;
2105 mt.max_avg_runtime_us = *max;
2106 }
2107 LayerMatch::HintEquals(hint) => {
2108 mt.kind = bpf_intf::layer_match_kind_MATCH_HINT_EQUALS as i32;
2109 mt.hint = *hint;
2110 }
2111 LayerMatch::SystemCpuUtilBelow(threshold) => {
2112 mt.kind = bpf_intf::layer_match_kind_MATCH_SYSTEM_CPU_UTIL_BELOW as i32;
2113 mt.system_cpu_util_below = (*threshold * 10000.0) as u64;
2114 }
2115 LayerMatch::DsqInsertBelow(threshold) => {
2116 mt.kind = bpf_intf::layer_match_kind_MATCH_DSQ_INSERT_BELOW as i32;
2117 mt.dsq_insert_below = (*threshold * 10000.0) as u64;
2118 }
2119 LayerMatch::NumaNode(node_id) => {
2120 if *node_id as usize >= topo.nodes.len() {
2121 bail!(
2122 "Spec {:?} has invalid NUMA node ID {} (available nodes: 0-{})",
2123 spec.name,
2124 node_id,
2125 topo.nodes.len() - 1
2126 );
2127 }
2128 mt.kind = bpf_intf::layer_match_kind_MATCH_NUMA_NODE as i32;
2129 mt.numa_node_id = *node_id;
2130 }
2131 }
2132 }
2133 layer.matches[or_i].nr_match_ands = or.len() as i32;
2134 }
2135
2136 layer.nr_match_ors = spec.matches.len() as u32;
2137 layer.kind = spec.kind.as_bpf_enum();
2138
2139 {
2140 let LayerCommon {
2141 min_exec_us,
2142 yield_ignore,
2143 perf,
2144 preempt,
2145 preempt_first,
2146 exclusive,
2147 skip_remote_node,
2148 prev_over_idle_core,
2149 growth_algo,
2150 slice_us,
2151 fifo,
2152 weight,
2153 disallow_open_after_us,
2154 disallow_preempt_after_us,
2155 xllc_mig_min_us,
2156 placement,
2157 member_expire_ms,
2158 ..
2159 } = spec.kind.common();
2160
2161 layer.slice_ns = *slice_us * 1000;
2162 layer.fifo.write(*fifo);
2163 layer.min_exec_ns = min_exec_us * 1000;
2164 layer.yield_step_ns = if *yield_ignore > 0.999 {
2165 0
2166 } else if *yield_ignore < 0.001 {
2167 layer.slice_ns
2168 } else {
2169 (layer.slice_ns as f64 * (1.0 - *yield_ignore)) as u64
2170 };
2171 let mut layer_name: String = spec.name.clone();
2172 layer_name.truncate(MAX_LAYER_NAME);
2173 copy_into_cstr(&mut layer.name, layer_name.as_str());
2174 layer.preempt.write(*preempt);
2175 layer.preempt_first.write(*preempt_first);
2176 layer.excl.write(*exclusive);
2177 layer.skip_remote_node.write(*skip_remote_node);
2178 layer.prev_over_idle_core.write(*prev_over_idle_core);
2179 layer.growth_algo = growth_algo.as_bpf_enum();
2180 layer.weight = *weight;
2181 layer.member_expire_ms = *member_expire_ms;
2182 layer.disallow_open_after_ns = match disallow_open_after_us.unwrap() {
2183 v if v == u64::MAX => v,
2184 v => v * 1000,
2185 };
2186 layer.disallow_preempt_after_ns = match disallow_preempt_after_us.unwrap() {
2187 v if v == u64::MAX => v,
2188 v => v * 1000,
2189 };
2190 layer.xllc_mig_min_ns = (xllc_mig_min_us * 1000.0) as u64;
2191 layer_weights.push(layer.weight.try_into().unwrap());
2192 layer.perf = u32::try_from(*perf)?;
2193
2194 let task_place = |place: u32| crate::types::layer_task_place(place);
2195 layer.task_place = match placement {
2196 LayerPlacement::Standard => {
2197 task_place(bpf_intf::layer_task_place_PLACEMENT_STD)
2198 }
2199 LayerPlacement::Sticky => {
2200 task_place(bpf_intf::layer_task_place_PLACEMENT_STICK)
2201 }
2202 LayerPlacement::Floating => {
2203 task_place(bpf_intf::layer_task_place_PLACEMENT_FLOAT)
2204 }
2205 };
2206 }
2207
2208 layer.is_protected.write(match spec.kind {
2209 LayerKind::Open { .. } => false,
2210 LayerKind::Confined { protected, .. } | LayerKind::Grouped { protected, .. } => {
2211 protected
2212 }
2213 });
2214
2215 layer.idle_confined.write(match spec.kind {
2216 LayerKind::Grouped { idle_confined, .. } => idle_confined,
2217 _ => false,
2218 });
2219
2220 match &spec.cpuset {
2221 Some(mask) => {
2222 Self::update_cpumask(mask, &mut layer.cpuset);
2223 layer.has_cpuset.write(true);
2224 }
2225 None => {
2226 for i in 0..layer.cpuset.len() {
2227 layer.cpuset[i] = u8::MAX;
2228 }
2229 layer.has_cpuset.write(false);
2230 }
2231 };
2232
2233 perf_set |= layer.perf > 0;
2234 }
2235
2236 layer_iteration_order.sort_by(|i, j| layer_weights[*i].cmp(&layer_weights[*j]));
2237 for (idx, layer_idx) in layer_iteration_order.iter().enumerate() {
2238 skel.maps
2239 .rodata_data
2240 .as_mut()
2241 .unwrap()
2242 .layer_iteration_order[idx] = *layer_idx as u32;
2243 }
2244
2245 if perf_set && !compat::ksym_exists("scx_bpf_cpuperf_set")? {
2246 warn!("cpufreq support not available, ignoring perf configurations");
2247 }
2248
2249 Ok(cgroup_regexes)
2250 }
2251
2252 fn init_nodes(skel: &mut OpenBpfSkel, _opts: &Opts, topo: &Topology) {
2253 skel.maps.rodata_data.as_mut().unwrap().nr_nodes = topo.nodes.len() as u32;
2254 skel.maps.rodata_data.as_mut().unwrap().nr_llcs = 0;
2255
2256 for (&node_id, node) in &topo.nodes {
2257 debug!("configuring node {}, LLCs {:?}", node_id, node.llcs.len());
2258 skel.maps.rodata_data.as_mut().unwrap().nr_llcs += node.llcs.len() as u32;
2259 let raw_numa_slice = node.span.as_raw_slice();
2260 let node_cpumask_slice =
2261 &mut skel.maps.rodata_data.as_mut().unwrap().numa_cpumasks[node_id];
2262 let (left, _) = node_cpumask_slice.split_at_mut(raw_numa_slice.len());
2263 left.clone_from_slice(raw_numa_slice);
2264 debug!(
2265 "node {} mask: {:?}",
2266 node_id,
2267 skel.maps.rodata_data.as_ref().unwrap().numa_cpumasks[node_id]
2268 );
2269
2270 for llc in node.llcs.values() {
2271 debug!("configuring llc {:?} for node {:?}", llc.id, node_id);
2272 skel.maps.rodata_data.as_mut().unwrap().llc_numa_id_map[llc.id] = node_id as u32;
2273 }
2274 }
2275
2276 for cpu in topo.all_cpus.values() {
2277 skel.maps.rodata_data.as_mut().unwrap().cpu_llc_id_map[cpu.id] = cpu.llc_id as u32;
2278 }
2279 }
2280
2281 fn init_cpu_prox_map(topo: &Topology, cpu_ctxs: &mut [bpf_intf::cpu_ctx]) {
2282 let radiate = |mut vec: Vec<usize>, center_id: usize| -> Vec<usize> {
2283 vec.sort_by_key(|&id| (center_id as i32 - id as i32).abs());
2284 vec
2285 };
2286 let radiate_cpu =
2287 |mut vec: Vec<usize>, center_cpu: usize, center_core: usize| -> Vec<usize> {
2288 vec.sort_by_key(|&id| {
2289 (
2290 (center_core as i32 - topo.all_cpus.get(&id).unwrap().core_id as i32).abs(),
2291 (center_cpu as i32 - id as i32).abs(),
2292 )
2293 });
2294 vec
2295 };
2296
2297 for (&cpu_id, cpu) in &topo.all_cpus {
2298 let mut core_span = topo.all_cores[&cpu.core_id].span.clone();
2300 let llc_span = &topo.all_llcs[&cpu.llc_id].span;
2301 let node_span = &topo.nodes[&cpu.node_id].span;
2302 let sys_span = &topo.span;
2303
2304 let sys_span = sys_span.and(&node_span.not());
2306 let node_span = node_span.and(&llc_span.not());
2307 let llc_span = llc_span.and(&core_span.not());
2308 core_span.clear_cpu(cpu_id).unwrap();
2309
2310 let mut sys_order: Vec<usize> = sys_span.iter().collect();
2312 let mut node_order: Vec<usize> = node_span.iter().collect();
2313 let mut llc_order: Vec<usize> = llc_span.iter().collect();
2314 let mut core_order: Vec<usize> = core_span.iter().collect();
2315
2316 sys_order = radiate_cpu(sys_order, cpu_id, cpu.core_id);
2321 node_order = radiate(node_order, cpu.node_id);
2322 llc_order = radiate_cpu(llc_order, cpu_id, cpu.core_id);
2323 core_order = radiate_cpu(core_order, cpu_id, cpu.core_id);
2324
2325 let mut order: Vec<usize> = vec![];
2327 let mut idx: usize = 0;
2328
2329 idx += 1;
2330 order.push(cpu_id);
2331
2332 idx += core_order.len();
2333 order.append(&mut core_order);
2334 let core_end = idx;
2335
2336 idx += llc_order.len();
2337 order.append(&mut llc_order);
2338 let llc_end = idx;
2339
2340 idx += node_order.len();
2341 order.append(&mut node_order);
2342 let node_end = idx;
2343
2344 idx += sys_order.len();
2345 order.append(&mut sys_order);
2346 let sys_end = idx;
2347
2348 debug!(
2349 "CPU[{}] proximity map[{}/{}/{}/{}]: {:?}",
2350 cpu_id, core_end, llc_end, node_end, sys_end, &order
2351 );
2352
2353 let pmap = &mut cpu_ctxs[cpu_id].prox_map;
2355 for (i, &cpu) in order.iter().enumerate() {
2356 pmap.cpus[i] = cpu as u16;
2357 }
2358 pmap.core_end = core_end as u32;
2359 pmap.llc_end = llc_end as u32;
2360 pmap.node_end = node_end as u32;
2361 pmap.sys_end = sys_end as u32;
2362 }
2363 }
2364
2365 fn convert_cpu_ctxs(cpu_ctxs: Vec<bpf_intf::cpu_ctx>) -> Vec<Vec<u8>> {
2366 cpu_ctxs
2367 .iter()
2368 .map(|cpu_ctx| unsafe { plain::as_bytes(cpu_ctx) }.to_vec())
2369 .collect()
2370 }
2371
2372 fn init_cpus(skel: &BpfSkel, layer_specs: &[LayerSpec], topo: &Topology) -> Result<()> {
2373 let key = (0_u32).to_ne_bytes();
2374 let mut cpu_ctxs: Vec<bpf_intf::cpu_ctx> = vec![];
2375 let cpu_ctxs_vec = skel
2376 .maps
2377 .cpu_ctxs
2378 .lookup_percpu(&key, libbpf_rs::MapFlags::ANY)
2379 .context("Failed to lookup cpu_ctx")?
2380 .unwrap();
2381
2382 let op_layers: Vec<u32> = layer_specs
2383 .iter()
2384 .enumerate()
2385 .filter(|(_idx, spec)| match &spec.kind {
2386 LayerKind::Open { .. } => spec.kind.common().preempt,
2387 _ => false,
2388 })
2389 .map(|(idx, _)| idx as u32)
2390 .collect();
2391 let on_layers: Vec<u32> = layer_specs
2392 .iter()
2393 .enumerate()
2394 .filter(|(_idx, spec)| match &spec.kind {
2395 LayerKind::Open { .. } => !spec.kind.common().preempt,
2396 _ => false,
2397 })
2398 .map(|(idx, _)| idx as u32)
2399 .collect();
2400 let gp_layers: Vec<u32> = layer_specs
2401 .iter()
2402 .enumerate()
2403 .filter(|(_idx, spec)| match &spec.kind {
2404 LayerKind::Grouped { .. } => spec.kind.common().preempt,
2405 _ => false,
2406 })
2407 .map(|(idx, _)| idx as u32)
2408 .collect();
2409 let gn_layers: Vec<u32> = layer_specs
2410 .iter()
2411 .enumerate()
2412 .filter(|(_idx, spec)| match &spec.kind {
2413 LayerKind::Grouped { .. } => !spec.kind.common().preempt,
2414 _ => false,
2415 })
2416 .map(|(idx, _)| idx as u32)
2417 .collect();
2418
2419 for cpu in 0..*NR_CPUS_POSSIBLE {
2421 cpu_ctxs.push(
2422 *plain::from_bytes(cpu_ctxs_vec[cpu].as_slice())
2423 .expect("cpu_ctx: short or misaligned buffer"),
2424 );
2425
2426 let topo_cpu = topo.all_cpus.get(&cpu).unwrap();
2427 let is_big = topo_cpu.core_type == CoreType::Big { turbo: true };
2428 cpu_ctxs[cpu].cpu = cpu as i32;
2429 cpu_ctxs[cpu].layer_id = MAX_LAYERS as u32;
2430 cpu_ctxs[cpu].is_big = is_big;
2431
2432 fastrand::seed(cpu as u64);
2433
2434 let mut ogp_order = op_layers.clone();
2435 ogp_order.append(&mut gp_layers.clone());
2436 fastrand::shuffle(&mut ogp_order);
2437
2438 let mut ogn_order = on_layers.clone();
2439 ogn_order.append(&mut gn_layers.clone());
2440 fastrand::shuffle(&mut ogn_order);
2441
2442 let mut op_order = op_layers.clone();
2443 fastrand::shuffle(&mut op_order);
2444
2445 let mut on_order = on_layers.clone();
2446 fastrand::shuffle(&mut on_order);
2447
2448 let mut gp_order = gp_layers.clone();
2449 fastrand::shuffle(&mut gp_order);
2450
2451 let mut gn_order = gn_layers.clone();
2452 fastrand::shuffle(&mut gn_order);
2453
2454 for i in 0..MAX_LAYERS {
2455 cpu_ctxs[cpu].ogp_layer_order[i] =
2456 ogp_order.get(i).cloned().unwrap_or(MAX_LAYERS as u32);
2457 cpu_ctxs[cpu].ogn_layer_order[i] =
2458 ogn_order.get(i).cloned().unwrap_or(MAX_LAYERS as u32);
2459
2460 cpu_ctxs[cpu].op_layer_order[i] =
2461 op_order.get(i).cloned().unwrap_or(MAX_LAYERS as u32);
2462 cpu_ctxs[cpu].on_layer_order[i] =
2463 on_order.get(i).cloned().unwrap_or(MAX_LAYERS as u32);
2464 cpu_ctxs[cpu].gp_layer_order[i] =
2465 gp_order.get(i).cloned().unwrap_or(MAX_LAYERS as u32);
2466 cpu_ctxs[cpu].gn_layer_order[i] =
2467 gn_order.get(i).cloned().unwrap_or(MAX_LAYERS as u32);
2468 }
2469 }
2470
2471 Self::init_cpu_prox_map(topo, &mut cpu_ctxs);
2472
2473 skel.maps
2474 .cpu_ctxs
2475 .update_percpu(
2476 &key,
2477 &Self::convert_cpu_ctxs(cpu_ctxs),
2478 libbpf_rs::MapFlags::ANY,
2479 )
2480 .context("Failed to update cpu_ctx")?;
2481
2482 Ok(())
2483 }
2484
2485 fn init_single_prox_map_per_llc(
2486 skel: &mut BpfSkel,
2487 topo: &Topology,
2488 prox_map_idx: &usize,
2489 ) -> Result<()> {
2490 for (&llc_id, llc) in &topo.all_llcs {
2491 let mut node_order: Vec<usize> =
2493 topo.nodes[&llc.node_id].llcs.keys().cloned().collect();
2494 let mut sys_order: Vec<usize> = topo.all_llcs.keys().cloned().collect();
2495
2496 sys_order.retain(|id| !node_order.contains(id));
2498 node_order.retain(|&id| id != llc_id);
2499
2500 fastrand::seed((*prox_map_idx as u64) << 32 | llc_id as u64);
2503 fastrand::shuffle(&mut sys_order);
2504 fastrand::shuffle(&mut node_order);
2505
2506 let mut order: Vec<usize> = vec![];
2508 let mut idx: usize = 0;
2509
2510 idx += 1;
2511 order.push(llc_id);
2512
2513 idx += node_order.len();
2514 order.append(&mut node_order);
2515 let node_end = idx;
2516
2517 idx += sys_order.len();
2518 order.append(&mut sys_order);
2519 let sys_end = idx;
2520
2521 debug!(
2522 "LLC[{}] proximity map {}[{}/{}]: {:?}",
2523 llc_id, prox_map_idx, node_end, sys_end, &order
2524 );
2525
2526 let key = (llc_id as u32).to_ne_bytes();
2531 let v = skel
2532 .maps
2533 .llc_data
2534 .lookup(&key, libbpf_rs::MapFlags::ANY)
2535 .unwrap()
2536 .unwrap();
2537 let mut llcc: bpf_intf::llc_ctx =
2538 *plain::from_bytes(v.as_slice()).expect("llc_ctx: short or misaligned buffer");
2539
2540 let pmap = &mut llcc.prox_maps[*prox_map_idx];
2541 for (i, &llc_id) in order.iter().enumerate() {
2542 pmap.llcs[i] = llc_id as u16;
2543 }
2544 pmap.node_end = node_end as u32;
2545 pmap.sys_end = sys_end as u32;
2546
2547 skel.maps.llc_data.update(
2548 &key,
2549 unsafe { plain::as_bytes(&llcc) },
2550 libbpf_rs::MapFlags::ANY,
2551 )?
2552 }
2553
2554 Ok(())
2555 }
2556
2557 fn init_llc_prox_map(skel: &mut BpfSkel, topo: &Topology) -> Result<()> {
2558 let num_proximity_maps = bpf_intf::consts_NUM_PROXIMITY_MAPS as usize;
2559 for prox_map_idx in 0..num_proximity_maps {
2560 Self::init_single_prox_map_per_llc(skel, topo, &prox_map_idx)?;
2561 }
2562
2563 Ok(())
2564 }
2565
2566 fn init_node_prox_map(skel: &mut BpfSkel, topo: &Topology) -> Result<()> {
2567 for (&node_id, node) in &topo.nodes {
2568 let mut order: Vec<(usize, usize)> = node
2569 .distance
2570 .iter()
2571 .enumerate()
2572 .filter(|&(nid, _)| nid != node_id && topo.nodes.contains_key(&nid))
2573 .map(|(nid, &dist)| (nid, dist))
2574 .collect();
2575 order.sort_by_key(|&(_, dist)| dist);
2576
2577 let key = (node_id as u32).to_ne_bytes();
2578
2579 let v = skel.maps.node_data.lookup(&key, libbpf_rs::MapFlags::ANY);
2581 let mut nodec: bpf_intf::node_ctx = match v {
2582 Ok(Some(v)) => {
2583 *plain::from_bytes(v.as_slice()).expect("node_ctx: short or misaligned buffer")
2584 }
2585 _ => unsafe { MaybeUninit::zeroed().assume_init() },
2586 };
2587
2588 let pmap = &mut nodec.prox_map;
2589 for (i, &(nid, _)) in order.iter().enumerate() {
2590 pmap.nodes[i] = nid as u16;
2591 }
2592 pmap.sys_end = order.len() as u32;
2593
2594 let order_vec: Vec<_> = order.iter().map(|(n, d)| (*n, *d)).collect();
2595 debug!(
2596 "NODE[{}] prox_map[{}]: {:?}",
2597 node_id, pmap.sys_end, order_vec
2598 );
2599
2600 skel.maps.node_data.update(
2601 &key,
2602 unsafe { plain::as_bytes(&nodec) },
2603 libbpf_rs::MapFlags::ANY,
2604 )?;
2605 }
2606 Ok(())
2607 }
2608
2609 fn init_node_ctx(skel: &mut BpfSkel, topo: &Topology, nr_layers: usize) -> Result<()> {
2610 let all_layers: Vec<u32> = (0..nr_layers as u32).collect();
2611 let node_empty_layers: Vec<Vec<u32>> =
2612 (0..topo.nodes.len()).map(|_| all_layers.clone()).collect();
2613 Self::refresh_node_ctx(skel, topo, &node_empty_layers, true);
2614 Ok(())
2615 }
2616
2617 fn init(
2618 opts: &'a Opts,
2619 layer_specs: &[LayerSpec],
2620 open_object: &'a mut MaybeUninit<OpenObject>,
2621 hint_to_layer_map: &HashMap<u64, HintLayerInfo>,
2622 membw_tracking: bool,
2623 ) -> Result<Self> {
2624 let nr_layers = layer_specs.len();
2625 let mut disable_topology = opts.disable_topology.unwrap_or(false);
2626
2627 let mut topo = if disable_topology {
2628 Topology::with_flattened_llc_node()?
2629 } else if opts.topology.virt_llc.is_some() {
2630 Topology::with_args(&opts.topology)?
2631 } else {
2632 Topology::new()?
2633 };
2634
2635 topo.split_cpuless_nodes();
2638 if topo.nodes.len() > bpf_intf::consts_MAX_NUMA_NODES as usize {
2639 bail!(
2640 "{} NUMA nodes exceed the maximum of {}",
2641 topo.nodes.len(),
2642 bpf_intf::consts_MAX_NUMA_NODES
2643 );
2644 }
2645 let topo = Arc::new(topo);
2646
2647 if topo.nodes.keys().enumerate().any(|(i, &k)| i != k) {
2654 bail!("Holes in node IDs detected: {:?}", topo.nodes.keys());
2655 }
2656 if topo.all_llcs.keys().enumerate().any(|(i, &k)| i != k) {
2657 bail!("Holes in LLC IDs detected: {:?}", topo.all_llcs.keys());
2658 }
2659 if topo.all_cpus.keys().enumerate().any(|(i, &k)| i != k) {
2660 bail!("Holes in CPU IDs detected: {:?}", topo.all_cpus.keys());
2661 }
2662
2663 let netdevs = if opts.netdev_irq_balance {
2664 let devs = read_netdevs()?;
2665 let total_irqs: usize = devs.values().map(|d| d.irqs.len()).sum();
2666 let breakdown = devs
2667 .iter()
2668 .map(|(iface, d)| format!("{iface}={}", d.irqs.len()))
2669 .collect::<Vec<_>>()
2670 .join(", ");
2671 info!(
2672 "Netdev IRQ balancing enabled: overriding {total_irqs} IRQ{} \
2673 across {} interface{} [{breakdown}]",
2674 if total_irqs == 1 { "" } else { "s" },
2675 devs.len(),
2676 if devs.len() == 1 { "" } else { "s" },
2677 );
2678 devs
2679 } else {
2680 BTreeMap::new()
2681 };
2682
2683 if !disable_topology {
2684 if topo.nodes.len() == 1 && topo.nodes[&0].llcs.len() == 1 {
2685 disable_topology = true;
2686 };
2687 info!(
2688 "Topology awareness not specified, selecting {} based on hardware",
2689 if disable_topology {
2690 "disabled"
2691 } else {
2692 "enabled"
2693 }
2694 );
2695 };
2696
2697 let cpu_pool = CpuPool::new(topo.clone(), opts.allow_partial_core)?;
2698
2699 let layer_specs: Vec<_> = if disable_topology {
2702 info!("Disabling topology awareness");
2703 layer_specs
2704 .iter()
2705 .cloned()
2706 .map(|mut s| {
2707 s.kind.common_mut().nodes.clear();
2708 s.kind.common_mut().llcs.clear();
2709 s
2710 })
2711 .collect()
2712 } else {
2713 layer_specs.to_vec()
2714 };
2715
2716 for spec in layer_specs.iter() {
2718 let mut seen = BTreeSet::new();
2719 for &node_id in spec.nodes().iter() {
2720 if !topo.nodes.contains_key(&node_id) {
2721 bail!(
2722 "layer {:?}: nodes references node {} which does not \
2723 exist in the topology (available: {:?})",
2724 spec.name,
2725 node_id,
2726 topo.nodes.keys().collect::<Vec<_>>()
2727 );
2728 }
2729 if !seen.insert(node_id) {
2730 bail!(
2731 "layer {:?}: nodes contains duplicate node {}",
2732 spec.name,
2733 node_id
2734 );
2735 }
2736 }
2737
2738 seen.clear();
2739 for &llc_id in spec.llcs().iter() {
2740 if !topo.all_llcs.contains_key(&llc_id) {
2741 bail!(
2742 "layer {:?}: llcs references LLC {} which does not \
2743 exist in the topology (available: {:?})",
2744 spec.name,
2745 llc_id,
2746 topo.all_llcs.keys().collect::<Vec<_>>()
2747 );
2748 }
2749 if !seen.insert(llc_id) {
2750 bail!(
2751 "layer {:?}: llcs contains duplicate LLC {}",
2752 spec.name,
2753 llc_id
2754 );
2755 }
2756 }
2757 }
2758
2759 for spec in layer_specs.iter() {
2760 let has_numa_node_match = spec
2761 .matches
2762 .iter()
2763 .flatten()
2764 .any(|m| matches!(m, LayerMatch::NumaNode(_)));
2765 let has_node_spread_algo = matches!(
2766 spec.kind.common().growth_algo,
2767 LayerGrowthAlgo::NodeSpread
2768 | LayerGrowthAlgo::NodeSpreadReverse
2769 | LayerGrowthAlgo::NodeSpreadRandom
2770 );
2771 if has_numa_node_match && has_node_spread_algo {
2772 bail!(
2773 "layer {:?}: NumaNode matcher cannot be combined with {:?} \
2774 growth algorithm. NodeSpread* allocates CPUs equally across \
2775 ALL NUMA nodes, but NumaNode restricts tasks to one node's \
2776 CPUs — CPUs on other nodes are wasted and utilization \
2777 will never exceed 1/numa_nodes. Use a non-spread algorithm \
2778 (e.g. Linear, Topo) instead.",
2779 spec.name,
2780 spec.kind.common().growth_algo
2781 );
2782 }
2783 }
2784
2785 init_libbpf_logging(None);
2787 let kfuncs_in_syscall = scx_bpf_compat::kfuncs_supported_in_syscall()?;
2788 if !kfuncs_in_syscall {
2789 warn!("Using slow path: kfuncs not supported in syscall programs (a8e03b6bbb2c ∉ ker)");
2790 }
2791
2792 let debug_level = if opts.log_level.contains("trace") {
2794 2
2795 } else if opts.log_level.contains("debug") {
2796 1
2797 } else {
2798 0
2799 };
2800 let mut skel_builder = BpfSkelBuilder::default();
2801 skel_builder.obj_builder.debug(debug_level > 1);
2802
2803 info!(
2804 "Running scx_layered (build ID: {})",
2805 build_id::full_version(env!("CARGO_PKG_VERSION"))
2806 );
2807 let open_opts = opts.libbpf.clone().into_bpf_open_opts();
2808 let mut skel = scx_ops_open!(skel_builder, open_object, layered, open_opts)?;
2809
2810 skel.progs.scx_pmu_switch_tc.set_autoload(membw_tracking);
2812 skel.progs.scx_pmu_tick_tc.set_autoload(membw_tracking);
2813
2814 let mut loaded_kprobes = HashSet::new();
2815
2816 if opts.enable_gpu_support {
2819 if opts.gpu_kprobe_level >= 1 {
2822 compat::cond_kprobe_load("nvidia_open", &skel.progs.kprobe_nvidia_open)?;
2823 loaded_kprobes.insert("nvidia_open");
2824 }
2825 if opts.gpu_kprobe_level >= 2 {
2828 compat::cond_kprobe_load("nvidia_mmap", &skel.progs.kprobe_nvidia_mmap)?;
2829 loaded_kprobes.insert("nvidia_mmap");
2830 }
2831 if opts.gpu_kprobe_level >= 3 {
2832 compat::cond_kprobe_load("nvidia_poll", &skel.progs.kprobe_nvidia_poll)?;
2833 loaded_kprobes.insert("nvidia_poll");
2834 }
2835 }
2836
2837 let ext_sched_class_addr = get_kallsyms_addr("ext_sched_class");
2838 let idle_sched_class_addr = get_kallsyms_addr("idle_sched_class");
2839
2840 let event = if membw_tracking {
2841 setup_membw_tracking(&mut skel)?
2842 } else {
2843 0
2844 };
2845
2846 let rodata = skel.maps.rodata_data.as_mut().unwrap();
2847
2848 if let (Ok(ext_addr), Ok(idle_addr)) = (ext_sched_class_addr, idle_sched_class_addr) {
2849 rodata.ext_sched_class_addr = ext_addr;
2850 rodata.idle_sched_class_addr = idle_addr;
2851 } else {
2852 warn!(
2853 "Unable to get sched_class addresses from /proc/kallsyms, disabling skip_preempt."
2854 );
2855 }
2856
2857 rodata.slice_ns = scx_enums.SCX_SLICE_DFL;
2858 rodata.max_exec_ns = 20 * scx_enums.SCX_SLICE_DFL;
2859
2860 skel.struct_ops.layered_mut().exit_dump_len = opts.exit_dump_len;
2862
2863 if !opts.disable_queued_wakeup {
2864 match *compat::SCX_OPS_ALLOW_QUEUED_WAKEUP {
2865 0 => info!("Kernel does not support queued wakeup optimization"),
2866 v => skel.struct_ops.layered_mut().flags |= v,
2867 }
2868 }
2869
2870 rodata.percpu_kthread_preempt = !opts.disable_percpu_kthread_preempt;
2871 rodata.percpu_kthread_preempt_all =
2872 !opts.disable_percpu_kthread_preempt && opts.percpu_kthread_preempt_all;
2873 rodata.debug = debug_level as u32;
2874 rodata.slice_ns = opts.slice_us * 1000;
2875 rodata.max_exec_ns = if opts.max_exec_us > 0 {
2876 opts.max_exec_us * 1000
2877 } else {
2878 opts.slice_us * 1000 * 20
2879 };
2880 rodata.nr_cpu_ids = *NR_CPU_IDS as u32;
2881 rodata.nr_possible_cpus = *NR_CPUS_POSSIBLE as u32;
2882 rodata.smt_enabled = topo.smt_enabled;
2883 rodata.has_little_cores = topo.has_little_cores();
2884 rodata.antistall_sec = opts.antistall_sec;
2885 rodata.monitor_disable = opts.monitor_disable;
2886 rodata.lo_fb_wait_ns = opts.lo_fb_wait_us * 1000;
2887 rodata.lo_fb_share_ppk = ((opts.lo_fb_share * 1024.0) as u32).clamp(1, 1024);
2888 rodata.enable_antistall = !opts.disable_antistall;
2889 rodata.enable_match_debug = opts.enable_match_debug;
2890 rodata.enable_gpu_support = opts.enable_gpu_support;
2891 rodata.kfuncs_supported_in_syscall = kfuncs_in_syscall;
2892
2893 for (cpu, sib) in topo.sibling_cpus().iter().enumerate() {
2894 rodata.__sibling_cpu[cpu] = *sib;
2895 }
2896 for cpu in topo.all_cpus.keys() {
2897 rodata.all_cpus[cpu / 8] |= 1 << (cpu % 8);
2898 }
2899
2900 rodata.nr_op_layers = layer_specs
2901 .iter()
2902 .filter(|spec| match &spec.kind {
2903 LayerKind::Open { .. } => spec.kind.common().preempt,
2904 _ => false,
2905 })
2906 .count() as u32;
2907 rodata.nr_on_layers = layer_specs
2908 .iter()
2909 .filter(|spec| match &spec.kind {
2910 LayerKind::Open { .. } => !spec.kind.common().preempt,
2911 _ => false,
2912 })
2913 .count() as u32;
2914 rodata.nr_gp_layers = layer_specs
2915 .iter()
2916 .filter(|spec| match &spec.kind {
2917 LayerKind::Grouped { .. } => spec.kind.common().preempt,
2918 _ => false,
2919 })
2920 .count() as u32;
2921 rodata.nr_gn_layers = layer_specs
2922 .iter()
2923 .filter(|spec| match &spec.kind {
2924 LayerKind::Grouped { .. } => !spec.kind.common().preempt,
2925 _ => false,
2926 })
2927 .count() as u32;
2928 rodata.nr_excl_layers = layer_specs
2929 .iter()
2930 .filter(|spec| spec.kind.common().exclusive)
2931 .count() as u32;
2932
2933 let mut min_open = u64::MAX;
2934 let mut min_preempt = u64::MAX;
2935
2936 for spec in layer_specs.iter() {
2937 if let LayerKind::Open { common, .. } = &spec.kind {
2938 min_open = min_open.min(common.disallow_open_after_us.unwrap());
2939 min_preempt = min_preempt.min(common.disallow_preempt_after_us.unwrap());
2940 }
2941 }
2942
2943 rodata.min_open_layer_disallow_open_after_ns = match min_open {
2944 u64::MAX => *DFL_DISALLOW_OPEN_AFTER_US,
2945 v => v,
2946 };
2947 rodata.min_open_layer_disallow_preempt_after_ns = match min_preempt {
2948 u64::MAX => *DFL_DISALLOW_PREEMPT_AFTER_US,
2949 v => v,
2950 };
2951
2952 let layered_task_hint_map_path = &opts.task_hint_map;
2957 let hint_map = &mut skel.maps.scx_layered_task_hint_map;
2958 if !layered_task_hint_map_path.is_empty() {
2960 hint_map.set_pin_path(layered_task_hint_map_path).unwrap();
2961 rodata.task_hint_map_enabled = true;
2962 }
2963
2964 if !opts.hi_fb_thread_name.is_empty() {
2965 let bpf_hi_fb_thread_name = &mut rodata.hi_fb_thread_name;
2966 copy_into_cstr(bpf_hi_fb_thread_name, opts.hi_fb_thread_name.as_str());
2967 rodata.enable_hi_fb_thread_name_match = true;
2968 }
2969
2970 let cgroup_regexes = Self::init_layers(&mut skel, &layer_specs, &topo)?;
2971 skel.maps.rodata_data.as_mut().unwrap().nr_cgroup_regexes = cgroup_regexes.len() as u32;
2972 Self::init_nodes(&mut skel, opts, &topo);
2973
2974 let mut skel = scx_ops_load!(skel, layered, uei)?;
2975
2976 if !hint_to_layer_map.is_empty() {
2978 for (k, v) in hint_to_layer_map.iter() {
2979 let key: u32 = *k as u32;
2980
2981 let mut info_bytes = vec![0u8; std::mem::size_of::<bpf_intf::hint_layer_info>()];
2983 let info_ptr = info_bytes.as_mut_ptr() as *mut bpf_intf::hint_layer_info;
2984 unsafe {
2985 (*info_ptr).layer_id = v.layer_id as u32;
2986 (*info_ptr).system_cpu_util_below = match v.system_cpu_util_below {
2987 Some(threshold) => (threshold * 10000.0) as u64,
2988 None => u64::MAX, };
2990 (*info_ptr).dsq_insert_below = match v.dsq_insert_below {
2991 Some(threshold) => (threshold * 10000.0) as u64,
2992 None => u64::MAX, };
2994 }
2995
2996 skel.maps.hint_to_layer_id_map.update(
2997 &key.to_ne_bytes(),
2998 &info_bytes,
2999 libbpf_rs::MapFlags::ANY,
3000 )?;
3001 }
3002 }
3003
3004 if membw_tracking {
3005 create_perf_fds(&mut skel, event)?;
3006 }
3007
3008 let mut layers = vec![];
3009 let layer_growth_orders =
3010 LayerGrowthAlgo::layer_core_orders(&cpu_pool, &layer_specs, &topo)?;
3011 for (idx, spec) in layer_specs.iter().enumerate() {
3012 let growth_order = layer_growth_orders
3013 .get(&idx)
3014 .with_context(|| "layer has no growth order".to_string())?;
3015 layers.push(Layer::new(spec, &topo, growth_order)?);
3016 }
3017
3018 let mut idle_qos_enabled = layers
3019 .iter()
3020 .any(|layer| layer.kind.common().idle_resume_us.unwrap_or(0) > 0);
3021 if idle_qos_enabled && !cpu_idle_resume_latency_supported() {
3022 warn!("idle_resume_us not supported, ignoring");
3023 idle_qos_enabled = false;
3024 }
3025
3026 Self::init_cpus(&skel, &layer_specs, &topo)?;
3027 Self::init_llc_prox_map(&mut skel, &topo)?;
3028 Self::init_node_prox_map(&mut skel, &topo)?;
3029 Self::init_node_ctx(&mut skel, &topo, nr_layers)?;
3030
3031 let proc_reader = fb_procfs::ProcReader::new();
3033
3034 let input = ProgramInput {
3036 ..Default::default()
3037 };
3038 let prog = &mut skel.progs.initialize_pid_namespace;
3039
3040 let _ = prog.test_run(input);
3041
3042 if !layered_task_hint_map_path.is_empty() {
3051 let path = CString::new(layered_task_hint_map_path.as_bytes()).unwrap();
3052 let mode: libc::mode_t = 0o666;
3053 unsafe {
3054 if libc::chmod(path.as_ptr(), mode) != 0 {
3055 trace!("'chmod' to 666 of task hint map failed, continuing...");
3056 }
3057 }
3058 }
3059
3060 let struct_ops = scx_ops_attach!(skel, layered)?;
3062
3063 if opts.enable_gpu_support {
3065 if loaded_kprobes.contains("nvidia_open") {
3066 compat::cond_kprobe_attach("nvidia_open", &skel.progs.kprobe_nvidia_open)?;
3067 }
3068 if loaded_kprobes.contains("nvidia_mmap") {
3069 compat::cond_kprobe_attach("nvidia_mmap", &skel.progs.kprobe_nvidia_mmap)?;
3070 }
3071 if loaded_kprobes.contains("nvidia_poll") {
3072 compat::cond_kprobe_attach("nvidia_poll", &skel.progs.kprobe_nvidia_poll)?;
3073 }
3074 }
3075
3076 let stats_server = StatsServer::new(stats::server_data()).launch()?;
3077 let mut gpu_task_handler =
3078 GpuTaskAffinitizer::new(opts.gpu_affinitize_secs, opts.enable_gpu_affinitize);
3079 gpu_task_handler.init(topo.clone());
3080
3081 let sched = Self {
3082 struct_ops: Some(struct_ops),
3083 layer_specs,
3084
3085 sched_intv: Duration::from_secs_f64(opts.interval),
3086 layer_refresh_intv: Duration::from_millis(opts.layer_refresh_ms_avgruntime),
3087
3088 cpu_pool,
3089 layers,
3090 idle_qos_enabled,
3091
3092 sched_stats: Stats::new(
3093 &mut skel,
3094 &proc_reader,
3095 topo.clone(),
3096 &gpu_task_handler,
3097 opts.util_compensation,
3098 )?,
3099 layer_peak_utils: vec![0.0; nr_layers],
3100
3101 cgroup_regexes: Some(cgroup_regexes),
3102 nr_layer_cpus_ranges: vec![(0, 0); nr_layers],
3103 xnuma_mig_src: vec![vec![false; topo.nodes.len()]; nr_layers],
3104 growth_denied: vec![vec![false; topo.nodes.len()]; nr_layers],
3105 processing_dur: Default::default(),
3106
3107 proc_reader,
3108 skel,
3109
3110 topo,
3111 netdevs,
3112 stats_server,
3113 gpu_task_handler,
3114 };
3115
3116 info!("Layered Scheduler Attached. Run `scx_layered --monitor` for metrics.");
3117
3118 Ok(sched)
3119 }
3120
3121 fn update_cpumask(mask: &Cpumask, bpfmask: &mut [u8]) {
3122 for cpu in 0..mask.len() {
3123 if mask.test_cpu(cpu) {
3124 bpfmask[cpu / 8] |= 1 << (cpu % 8);
3125 } else {
3126 bpfmask[cpu / 8] &= !(1 << (cpu % 8));
3127 }
3128 }
3129 }
3130
3131 fn update_bpf_layer_cpumask(layer: &Layer, bpf_layer: &mut types::layer) {
3132 trace!("[{}] Updating BPF CPUs: {}", layer.name, &layer.cpus);
3133 Self::update_cpumask(&layer.cpus, &mut bpf_layer.cpus);
3134
3135 bpf_layer.nr_cpus = layer.nr_cpus as u32;
3136 for (llc_id, &nr_llc_cpus) in layer.nr_llc_cpus.iter().enumerate() {
3137 bpf_layer.nr_llc_cpus[llc_id] = nr_llc_cpus as u32;
3138 }
3139 for (node_id, &nr_node_cpus) in layer.nr_node_cpus.iter().enumerate() {
3140 bpf_layer.node[node_id].nr_cpus = nr_node_cpus as u32;
3141 }
3142
3143 bpf_layer.refresh_cpus = 1;
3144 }
3145
3146 fn update_netdev_cpumasks(&mut self) -> Result<()> {
3147 let available_cpus = self.cpu_pool.available_cpus();
3148 if available_cpus.is_empty() {
3149 return Ok(());
3150 }
3151
3152 for (iface, netdev) in self.netdevs.iter_mut() {
3153 let node_cpus = match netdev.node() {
3155 Some(id) => self
3156 .topo
3157 .nodes
3158 .values()
3159 .find(|n| n.id == id)
3160 .ok_or_else(|| anyhow!("Failed to get netdev node"))?
3161 .span
3162 .clone(),
3163 None => self.topo.span.clone(),
3164 };
3165 for (irq, irqmask) in netdev.irqs.iter_mut() {
3166 irqmask.clear_all();
3167 for cpu in available_cpus.iter() {
3168 if !node_cpus.test_cpu(cpu) {
3169 continue;
3170 }
3171 let _ = irqmask.set_cpu(cpu);
3172 }
3173 if irqmask.weight() == 0 {
3175 for cpu in node_cpus.iter() {
3176 let _ = irqmask.set_cpu(cpu);
3177 }
3178 }
3179 trace!("{} updating irq {} cpumask {:?}", iface, irq, irqmask);
3180 }
3181 netdev.apply_cpumasks()?;
3182 debug!(
3183 "{iface}: applied affinity override to {} IRQ{}",
3184 netdev.irqs.len(),
3185 if netdev.irqs.len() == 1 { "" } else { "s" },
3186 );
3187 }
3188
3189 Ok(())
3190 }
3191
3192 fn clamp_target_by_membw(
3193 &self,
3194 layer: &Layer,
3195 membw_limit: f64,
3196 membw: f64,
3197 curtarget: u64,
3198 ) -> usize {
3199 let ncpu: u64 = layer.cpus.weight() as u64;
3200 let membw = (membw * 1024_f64.powf(3.0)).round() as u64;
3201 let membw_limit = (membw_limit * 1024_f64.powf(3.0)).round() as u64;
3202 let last_membw_percpu = membw.checked_div(ncpu).unwrap_or(0);
3203
3204 if membw_limit == 0 || last_membw_percpu == 0 {
3207 return curtarget as usize;
3208 }
3209
3210 (membw_limit / last_membw_percpu) as usize
3211 }
3212
3213 fn calc_raw_demands(&self, targets: &[(usize, usize)]) -> Vec<LayerDemand> {
3217 let au = self.cpu_pool.alloc_unit();
3218 let pinned_utils = &self.sched_stats.layer_node_pinned_utils;
3219 let nr_nodes = self.topo.nodes.len();
3220
3221 targets
3222 .iter()
3223 .enumerate()
3224 .map(|(idx, &(target, _min))| {
3225 let layer = &self.layers[idx];
3226 let weight = layer.kind.common().weight as usize;
3227
3228 if matches!(layer.kind, LayerKind::Open { .. }) {
3230 return LayerDemand {
3231 raw_pinned: vec![0; nr_nodes],
3232 raw_unpinned: 0,
3233 weight,
3234 spread: false,
3235 };
3236 }
3237
3238 let spread = matches!(
3243 layer.growth_algo,
3244 LayerGrowthAlgo::NodeSpread
3245 | LayerGrowthAlgo::NodeSpreadReverse
3246 | LayerGrowthAlgo::NodeSpreadRandom
3247 );
3248
3249 let util_high = match &layer.kind {
3250 LayerKind::Confined { util_range, .. }
3251 | LayerKind::Grouped { util_range, .. } => util_range.1,
3252 _ => 1.0,
3253 };
3254
3255 let mut raw_pinned = vec![0usize; nr_nodes];
3257 for n in 0..nr_nodes {
3258 let pu = pinned_utils[idx][n];
3259 if pu < 0.01 {
3260 continue;
3261 }
3262 let node_span = &self.topo.nodes[&n].span;
3264 if layer.allowed_cpus.and(node_span).is_empty() {
3265 continue;
3266 }
3267 let cpus = (pu / util_high).ceil() as usize;
3268 let units = cpus.div_ceil(au);
3270 raw_pinned[n] = units;
3271 }
3272
3273 let target_units = target.div_ceil(au);
3275 let pinned_units: usize = raw_pinned.iter().sum();
3276 let raw_unpinned = target_units.saturating_sub(pinned_units);
3277
3278 LayerDemand {
3279 raw_pinned,
3280 raw_unpinned,
3281 weight,
3282 spread,
3283 }
3284 })
3285 .collect()
3286 }
3287
3288 fn calc_target_nr_cpus(&mut self) -> Vec<(usize, usize)> {
3296 let nr_cpus = self.cpu_pool.topo.all_cpus.len();
3297 let membws = &self.sched_stats.layer_membws;
3298
3299 let mut records: Vec<(u64, u64, u64, u64, usize, usize, usize)> = vec![];
3300 let mut targets: Vec<(usize, usize)> = vec![];
3301
3302 for (idx, layer) in self.layers.iter().enumerate() {
3303 targets.push(match &layer.kind {
3304 LayerKind::Confined {
3305 util_range,
3306 cpus_range,
3307 cpus_range_frac,
3308 membw_gb,
3309 ..
3310 }
3311 | LayerKind::Grouped {
3312 util_range,
3313 cpus_range,
3314 cpus_range_frac,
3315 membw_gb,
3316 ..
3317 } => {
3318 let cpus_range =
3319 resolve_cpus_pct_range(cpus_range, cpus_range_frac, nr_cpus).unwrap();
3320
3321 let (owned, open) = {
3326 let utils = if self.sched_stats.util_compensation {
3327 &self.sched_stats.layer_utils_compensated[idx]
3328 } else {
3329 &self.sched_stats.layer_utils[idx]
3330 };
3331 (utils[LAYER_USAGE_OWNED], utils[LAYER_USAGE_OPEN])
3332 };
3333
3334 let membw_owned = membws[idx][LAYER_USAGE_OWNED];
3335 let membw_open = membws[idx][LAYER_USAGE_OPEN];
3336
3337 let mut util = owned;
3338 let mut membw = membw_owned;
3339 if layer.kind.util_includes_open_cputime() || layer.nr_cpus == 0 {
3340 util += open;
3341 membw += membw_open;
3342 }
3343
3344 let util = if util < 0.01 { 0.0 } else { util };
3345
3346 let peak_util = if layer.kind.common().util_peak_half_life_ms == 0 {
3347 util
3348 } else {
3349 update_peak_util(
3350 self.layer_peak_utils[idx],
3351 util,
3352 Duration::from_millis(layer.kind.common().util_peak_half_life_ms),
3353 self.sched_stats.elapsed,
3354 )
3355 };
3356 self.layer_peak_utils[idx] = peak_util;
3357 let (low, high) = calc_peak_aware_target_range(util, peak_util, *util_range);
3358
3359 let membw_limit = match membw_gb {
3360 Some(membw_limit) => *membw_limit,
3361 None => 0.0,
3362 };
3363
3364 trace!(
3365 "layer {0} (membw, membw_limit): ({membw} gi_b, {membw_limit} gi_b)",
3366 layer.name
3367 );
3368
3369 let target = layer.cpus.weight().clamp(low, high);
3370
3371 records.push((
3372 (owned * 100.0) as u64,
3373 (open * 100.0) as u64,
3374 (util * 100.0) as u64,
3375 (peak_util * 100.0) as u64,
3376 low,
3377 high,
3378 target,
3379 ));
3380
3381 let target = target.clamp(cpus_range.0, cpus_range.1);
3382 let membw_target =
3383 self.clamp_target_by_membw(layer, membw_limit, membw, target as u64);
3384
3385 trace!("CPU target pre- and post-membw adjustment: {target} -> {membw_target}");
3386
3387 if membw_target < cpus_range.0 {
3390 warn!("cannot satisfy memory bw limit for layer {}", layer.name);
3391 warn!("membw_target {membw_target} low {}", cpus_range.0);
3392 };
3393
3394 let target = membw_target.clamp(cpus_range.0, target);
3397
3398 (target, cpus_range.0)
3399 }
3400 LayerKind::Open { .. } => (0, 0),
3401 });
3402 }
3403
3404 trace!(
3405 "(owned, open, util, peak, low, high, target): {:?}",
3406 &records
3407 );
3408 targets
3409 }
3410
3411 fn compute_target_llcs(target: usize, node_llcs: &BTreeMap<usize, Arc<Llc>>) -> (usize, usize) {
3415 let mut remaining = target;
3416 let mut full = 0;
3417 for llc in node_llcs.values() {
3418 let cpus_in_llc = llc.span.weight();
3419 if remaining >= cpus_in_llc {
3420 full += 1;
3421 remaining -= cpus_in_llc;
3422 } else {
3423 let mut extra = 0;
3424 for core in llc.cores.values() {
3425 if remaining == 0 {
3426 break;
3427 }
3428 extra += 1;
3429 remaining = remaining.saturating_sub(core.cpus.len());
3430 }
3431 return (full, extra);
3432 }
3433 }
3434 (full, 0)
3435 }
3436
3437 fn recompute_layer_core_order(
3442 &mut self,
3443 layer_targets: &[(usize, usize)],
3444 layer_allocs: &[LayerAlloc],
3445 au: usize,
3446 ) -> Result<bool> {
3447 let nr_nodes = self.topo.nodes.len();
3448
3449 debug!(
3451 " free: before pass: free_llcs={:?}",
3452 self.cpu_pool.free_llcs
3453 );
3454 for &(idx, _) in layer_targets.iter().rev() {
3455 let layer = &mut self.layers[idx];
3456
3457 if layer.growth_algo != LayerGrowthAlgo::StickyDynamic {
3458 continue;
3459 }
3460
3461 let alloc = &layer_allocs[idx];
3462
3463 for n in 0..nr_nodes {
3464 let assigned_on_n = layer.assigned_llcs[n].len();
3465 let node = self.topo.nodes.get(&n).unwrap();
3466 let target_full_n =
3467 Self::compute_target_llcs(alloc.node_target(n) * au, &node.llcs).0;
3468 let mut to_free = assigned_on_n.saturating_sub(target_full_n);
3469
3470 debug!(
3471 " free: layer={} node={} assigned={} target_full={} to_free={}",
3472 layer.name, n, assigned_on_n, target_full_n, to_free,
3473 );
3474
3475 while to_free > 0 {
3476 if let Some(llc) = layer.assigned_llcs[n].pop() {
3477 self.cpu_pool.return_llc(llc);
3478 to_free -= 1;
3479 debug!(" layer={} freed_llc={} from node={}", layer.name, llc, n);
3480 } else {
3481 break;
3482 }
3483 }
3484 }
3485 }
3486 debug!(" free: after pass: free_llcs={:?}", self.cpu_pool.free_llcs);
3487
3488 for &(idx, _) in layer_targets.iter().rev() {
3490 let layer = &mut self.layers[idx];
3491
3492 if layer.growth_algo != LayerGrowthAlgo::StickyDynamic {
3493 continue;
3494 }
3495
3496 let alloc = &layer_allocs[idx];
3497
3498 for n in 0..nr_nodes {
3499 let cur_on_n = layer.assigned_llcs[n].len();
3500 let node = self.topo.nodes.get(&n).unwrap();
3501 let target_full_n =
3502 Self::compute_target_llcs(alloc.node_target(n) * au, &node.llcs).0;
3503 let mut to_alloc = target_full_n.saturating_sub(cur_on_n);
3504
3505 debug!(
3506 " alloc: layer={} node={} cur={} target_full={} to_alloc={} free={}",
3507 layer.name,
3508 n,
3509 cur_on_n,
3510 target_full_n,
3511 to_alloc,
3512 self.cpu_pool.free_llcs.get(&n).map_or(0, |v| v.len()),
3513 );
3514
3515 while to_alloc > 0 {
3516 if let Some(llc) = self.cpu_pool.take_llc_from_node(n) {
3517 layer.assigned_llcs[n].push(llc);
3518 to_alloc -= 1;
3519 debug!(" layer={} alloc_llc={} on node={}", layer.name, llc, n);
3520 } else {
3521 break;
3522 }
3523 }
3524 }
3525
3526 debug!(
3527 " alloc: layer={} assigned_llcs={:?}",
3528 layer.name, layer.assigned_llcs
3529 );
3530 }
3531
3532 for &(idx, _) in layer_targets.iter() {
3534 let layer = &mut self.layers[idx];
3535
3536 if layer.growth_algo != LayerGrowthAlgo::StickyDynamic {
3537 continue;
3538 }
3539
3540 layer.core_order = vec![Vec::new(); nr_nodes];
3541 let alloc = &layer_allocs[idx];
3542
3543 for n in 0..nr_nodes {
3544 let node = self.topo.nodes.get(&n).unwrap();
3545 let mut extra = Self::compute_target_llcs(alloc.node_target(n) * au, &node.llcs).1;
3546
3547 if let Some(node_llcs) = self.cpu_pool.free_llcs.get_mut(&n) {
3548 for entry in node_llcs.iter_mut() {
3549 if extra == 0 {
3550 break;
3551 }
3552 let llc_id = entry.0;
3553 let llc = self.topo.all_llcs.get(&llc_id).unwrap();
3554 let avail = llc.cores.len() - entry.1;
3555 let mut used = extra.min(avail);
3556 let cores_to_add = used;
3557
3558 let shift = entry.1;
3559 entry.1 += used;
3560
3561 for core in llc.cores.iter().skip(shift) {
3562 if used == 0 {
3563 break;
3564 }
3565 layer.core_order[n].push(core.1.id);
3566 used -= 1;
3567 }
3568
3569 extra -= cores_to_add;
3570 }
3571 }
3572 }
3573
3574 for node_cores in &mut layer.core_order {
3575 node_cores.reverse();
3576 }
3577 }
3578
3579 for node_llcs in self.cpu_pool.free_llcs.values_mut() {
3581 for entry in node_llcs.iter_mut() {
3582 entry.1 = 0;
3583 }
3584 }
3585
3586 for &(idx, _) in layer_targets.iter() {
3588 let layer = &mut self.layers[idx];
3589
3590 if layer.growth_algo != LayerGrowthAlgo::StickyDynamic {
3591 continue;
3592 }
3593
3594 let all_assigned: HashSet<usize> =
3595 layer.assigned_llcs.iter().flatten().copied().collect();
3596
3597 for core in self.topo.all_cores.iter() {
3598 let llc_id = core.1.llc_id;
3599 if all_assigned.contains(&llc_id) {
3600 let nid = core.1.node_id;
3601 layer.core_order[nid].push(core.1.id);
3602 }
3603 }
3604 for node_cores in &mut layer.core_order {
3605 node_cores.reverse();
3606 }
3607
3608 debug!(
3609 " alloc: layer={} core_order={:?}",
3610 layer.name, layer.core_order
3611 );
3612 }
3613
3614 let mut updated = false;
3617
3618 for &(idx, _) in layer_targets.iter() {
3620 let layer = &mut self.layers[idx];
3621
3622 if layer.growth_algo != LayerGrowthAlgo::StickyDynamic {
3623 continue;
3624 }
3625
3626 for n in 0..nr_nodes {
3627 let mut node_target = Cpumask::new();
3628 for &core_id in &layer.core_order[n] {
3629 if let Some(core) = self.topo.all_cores.get(&core_id) {
3630 node_target |= &core.span;
3631 }
3632 }
3633 node_target &= &layer.allowed_cpus;
3634
3635 let node_span = &self.topo.nodes[&n].span;
3636 let node_cur = layer.cpus.and(node_span);
3637 let cpus_to_free = node_cur.and(&node_target.not());
3638
3639 if cpus_to_free.weight() > 0 {
3640 debug!(
3641 " apply: layer={} freeing CPUs on node {}: {}",
3642 layer.name, n, cpus_to_free
3643 );
3644 layer.cpus &= &cpus_to_free.not();
3645 layer.nr_cpus -= cpus_to_free.weight();
3646 for cpu in cpus_to_free.iter() {
3647 layer.nr_llc_cpus[self.cpu_pool.topo.all_cpus[&cpu].llc_id] -= 1;
3648 layer.nr_node_cpus[n] -= 1;
3649 }
3650 self.cpu_pool.free(&cpus_to_free)?;
3651 updated = true;
3652 }
3653 }
3654 }
3655
3656 for &(idx, _) in layer_targets.iter() {
3658 let layer = &mut self.layers[idx];
3659
3660 if layer.growth_algo != LayerGrowthAlgo::StickyDynamic {
3661 continue;
3662 }
3663
3664 for n in 0..nr_nodes {
3665 let mut node_target = Cpumask::new();
3666 for &core_id in &layer.core_order[n] {
3667 if let Some(core) = self.topo.all_cores.get(&core_id) {
3668 node_target |= &core.span;
3669 }
3670 }
3671 node_target &= &layer.allowed_cpus;
3672
3673 let available_cpus = self.cpu_pool.available_cpus();
3674 let desired_to_alloc = node_target.and(&layer.cpus.clone().not());
3675 let cpus_to_alloc = desired_to_alloc.clone().and(&available_cpus);
3676
3677 if desired_to_alloc.weight() > cpus_to_alloc.weight() {
3678 debug!(
3679 " apply: layer={} node {} wanted to alloc {} CPUs but only {} available",
3680 layer.name,
3681 n,
3682 desired_to_alloc.weight(),
3683 cpus_to_alloc.weight()
3684 );
3685 }
3686
3687 if cpus_to_alloc.weight() > 0 {
3688 debug!(
3689 " apply: layer={} allocating CPUs on node {}: {}",
3690 layer.name, n, cpus_to_alloc
3691 );
3692 layer.cpus |= &cpus_to_alloc;
3693 layer.nr_cpus += cpus_to_alloc.weight();
3694 for cpu in cpus_to_alloc.iter() {
3695 layer.nr_llc_cpus[self.cpu_pool.topo.all_cpus[&cpu].llc_id] += 1;
3696 layer.nr_node_cpus[n] += 1;
3697 }
3698 self.cpu_pool.mark_allocated(&cpus_to_alloc)?;
3699 updated = true;
3700 }
3701 }
3702
3703 debug!(
3704 " apply: layer={} final cpus.weight()={} nr_cpus={}",
3705 layer.name,
3706 layer.cpus.weight(),
3707 layer.nr_cpus
3708 );
3709 }
3710
3711 Ok(updated)
3712 }
3713
3714 fn refresh_node_ctx(
3715 skel: &mut BpfSkel,
3716 topo: &Topology,
3717 node_empty_layers: &[Vec<u32>],
3718 init: bool,
3719 ) {
3720 for &nid in topo.nodes.keys() {
3721 let mut arg: bpf_intf::refresh_node_ctx_arg =
3722 unsafe { MaybeUninit::zeroed().assume_init() };
3723 arg.node_id = nid as u32;
3724 arg.init = init as u32;
3725
3726 let empty = &node_empty_layers[nid];
3727 arg.nr_empty_layer_ids = empty.len() as u32;
3728 for (i, &lid) in empty.iter().enumerate() {
3729 arg.empty_layer_ids[i] = lid;
3730 }
3731 for i in empty.len()..MAX_LAYERS {
3732 arg.empty_layer_ids[i] = MAX_LAYERS as u32;
3733 }
3734
3735 if init {
3736 let node = &topo.nodes[&nid];
3738 let llcs: Vec<u32> = node.llcs.keys().map(|&id| id as u32).collect();
3739 arg.nr_llcs = llcs.len() as u32;
3740 for (i, &llc_id) in llcs.iter().enumerate() {
3741 arg.llcs[i] = llc_id;
3742 }
3743 }
3744
3745 let input = ProgramInput {
3746 context_in: Some(unsafe { plain::as_mut_bytes(&mut arg) }),
3747 ..Default::default()
3748 };
3749 let _ = skel.progs.refresh_node_ctx.test_run(input);
3750 }
3751 }
3752
3753 fn refresh_cpumasks(&mut self) -> Result<()> {
3754 let layer_is_open = |layer: &Layer| matches!(layer.kind, LayerKind::Open { .. });
3755
3756 let mut updated = false;
3757 let raw_targets = self.calc_target_nr_cpus();
3758 let au = self.cpu_pool.alloc_unit();
3759 let total_cpus = self.cpu_pool.topo.all_cpus.len();
3760
3761 let targets: Vec<(usize, usize)> = raw_targets
3766 .iter()
3767 .enumerate()
3768 .map(|(idx, &(target, min))| {
3769 let cur = self.layers[idx].nr_cpus;
3770 if target < cur {
3771 let dampened = cur - (cur - target).div_ceil(2);
3772 (dampened.max(min), min)
3773 } else {
3774 (target, min)
3775 }
3776 })
3777 .collect();
3778
3779 let demands = self.calc_raw_demands(&targets);
3781 let nr_nodes = self.topo.nodes.len();
3782 let node_caps: Vec<usize> = self
3783 .topo
3784 .nodes
3785 .values()
3786 .map(|n| n.span.weight() / au)
3787 .collect();
3788 let all_layer_nodes: Vec<&[usize]> = self
3789 .layer_specs
3790 .iter()
3791 .map(|s| s.nodes().as_slice())
3792 .collect();
3793 let norders: Vec<Vec<usize>> = (0..self.layers.len())
3794 .map(|idx| {
3795 layer_core_growth::node_order(
3796 self.layer_specs[idx].nodes(),
3797 &self.topo,
3798 idx,
3799 &all_layer_nodes,
3800 )
3801 })
3802 .collect();
3803 let node_groups: Vec<Vec<Vec<usize>>> = (0..self.layers.len())
3804 .map(|idx| {
3805 layer_core_growth::node_groups(
3806 self.layer_specs[idx].nodes(),
3807 &self.topo,
3808 idx,
3809 &all_layer_nodes,
3810 &self.layer_specs[idx].kind.common().growth_algo,
3811 )
3812 })
3813 .collect();
3814 let layer_allocs = unified_alloc(total_cpus / au, &node_caps, &demands, &node_groups);
3815
3816 let cpu_targets: Vec<usize> = layer_allocs.iter().map(|a| a.total() * au).collect();
3819
3820 let prev_nr_cpus: Vec<usize> = self.layers.iter().map(|l| l.nr_cpus).collect();
3822
3823 let mut ascending: Vec<(usize, usize)> = cpu_targets.iter().copied().enumerate().collect();
3824 ascending.sort_by_key(|a| a.1);
3825
3826 let prev_node_cpus: Vec<Vec<usize>> =
3828 self.layers.iter().map(|l| l.nr_node_cpus.clone()).collect();
3829
3830 let use_sd_alloc = self.topo.all_llcs.len() > 1;
3834 let sticky_dynamic_updated = if use_sd_alloc {
3835 self.recompute_layer_core_order(&ascending, &layer_allocs, au)?
3836 } else {
3837 false
3838 };
3839 updated |= sticky_dynamic_updated;
3840
3841 if sticky_dynamic_updated {
3843 for (idx, layer) in self.layers.iter().enumerate() {
3844 if layer.growth_algo == LayerGrowthAlgo::StickyDynamic {
3845 Self::update_bpf_layer_cpumask(
3846 layer,
3847 &mut self.skel.maps.bss_data.as_mut().unwrap().layers[idx],
3848 );
3849 }
3850 }
3851 }
3852
3853 for &(idx, _target) in ascending.iter().rev() {
3855 let layer = &mut self.layers[idx];
3856 if layer_is_open(layer) {
3857 continue;
3858 }
3859
3860 if layer.growth_algo == LayerGrowthAlgo::StickyDynamic && use_sd_alloc {
3862 continue;
3863 }
3864
3865 let alloc = &layer_allocs[idx];
3866 let mut freed = false;
3867
3868 for n in 0..nr_nodes {
3869 let desired = alloc.node_target(n) * au;
3870 let mut to_free = layer.nr_node_cpus[n].saturating_sub(desired);
3871 let node_span = &self.topo.nodes[&n].span;
3872
3873 while to_free > 0 {
3874 let node_cands = layer.cpus.and(node_span);
3875 let cpus_to_free = match self
3876 .cpu_pool
3877 .next_to_free(&node_cands, layer.core_order[n].iter().rev())?
3878 {
3879 Some(ret) => ret,
3880 None => break,
3881 };
3882 let nr = cpus_to_free.weight();
3883 trace!(
3884 "[{}] freeing CPUs on node {}: {}",
3885 layer.name, n, &cpus_to_free
3886 );
3887 layer.cpus &= &cpus_to_free.not();
3888 layer.nr_cpus -= nr;
3889 for cpu in cpus_to_free.iter() {
3890 let node_id = self.cpu_pool.topo.all_cpus[&cpu].node_id;
3891 layer.nr_llc_cpus[self.cpu_pool.topo.all_cpus[&cpu].llc_id] -= 1;
3892 layer.nr_node_cpus[node_id] -= 1;
3893 layer.nr_pinned_cpus[node_id] =
3894 layer.nr_pinned_cpus[node_id].min(layer.nr_node_cpus[node_id]);
3895 }
3896 self.cpu_pool.free(&cpus_to_free)?;
3897 to_free = to_free.saturating_sub(nr);
3898 freed = true;
3899 }
3900 }
3901
3902 if freed {
3903 Self::update_bpf_layer_cpumask(
3904 layer,
3905 &mut self.skel.maps.bss_data.as_mut().unwrap().layers[idx],
3906 );
3907 updated = true;
3908 }
3909 }
3910
3911 for &(idx, _target) in &ascending {
3913 let layer = &mut self.layers[idx];
3914
3915 if layer_is_open(layer) {
3916 continue;
3917 }
3918
3919 if layer.growth_algo == LayerGrowthAlgo::StickyDynamic && use_sd_alloc {
3921 continue;
3922 }
3923
3924 let alloc = &layer_allocs[idx];
3925 let norder = &norders[idx];
3926 let mut alloced = false;
3927
3928 for &node_id in norder.iter() {
3929 let node_target = alloc.node_target(node_id) * au;
3930 let cur_node = layer.nr_node_cpus[node_id];
3931 if node_target <= cur_node {
3932 continue;
3933 }
3934 let pinned_target = alloc.pinned[node_id] * au;
3935 let mut nr_to_alloc = node_target - cur_node;
3936 let node_span = &self.topo.nodes[&node_id].span;
3937 let node_allowed = layer.allowed_cpus.and(node_span);
3938
3939 while nr_to_alloc > 0 {
3940 let nr_alloced = match self.cpu_pool.alloc_cpus(
3941 &node_allowed,
3942 &layer.core_order[node_id],
3943 nr_to_alloc,
3944 ) {
3945 Some(new_cpus) => {
3946 let nr = new_cpus.weight();
3947 layer.cpus |= &new_cpus;
3948 layer.nr_cpus += nr;
3949 for cpu in new_cpus.iter() {
3950 layer.nr_llc_cpus[self.cpu_pool.topo.all_cpus[&cpu].llc_id] += 1;
3951 let nid = self.cpu_pool.topo.all_cpus[&cpu].node_id;
3952 layer.nr_node_cpus[nid] += 1;
3953 if layer.nr_pinned_cpus[nid] < pinned_target {
3954 layer.nr_pinned_cpus[nid] += 1;
3955 }
3956 }
3957 nr
3958 }
3959 None => 0,
3960 };
3961 if nr_alloced == 0 {
3962 break;
3963 }
3964 alloced = true;
3965 nr_to_alloc -= nr_alloced.min(nr_to_alloc);
3966 }
3967 }
3968
3969 if alloced {
3970 Self::update_bpf_layer_cpumask(
3971 layer,
3972 &mut self.skel.maps.bss_data.as_mut().unwrap().layers[idx],
3973 );
3974 updated = true;
3975 }
3976 }
3977
3978 let total_allocated: usize = self.layers.iter().map(|l| l.nr_cpus).sum();
3983 let fully_allocated = total_allocated >= total_cpus;
3984 for (idx, layer) in self.layers.iter().enumerate() {
3985 if !layer_is_open(layer) {
3986 self.skel.maps.bss_data.as_mut().unwrap().layers[idx]
3987 .fully_allocated
3988 .write(fully_allocated);
3989 }
3990 }
3991
3992 let node_utils = &self.sched_stats.layer_node_utils;
3997 let pinned_utils = &self.sched_stats.layer_node_pinned_utils;
3998 for (idx, layer) in self.layers.iter().enumerate() {
3999 self.growth_denied[idx].fill(false);
4000 let util_high = match layer.kind.util_range() {
4001 Some((_, high)) => high,
4002 None => continue,
4003 };
4004 for n in 0..nr_nodes {
4005 let unpinned_util = (node_utils[idx][n] - pinned_utils[idx][n]).max(0.0);
4006 let unpinned_cpus_needed = unpinned_util / util_high;
4007 let unpinned_cpus_have =
4008 layer.nr_node_cpus[n].saturating_sub(layer.nr_pinned_cpus[n]) as f64;
4009 let wanted = unpinned_cpus_needed > unpinned_cpus_have;
4010 let got = layer.nr_node_cpus[n] > prev_node_cpus[idx][n];
4011 if wanted && !got {
4012 self.growth_denied[idx][n] = true;
4013 }
4014 }
4015 }
4016
4017 if updated {
4019 for (idx, layer) in self.layers.iter().enumerate() {
4020 if layer_is_open(layer) {
4021 continue;
4022 }
4023 let prev = prev_nr_cpus[idx];
4024 let cur = layer.nr_cpus;
4025 if prev != cur {
4026 debug!(
4027 "ALLOC {} algo={:?} cpus:{}→{} mask={:x}",
4028 layer.name, layer.growth_algo, prev, cur, layer.cpus,
4029 );
4030 }
4031 }
4032 debug!(
4033 "ALLOC pool_available={}",
4034 self.cpu_pool.available_cpus().weight()
4035 );
4036 }
4037
4038 if updated {
4040 let nr_nodes = self.topo.nodes.len();
4041 for (idx, layer) in self.layers.iter().enumerate() {
4042 if layer_is_open(layer) {
4043 continue;
4044 }
4045 let prev = &prev_node_cpus[idx];
4046 let cur = &layer.nr_node_cpus;
4047 if prev == cur {
4048 continue;
4049 }
4050 let per_node: String = (0..nr_nodes)
4051 .map(|n| format!("n{}:{}→{}", n, prev[n], cur[n]))
4052 .collect::<Vec<_>>()
4053 .join(" ");
4054 let prev_total: usize = prev.iter().sum();
4055 let cur_total: usize = cur[..nr_nodes].iter().sum();
4056 let target: String = (0..nr_nodes)
4057 .map(|n| format!("n{}:{}", n, layer_allocs[idx].node_target(n) * au))
4058 .collect::<Vec<_>>()
4059 .join(" ");
4060 debug!(
4061 "ALLOC {} algo={:?} {} total:{}→{} target:[{}] mask={:x}",
4062 layer.name,
4063 layer.growth_algo,
4064 per_node,
4065 prev_total,
4066 cur_total,
4067 target,
4068 layer.cpus,
4069 );
4070 }
4071 debug!(
4072 "ALLOC pool_available={}",
4073 self.cpu_pool.available_cpus().weight()
4074 );
4075 }
4076
4077 if updated {
4079 for (idx, layer) in self.layers.iter_mut().enumerate() {
4080 if !layer_is_open(layer) {
4081 continue;
4082 }
4083
4084 let bpf_layer = &mut self.skel.maps.bss_data.as_mut().unwrap().layers[idx];
4085 let available_cpus = self.cpu_pool.available_cpus().and(&layer.allowed_cpus);
4086 let nr_available_cpus = available_cpus.weight();
4087
4088 layer.cpus = available_cpus;
4092 layer.nr_cpus = nr_available_cpus;
4093 for llc in self.cpu_pool.topo.all_llcs.values() {
4094 layer.nr_llc_cpus[llc.id] = layer.cpus.and(&llc.span).weight();
4095 }
4096 for node in self.cpu_pool.topo.nodes.values() {
4097 layer.nr_node_cpus[node.id] = layer.cpus.and(&node.span).weight();
4098 layer.nr_pinned_cpus[node.id] = 0;
4099 }
4100 Self::update_bpf_layer_cpumask(layer, bpf_layer);
4101 }
4102
4103 for (&node_id, &cpu) in &self.cpu_pool.fallback_cpus {
4104 self.skel.maps.bss_data.as_mut().unwrap().fallback_cpus[node_id] = cpu as u32;
4105 }
4106
4107 for (lidx, layer) in self.layers.iter().enumerate() {
4108 self.nr_layer_cpus_ranges[lidx] = (
4109 self.nr_layer_cpus_ranges[lidx].0.min(layer.nr_cpus),
4110 self.nr_layer_cpus_ranges[lidx].1.max(layer.nr_cpus),
4111 );
4112 }
4113
4114 let input = ProgramInput {
4116 ..Default::default()
4117 };
4118 let prog = &mut self.skel.progs.refresh_layer_cpumasks;
4119 let _ = prog.test_run(input);
4120
4121 let nr_nodes = self.topo.nodes.len();
4123 let node_empty_layers: Vec<Vec<u32>> = (0..nr_nodes)
4124 .map(|nid| {
4125 self.layers
4126 .iter()
4127 .enumerate()
4128 .filter(|(_lidx, layer)| layer.nr_node_cpus[nid] == 0)
4129 .map(|(lidx, _)| lidx as u32)
4130 .collect()
4131 })
4132 .collect();
4133 Self::refresh_node_ctx(&mut self.skel, &self.topo, &node_empty_layers, false);
4134 }
4135
4136 if let Err(e) = self.update_netdev_cpumasks() {
4137 warn!("Failed to update netdev IRQ cpumasks: {:#}", e);
4138 }
4139 Ok(())
4140 }
4141
4142 fn refresh_idle_qos(&mut self) -> Result<()> {
4143 if !self.idle_qos_enabled {
4144 return Ok(());
4145 }
4146
4147 let mut cpu_idle_qos = vec![0; *NR_CPU_IDS];
4148 for layer in self.layers.iter() {
4149 let idle_resume_us = layer.kind.common().idle_resume_us.unwrap_or(0) as i32;
4150 for cpu in layer.cpus.iter() {
4151 cpu_idle_qos[cpu] = idle_resume_us;
4152 }
4153 }
4154
4155 for (cpu, idle_resume_usec) in cpu_idle_qos.iter().enumerate() {
4156 update_cpu_idle_resume_latency(cpu, *idle_resume_usec)?;
4157 }
4158
4159 Ok(())
4160 }
4161
4162 fn refresh_xnuma(&mut self) {
4163 let nr_nodes = self.topo.nodes.len();
4164 if nr_nodes <= 1 {
4165 return;
4166 }
4167
4168 let duty_sums = &self.sched_stats.layer_node_duty_sums;
4169
4170 for (layer_idx, spec) in self.layer_specs.iter().enumerate() {
4171 let common = spec.kind.common();
4172 let threshold = common.xnuma_threshold;
4173 let threshold_delta = common.xnuma_threshold_delta;
4174 let bpf_layer = &mut self.skel.maps.bss_data.as_mut().unwrap().layers[layer_idx];
4175
4176 if threshold.0 <= 0.0 && threshold.1 <= 0.0 {
4177 for src in 0..nr_nodes {
4179 bpf_layer.node[src].xnuma_is_mig_src.write(true);
4180 for dst in 0..nr_nodes {
4181 bpf_layer.node[src].xnuma[dst].rate = u64::MAX;
4182 }
4183 }
4184 self.xnuma_mig_src[layer_idx].fill(false);
4185 continue;
4186 }
4187
4188 let layer = &self.layers[layer_idx];
4189 let is_mig_src = xnuma_check_active(
4190 &duty_sums[layer_idx],
4191 &layer.nr_node_cpus,
4192 threshold,
4193 threshold_delta,
4194 &self.growth_denied[layer_idx],
4195 &self.xnuma_mig_src[layer_idx],
4196 );
4197
4198 self.xnuma_mig_src[layer_idx] = is_mig_src.clone();
4199
4200 let result = xnuma_compute_rates(&duty_sums[layer_idx], &layer.nr_node_cpus);
4201
4202 for src in 0..nr_nodes {
4205 for dst in 0..nr_nodes {
4206 bpf_layer.node[src].xnuma[dst].rate = result.rates[src][dst];
4207 }
4208 }
4209 for (nid, is_src) in is_mig_src.iter().enumerate().take(nr_nodes) {
4210 bpf_layer.node[nid].xnuma_is_mig_src.write(*is_src);
4211 }
4212 }
4213 }
4214
4215 fn step(&mut self) -> Result<()> {
4216 let started_at = Instant::now();
4217 self.sched_stats.refresh(
4218 &mut self.skel,
4219 &self.proc_reader,
4220 started_at,
4221 self.processing_dur,
4222 &self.gpu_task_handler,
4223 )?;
4224
4225 self.skel
4227 .maps
4228 .bss_data
4229 .as_mut()
4230 .unwrap()
4231 .system_cpu_util_ewma = (self.sched_stats.system_cpu_util_ewma * 10000.0) as u64;
4232
4233 for layer_id in 0..self.sched_stats.nr_layers {
4234 self.skel
4235 .maps
4236 .bss_data
4237 .as_mut()
4238 .unwrap()
4239 .layer_dsq_insert_ewma[layer_id] =
4240 (self.sched_stats.layer_dsq_insert_ewma[layer_id] * 10000.0) as u64;
4241 }
4242
4243 self.refresh_cpumasks()?;
4244 self.refresh_xnuma();
4245 self.refresh_idle_qos()?;
4246 self.gpu_task_handler.maybe_affinitize();
4247 self.processing_dur += Instant::now().duration_since(started_at);
4248 Ok(())
4249 }
4250
4251 fn generate_sys_stats(
4252 &mut self,
4253 stats: &Stats,
4254 cpus_ranges: &mut [(usize, usize)],
4255 ) -> Result<SysStats> {
4256 let bstats = &stats.bpf_stats;
4257 let mut sys_stats = SysStats::new(stats, bstats, &self.cpu_pool.fallback_cpus)?;
4258
4259 for (lidx, (spec, layer)) in self.layer_specs.iter().zip(self.layers.iter()).enumerate() {
4260 let layer_stats = LayerStats::new(
4261 lidx,
4262 layer,
4263 stats,
4264 bstats,
4265 cpus_ranges[lidx],
4266 self.xnuma_mig_src[lidx].iter().any(|&a| a),
4267 );
4268 sys_stats.layers.insert(spec.name.to_string(), layer_stats);
4269 cpus_ranges[lidx] = (layer.nr_cpus, layer.nr_cpus);
4270 }
4271
4272 Ok(sys_stats)
4273 }
4274
4275 fn process_cgroup_creation(
4277 path: &Path,
4278 cgroup_regexes: &HashMap<u32, Regex>,
4279 cgroup_path_to_id: &mut HashMap<String, u64>,
4280 sender: &crossbeam::channel::Sender<CgroupEvent>,
4281 ) {
4282 let path_str = path.to_string_lossy().to_string();
4283
4284 let cgroup_id = std::fs::metadata(path)
4286 .map(|metadata| {
4287 use std::os::unix::fs::MetadataExt;
4288 metadata.ino()
4289 })
4290 .unwrap_or(0);
4291
4292 let mut match_bitmap = 0u64;
4294 for (rule_id, regex) in cgroup_regexes {
4295 if regex.is_match(&path_str) {
4296 match_bitmap |= 1u64 << rule_id;
4297 }
4298 }
4299
4300 cgroup_path_to_id.insert(path_str.clone(), cgroup_id);
4302
4303 if let Err(e) = sender.send(CgroupEvent::Created {
4305 path: path_str,
4306 cgroup_id,
4307 match_bitmap,
4308 }) {
4309 error!("Failed to send cgroup creation event: {}", e);
4310 }
4311 }
4312
4313 fn start_cgroup_watcher(
4314 shutdown: Arc<AtomicBool>,
4315 cgroup_regexes: HashMap<u32, Regex>,
4316 ) -> Result<Receiver<CgroupEvent>> {
4317 let mut inotify = Inotify::init().context("Failed to initialize inotify")?;
4318 let mut wd_to_path = HashMap::new();
4319
4320 let (sender, receiver) = crossbeam::channel::bounded::<CgroupEvent>(1024);
4322
4323 let root_wd = inotify
4325 .watches()
4326 .add("/sys/fs/cgroup", WatchMask::CREATE | WatchMask::DELETE)
4327 .context("Failed to add watch for /sys/fs/cgroup")?;
4328 wd_to_path.insert(root_wd, PathBuf::from("/sys/fs/cgroup"));
4329
4330 Self::add_recursive_watches(&mut inotify, &mut wd_to_path, Path::new("/sys/fs/cgroup"))?;
4332
4333 std::thread::spawn(move || {
4335 let mut buffer = [0; 4096];
4336 let inotify_fd = inotify.as_raw_fd();
4337 let mut cgroup_path_to_id = HashMap::<String, u64>::new();
4339
4340 for entry in WalkDir::new("/sys/fs/cgroup")
4342 .into_iter()
4343 .filter_map(|e| e.ok())
4344 .filter(|e| e.file_type().is_dir())
4345 {
4346 let path = entry.path();
4347 Self::process_cgroup_creation(
4348 path,
4349 &cgroup_regexes,
4350 &mut cgroup_path_to_id,
4351 &sender,
4352 );
4353 }
4354
4355 while !shutdown.load(Ordering::Relaxed) {
4356 let ready = unsafe {
4358 let mut read_fds: libc::fd_set = std::mem::zeroed();
4359 libc::FD_ZERO(&mut read_fds);
4360 libc::FD_SET(inotify_fd, &mut read_fds);
4361
4362 let mut timeout = libc::timeval {
4363 tv_sec: 0,
4364 tv_usec: 100_000, };
4366
4367 libc::select(
4368 inotify_fd + 1,
4369 &mut read_fds,
4370 std::ptr::null_mut(),
4371 std::ptr::null_mut(),
4372 &mut timeout,
4373 )
4374 };
4375
4376 if ready <= 0 {
4377 continue;
4379 }
4380
4381 let events = match inotify.read_events(&mut buffer) {
4383 Ok(events) => events,
4384 Err(e) => {
4385 error!("Error reading inotify events: {}", e);
4386 break;
4387 }
4388 };
4389
4390 for event in events {
4391 if !event.mask.contains(inotify::EventMask::CREATE)
4392 && !event.mask.contains(inotify::EventMask::DELETE)
4393 {
4394 continue;
4395 }
4396
4397 let name = match event.name {
4398 Some(name) => name,
4399 None => continue,
4400 };
4401
4402 let parent_path = match wd_to_path.get(&event.wd) {
4403 Some(parent) => parent,
4404 None => {
4405 warn!("Unknown watch descriptor: {:?}", event.wd);
4406 continue;
4407 }
4408 };
4409
4410 let path = parent_path.join(name.to_string_lossy().as_ref());
4411
4412 if event.mask.contains(inotify::EventMask::CREATE) {
4413 if !path.is_dir() {
4414 continue;
4415 }
4416
4417 Self::process_cgroup_creation(
4418 &path,
4419 &cgroup_regexes,
4420 &mut cgroup_path_to_id,
4421 &sender,
4422 );
4423
4424 match inotify
4426 .watches()
4427 .add(&path, WatchMask::CREATE | WatchMask::DELETE)
4428 {
4429 Ok(wd) => {
4430 wd_to_path.insert(wd, path.clone());
4431 }
4432 Err(e) => {
4433 warn!(
4434 "Failed to add watch for new cgroup {}: {}",
4435 path.display(),
4436 e
4437 );
4438 }
4439 }
4440 } else if event.mask.contains(inotify::EventMask::DELETE) {
4441 let path_str = path.to_string_lossy().to_string();
4442
4443 let cgroup_id = cgroup_path_to_id.remove(&path_str).unwrap_or(0);
4445
4446 if let Err(e) = sender.send(CgroupEvent::Removed {
4448 path: path_str,
4449 cgroup_id,
4450 }) {
4451 error!("Failed to send cgroup removal event: {}", e);
4452 }
4453
4454 let wd_to_remove = wd_to_path.iter().find_map(|(wd, watched_path)| {
4456 if watched_path == &path {
4457 Some(wd.clone())
4458 } else {
4459 None
4460 }
4461 });
4462 if let Some(wd) = wd_to_remove {
4463 wd_to_path.remove(&wd);
4464 }
4465 }
4466 }
4467 }
4468 });
4469
4470 Ok(receiver)
4471 }
4472
4473 fn add_recursive_watches(
4474 inotify: &mut Inotify,
4475 wd_to_path: &mut HashMap<inotify::WatchDescriptor, PathBuf>,
4476 path: &Path,
4477 ) -> Result<()> {
4478 for entry in WalkDir::new(path)
4479 .into_iter()
4480 .filter_map(|e| e.ok())
4481 .filter(|e| e.file_type().is_dir())
4482 .skip(1)
4483 {
4484 let entry_path = entry.path();
4485 match inotify
4487 .watches()
4488 .add(entry_path, WatchMask::CREATE | WatchMask::DELETE)
4489 {
4490 Ok(wd) => {
4491 wd_to_path.insert(wd, entry_path.to_path_buf());
4492 }
4493 Err(e) => {
4494 debug!("Failed to add watch for {}: {}", entry_path.display(), e);
4495 }
4496 }
4497 }
4498 Ok(())
4499 }
4500
4501 fn run(&mut self, shutdown: Arc<AtomicBool>) -> Result<UserExitInfo> {
4502 let (res_ch, req_ch) = self.stats_server.channels();
4503 let mut next_sched_at = Instant::now() + self.sched_intv;
4504 let enable_layer_refresh = !self.layer_refresh_intv.is_zero();
4505 let mut next_layer_refresh_at = Instant::now() + self.layer_refresh_intv;
4506 let mut cpus_ranges = HashMap::<ThreadId, Vec<(usize, usize)>>::new();
4507
4508 let cgroup_regexes = self.cgroup_regexes.take().unwrap();
4510 let cgroup_event_rx = if !cgroup_regexes.is_empty() {
4511 Some(Self::start_cgroup_watcher(
4512 shutdown.clone(),
4513 cgroup_regexes,
4514 )?)
4515 } else {
4516 None
4517 };
4518
4519 while !shutdown.load(Ordering::Relaxed) && !uei_exited!(&self.skel, uei) {
4520 let now = Instant::now();
4521
4522 if now >= next_sched_at {
4523 self.step()?;
4524 while next_sched_at < now {
4525 next_sched_at += self.sched_intv;
4526 }
4527 }
4528
4529 if enable_layer_refresh && now >= next_layer_refresh_at {
4530 self.skel
4531 .maps
4532 .bss_data
4533 .as_mut()
4534 .unwrap()
4535 .layer_refresh_seq_avgruntime += 1;
4536 while next_layer_refresh_at < now {
4537 next_layer_refresh_at += self.layer_refresh_intv;
4538 }
4539 }
4540
4541 let timeout_duration = next_sched_at.saturating_duration_since(Instant::now());
4543 let never_rx = crossbeam::channel::never();
4544 let cgroup_rx = cgroup_event_rx.as_ref().unwrap_or(&never_rx);
4545
4546 select! {
4547 recv(req_ch) -> msg => match msg {
4548 Ok(StatsReq::Hello(tid)) => {
4549 cpus_ranges.insert(
4550 tid,
4551 self.layers.iter().map(|l| (l.nr_cpus, l.nr_cpus)).collect(),
4552 );
4553 let stats =
4554 Stats::new(&mut self.skel, &self.proc_reader, self.topo.clone(), &self.gpu_task_handler, self.sched_stats.util_compensation)?;
4555 res_ch.send(StatsRes::Hello(Box::new(stats)))?;
4556 }
4557 Ok(StatsReq::Refresh(tid, mut stats)) => {
4558 for i in 0..self.nr_layer_cpus_ranges.len() {
4560 for ranges in cpus_ranges.values_mut() {
4561 ranges[i] = (
4562 ranges[i].0.min(self.nr_layer_cpus_ranges[i].0),
4563 ranges[i].1.max(self.nr_layer_cpus_ranges[i].1),
4564 );
4565 }
4566 self.nr_layer_cpus_ranges[i] =
4567 (self.layers[i].nr_cpus, self.layers[i].nr_cpus);
4568 }
4569
4570 stats.refresh(
4571 &mut self.skel,
4572 &self.proc_reader,
4573 now,
4574 self.processing_dur,
4575 &self.gpu_task_handler,
4576 )?;
4577 let sys_stats =
4578 self.generate_sys_stats(&stats, cpus_ranges.get_mut(&tid).unwrap())?;
4579 res_ch.send(StatsRes::Refreshed(Box::new((*stats, sys_stats))))?;
4580 }
4581 Ok(StatsReq::Bye(tid)) => {
4582 cpus_ranges.remove(&tid);
4583 res_ch.send(StatsRes::Bye)?;
4584 }
4585 Err(e) => Err(e)?,
4586 },
4587
4588 recv(cgroup_rx) -> event => match event {
4589 Ok(CgroupEvent::Created { path, cgroup_id, match_bitmap }) => {
4590 self.skel.maps.cgroup_match_bitmap.update(
4592 &cgroup_id.to_ne_bytes(),
4593 &match_bitmap.to_ne_bytes(),
4594 libbpf_rs::MapFlags::ANY,
4595 ).with_context(|| format!(
4596 "Failed to insert cgroup {}({}) into BPF map. Cgroup map may be full \
4597 (max 16384 entries). Aborting.",
4598 cgroup_id, path
4599 ))?;
4600
4601 debug!("Added cgroup {} to BPF map with bitmap 0x{:x}", cgroup_id, match_bitmap);
4602 }
4603 Ok(CgroupEvent::Removed { path, cgroup_id }) => {
4604 if let Err(e) = self.skel.maps.cgroup_match_bitmap.delete(&cgroup_id.to_ne_bytes()) {
4606 warn!("Failed to delete cgroup {} from BPF map: {}", cgroup_id, e);
4607 } else {
4608 debug!("Removed cgroup {}({}) from BPF map", cgroup_id, path);
4609 }
4610 }
4611 Err(e) => {
4612 error!("Error receiving cgroup event: {}", e);
4613 }
4614 },
4615
4616 recv(crossbeam::channel::after(timeout_duration)) -> _ => {
4617 }
4619 }
4620 }
4621
4622 let _ = self.struct_ops.take();
4623 uei_report!(&self.skel, uei)
4624 }
4625}
4626
4627impl Drop for Scheduler<'_> {
4628 fn drop(&mut self) {
4629 info!("Unregister {SCHEDULER_NAME} scheduler");
4630
4631 if !self.netdevs.is_empty() {
4632 for (iface, netdev) in &self.netdevs {
4633 if let Err(e) = netdev.restore_cpumasks() {
4634 warn!("Failed to restore {iface} IRQ affinity: {e}");
4635 }
4636 }
4637 info!("Restored original netdev IRQ affinity");
4638 }
4639
4640 if let Some(struct_ops) = self.struct_ops.take() {
4641 drop(struct_ops);
4642 }
4643 }
4644}
4645
4646fn write_example_file(path: &str) -> Result<()> {
4647 let mut f = fs::OpenOptions::new()
4648 .create_new(true)
4649 .write(true)
4650 .open(path)?;
4651 Ok(f.write_all(serde_json::to_string_pretty(&*EXAMPLE_CONFIG)?.as_bytes())?)
4652}
4653
4654struct HintLayerInfo {
4655 layer_id: usize,
4656 system_cpu_util_below: Option<f64>,
4657 dsq_insert_below: Option<f64>,
4658}
4659
4660fn verify_layer_specs(specs: &[LayerSpec]) -> Result<HashMap<u64, HintLayerInfo>> {
4661 let mut hint_to_layer_map = HashMap::<u64, (usize, String, Option<f64>, Option<f64>)>::new();
4662
4663 let nr_specs = specs.len();
4664 if nr_specs == 0 {
4665 bail!("No layer spec");
4666 }
4667 if nr_specs > MAX_LAYERS {
4668 bail!("Too many layer specs");
4669 }
4670
4671 for (idx, spec) in specs.iter().enumerate() {
4672 if idx < nr_specs - 1 {
4673 if spec.matches.is_empty() {
4674 bail!("Non-terminal spec {:?} has NULL matches", spec.name);
4675 }
4676 } else if spec.matches.len() != 1 || !spec.matches[0].is_empty() {
4677 bail!("Terminal spec {:?} must have an empty match", spec.name);
4678 }
4679
4680 if spec.matches.len() > MAX_LAYER_MATCH_ORS {
4681 bail!(
4682 "Spec {:?} has too many ({}) OR match blocks",
4683 spec.name,
4684 spec.matches.len()
4685 );
4686 }
4687
4688 for (ands_idx, ands) in spec.matches.iter().enumerate() {
4689 if ands.len() > NR_LAYER_MATCH_KINDS {
4690 bail!(
4691 "Spec {:?}'s {}th OR block has too many ({}) match conditions",
4692 spec.name,
4693 ands_idx,
4694 ands.len()
4695 );
4696 }
4697 let mut hint_equals_cnt = 0;
4698 let mut system_cpu_util_below_cnt = 0;
4699 let mut dsq_insert_below_cnt = 0;
4700 let mut hint_value: Option<u64> = None;
4701 let mut system_cpu_util_threshold: Option<f64> = None;
4702 let mut dsq_insert_threshold: Option<f64> = None;
4703 for one in ands.iter() {
4704 match one {
4705 LayerMatch::CgroupPrefix(prefix) => {
4706 if prefix.len() > MAX_PATH {
4707 bail!("Spec {:?} has too long a cgroup prefix", spec.name);
4708 }
4709 }
4710 LayerMatch::CgroupSuffix(suffix) => {
4711 if suffix.len() > MAX_PATH {
4712 bail!("Spec {:?} has too long a cgroup suffix", spec.name);
4713 }
4714 }
4715 LayerMatch::CgroupContains(substr) => {
4716 if substr.len() > MAX_PATH {
4717 bail!("Spec {:?} has too long a cgroup substr", spec.name);
4718 }
4719 }
4720 LayerMatch::CommPrefix(prefix) => {
4721 if prefix.len() > MAX_COMM {
4722 bail!("Spec {:?} has too long a comm prefix", spec.name);
4723 }
4724 }
4725 LayerMatch::PcommPrefix(prefix) => {
4726 if prefix.len() > MAX_COMM {
4727 bail!("Spec {:?} has too long a process name prefix", spec.name);
4728 }
4729 }
4730 LayerMatch::SystemCpuUtilBelow(threshold) => {
4731 if *threshold < 0.0 || *threshold > 1.0 {
4732 bail!(
4733 "Spec {:?} has SystemCpuUtilBelow threshold outside the range [0.0, 1.0]",
4734 spec.name
4735 );
4736 }
4737 system_cpu_util_threshold = Some(*threshold);
4738 system_cpu_util_below_cnt += 1;
4739 }
4740 LayerMatch::DsqInsertBelow(threshold) => {
4741 if *threshold < 0.0 || *threshold > 1.0 {
4742 bail!(
4743 "Spec {:?} has DsqInsertBelow threshold outside the range [0.0, 1.0]",
4744 spec.name
4745 );
4746 }
4747 dsq_insert_threshold = Some(*threshold);
4748 dsq_insert_below_cnt += 1;
4749 }
4750 LayerMatch::HintEquals(hint) => {
4751 if *hint > 1024 {
4752 bail!(
4753 "Spec {:?} has hint value outside the range [0, 1024]",
4754 spec.name
4755 );
4756 }
4757 hint_value = Some(*hint);
4758 hint_equals_cnt += 1;
4759 }
4760 _ => {}
4761 }
4762 }
4763 if hint_equals_cnt > 1 {
4764 bail!("Only 1 HintEquals match permitted per AND block");
4765 }
4766 let high_freq_matcher_cnt = system_cpu_util_below_cnt + dsq_insert_below_cnt;
4767 if high_freq_matcher_cnt > 0 {
4768 if hint_equals_cnt != 1 {
4769 bail!(
4770 "High-frequency matchers (SystemCpuUtilBelow, DsqInsertBelow) must be used with one HintEquals"
4771 );
4772 }
4773 if system_cpu_util_below_cnt > 1 {
4774 bail!("Only 1 SystemCpuUtilBelow match permitted per AND block");
4775 }
4776 if dsq_insert_below_cnt > 1 {
4777 bail!("Only 1 DsqInsertBelow match permitted per AND block");
4778 }
4779 if ands.len() != hint_equals_cnt + system_cpu_util_below_cnt + dsq_insert_below_cnt
4780 {
4781 bail!(
4782 "High-frequency matchers must be used only with HintEquals (no other matchers)"
4783 );
4784 }
4785 } else if hint_equals_cnt == 1 && ands.len() != 1 {
4786 bail!("HintEquals match cannot be in conjunction with other matches");
4787 }
4788
4789 if let Some(hint) = hint_value {
4791 if let Some((layer_id, name, _, _)) = hint_to_layer_map.get(&hint) {
4792 if *layer_id != idx {
4793 bail!(
4794 "Spec {:?} has hint value ({}) that is already mapped to Spec {:?}",
4795 spec.name,
4796 hint,
4797 name
4798 );
4799 }
4800 } else {
4801 hint_to_layer_map.insert(
4802 hint,
4803 (
4804 idx,
4805 spec.name.clone(),
4806 system_cpu_util_threshold,
4807 dsq_insert_threshold,
4808 ),
4809 );
4810 }
4811 }
4812 }
4813
4814 match spec.kind {
4815 LayerKind::Confined {
4816 cpus_range,
4817 util_range,
4818 ..
4819 }
4820 | LayerKind::Grouped {
4821 cpus_range,
4822 util_range,
4823 ..
4824 } => {
4825 if let Some((cpus_min, cpus_max)) = cpus_range
4826 && cpus_min > cpus_max
4827 {
4828 bail!(
4829 "Spec {:?} has invalid cpus_range({}, {})",
4830 spec.name,
4831 cpus_min,
4832 cpus_max
4833 );
4834 }
4835 if util_range.0 >= util_range.1 {
4836 bail!(
4837 "Spec {:?} has invalid util_range ({}, {})",
4838 spec.name,
4839 util_range.0,
4840 util_range.1
4841 );
4842 }
4843 }
4844 _ => {}
4845 }
4846 }
4847
4848 Ok(hint_to_layer_map
4849 .into_iter()
4850 .map(|(k, v)| {
4851 (
4852 k,
4853 HintLayerInfo {
4854 layer_id: v.0,
4855 system_cpu_util_below: v.2,
4856 dsq_insert_below: v.3,
4857 },
4858 )
4859 })
4860 .collect())
4861}
4862
4863fn name_suffix(cgroup: &str, len: usize) -> String {
4864 let suffixlen = std::cmp::min(len, cgroup.len());
4865 let suffixrev: String = cgroup.chars().rev().take(suffixlen).collect();
4866
4867 suffixrev.chars().rev().collect()
4868}
4869
4870fn traverse_sysfs(dir: &Path) -> Result<Vec<PathBuf>> {
4871 let mut paths = vec![];
4872
4873 if !dir.is_dir() {
4874 panic!("path {:?} does not correspond to directory", dir);
4875 }
4876
4877 let direntries = fs::read_dir(dir)?;
4878
4879 for entry in direntries {
4880 let path = entry?.path();
4881 if path.is_dir() {
4882 paths.append(&mut traverse_sysfs(&path)?);
4883 paths.push(path);
4884 }
4885 }
4886
4887 Ok(paths)
4888}
4889
4890fn find_cpumask(cgroup: &str) -> Cpumask {
4891 let mut path = String::from(cgroup);
4892 path.push_str("/cpuset.cpus.effective");
4893
4894 let description = fs::read_to_string(&mut path).unwrap();
4895
4896 Cpumask::from_cpulist(&description).unwrap()
4897}
4898
4899fn expand_template(rule: &LayerMatch) -> Result<Vec<(LayerMatch, Cpumask)>> {
4900 match rule {
4901 LayerMatch::CgroupSuffix(suffix) => Ok(traverse_sysfs(Path::new("/sys/fs/cgroup"))?
4902 .into_iter()
4903 .map(|cgroup| String::from(cgroup.to_str().expect("could not parse cgroup path")))
4904 .filter(|cgroup| cgroup.ends_with(suffix))
4905 .map(|cgroup| {
4906 (
4907 {
4908 let mut slashterminated = cgroup.clone();
4909 slashterminated.push('/');
4910 LayerMatch::CgroupSuffix(name_suffix(&slashterminated, 64))
4911 },
4912 find_cpumask(&cgroup),
4913 )
4914 })
4915 .collect()),
4916 LayerMatch::CgroupRegex(expr) => Ok(traverse_sysfs(Path::new("/sys/fs/cgroup"))?
4917 .into_iter()
4918 .map(|cgroup| String::from(cgroup.to_str().expect("could not parse cgroup path")))
4919 .filter(|cgroup| {
4920 let re = Regex::new(expr).unwrap();
4921 re.is_match(cgroup)
4922 })
4923 .map(|cgroup| {
4924 (
4925 {
4929 let mut slashterminated = cgroup.clone();
4930 slashterminated.push('/');
4931 LayerMatch::CgroupSuffix(name_suffix(&slashterminated, 64))
4932 },
4933 find_cpumask(&cgroup),
4934 )
4935 })
4936 .collect()),
4937 _ => panic!("Unimplemented template enum {:?}", rule),
4938 }
4939}
4940
4941fn create_perf_fds(skel: &mut BpfSkel, event: u64) -> Result<()> {
4942 let mut attr = perf::bindings::perf_event_attr {
4943 size: std::mem::size_of::<perf::bindings::perf_event_attr>() as u32,
4944 type_: perf::bindings::PERF_TYPE_RAW,
4945 config: event,
4946 sample_type: 0u64,
4947 ..Default::default()
4948 };
4949 attr.__bindgen_anon_1.sample_period = 0u64;
4950 attr.set_disabled(0);
4951
4952 let perf_events_map = &skel.maps.scx_pmu_map;
4953 let map_fd = unsafe { libbpf_sys::bpf_map__fd(perf_events_map.as_libbpf_object().as_ptr()) };
4954
4955 let mut failures = 0u64;
4956
4957 for cpu in 0..*NR_CPUS_POSSIBLE {
4958 let fd = unsafe { perf::perf_event_open(&mut attr as *mut _, -1, cpu as i32, -1, 0) };
4959 if fd < 0 {
4960 failures += 1;
4961 trace!(
4962 "perf_event_open failed cpu={cpu} errno={}",
4963 std::io::Error::last_os_error()
4964 );
4965 continue;
4966 }
4967
4968 let key = cpu as u32;
4969 let val = fd as u32;
4970 let ret = unsafe {
4971 libbpf_sys::bpf_map_update_elem(
4972 map_fd,
4973 &key as *const _ as *const _,
4974 &val as *const _ as *const _,
4975 0,
4976 )
4977 };
4978 if ret != 0 {
4979 trace!("bpf_map_update_elem failed cpu={cpu} fd={fd} ret={ret}");
4980 } else {
4981 trace!("mapped cpu={cpu} -> fd={fd}");
4982 }
4983 }
4984
4985 if failures > 0 {
4986 println!("membw tracking: failed to install {failures} counters");
4987 }
4989
4990 Ok(())
4991}
4992
4993fn setup_membw_tracking(skel: &mut OpenBpfSkel) -> Result<u64> {
4995 let pmumanager = PMUManager::new()?;
4996 let codename = &pmumanager.codename as &str;
4997
4998 let pmuspec = match codename {
4999 "amdzen1" | "amdzen2" | "amdzen3" => {
5000 trace!("found AMD codename {codename}");
5001 pmumanager.pmus.get("ls_any_fills_from_sys.mem_io_local")
5002 }
5003 "amdzen4" | "amdzen5" => {
5004 trace!("found AMD codename {codename}");
5005 pmumanager.pmus.get("ls_any_fills_from_sys.dram_io_all")
5006 }
5007
5008 "haswell" | "broadwell" | "broadwellde" | "broadwellx" | "skylake" | "skylakex"
5009 | "cascadelakex" | "arrowlake" | "meteorlake" | "sapphirerapids" | "emeraldrapids"
5010 | "graniterapids" => {
5011 trace!("found Intel codename {codename}");
5012 pmumanager.pmus.get("LONGEST_LAT_CACHE.MISS")
5013 }
5014
5015 _ => {
5016 trace!("found unknown codename {codename}");
5017 None
5018 }
5019 };
5020
5021 let spec = pmuspec.ok_or("not_found").unwrap();
5022 let config = (spec.umask << 8) | spec.event[0];
5023
5024 skel.maps.rodata_data.as_mut().unwrap().membw_event = config;
5026
5027 Ok(config)
5028}
5029
5030#[clap_main::clap_main]
5031fn main(opts: Opts) -> Result<()> {
5032 if opts.version {
5033 println!(
5034 "scx_layered {}",
5035 build_id::full_version(env!("CARGO_PKG_VERSION"))
5036 );
5037 return Ok(());
5038 }
5039
5040 if opts.help_stats {
5041 stats::server_data().describe_meta(&mut std::io::stdout(), None)?;
5042 return Ok(());
5043 }
5044
5045 let env_filter = EnvFilter::try_from_default_env()
5046 .or_else(|_| match EnvFilter::try_new(&opts.log_level) {
5047 Ok(filter) => Ok(filter),
5048 Err(e) => {
5049 eprintln!(
5050 "invalid log envvar: {}, using info, err is: {}",
5051 opts.log_level, e
5052 );
5053 EnvFilter::try_new("info")
5054 }
5055 })
5056 .unwrap_or_else(|_| EnvFilter::new("info"));
5057
5058 match tracing_subscriber::fmt()
5059 .with_env_filter(env_filter)
5060 .with_target(true)
5061 .with_thread_ids(true)
5062 .with_file(true)
5063 .with_line_number(true)
5064 .try_init()
5065 {
5066 Ok(()) => {}
5067 Err(e) => eprintln!("failed to init logger: {}", e),
5068 }
5069
5070 if opts.verbose > 0 {
5071 warn!("Setting verbose via -v is deprecated and will be an error in future releases.");
5072 }
5073
5074 if opts.no_load_frac_limit {
5075 warn!("--no-load-frac-limit is deprecated and noop");
5076 }
5077 if opts.layer_preempt_weight_disable != 0.0 {
5078 warn!("--layer-preempt-weight-disable is deprecated and noop");
5079 }
5080 if opts.layer_growth_weight_disable != 0.0 {
5081 warn!("--layer-growth-weight-disable is deprecated and noop");
5082 }
5083 if opts.local_llc_iteration {
5084 warn!("--local_llc_iteration is deprecated and noop");
5085 }
5086
5087 debug!("opts={:?}", &opts);
5088
5089 if let Some(run_id) = opts.run_id {
5090 info!("scx_layered run_id: {}", run_id);
5091 }
5092
5093 let shutdown = Arc::new(AtomicBool::new(false));
5094 let shutdown_clone = shutdown.clone();
5095 ctrlc::set_handler(move || {
5096 shutdown_clone.store(true, Ordering::Relaxed);
5097 })
5098 .context("Error setting Ctrl-C handler")?;
5099
5100 if let Some(intv) = opts.monitor.or(opts.stats) {
5101 let shutdown_copy = shutdown.clone();
5102 let stats_columns = opts.stats_columns;
5103 let stats_no_llc = opts.stats_no_llc;
5104 let jh = std::thread::spawn(move || {
5105 match stats::monitor(
5106 Duration::from_secs_f64(intv),
5107 shutdown_copy,
5108 stats_columns,
5109 stats_no_llc,
5110 ) {
5111 Ok(_) => {
5112 debug!("stats monitor thread finished successfully")
5113 }
5114 Err(error_object) => {
5115 warn!(
5116 "stats monitor thread finished because of an error {}",
5117 error_object
5118 )
5119 }
5120 }
5121 });
5122 if opts.monitor.is_some() {
5123 let _ = jh.join();
5124 return Ok(());
5125 }
5126 }
5127
5128 if let Some(path) = &opts.example {
5129 write_example_file(path)?;
5130 return Ok(());
5131 }
5132
5133 let mut layer_config = match opts.run_example {
5134 true => EXAMPLE_CONFIG.clone(),
5135 false => LayerConfig { specs: vec![] },
5136 };
5137
5138 for (idx, input) in opts.specs.iter().enumerate() {
5139 let specs = LayerSpec::parse(input)
5140 .context(format!("Failed to parse specs[{}] ({:?})", idx, input))?;
5141
5142 for spec in specs {
5143 match spec.template {
5144 Some(ref rule) => {
5145 let matches = expand_template(rule)?;
5146 if matches.is_empty() {
5149 layer_config.specs.push(spec);
5150 } else {
5151 for (mt, mask) in matches {
5152 let mut genspec = spec.clone();
5153
5154 genspec.cpuset = Some(mask);
5155
5156 for orterm in &mut genspec.matches {
5158 orterm.push(mt.clone());
5159 }
5160
5161 match &mt {
5162 LayerMatch::CgroupSuffix(cgroup) => genspec.name.push_str(cgroup),
5163 _ => bail!("Template match has unexpected type"),
5164 }
5165
5166 layer_config.specs.push(genspec);
5168 }
5169 }
5170 }
5171
5172 None => {
5173 layer_config.specs.push(spec);
5174 }
5175 }
5176 }
5177 }
5178
5179 for spec in layer_config.specs.iter_mut() {
5180 let common = spec.kind.common_mut();
5181
5182 if common.slice_us == 0 {
5183 common.slice_us = opts.slice_us;
5184 }
5185
5186 if common.weight == 0 {
5187 common.weight = DEFAULT_LAYER_WEIGHT;
5188 }
5189 common.weight = common.weight.clamp(MIN_LAYER_WEIGHT, MAX_LAYER_WEIGHT);
5190
5191 if common.preempt {
5192 if common.disallow_open_after_us.is_some() {
5193 warn!(
5194 "Preempt layer {} has non-null disallow_open_after_us, ignored",
5195 &spec.name
5196 );
5197 }
5198 if common.disallow_preempt_after_us.is_some() {
5199 warn!(
5200 "Preempt layer {} has non-null disallow_preempt_after_us, ignored",
5201 &spec.name
5202 );
5203 }
5204 common.disallow_open_after_us = Some(u64::MAX);
5205 common.disallow_preempt_after_us = Some(u64::MAX);
5206 } else {
5207 if common.disallow_open_after_us.is_none() {
5208 common.disallow_open_after_us = Some(*DFL_DISALLOW_OPEN_AFTER_US);
5209 }
5210
5211 if common.disallow_preempt_after_us.is_none() {
5212 common.disallow_preempt_after_us = Some(*DFL_DISALLOW_PREEMPT_AFTER_US);
5213 }
5214 }
5215
5216 if common.idle_smt.is_some() {
5217 warn!("Layer {} has deprecated flag \"idle_smt\"", &spec.name);
5218 }
5219
5220 if common.allow_node_aligned.is_some() {
5221 warn!(
5222 "Layer {} has deprecated flag \"allow_node_aligned\", node-aligned tasks are now always dispatched on layer DSQs",
5223 &spec.name
5224 );
5225 }
5226 }
5227
5228 let membw_required = layer_config.specs.iter().any(|spec| match spec.kind {
5229 LayerKind::Confined { membw_gb, .. } | LayerKind::Grouped { membw_gb, .. } => {
5230 membw_gb.is_some()
5231 }
5232 LayerKind::Open { .. } => false,
5233 });
5234
5235 if opts.print_and_exit {
5236 println!("specs={}", serde_json::to_string_pretty(&layer_config)?);
5237 return Ok(());
5238 }
5239
5240 debug!("specs={}", serde_json::to_string_pretty(&layer_config)?);
5241 let hint_to_layer_map = verify_layer_specs(&layer_config.specs)?;
5242
5243 let mut open_object = MaybeUninit::uninit();
5244 loop {
5245 let mut sched = Scheduler::init(
5246 &opts,
5247 &layer_config.specs,
5248 &mut open_object,
5249 &hint_to_layer_map,
5250 membw_required,
5251 )?;
5252 if !sched.run(shutdown.clone())?.should_restart() {
5253 break;
5254 }
5255 }
5256
5257 Ok(())
5258}
5259
5260#[cfg(test)]
5261mod peak_util_tests {
5262 use super::*;
5263
5264 #[test]
5265 fn test_peak_pins_recent_burst() {
5266 let elapsed = Duration::from_millis(100);
5267 let half_life = Duration::from_secs(1);
5268 let mut peak = 0.0;
5269
5270 for _ in 0..5 {
5271 peak = update_peak_util(peak, 0.1, half_life, elapsed);
5272 }
5273
5274 peak = update_peak_util(peak, 2.0, half_life, elapsed);
5275
5276 for _ in 0..5 {
5277 peak = update_peak_util(peak, 0.1, half_life, elapsed);
5278 }
5279
5280 assert!(
5281 peak > 1.3,
5282 "peak should still hold the recent burst, got {peak}"
5283 );
5284 }
5285
5286 #[test]
5287 fn test_peak_decays_after_quiet_period() {
5288 let elapsed = Duration::from_millis(100);
5289 let half_life = Duration::from_secs(1);
5290 let mut peak = 2.0;
5291
5292 for _ in 0..50 {
5293 peak = update_peak_util(peak, 0.0, half_life, elapsed);
5294 }
5295
5296 assert!(
5297 peak < 0.1,
5298 "peak should decay after a long quiet period, got {peak}"
5299 );
5300 }
5301
5302 #[test]
5303 fn test_peak_matches_steady_input() {
5304 let elapsed = Duration::from_millis(100);
5305 let half_life = Duration::from_secs(1);
5306 let mut peak = 0.0;
5307
5308 for _ in 0..30 {
5309 peak = update_peak_util(peak, 1.0, half_life, elapsed);
5310 }
5311
5312 assert!(
5313 (peak - 1.0).abs() < 1e-9,
5314 "peak should match steady-state input, got {peak}"
5315 );
5316 }
5317
5318 #[test]
5319 fn test_peak_only_changes_shrink_target() {
5320 let util_range = (0.7, 0.9);
5321 let (low_no_peak, high_no_peak) = calc_peak_aware_target_range(1.8, 1.8, util_range);
5322 let (low_with_peak, high_with_peak) = calc_peak_aware_target_range(1.8, 3.1, util_range);
5323
5324 assert_eq!(low_no_peak, 2);
5325 assert_eq!(
5326 low_with_peak, low_no_peak,
5327 "peak should not affect growth target"
5328 );
5329 assert_eq!(high_no_peak, 2);
5330 assert_eq!(
5331 high_with_peak, 4,
5332 "peak should only widen the shrink boundary"
5333 );
5334 }
5335}
5336
5337#[cfg(test)]
5338mod xnuma_tests {
5339 use super::*;
5340
5341 const THRESH: (f64, f64) = (0.6, 0.7);
5343 const DELTA: (f64, f64) = (0.2, 0.3);
5344
5345 #[test]
5350 fn test_activation_below_threshold() {
5351 let duty = vec![40.0, 40.0];
5353 let allocs = vec![96, 96];
5354 let gd = vec![true, true];
5355 let cur = vec![false, false];
5356 let result = xnuma_check_active(&duty, &allocs, THRESH, DELTA, &gd, &cur);
5357 assert!(!result[0]);
5359 assert!(!result[1]);
5360 }
5361
5362 #[test]
5363 fn test_activation_above_all_thresholds() {
5364 let duty = vec![90.0, 20.0];
5366 let allocs = vec![96, 96];
5367 let gd = vec![true, false];
5368 let cur = vec![false, false];
5369 let result = xnuma_check_active(&duty, &allocs, THRESH, DELTA, &gd, &cur);
5370 assert!(result[0]);
5372 assert!(!result[1]);
5374 }
5375
5376 #[test]
5377 fn test_activation_requires_growth_denied() {
5378 let duty = vec![90.0, 20.0];
5380 let allocs = vec![96, 96];
5381 let gd = vec![false, false]; let cur = vec![false, false];
5383 let result = xnuma_check_active(&duty, &allocs, THRESH, DELTA, &gd, &cur);
5384 assert!(!result[0]); }
5386
5387 #[test]
5388 fn test_symmetric_high_load_stays_closed() {
5389 let duty = vec![80.0, 80.0];
5391 let allocs = vec![96, 96];
5392 let gd = vec![true, true];
5393 let cur = vec![false, false];
5394 let result = xnuma_check_active(&duty, &allocs, THRESH, DELTA, &gd, &cur);
5395 assert!(!result[0]);
5397 assert!(!result[1]);
5398 }
5399
5400 #[test]
5401 fn test_hysteresis_stays_active() {
5402 let duty = vec![75.0, 20.0];
5404 let allocs = vec![96, 96];
5405 let gd = vec![true, false];
5406 let cur = vec![true, false]; let result = xnuma_check_active(&duty, &allocs, THRESH, DELTA, &gd, &cur);
5408 assert!(result[0]);
5412 }
5413
5414 #[test]
5415 fn test_hysteresis_stays_inactive() {
5416 let duty = vec![75.0, 20.0];
5418 let allocs = vec![96, 96];
5419 let gd = vec![true, false];
5420 let cur = vec![false, false]; let result = xnuma_check_active(&duty, &allocs, THRESH, DELTA, &gd, &cur);
5422 assert!(!result[0]);
5424 }
5425
5426 #[test]
5427 fn test_deactivation_load_drops() {
5428 let duty = vec![50.0, 50.0];
5430 let allocs = vec![96, 96];
5431 let gd = vec![true, true];
5432 let cur = vec![true, false];
5433 let result = xnuma_check_active(&duty, &allocs, THRESH, DELTA, &gd, &cur);
5434 assert!(!result[0]);
5436 }
5437
5438 #[test]
5439 fn test_deactivation_growth_succeeds() {
5440 let duty = vec![90.0, 20.0];
5442 let allocs = vec![96, 96];
5443 let gd = vec![false, false]; let cur = vec![true, false];
5445 let result = xnuma_check_active(&duty, &allocs, THRESH, DELTA, &gd, &cur);
5446 assert!(!result[0]);
5448 }
5449
5450 #[test]
5451 fn test_zero_alloc_with_duty_and_growth_denied() {
5452 let duty = vec![50.0, 0.0];
5454 let allocs = vec![0, 96];
5455 let gd = vec![true, false];
5456 let cur = vec![false, false];
5457 let result = xnuma_check_active(&duty, &allocs, THRESH, DELTA, &gd, &cur);
5458 assert!(result[0]); }
5460
5461 #[test]
5462 fn test_all_zero_alloc() {
5463 let duty = vec![0.0, 0.0];
5464 let allocs = vec![0, 0];
5465 let gd = vec![true, true];
5466 let cur = vec![true, true];
5467 let result = xnuma_check_active(&duty, &allocs, THRESH, DELTA, &gd, &cur);
5468 assert!(!result[0]);
5469 assert!(!result[1]);
5470 }
5471
5472 #[test]
5473 fn test_three_nodes_mixed() {
5474 let duty = vec![90.0, 40.0, 10.0];
5476 let allocs = vec![96, 96, 96];
5477 let gd = vec![true, true, false];
5478 let cur = vec![false, false, false];
5479 let result = xnuma_check_active(&duty, &allocs, THRESH, DELTA, &gd, &cur);
5480 assert!(result[0]);
5483 assert!(!result[1]);
5485 assert!(!result[2]);
5487 }
5488
5489 #[test]
5490 fn test_per_node_independence() {
5491 let duty = vec![90.0, 50.0];
5493 let allocs = vec![96, 96];
5494 let gd = vec![true, true];
5495 let cur = vec![true, true];
5496 let result = xnuma_check_active(&duty, &allocs, THRESH, DELTA, &gd, &cur);
5497 assert!(result[0]);
5501 assert!(!result[1]);
5503 }
5504
5505 #[test]
5510 fn test_rates_balanced_load() {
5511 let duty = vec![48.0, 48.0];
5513 let allocs = vec![96, 96];
5514 let result = xnuma_compute_rates(&duty, &allocs);
5515
5516 for src in 0..2 {
5518 for dst in 0..2 {
5519 assert_eq!(result.rates[src][dst], 0);
5520 }
5521 }
5522 }
5523
5524 #[test]
5525 fn test_rates_one_overloaded() {
5526 let duty = vec![80.0, 40.0];
5531 let allocs = vec![96, 96];
5532 let result = xnuma_compute_rates(&duty, &allocs);
5533
5534 assert!(result.rates[0][1] > 0);
5536 assert_eq!(result.rates[1][0], 0);
5538 assert_eq!(result.rates[0][0], 0);
5540 assert_eq!(result.rates[1][1], 0);
5541
5542 let expected_rate = (20.0 * XNUMA_RATE_DAMPEN * DUTY_CYCLE_SCALE) as u64;
5544 assert_eq!(result.rates[0][1], expected_rate);
5545 }
5546
5547 #[test]
5548 fn test_rates_asymmetric_allocation() {
5549 let duty = vec![48.0, 48.0];
5554 let allocs = vec![48, 144];
5555 let result = xnuma_compute_rates(&duty, &allocs);
5556
5557 let expected_rate = (24.0 * XNUMA_RATE_DAMPEN * DUTY_CYCLE_SCALE) as u64;
5558 assert_eq!(result.rates[0][1], expected_rate);
5559 assert_eq!(result.rates[1][0], 0);
5560 }
5561
5562 #[test]
5567 fn test_rates_three_nodes_one_source() {
5568 let duty = vec![120.0, 30.0, 30.0];
5575 let allocs = vec![96, 96, 96];
5576 let result = xnuma_compute_rates(&duty, &allocs);
5577
5578 let rate_01 = (30.0 * XNUMA_RATE_DAMPEN * DUTY_CYCLE_SCALE) as u64;
5582 let rate_02 = (30.0 * XNUMA_RATE_DAMPEN * DUTY_CYCLE_SCALE) as u64;
5583 assert_eq!(result.rates[0][1], rate_01);
5584 assert_eq!(result.rates[0][2], rate_02);
5585
5586 assert_eq!(result.rates[1][0], 0);
5588 assert_eq!(result.rates[2][0], 0);
5589 assert_eq!(result.rates[1][2], 0);
5590 assert_eq!(result.rates[2][1], 0);
5591 }
5592
5593 #[test]
5594 fn test_rates_three_nodes_unequal_deficit() {
5595 let duty = vec![120.0, 50.0, 10.0];
5602 let allocs = vec![96, 96, 96];
5603 let result = xnuma_compute_rates(&duty, &allocs);
5604
5605 let rate_01 = (10.0 * XNUMA_RATE_DAMPEN * DUTY_CYCLE_SCALE) as u64;
5609 let rate_02 = (50.0 * XNUMA_RATE_DAMPEN * DUTY_CYCLE_SCALE) as u64;
5610 assert_eq!(result.rates[0][1], rate_01);
5611 assert_eq!(result.rates[0][2], rate_02);
5612 }
5613
5614 #[test]
5615 fn test_rates_two_sources_one_sink() {
5616 let duty = vec![80.0, 70.0, 30.0];
5623 let allocs = vec![96, 96, 96];
5624 let result = xnuma_compute_rates(&duty, &allocs);
5625
5626 let rate_02 = (20.0 * XNUMA_RATE_DAMPEN * DUTY_CYCLE_SCALE) as u64;
5630 let rate_12 = (10.0 * XNUMA_RATE_DAMPEN * DUTY_CYCLE_SCALE) as u64;
5631 assert_eq!(result.rates[0][2], rate_02);
5632 assert_eq!(result.rates[1][2], rate_12);
5633
5634 assert_eq!(result.rates[0][1], 0);
5636 assert_eq!(result.rates[1][0], 0);
5637 }
5638
5639 #[test]
5644 fn test_conservation_per_source_outbound() {
5645 let duty = vec![100.0, 30.0, 50.0, 20.0];
5648 let allocs = vec![96, 96, 96, 96];
5649 let nr = 4;
5650 let result = xnuma_compute_rates(&duty, &allocs);
5651
5652 let total_duty: f64 = duty.iter().sum();
5653 let total_alloc: f64 = allocs.iter().map(|&a| a as f64).sum();
5654 let eq_ratio = total_duty / total_alloc;
5655
5656 for src in 0..nr {
5657 let expected = eq_ratio * allocs[src] as f64;
5658 let surplus = (duty[src] - expected).max(0.0);
5659 let total_outbound: u64 = (0..nr).map(|dst| result.rates[src][dst]).sum();
5660 let expected_rate = (surplus * XNUMA_RATE_DAMPEN * DUTY_CYCLE_SCALE) as u64;
5661 assert_eq!(
5662 total_outbound, expected_rate,
5663 "node {} outbound mismatch",
5664 src
5665 );
5666 }
5667 }
5668
5669 #[test]
5670 fn test_conservation_surplus_equals_deficit() {
5671 let duty = [100.0, 30.0, 50.0];
5673 let allocs = [96, 96, 96];
5674
5675 let total_duty: f64 = duty.iter().sum();
5676 let total_alloc: f64 = allocs.iter().map(|&a| a as f64).sum();
5677 let eq_ratio = total_duty / total_alloc;
5678
5679 let mut total_surplus = 0.0f64;
5680 let mut total_deficit = 0.0f64;
5681 for i in 0..3 {
5682 let expected = eq_ratio * allocs[i] as f64;
5683 let delta = duty[i] - expected;
5684 if delta > 0.0 {
5685 total_surplus += delta;
5686 } else {
5687 total_deficit += -delta;
5688 }
5689 }
5690 assert!((total_surplus - total_deficit).abs() < 1e-10);
5691 }
5692
5693 #[test]
5694 fn test_self_rates_always_zero() {
5695 let duty = vec![100.0, 30.0, 50.0];
5696 let allocs = vec![96, 96, 96];
5697 let result = xnuma_compute_rates(&duty, &allocs);
5698
5699 for nid in 0..3 {
5700 assert_eq!(result.rates[nid][nid], 0);
5701 }
5702 }
5703
5704 #[test]
5705 fn test_deficit_nodes_have_zero_outbound() {
5706 let duty = vec![100.0, 20.0, 30.0, 50.0];
5708 let allocs = vec![96, 96, 96, 96];
5709 let result = xnuma_compute_rates(&duty, &allocs);
5710
5711 for nid in 0..4 {
5715 let outbound: u64 = (0..4).map(|dst| result.rates[nid][dst]).sum();
5716 if outbound == 0 {
5717 for dst in 0..4 {
5718 assert_eq!(
5719 result.rates[nid][dst], 0,
5720 "deficit node {} has non-zero rate to {}",
5721 nid, dst
5722 );
5723 }
5724 }
5725 }
5726 }
5727
5728 #[test]
5733 fn test_rates_zero_duty_everywhere() {
5734 let duty = vec![0.0, 0.0];
5735 let allocs = vec![96, 96];
5736 let result = xnuma_compute_rates(&duty, &allocs);
5737
5738 for src in 0..2 {
5739 for dst in 0..2 {
5740 assert_eq!(result.rates[src][dst], 0);
5741 }
5742 }
5743 }
5744
5745 #[test]
5746 fn test_rates_zero_alloc() {
5747 let duty = vec![50.0, 50.0];
5748 let allocs = vec![0, 0];
5749 let result = xnuma_compute_rates(&duty, &allocs);
5750
5751 for src in 0..2 {
5753 for dst in 0..2 {
5754 assert_eq!(result.rates[src][dst], 0);
5755 }
5756 }
5757 }
5758
5759 #[test]
5760 fn test_rates_all_load_one_node() {
5761 let duty = vec![96.0, 0.0];
5767 let allocs = vec![96, 96];
5768 let result = xnuma_compute_rates(&duty, &allocs);
5769
5770 let expected_rate = (48.0 * XNUMA_RATE_DAMPEN * DUTY_CYCLE_SCALE) as u64;
5771 assert_eq!(result.rates[0][1], expected_rate);
5772 }
5773
5774 #[test]
5775 fn test_rates_single_node() {
5776 let duty = vec![96.0];
5778 let allocs = vec![96];
5779 let result = xnuma_compute_rates(&duty, &allocs);
5780
5781 assert_eq!(result.rates[0][0], 0);
5782 }
5783
5784 #[test]
5785 fn test_rates_one_node_zero_alloc() {
5786 let duty = vec![80.0, 0.0];
5791 let allocs = vec![96, 0];
5792 let result = xnuma_compute_rates(&duty, &allocs);
5793
5794 assert_eq!(result.rates[0][1], 0);
5798 assert_eq!(result.rates[1][0], 0);
5799 }
5800
5801 #[test]
5806 fn test_rate_scaling() {
5807 let duty = vec![80.0, 40.0];
5809 let allocs = vec![96, 96];
5810 let result = xnuma_compute_rates(&duty, &allocs);
5811
5812 let expected = (20.0 * XNUMA_RATE_DAMPEN * DUTY_CYCLE_SCALE) as u64;
5815 assert_eq!(result.rates[0][1], expected);
5816 assert_eq!(expected, 10 * (1 << 20));
5817 }
5818
5819 #[test]
5820 fn test_rates_tiny_imbalance() {
5821 let duty = vec![48.001, 47.999];
5823 let allocs = vec![96, 96];
5824 let result = xnuma_compute_rates(&duty, &allocs);
5825
5826 assert!(result.rates[0][1] > 0);
5828 assert!(result.rates[0][1] < (1 << 20)); }
5830
5831 #[test]
5832 fn test_rates_large_values() {
5833 let duty = vec![300.0, 100.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0];
5835 let allocs = vec![96, 96, 96, 96, 96, 96, 96, 96];
5836 let result = xnuma_compute_rates(&duty, &allocs);
5837
5838 assert!(result.rates[0][2] > 0); assert!(result.rates[1][2] > 0); let total_duty: f64 = duty.iter().sum();
5846 let total_alloc: f64 = allocs.iter().map(|&a| a as f64).sum();
5847 let eq_ratio = total_duty / total_alloc;
5848 let nr = 8;
5849 for src in 0..nr {
5850 let surplus = (duty[src] - eq_ratio * allocs[src] as f64).max(0.0);
5851 let outbound: u64 = (0..nr).map(|dst| result.rates[src][dst]).sum();
5852 let expected = (surplus * XNUMA_RATE_DAMPEN * DUTY_CYCLE_SCALE) as u64;
5853 let tolerance = nr as u64; assert!(
5855 outbound.abs_diff(expected) <= tolerance,
5856 "node {} outbound {} vs expected {}, diff {}",
5857 src,
5858 outbound,
5859 expected,
5860 outbound.abs_diff(expected)
5861 );
5862 }
5863 }
5864
5865 #[test]
5870 fn test_hysteresis_cycle() {
5871 let allocs = vec![96, 96];
5872 let gd = vec![true, false]; let active =
5876 xnuma_check_active(&[40.0, 40.0], &allocs, THRESH, DELTA, &gd, &[false, false]);
5877 assert!(!active[0]); let active =
5881 xnuma_check_active(&[90.0, 20.0], &allocs, THRESH, DELTA, &gd, &[false, false]);
5882 assert!(active[0]); let active = xnuma_check_active(&[75.0, 20.0], &allocs, THRESH, DELTA, &gd, &[true, false]);
5886 assert!(active[0]); let active = xnuma_check_active(&[50.0, 50.0], &allocs, THRESH, DELTA, &gd, &[true, false]);
5890 assert!(!active[0]); }
5892
5893 #[test]
5894 fn test_hysteresis_growth_toggle() {
5895 let allocs = vec![96, 96];
5897 let gd_denied = vec![true, false];
5898 let gd_ok = vec![false, false];
5899
5900 let active = xnuma_check_active(
5902 &[90.0, 20.0],
5903 &allocs,
5904 THRESH,
5905 DELTA,
5906 &gd_denied,
5907 &[false, false],
5908 );
5909 assert!(active[0]);
5910
5911 let active = xnuma_check_active(
5913 &[90.0, 20.0],
5914 &allocs,
5915 THRESH,
5916 DELTA,
5917 &gd_ok,
5918 &[true, false],
5919 );
5920 assert!(!active[0]); }
5922
5923 #[test]
5928 fn test_proportional_sink_distribution() {
5929 let duty = vec![180.0, 20.0, 40.0, 0.0];
5938 let allocs = vec![96, 96, 96, 96];
5939 let result = xnuma_compute_rates(&duty, &allocs);
5940
5941 assert_eq!(
5945 result.rates[0][1],
5946 (40.0 * XNUMA_RATE_DAMPEN * DUTY_CYCLE_SCALE) as u64
5947 );
5948 assert_eq!(
5949 result.rates[0][2],
5950 (20.0 * XNUMA_RATE_DAMPEN * DUTY_CYCLE_SCALE) as u64
5951 );
5952 assert_eq!(
5953 result.rates[0][3],
5954 (60.0 * XNUMA_RATE_DAMPEN * DUTY_CYCLE_SCALE) as u64
5955 );
5956
5957 let total_from_n0: u64 = (0..4).map(|dst| result.rates[0][dst]).sum();
5959 assert_eq!(
5960 total_from_n0,
5961 (120.0 * XNUMA_RATE_DAMPEN * DUTY_CYCLE_SCALE) as u64
5962 );
5963 }
5964}
5965
5966#[cfg(test)]
5967mod util_compensation_tests {
5968 fn compute_scale(delta_total: u64, overhead: u64) -> f64 {
5970 let available = delta_total.saturating_sub(overhead);
5971 if available > 0 {
5972 (delta_total as f64 / available as f64).clamp(1.0, 20.0)
5973 } else {
5974 1.0
5975 }
5976 }
5977
5978 fn scaled_aggregate(
5980 deltas: &[Vec<u64>],
5981 scales: &[f64],
5982 nr_layers: usize,
5983 elapsed: f64,
5984 ) -> Vec<f64> {
5985 (0..nr_layers)
5986 .map(|layer| {
5987 let mut sum = 0.0f64;
5988 for (cpu, cpu_deltas) in deltas.iter().enumerate() {
5989 sum += cpu_deltas[layer] as f64 * scales[cpu];
5990 }
5991 sum / 1_000_000_000.0 / elapsed
5992 })
5993 .collect()
5994 }
5995
5996 #[test]
5997 fn test_scale_no_overhead() {
5998 assert!((compute_scale(1000, 0) - 1.0).abs() < 0.01);
6000 }
6001
6002 #[test]
6003 fn test_scale_half_overhead() {
6004 assert!((compute_scale(1000, 500) - 2.0).abs() < 0.01);
6006 }
6007
6008 #[test]
6009 fn test_scale_high_overhead() {
6010 assert!((compute_scale(1000, 900) - 10.0).abs() < 0.01);
6012 }
6013
6014 #[test]
6015 fn test_scale_very_high_overhead() {
6016 assert!((compute_scale(1000, 950) - 20.0).abs() < 0.01);
6018 }
6019
6020 #[test]
6021 fn test_scale_clamped_at_max() {
6022 assert!((compute_scale(1000, 980) - 20.0).abs() < 0.01);
6024 }
6025
6026 #[test]
6027 fn test_scale_all_overhead() {
6028 assert!((compute_scale(1000, 1000) - 1.0).abs() < 0.01);
6030 }
6031
6032 #[test]
6033 fn test_scale_idle_cpu() {
6034 assert!((compute_scale(0, 0) - 1.0).abs() < 0.01);
6036 }
6037
6038 #[test]
6039 fn test_scale_small_overhead_mostly_idle() {
6040 let s = compute_scale(10000, 100);
6043 assert!((s - 1.01).abs() < 0.01, "expected ~1.01, got {}", s);
6044 }
6045
6046 #[test]
6047 fn test_uniform_scale_matches_unscaled() {
6048 let deltas = vec![vec![1_000_000_000u64; 2]; 4];
6049 let scales = vec![1.0; 4];
6050 let result = scaled_aggregate(&deltas, &scales, 2, 1.0);
6051 assert!((result[0] - 4.0).abs() < 0.01);
6052 assert!((result[1] - 4.0).abs() < 0.01);
6053 }
6054
6055 #[test]
6056 fn test_hot_cpu_weighted_more() {
6057 let deltas = vec![vec![900_000_000, 0], vec![100_000_000, 0]];
6061 let scales = vec![2.0, 1.0];
6062 let result = scaled_aggregate(&deltas, &scales, 2, 1.0);
6063 assert!(
6064 (result[0] - 1.9).abs() < 0.01,
6065 "expected 1.9, got {}",
6066 result[0]
6067 );
6068 }
6069
6070 #[test]
6071 fn test_cold_cpu_weighted_less() {
6072 let deltas = vec![vec![100_000_000, 0], vec![900_000_000, 0]];
6073 let scales = vec![2.0, 1.0];
6074 let result = scaled_aggregate(&deltas, &scales, 2, 1.0);
6075 assert!(
6076 (result[0] - 1.1).abs() < 0.01,
6077 "expected 1.1, got {}",
6078 result[0]
6079 );
6080 }
6081
6082 #[test]
6083 fn test_no_usage_no_compensation() {
6084 let deltas = vec![vec![0u64; 2]; 4];
6085 let scales = vec![5.0; 4];
6086 let result = scaled_aggregate(&deltas, &scales, 2, 1.0);
6087 assert_eq!(result[0], 0.0);
6088 assert_eq!(result[1], 0.0);
6089 }
6090
6091 #[test]
6092 fn test_multilayer_independent_scaling() {
6093 let deltas = vec![
6094 vec![800_000_000, 200_000_000],
6095 vec![200_000_000, 800_000_000],
6096 ];
6097 let scales = vec![3.0, 1.0];
6098 let result = scaled_aggregate(&deltas, &scales, 2, 1.0);
6099 assert!((result[0] - 2.6).abs() < 0.01);
6100 assert!((result[1] - 1.4).abs() < 0.01);
6101 }
6102
6103 #[test]
6104 fn test_elapsed_time_normalization() {
6105 let deltas = vec![vec![500_000_000u64; 1]; 1];
6106 let scales = vec![1.0];
6107 let result = scaled_aggregate(&deltas, &scales, 1, 2.0);
6108 assert!((result[0] - 0.25).abs() < 0.01);
6109 }
6110
6111 #[test]
6112 fn test_many_cpus_mixed_scales() {
6113 let deltas = vec![vec![1_000_000_000u64; 1]; 8];
6114 let scales = vec![2.5, 1.0, 2.5, 1.0, 2.5, 1.0, 2.5, 1.0];
6115 let result = scaled_aggregate(&deltas, &scales, 1, 1.0);
6116 assert!((result[0] - 14.0).abs() < 0.01);
6117 }
6118
6119 #[test]
6120 fn test_compensated_ge_raw() {
6121 let deltas = vec![
6123 vec![500_000_000u64; 3],
6124 vec![300_000_000; 3],
6125 vec![200_000_000; 3],
6126 ];
6127 let scales_raw = vec![1.0; 3];
6128 let scales_comp = vec![1.5, 2.0, 1.0];
6129 let raw = scaled_aggregate(&deltas, &scales_raw, 3, 1.0);
6130 let comp = scaled_aggregate(&deltas, &scales_comp, 3, 1.0);
6131 for i in 0..3 {
6132 assert!(
6133 comp[i] >= raw[i] - 0.001,
6134 "layer {}: comp {} < raw {}",
6135 i,
6136 comp[i],
6137 raw[i]
6138 );
6139 }
6140 }
6141}