1mod bpf_skel;
9pub use bpf_skel::*;
10pub mod bpf_intf;
11pub use bpf_intf::*;
12
13mod stats;
14use std::collections::HashSet;
15use std::ffi::c_int;
16use std::mem::MaybeUninit;
17use std::sync::atomic::AtomicBool;
18use std::sync::atomic::Ordering;
19use std::sync::Arc;
20use std::time::Duration;
21
22use anyhow::bail;
23use anyhow::Context;
24use anyhow::Result;
25use clap::Parser;
26use crossbeam::channel::RecvTimeoutError;
27use libbpf_rs::MapCore;
28use libbpf_rs::MapFlags;
29use libbpf_rs::OpenObject;
30use libbpf_rs::ProgramInput;
31use log::warn;
32use log::{debug, info};
33use scx_stats::prelude::*;
34use scx_utils::build_id;
35use scx_utils::compat;
36use scx_utils::get_primary_cpus;
37use scx_utils::libbpf_clap_opts::LibbpfOpts;
38use scx_utils::perf::parse_perf_event;
39use scx_utils::perf::setup_perf_events;
40use scx_utils::perf::PerfEventSpec;
41use scx_utils::scx_ops_attach;
42use scx_utils::scx_ops_load;
43use scx_utils::scx_ops_open;
44use scx_utils::try_set_rlimit_infinity;
45use scx_utils::uei_exited;
46use scx_utils::uei_report;
47use scx_utils::CoreType;
48use scx_utils::Cpumask;
49use scx_utils::Powermode;
50use scx_utils::Topology;
51use scx_utils::UserExitInfo;
52use scx_utils::NR_CPUS_POSSIBLE;
53use scx_utils::NR_CPU_IDS;
54use stats::Metrics;
55
56const SCHEDULER_NAME: &str = "scx_forge";
57const FORGE_MAX_TOPO_DOMAINS_USIZE: usize = 4096;
58const FORGE_MAX_TOPO_DISTANCES_USIZE: usize = 65536;
59const FORGE_TOPO_CPUMASK_WORDS_USIZE: usize = FORGE_MAX_TOPO_DOMAINS_USIZE / 64;
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
65enum DsqTopology {
66 Cpu,
68 Llc,
70 Node,
72 Global,
74}
75
76impl DsqTopology {
77 fn to_bpf(self) -> u32 {
79 match self {
80 DsqTopology::Cpu => bpf_intf::topology_dsq_type_TOPO_DSQ_CPU,
81 DsqTopology::Llc => bpf_intf::topology_dsq_type_TOPO_DSQ_LLC,
82 DsqTopology::Node => bpf_intf::topology_dsq_type_TOPO_DSQ_NODE,
83 DsqTopology::Global => bpf_intf::topology_dsq_type_TOPO_DSQ_GLOBAL,
84 }
85 }
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
91enum QueueOrdering {
92 Vruntime,
94 Deadline,
96 Fifo,
98}
99
100impl QueueOrdering {
101 fn to_bpf(self) -> u32 {
103 match self {
104 QueueOrdering::Vruntime => bpf_intf::ordering_type_ORDER_VRUNTIME,
105 QueueOrdering::Deadline => bpf_intf::ordering_type_ORDER_DEADLINE,
106 QueueOrdering::Fifo => bpf_intf::ordering_type_ORDER_FIFO,
107 }
108 }
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
114enum IdlePolicy {
115 Capacity,
117 Wakee,
119 Waker,
121 Thread,
124 Sticky,
126}
127
128impl IdlePolicy {
129 fn to_bpf(self) -> u32 {
131 match self {
132 IdlePolicy::Capacity => bpf_intf::idle_policy_type_IDLE_CAPACITY,
133 IdlePolicy::Wakee => bpf_intf::idle_policy_type_IDLE_WAKEE,
134 IdlePolicy::Waker => bpf_intf::idle_policy_type_IDLE_WAKER,
135 IdlePolicy::Thread => bpf_intf::idle_policy_type_IDLE_THREAD,
136 IdlePolicy::Sticky => bpf_intf::idle_policy_type_IDLE_STICKY,
137 }
138 }
139}
140
141#[derive(Debug, Parser)]
142struct Opts {
143 #[clap(short = 's', long, default_value = "1000")]
145 slice_us: u64,
146
147 #[clap(long, value_enum, default_value_t = DsqTopology::Global)]
149 dsq_topology: DsqTopology,
150
151 #[clap(long, value_enum, default_value_t = QueueOrdering::Vruntime)]
153 ordering: QueueOrdering,
154
155 #[clap(long, value_enum, default_value_t = IdlePolicy::Wakee)]
157 idle_policy: IdlePolicy,
158
159 #[clap(short = 'w', long, action = clap::ArgAction::SetTrue)]
161 no_wake_sync: bool,
162
163 #[clap(short = 'm', long)]
174 primary_domain: Option<String>,
175
176 #[clap(short = 'p', long, action = clap::ArgAction::SetTrue)]
179 enable_preemption: bool,
180
181 #[clap(short = 'e', long, default_value = "0x0", value_parser = parse_perf_event)]
185 perf_config: PerfEventSpec,
186
187 #[clap(short = 'E', long, default_value = "1000")]
191 perf_threshold: u64,
192
193 #[clap(short = 'y', long, default_value = "0x0", value_parser = parse_perf_event)]
197 perf_sticky: PerfEventSpec,
198
199 #[clap(short = 'Y', long, default_value = "1000")]
203 perf_sticky_threshold: u64,
204
205 #[clap(long, default_value = "0")]
207 exit_dump_len: u32,
208
209 #[clap(long)]
211 stats: Option<f64>,
212
213 #[clap(long)]
216 monitor: Option<f64>,
217
218 #[clap(short = 'd', long, action = clap::ArgAction::SetTrue)]
220 debug: bool,
221
222 #[clap(short = 'v', long, action = clap::ArgAction::SetTrue)]
224 verbose: bool,
225
226 #[clap(short = 'V', long, action = clap::ArgAction::SetTrue)]
228 version: bool,
229
230 #[clap(long)]
232 help_stats: bool,
233
234 #[clap(flatten, next_help_heading = "Libbpf Options")]
235 pub libbpf: LibbpfOpts,
236}
237
238pub fn parse_cpu_list(optarg: &str) -> Result<Vec<usize>, String> {
239 let mut cpus = Vec::new();
240 let mut seen = HashSet::new();
241
242 if let Some(mode) = match optarg {
244 "powersave" => Some(Powermode::Powersave),
245 "performance" => Some(Powermode::Performance),
246 "all" => Some(Powermode::Any),
247 _ => None,
248 } {
249 return get_primary_cpus(mode).map_err(|e| e.to_string());
250 }
251
252 if optarg
254 .chars()
255 .any(|c| !c.is_ascii_digit() && c != '-' && c != ',' && !c.is_whitespace())
256 {
257 return Err("Invalid character in CPU list".to_string());
258 }
259
260 let cleaned = optarg.replace(' ', "\t");
261
262 for token in cleaned.split(',') {
263 let token = token.trim_matches(|c: char| c.is_whitespace());
264
265 if token.is_empty() {
266 continue;
267 }
268
269 if let Some((start_str, end_str)) = token.split_once('-') {
270 let start = start_str
271 .trim()
272 .parse::<usize>()
273 .map_err(|_| "Invalid range start")?;
274 let end = end_str
275 .trim()
276 .parse::<usize>()
277 .map_err(|_| "Invalid range end")?;
278
279 if start > end {
280 return Err(format!("Invalid CPU range: {}-{}", start, end));
281 }
282
283 for i in start..=end {
284 if cpus.len() >= *NR_CPU_IDS {
285 return Err(format!("Too many CPUs specified (max {})", *NR_CPU_IDS));
286 }
287 if seen.insert(i) {
288 cpus.push(i);
289 }
290 }
291 } else {
292 let cpu = token
293 .parse::<usize>()
294 .map_err(|_| format!("Invalid CPU: {}", token))?;
295 if cpus.len() >= *NR_CPU_IDS {
296 return Err(format!("Too many CPUs specified (max {})", *NR_CPU_IDS));
297 }
298 if seen.insert(cpu) {
299 cpus.push(cpu);
300 }
301 }
302 }
303
304 Ok(cpus)
305}
306
307struct Scheduler<'a> {
308 skel: BpfSkel<'a>,
309 struct_ops: Option<libbpf_rs::Link>,
310 stats_server: StatsServer<(), Metrics>,
311 user_restart: bool,
312}
313
314impl<'a> Scheduler<'a> {
315 fn init(opts: &'a Opts, open_object: &'a mut MaybeUninit<OpenObject>) -> Result<Self> {
316 try_set_rlimit_infinity();
317
318 info!(
319 "{} {}",
320 SCHEDULER_NAME,
321 build_id::full_version(env!("CARGO_PKG_VERSION")),
322 );
323
324 let topo = Topology::new().unwrap();
326 if *NR_CPU_IDS > FORGE_TOPO_CPUMASK_WORDS_USIZE * 64 {
327 bail!(
328 "CPU ID space {} exceeds scx_forge topology map limit {}",
329 *NR_CPU_IDS,
330 FORGE_TOPO_CPUMASK_WORDS_USIZE * 64
331 );
332 }
333
334 let smt_enabled = topo.smt_enabled;
336 if !smt_enabled {
337 info!("Disabling SMT optimizations");
338 }
339
340 let nr_nodes = topo
342 .nodes
343 .values()
344 .filter(|node| !node.all_cpus.is_empty())
345 .count();
346 info!("NUMA nodes: {}", nr_nodes);
347
348 info!(
350 "scheduler options: {}",
351 std::env::args().collect::<Vec<_>>().join(" ")
352 );
353
354 let mut skel_builder = BpfSkelBuilder::default();
356 skel_builder.obj_builder.debug(opts.verbose);
357 let open_opts = opts.libbpf.clone().into_bpf_open_opts();
358 let mut skel = scx_ops_open!(skel_builder, open_object, forge_ops, open_opts)?;
359
360 if !compat::struct_has_field("sched_ext_ops", "cgroup_init").unwrap_or(false) {
361 warn!("kernel doesn't support ops.cgroup_init(), disabling");
362 skel.struct_ops.forge_ops_mut().cgroup_init = std::ptr::null_mut();
363 }
364 if !compat::struct_has_field("sched_ext_ops", "cgroup_exit").unwrap_or(false) {
365 warn!("kernel doesn't support ops.cgroup_exit(), disabling");
366 skel.struct_ops.forge_ops_mut().cgroup_exit = std::ptr::null_mut();
367 }
368 if !compat::struct_has_field("sched_ext_ops", "cgroup_set_weight").unwrap_or(false) {
369 warn!("kernel doesn't support ops.cgroup_set_weight(), disabling");
370 skel.struct_ops.forge_ops_mut().cgroup_set_weight = std::ptr::null_mut();
371 }
372 if !compat::struct_has_field("sched_ext_ops", "cgroup_move").unwrap_or(false) {
373 warn!("kernel doesn't support ops.cgroup_move(), disabling");
374 skel.struct_ops.forge_ops_mut().cgroup_move = std::ptr::null_mut();
375 }
376
377 skel.struct_ops.forge_ops_mut().exit_dump_len = opts.exit_dump_len;
378
379 let rodata = skel.maps.rodata_data.as_mut().unwrap();
381 rodata.slice_ns = opts.slice_us * 1000;
382 rodata.debug = opts.debug;
383 rodata.smt_enabled = smt_enabled;
384 rodata.topo_dsq = opts.dsq_topology.to_bpf();
385 rodata.ordering = opts.ordering.to_bpf();
386 rodata.idle_policy = opts.idle_policy.to_bpf();
387 rodata.no_wake_sync = opts.no_wake_sync;
388 rodata.preemption = opts.enable_preemption;
389
390 rodata.perf_config = opts.perf_config.event_id;
394 rodata.perf_threshold = opts.perf_threshold;
395 rodata.perf_sticky = opts.perf_sticky.event_id;
396 rodata.perf_sticky_threshold = opts.perf_sticky_threshold;
397
398 let primary_cpus = if let Some(ref domain) = opts.primary_domain {
400 match parse_cpu_list(domain) {
401 Ok(cpus) => cpus,
402 Err(e) => bail!("Error parsing primary domain: {}", e),
403 }
404 } else {
405 (0..*NR_CPU_IDS).collect()
406 };
407 if primary_cpus.len() < *NR_CPU_IDS {
408 info!("Primary CPUs: {:?}", primary_cpus);
409 rodata.primary_all = false;
410 } else {
411 rodata.primary_all = true;
412 }
413
414 let cpus: Vec<_> = topo.all_cpus.values().collect();
416 let max_cap = cpus
417 .iter()
418 .map(|cpu| cpu.cpu_capacity)
419 .max()
420 .unwrap_or(1)
421 .max(1);
422 let cpu_capacities: std::collections::HashMap<_, _> = cpus
423 .iter()
424 .map(|cpu| {
425 let normalized = (cpu.cpu_capacity * 1024 / max_cap).clamp(1, 1024);
426 (cpu.id, normalized as u64)
427 })
428 .collect();
429 rodata.all_cpus_same_capacity = cpus.iter().all(|cpu| cpu.cpu_capacity == max_cap);
430
431 let mut llc_id_to_dense = std::collections::HashMap::new();
433 for (dense_id, (_, llc)) in topo.all_llcs.iter().enumerate() {
434 llc_id_to_dense.insert(llc.id, dense_id);
435 }
436
437 skel.struct_ops.forge_ops_mut().flags = *compat::SCX_OPS_ENQ_EXITING
439 | *compat::SCX_OPS_ENQ_LAST
440 | *compat::SCX_OPS_ENQ_MIGRATION_DISABLED
441 | *compat::SCX_OPS_ALLOW_QUEUED_WAKEUP
442 | *compat::SCX_OPS_BUILTIN_IDLE_PER_NODE;
443 info!(
444 "scheduler flags: {:#x}",
445 skel.struct_ops.forge_ops_mut().flags
446 );
447
448 let mut skel = scx_ops_load!(skel, forge_ops, uei)?;
450
451 Self::init_topology_maps(
452 &mut skel,
453 &topo,
454 &llc_id_to_dense,
455 &cpu_capacities,
456 smt_enabled,
457 )?;
458
459 if smt_enabled {
461 Self::init_smt_domains(&mut skel, &topo)?;
462 }
463
464 if primary_cpus.len() < *NR_CPU_IDS {
466 for cpu in primary_cpus {
467 if let Err(err) = Self::enable_primary_cpu(&mut skel, cpu as i32) {
468 bail!("failed to add CPU {} to primary domain: error {}", cpu, err);
469 }
470 }
471 }
472
473 for cpu in topo.all_cpus.values() {
475 let dense_llc = llc_id_to_dense.get(&cpu.llc_id).copied().unwrap_or(0);
476 if opts.verbose {
477 let cap = cpu_capacities.get(&cpu.id).copied().unwrap_or(0);
478 info!(
479 "CPU{} -> node{} LLC{} (cpu capacity: {})",
480 cpu.id, cpu.node_id, dense_llc, cap
481 );
482 }
483 skel.maps.cpu_node_map.update(
484 &(cpu.id as u32).to_ne_bytes(),
485 &(cpu.node_id as u32).to_ne_bytes(),
486 MapFlags::ANY,
487 )?;
488 skel.maps.cpu_llc_map.update(
489 &(cpu.id as u32).to_ne_bytes(),
490 &(dense_llc as u32).to_ne_bytes(),
491 MapFlags::ANY,
492 )?;
493 }
494
495 if opts.perf_config.event_id > 0 || opts.perf_sticky.event_id > 0 {
500 let nr_cpus = *NR_CPU_IDS;
501 info!("Setting up performance counters for {} CPUs...", nr_cpus);
502 let sticky_counter_idx = if opts.perf_config.event_id > 0 { 1 } else { 0 };
503 for cpu in 0..nr_cpus {
504 if opts.perf_config.event_id > 0 {
505 setup_perf_events(&skel.maps.scx_pmu_map, cpu as i32, &opts.perf_config, 0)
506 .with_context(|| {
507 format!(
508 "setting up perf event '{}' on CPU {}",
509 opts.perf_config.display_name, cpu
510 )
511 })?;
512 }
513 if opts.perf_sticky.event_id > 0 {
514 setup_perf_events(
515 &skel.maps.scx_pmu_map,
516 cpu as i32,
517 &opts.perf_sticky,
518 sticky_counter_idx,
519 )
520 .with_context(|| {
521 format!(
522 "setting up sticky perf event '{}' on CPU {}",
523 opts.perf_sticky.display_name, cpu
524 )
525 })?;
526 }
527 }
528 info!("Performance counters configured for all CPUs");
529 }
530
531 info!("{SCHEDULER_NAME} can be used with scx-forge-agent for policy optimization");
532
533 let struct_ops = Some(scx_ops_attach!(skel, forge_ops, false)?);
535 let stats_server = StatsServer::new(stats::server_data()).launch()?;
536
537 Ok(Self {
538 skel,
539 struct_ops,
540 stats_server,
541 user_restart: false,
542 })
543 }
544
545 fn bytes_of<T>(value: &T) -> &[u8] {
546 unsafe {
547 std::slice::from_raw_parts(value as *const T as *const u8, std::mem::size_of::<T>())
548 }
549 }
550
551 fn topo_core_type(core_type: &CoreType) -> u32 {
552 match core_type {
553 CoreType::Big { turbo: true } => 1,
554 CoreType::Big { turbo: false } => 0,
555 CoreType::Little => 2,
556 }
557 }
558
559 fn topo_cpumask(mask: &Cpumask) -> Result<forge_topo_cpumask> {
560 let raw = mask.as_raw_slice();
561 if raw.len() > FORGE_TOPO_CPUMASK_WORDS_USIZE {
562 bail!(
563 "topology cpumask uses {} words, but scx_forge supports {}",
564 raw.len(),
565 FORGE_TOPO_CPUMASK_WORDS_USIZE
566 );
567 }
568
569 let mut topo_mask: forge_topo_cpumask = unsafe { std::mem::zeroed() };
570 topo_mask.bits[..raw.len()].copy_from_slice(raw);
571 Ok(topo_mask)
572 }
573
574 fn init_topology_maps(
575 skel: &mut BpfSkel<'_>,
576 topo: &Topology,
577 llc_id_to_dense: &std::collections::HashMap<usize, usize>,
578 cpu_capacities: &std::collections::HashMap<usize, u64>,
579 smt_enabled: bool,
580 ) -> Result<()> {
581 if topo.all_cpus.len() > FORGE_MAX_TOPO_DOMAINS_USIZE
582 || topo.all_cores.len() > FORGE_MAX_TOPO_DOMAINS_USIZE
583 || topo.all_llcs.len() > FORGE_MAX_TOPO_DOMAINS_USIZE
584 || topo.nodes.len() > FORGE_MAX_TOPO_DOMAINS_USIZE
585 {
586 bail!(
587 "topology exceeds scx_forge map limits: cpus={} cores={} llcs={} nodes={} max={}",
588 topo.all_cpus.len(),
589 topo.all_cores.len(),
590 topo.all_llcs.len(),
591 topo.nodes.len(),
592 FORGE_MAX_TOPO_DOMAINS_USIZE
593 );
594 }
595
596 let nr_distances = topo
597 .nodes
598 .values()
599 .map(|node| node.distance.len())
600 .sum::<usize>();
601 if nr_distances > FORGE_MAX_TOPO_DISTANCES_USIZE {
602 bail!(
603 "NUMA distance entries {} exceed scx_forge map limit {}",
604 nr_distances,
605 FORGE_MAX_TOPO_DISTANCES_USIZE
606 );
607 }
608
609 let mut topo_info: forge_topology = unsafe { std::mem::zeroed() };
610 topo_info.nr_cpu_ids = *NR_CPU_IDS as u32;
611 topo_info.nr_possible_cpus = *NR_CPUS_POSSIBLE as u32;
612 topo_info.nr_online_cpus = topo.all_cpus.len() as u32;
613 topo_info.nr_nodes = topo.nodes.len() as u32;
614 topo_info.nr_llcs = topo.all_llcs.len() as u32;
615 topo_info.nr_cores = topo.all_cores.len() as u32;
616 topo_info.nr_cpus = topo.all_cpus.len() as u32;
617 topo_info.smt_enabled = smt_enabled as u8;
618 topo_info.span = Self::topo_cpumask(&topo.span)?;
619 skel.maps.topo_info_map.update(
620 &0u32.to_ne_bytes(),
621 Self::bytes_of(&topo_info),
622 MapFlags::ANY,
623 )?;
624
625 for (idx, cpu_id) in topo.all_cpus.keys().enumerate() {
626 skel.maps.topo_cpu_ids.update(
627 &(idx as u32).to_ne_bytes(),
628 &(*cpu_id as u32).to_ne_bytes(),
629 MapFlags::ANY,
630 )?;
631 }
632 for (idx, core_id) in topo.all_cores.keys().enumerate() {
633 skel.maps.topo_core_ids.update(
634 &(idx as u32).to_ne_bytes(),
635 &(*core_id as u32).to_ne_bytes(),
636 MapFlags::ANY,
637 )?;
638 }
639 for (idx, llc_id) in topo.all_llcs.keys().enumerate() {
640 skel.maps.topo_llc_ids.update(
641 &(idx as u32).to_ne_bytes(),
642 &(*llc_id as u32).to_ne_bytes(),
643 MapFlags::ANY,
644 )?;
645 }
646 for (idx, node_id) in topo.nodes.keys().enumerate() {
647 skel.maps.topo_node_ids.update(
648 &(idx as u32).to_ne_bytes(),
649 &(*node_id as u32).to_ne_bytes(),
650 MapFlags::ANY,
651 )?;
652 }
653
654 for cpu in topo.all_cpus.values() {
655 let dense_llc = llc_id_to_dense.get(&cpu.llc_id).copied().unwrap_or(0);
656 let mut topo_cpu: forge_topo_cpu = unsafe { std::mem::zeroed() };
657 topo_cpu.id = cpu.id as u32;
658 topo_cpu.core_id = cpu.core_id as u32;
659 topo_cpu.llc_id = cpu.llc_id as u32;
660 topo_cpu.llc_dense_id = dense_llc as u32;
661 topo_cpu.node_id = cpu.node_id as u32;
662 topo_cpu.package_id = cpu.package_id as u32;
663 topo_cpu.cluster_id = cpu.cluster_id as i32;
664 topo_cpu.l2_id = cpu.l2_id as u32;
665 topo_cpu.l3_id = cpu.l3_id as u32;
666 topo_cpu.smt_level = cpu.smt_level as u32;
667 topo_cpu.core_type = Self::topo_core_type(&cpu.core_type);
668 topo_cpu.min_freq = cpu.min_freq as u64;
669 topo_cpu.max_freq = cpu.max_freq as u64;
670 topo_cpu.base_freq = cpu.base_freq as u64;
671 topo_cpu.cpu_capacity = cpu_capacities.get(&cpu.id).copied().unwrap_or(1);
672 topo_cpu.pm_qos_resume_latency_us = cpu.pm_qos_resume_latency_us as u64;
673 topo_cpu.trans_lat_ns = cpu.trans_lat_ns as u64;
674 topo_cpu.cache_size = cpu.cache_size as u64;
675 skel.maps.topo_cpu_map.update(
676 &(cpu.id as u32).to_ne_bytes(),
677 Self::bytes_of(&topo_cpu),
678 MapFlags::ANY,
679 )?;
680 }
681
682 for core in topo.all_cores.values() {
683 let dense_llc = llc_id_to_dense.get(&core.llc_id).copied().unwrap_or(0);
684 let mut topo_core: forge_topo_core = unsafe { std::mem::zeroed() };
685 topo_core.id = core.id as u32;
686 topo_core.kernel_id = core.kernel_id as u32;
687 topo_core.cluster_id = core.cluster_id as i32;
688 topo_core.llc_id = core.llc_id as u32;
689 topo_core.llc_dense_id = dense_llc as u32;
690 topo_core.node_id = core.node_id as u32;
691 topo_core.nr_cpus = core.cpus.len() as u32;
692 topo_core.core_type = Self::topo_core_type(&core.core_type);
693 topo_core.span = Self::topo_cpumask(&core.span)?;
694 skel.maps.topo_core_map.update(
695 &(core.id as u32).to_ne_bytes(),
696 Self::bytes_of(&topo_core),
697 MapFlags::ANY,
698 )?;
699 }
700
701 for llc in topo.all_llcs.values() {
702 let dense_llc = llc_id_to_dense.get(&llc.id).copied().unwrap_or(0);
703 let mut topo_llc: forge_topo_llc = unsafe { std::mem::zeroed() };
704 topo_llc.id = llc.id as u32;
705 topo_llc.kernel_id = llc.kernel_id as u32;
706 topo_llc.dense_id = dense_llc as u32;
707 topo_llc.node_id = llc.node_id as u32;
708 topo_llc.nr_cores = llc.cores.len() as u32;
709 topo_llc.nr_cpus = llc.all_cpus.len() as u32;
710 topo_llc.span = Self::topo_cpumask(&llc.span)?;
711 skel.maps.topo_llc_map.update(
712 &(llc.id as u32).to_ne_bytes(),
713 Self::bytes_of(&topo_llc),
714 MapFlags::ANY,
715 )?;
716 }
717
718 for node in topo.nodes.values() {
719 let mut topo_node: forge_topo_node = unsafe { std::mem::zeroed() };
720 topo_node.id = node.id as u32;
721 topo_node.nr_llcs = node.llcs.len() as u32;
722 topo_node.nr_cores = node.all_cores.len() as u32;
723 topo_node.nr_cpus = node.all_cpus.len() as u32;
724 topo_node.nr_distances = node.distance.len() as u32;
725 topo_node.span = Self::topo_cpumask(&node.span)?;
726 skel.maps.topo_node_map.update(
727 &(node.id as u32).to_ne_bytes(),
728 Self::bytes_of(&topo_node),
729 MapFlags::ANY,
730 )?;
731
732 for (distance_idx, distance) in node.distance.iter().enumerate() {
733 let key = forge_topo_distance_key {
734 node_id: node.id as u32,
735 distance_idx: distance_idx as u32,
736 };
737 skel.maps.topo_distance_map.update(
738 Self::bytes_of(&key),
739 &(*distance as u32).to_ne_bytes(),
740 MapFlags::ANY,
741 )?;
742 }
743 }
744
745 Ok(())
746 }
747
748 fn enable_primary_cpu(skel: &mut BpfSkel<'_>, cpu: i32) -> Result<(), u32> {
749 let prog = &mut skel.progs.enable_primary_cpu;
750 let mut args = cpu_arg {
751 cpu_id: cpu as c_int,
752 };
753 let input = ProgramInput {
754 context_in: Some(unsafe {
755 std::slice::from_raw_parts_mut(
756 &mut args as *mut _ as *mut u8,
757 std::mem::size_of_val(&args),
758 )
759 }),
760 ..Default::default()
761 };
762 let out = prog.test_run(input).unwrap();
763 if out.return_value != 0 {
764 return Err(out.return_value);
765 }
766
767 Ok(())
768 }
769
770 fn enable_sibling_cpu(
771 skel: &mut BpfSkel<'_>,
772 cpu: usize,
773 sibling_cpu: usize,
774 ) -> Result<(), u32> {
775 let prog = &mut skel.progs.enable_sibling_cpu;
776 let mut args = domain_arg {
777 cpu_id: cpu as c_int,
778 sibling_cpu_id: sibling_cpu as c_int,
779 };
780 let input = ProgramInput {
781 context_in: Some(unsafe {
782 std::slice::from_raw_parts_mut(
783 &mut args as *mut _ as *mut u8,
784 std::mem::size_of_val(&args),
785 )
786 }),
787 ..Default::default()
788 };
789 let out = prog.test_run(input).unwrap();
790 if out.return_value != 0 {
791 return Err(out.return_value as u32);
792 }
793 Ok(())
794 }
795
796 fn init_smt_domains(skel: &mut BpfSkel<'_>, topo: &Topology) -> Result<(), std::io::Error> {
797 let smt_siblings = topo.sibling_cpus();
798 info!("SMT sibling CPUs: {:?}", smt_siblings);
799 for (cpu, sibling_cpu) in smt_siblings.iter().enumerate() {
800 Self::enable_sibling_cpu(skel, cpu, *sibling_cpu as usize).map_err(|e| {
801 std::io::Error::new(
802 std::io::ErrorKind::Other,
803 format!("enable_sibling_cpu: {}", e),
804 )
805 })?;
806 }
807 Ok(())
808 }
809
810 fn get_metrics(&self) -> Metrics {
811 let bss_data = self.skel.maps.bss_data.as_ref().unwrap();
812 Metrics {
813 nr_direct_dispatches: bss_data.nr_direct_dispatches,
814 nr_enqueues: bss_data.nr_enqueues,
815 nr_preempt_dispatches: bss_data.nr_preempt_dispatches,
816 nr_local_dispatches: bss_data.nr_local_dispatches,
817 nr_remote_dispatches: bss_data.nr_remote_dispatches,
818 nr_llc_dispatches: bss_data.nr_llc_dispatches,
819 nr_node_dispatches: bss_data.nr_node_dispatches,
820 nr_global_dispatches: bss_data.nr_global_dispatches,
821 nr_dequeues: bss_data.nr_dequeues,
822 nr_dispatch_dequeues: bss_data.nr_dispatch_dequeues,
823 nr_sched_change_dequeues: bss_data.nr_sched_change_dequeues,
824 nr_task_state_errors: bss_data.nr_task_state_errors,
825 nr_event_dispatches: bss_data.nr_event_dispatches,
826 nr_ev_sticky_dispatches: bss_data.nr_ev_sticky_dispatches,
827 }
828 }
829
830 pub fn exited(&mut self) -> bool {
831 uei_exited!(&self.skel, uei)
832 }
833
834 fn run(&mut self, shutdown: Arc<AtomicBool>) -> Result<UserExitInfo> {
835 let (res_ch, req_ch) = self.stats_server.channels();
836 while !shutdown.load(Ordering::Relaxed) && !self.exited() {
837 match req_ch.recv_timeout(Duration::from_secs(1)) {
838 Ok(()) => res_ch.send(self.get_metrics())?,
839 Err(RecvTimeoutError::Timeout) => {}
840 Err(e) => Err(e)?,
841 }
842 }
843
844 let _ = self.struct_ops.take();
845 uei_report!(&self.skel, uei)
846 }
847}
848
849impl Drop for Scheduler<'_> {
850 fn drop(&mut self) {
851 info!("Unregister {SCHEDULER_NAME} scheduler");
852 }
853}
854
855fn main() -> Result<()> {
856 let opts = Opts::parse();
857
858 if opts.version {
859 println!(
860 "{} {}",
861 SCHEDULER_NAME,
862 build_id::full_version(env!("CARGO_PKG_VERSION"))
863 );
864 return Ok(());
865 }
866
867 if opts.help_stats {
868 stats::server_data().describe_meta(&mut std::io::stdout(), None)?;
869 return Ok(());
870 }
871
872 let loglevel = simplelog::LevelFilter::Info;
873
874 let mut lcfg = simplelog::ConfigBuilder::new();
875 lcfg.set_time_offset_to_local()
876 .expect("Failed to set local time offset")
877 .set_time_level(simplelog::LevelFilter::Error)
878 .set_location_level(simplelog::LevelFilter::Off)
879 .set_target_level(simplelog::LevelFilter::Off)
880 .set_thread_level(simplelog::LevelFilter::Off);
881 simplelog::TermLogger::init(
882 loglevel,
883 lcfg.build(),
884 simplelog::TerminalMode::Stderr,
885 simplelog::ColorChoice::Auto,
886 )?;
887
888 let shutdown = Arc::new(AtomicBool::new(false));
889 let shutdown_clone = shutdown.clone();
890 ctrlc::set_handler(move || {
891 shutdown_clone.store(true, Ordering::Relaxed);
892 })
893 .context("Error setting Ctrl-C handler")?;
894
895 if let Some(intv) = opts.monitor.or(opts.stats) {
896 let shutdown_copy = shutdown.clone();
897 let jh = std::thread::spawn(move || {
898 match stats::monitor(Duration::from_secs_f64(intv), shutdown_copy) {
899 Ok(_) => {
900 debug!("stats monitor thread finished successfully")
901 }
902 Err(error_object) => {
903 warn!(
904 "stats monitor thread finished because of an error {}",
905 error_object
906 )
907 }
908 }
909 });
910 if opts.monitor.is_some() {
911 let _ = jh.join();
912 return Ok(());
913 }
914 }
915
916 let mut open_object = MaybeUninit::uninit();
917 loop {
918 let mut sched = Scheduler::init(&opts, &mut open_object)?;
919 if !sched.run(shutdown.clone())?.should_restart() {
920 if sched.user_restart {
921 continue;
922 }
923 break;
924 }
925 }
926
927 Ok(())
928}