1use crate::Cpumask;
73use crate::cpumask::read_cpulist;
74use crate::misc::read_file_byte;
75use crate::misc::read_file_usize_vec;
76use crate::misc::read_from_file;
77use anyhow::Result;
78use anyhow::bail;
79use glob::glob;
80use sscanf::sscanf;
81use std::collections::BTreeMap;
82use std::path::Path;
83use std::path::PathBuf;
84use std::sync::Arc;
85
86#[cfg(feature = "gpu-topology")]
87use crate::gpu::{Gpu, GpuIndex, create_gpus};
88
89lazy_static::lazy_static! {
90 pub static ref NR_CPU_IDS: usize = read_cpu_ids().unwrap().last().unwrap() + 1;
97
98 pub static ref NR_CPUS_POSSIBLE: usize = libbpf_rs::num_possible_cpus().unwrap();
105}
106
107#[derive(Debug, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
108pub enum CoreType {
109 Big { turbo: bool },
110 Little,
111}
112
113#[derive(Debug, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
114pub struct Cpu {
115 pub id: usize,
116 pub min_freq: usize,
117 pub max_freq: usize,
118 pub base_freq: usize,
121 pub cpu_capacity: usize,
123 pub smt_level: usize,
124 pub pm_qos_resume_latency_us: usize,
126 pub trans_lat_ns: usize,
127 pub l2_id: usize,
128 pub l3_id: usize,
129 pub cache_size: usize,
131 pub core_type: CoreType,
132
133 pub core_id: usize,
135 pub llc_id: usize,
136 pub node_id: usize,
137 pub package_id: usize,
138 pub cluster_id: isize,
139}
140
141#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
142pub struct Core {
143 pub id: usize,
145 pub kernel_id: usize,
147 pub cluster_id: isize,
148 pub cpus: BTreeMap<usize, Arc<Cpu>>,
149 pub span: Cpumask,
151 pub core_type: CoreType,
152
153 pub llc_id: usize,
155 pub node_id: usize,
156}
157
158#[derive(Debug, Clone)]
159pub struct Llc {
160 pub id: usize,
162 pub kernel_id: usize,
164 pub cores: BTreeMap<usize, Arc<Core>>,
165 pub span: Cpumask,
167
168 pub node_id: usize,
170
171 pub all_cpus: BTreeMap<usize, Arc<Cpu>>,
173}
174
175#[derive(Debug, Clone)]
176pub struct Node {
177 pub id: usize,
178 pub distance: Vec<usize>,
179 pub llcs: BTreeMap<usize, Arc<Llc>>,
180 pub span: Cpumask,
182
183 pub all_cores: BTreeMap<usize, Arc<Core>>,
185 pub all_cpus: BTreeMap<usize, Arc<Cpu>>,
186
187 #[cfg(feature = "gpu-topology")]
188 pub gpus: BTreeMap<GpuIndex, Gpu>,
189}
190
191#[derive(Debug)]
192pub struct Topology {
193 pub nodes: BTreeMap<usize, Node>,
194 pub span: Cpumask,
196 pub smt_enabled: bool,
198
199 pub all_llcs: BTreeMap<usize, Arc<Llc>>,
201 pub all_cores: BTreeMap<usize, Arc<Core>>,
202 pub all_cpus: BTreeMap<usize, Arc<Cpu>>,
203}
204
205impl Topology {
206 fn instantiate(span: Cpumask, mut nodes: BTreeMap<usize, Node>) -> Result<Self> {
207 let mut topo_llcs = BTreeMap::new();
211 let mut topo_cores = BTreeMap::new();
212 let mut topo_cpus = BTreeMap::new();
213
214 for (_node_id, node) in nodes.iter_mut() {
215 let mut node_cores = BTreeMap::new();
216 let mut node_cpus = BTreeMap::new();
217
218 for (&llc_id, llc) in node.llcs.iter_mut() {
219 let llc_mut = Arc::get_mut(llc).unwrap();
220 let mut llc_cpus = BTreeMap::new();
221
222 for (&core_id, core) in llc_mut.cores.iter_mut() {
223 let core_mut = Arc::get_mut(core).unwrap();
224 let smt_level = core_mut.cpus.len();
225
226 for (&cpu_id, cpu) in core_mut.cpus.iter_mut() {
227 let cpu_mut = Arc::get_mut(cpu).unwrap();
228 cpu_mut.smt_level = smt_level;
229
230 if topo_cpus
231 .insert(cpu_id, cpu.clone())
232 .or(node_cpus.insert(cpu_id, cpu.clone()))
233 .or(llc_cpus.insert(cpu_id, cpu.clone()))
234 .is_some()
235 {
236 bail!("Duplicate CPU ID {}", cpu_id);
237 }
238 }
239
240 topo_cores
243 .insert(core_id, core.clone())
244 .or(node_cores.insert(core_id, core.clone()));
245 }
246
247 llc_mut.all_cpus = llc_cpus;
248
249 if topo_llcs.insert(llc_id, llc.clone()).is_some() {
250 bail!("Duplicate LLC ID {}", llc_id);
251 }
252 }
253
254 node.all_cores = node_cores;
255 node.all_cpus = node_cpus;
256 }
257
258 Ok(Topology {
259 nodes,
260 span,
261 smt_enabled: is_smt_active().unwrap_or(false),
262 all_llcs: topo_llcs,
263 all_cores: topo_cores,
264 all_cpus: topo_cpus,
265 })
266 }
267
268 pub fn new() -> Result<Topology> {
270 let span = cpus_online()?;
271 let mut topo_ctx = TopoCtx::new();
272 let nodes = if Path::new("/sys/devices/system/node").exists() {
276 create_numa_nodes(&span, &mut topo_ctx)?
277 } else {
278 create_default_node(&span, &mut topo_ctx, false)?
279 };
280
281 Self::instantiate(span, nodes)
282 }
283
284 pub fn with_flattened_llc_node() -> Result<Topology> {
285 let span = cpus_online()?;
286 let mut topo_ctx = TopoCtx::new();
287 let nodes = create_default_node(&span, &mut topo_ctx, true)?;
288 Self::instantiate(span, nodes)
289 }
290
291 #[cfg(feature = "gpu-topology")]
293 pub fn gpus(&self) -> BTreeMap<GpuIndex, &Gpu> {
294 let mut gpus = BTreeMap::new();
295 for node in self.nodes.values() {
296 for (idx, gpu) in &node.gpus {
297 gpus.insert(*idx, gpu);
298 }
299 }
300 gpus
301 }
302
303 pub fn has_little_cores(&self) -> bool {
305 self.all_cores
306 .values()
307 .any(|c| c.core_type == CoreType::Little)
308 }
309
310 pub fn sibling_cpus(&self) -> Vec<i32> {
317 let mut sibling_cpu = vec![-1i32; *NR_CPUS_POSSIBLE];
318 for core in self.all_cores.values() {
319 let mut first = -1i32;
320 for &cpu in core.cpus.keys() {
321 if first < 0 {
322 first = cpu as i32;
323 } else {
324 sibling_cpu[first as usize] = cpu as i32;
325 sibling_cpu[cpu] = first;
326 break;
327 }
328 }
329 }
330 sibling_cpu
331 }
332}
333
334struct TopoCtx {
339 node_core_kernel_ids: BTreeMap<(usize, usize, usize), usize>,
341 node_llc_kernel_ids: BTreeMap<(usize, usize, usize), usize>,
343 l2_ids: BTreeMap<String, usize>,
345 l3_ids: BTreeMap<String, usize>,
347}
348
349impl TopoCtx {
350 fn new() -> TopoCtx {
351 let core_kernel_ids = BTreeMap::new();
352 let llc_kernel_ids = BTreeMap::new();
353 let l2_ids = BTreeMap::new();
354 let l3_ids = BTreeMap::new();
355 TopoCtx {
356 node_core_kernel_ids: core_kernel_ids,
357 node_llc_kernel_ids: llc_kernel_ids,
358 l2_ids,
359 l3_ids,
360 }
361 }
362}
363
364fn cpus_online() -> Result<Cpumask> {
365 let path = "/sys/devices/system/cpu/online";
366 let online = std::fs::read_to_string(path)?;
367 Cpumask::from_cpulist(&online)
368}
369
370fn get_cache_id(topo_ctx: &mut TopoCtx, cache_level_path: &PathBuf, cache_level: usize) -> usize {
371 let id_map = match cache_level {
373 2 => &mut topo_ctx.l2_ids,
374 3 => &mut topo_ctx.l3_ids,
375 _ => return usize::MAX,
376 };
377
378 let path = &cache_level_path.join("shared_cpu_list");
379 let key = match std::fs::read_to_string(path) {
380 Ok(key) => key,
381 Err(_) => return usize::MAX,
382 };
383
384 let id = *id_map.get(&key).unwrap_or(&usize::MAX);
385 if id != usize::MAX {
386 return id;
387 }
388
389 let id = read_from_file(&cache_level_path.join("id")).unwrap_or(usize::MAX);
391 if id != usize::MAX {
392 id_map.insert(key, id);
394 return id;
395 }
396
397 let id = id_map.len();
399 id_map.insert(key, id);
400
401 id
402}
403
404fn get_per_cpu_cache_size(cache_path: &PathBuf) -> Result<usize> {
405 let path_str = cache_path.to_str().unwrap();
406 let paths = glob(&(path_str.to_owned() + "/index[0-9]*"))?;
407 let mut tot_size = 0;
408
409 for index in paths.filter_map(Result::ok) {
410 let size = read_file_byte(&index.join("size")).unwrap_or(1024_usize);
415 let cpulist: String = read_from_file(&index.join("shared_cpu_list"))?;
416 let num_cpus = read_cpulist(&cpulist)?.len();
417 tot_size += size / num_cpus;
418 }
419
420 Ok(tot_size)
421}
422
423#[allow(clippy::too_many_arguments)]
424fn create_insert_cpu(
425 id: usize,
426 node: &mut Node,
427 online_mask: &Cpumask,
428 topo_ctx: &mut TopoCtx,
429 big_little: bool,
430 avg_cpu_freq: Option<(usize, usize)>,
431 capacity_src: Option<(String, usize, usize)>,
432 flatten_llc: bool,
433) -> Result<()> {
434 if !online_mask.test_cpu(id) {
438 return Ok(());
439 }
440
441 let cpu_str = format!("/sys/devices/system/cpu/cpu{}", id);
442 let cpu_path = Path::new(&cpu_str);
443
444 let top_path = cpu_path.join("topology");
446 let core_kernel_id = read_from_file(&top_path.join("core_id"))?;
447 let package_id = read_from_file(&top_path.join("physical_package_id"))?;
448 let cluster_id = read_from_file(&top_path.join("cluster_id"))?;
449
450 let cache_path = cpu_path.join("cache");
456 let l2_id = get_cache_id(topo_ctx, &cache_path.join(format!("index{}", 2)), 2);
457 let l3_id = get_cache_id(topo_ctx, &cache_path.join(format!("index{}", 3)), 3);
458 let llc_kernel_id = if flatten_llc {
459 0
460 } else if l3_id == usize::MAX {
461 l2_id
462 } else {
463 l3_id
464 };
465
466 let cache_size = get_per_cpu_cache_size(&cache_path).unwrap_or(0_usize);
468
469 let freq_path = cpu_path.join("cpufreq");
472 let min_freq = read_from_file(&freq_path.join("scaling_min_freq")).unwrap_or(0_usize);
473 let max_freq = read_from_file(&freq_path.join("scaling_max_freq")).unwrap_or(0_usize);
474 let base_freq = read_from_file(&freq_path.join("base_frequency")).unwrap_or(max_freq);
475 let trans_lat_ns =
476 read_from_file(&freq_path.join("cpuinfo_transition_latency")).unwrap_or(0_usize);
477
478 let (cap_suffix, _avg_rcap, max_rcap) = capacity_src.unwrap_or(("".to_string(), 1024, 1024));
480 let cap_path = cpu_path.join(cap_suffix);
481 let rcap = read_from_file(&cap_path).unwrap_or(max_rcap);
482 let cpu_capacity = (rcap * 1024) / max_rcap;
483
484 let power_path = cpu_path.join("power");
486 let pm_qos_resume_latency_us =
487 read_from_file(&power_path.join("pm_qos_resume_latency_us")).unwrap_or(0_usize);
488
489 let num_llcs = topo_ctx.node_llc_kernel_ids.len();
490 let llc_id = topo_ctx
491 .node_llc_kernel_ids
492 .entry((node.id, package_id, llc_kernel_id))
493 .or_insert(num_llcs);
494
495 let llc = node.llcs.entry(*llc_id).or_insert(Arc::new(Llc {
496 id: *llc_id,
497 cores: BTreeMap::new(),
498 span: Cpumask::new(),
499 all_cpus: BTreeMap::new(),
500
501 node_id: node.id,
502 kernel_id: llc_kernel_id,
503 }));
504 let llc_mut = Arc::get_mut(llc).unwrap();
505
506 let core_type = if !big_little {
507 CoreType::Big { turbo: false }
508 } else {
509 match avg_cpu_freq {
510 Some((avg_base_freq, top_max_freq)) => {
511 if max_freq == top_max_freq {
512 CoreType::Big { turbo: true }
513 } else if base_freq >= avg_base_freq {
514 CoreType::Big { turbo: false }
515 } else {
516 CoreType::Little
517 }
518 }
519 None => CoreType::Big { turbo: false },
520 }
521 };
522
523 let num_cores = topo_ctx.node_core_kernel_ids.len();
524 let core_id = topo_ctx
525 .node_core_kernel_ids
526 .entry((node.id, package_id, core_kernel_id))
527 .or_insert(num_cores);
528
529 let core = llc_mut.cores.entry(*core_id).or_insert(Arc::new(Core {
530 id: *core_id,
531 cpus: BTreeMap::new(),
532 span: Cpumask::new(),
533 core_type: core_type.clone(),
534
535 llc_id: *llc_id,
536 node_id: node.id,
537 kernel_id: core_kernel_id,
538 cluster_id: cluster_id,
539 }));
540 let core_mut = Arc::get_mut(core).unwrap();
541
542 core_mut.cpus.insert(
543 id,
544 Arc::new(Cpu {
545 id,
546 min_freq,
547 max_freq,
548 base_freq,
549 cpu_capacity,
550 smt_level: 0, pm_qos_resume_latency_us,
552 trans_lat_ns,
553 l2_id,
554 l3_id,
555 cache_size,
556 core_type: core_type.clone(),
557
558 core_id: *core_id,
559 llc_id: *llc_id,
560 node_id: node.id,
561 package_id,
562 cluster_id,
563 }),
564 );
565
566 if node.span.test_cpu(id) {
567 bail!("Node {} already had CPU {}", node.id, id);
568 }
569
570 core_mut.span.set_cpu(id)?;
572 llc_mut.span.set_cpu(id)?;
573 node.span.set_cpu(id)?;
574
575 Ok(())
576}
577
578fn read_cpu_ids() -> Result<Vec<usize>> {
579 let mut cpu_ids = vec![];
580 let cpu_paths = glob("/sys/devices/system/cpu/cpu[0-9]*")?;
581 for cpu_path in cpu_paths.filter_map(Result::ok) {
582 let cpu_str = cpu_path.to_str().unwrap().trim();
583 match sscanf!(cpu_str, "/sys/devices/system/cpu/cpu{usize}") {
584 Ok(val) => cpu_ids.push(val),
585 Err(_) => {
586 bail!("Failed to parse cpu ID {}", cpu_str);
587 }
588 }
589 }
590 cpu_ids.sort();
591 Ok(cpu_ids)
592}
593
594fn cpu_capacity_source() -> Option<(String, usize, usize)> {
595 let sources = [
598 "cpufreq/amd_pstate_highest_perf",
599 "acpi_cppc/highest_perf",
600 "cpu_capacity",
601 "cpufreq/cpuinfo_max_freq",
602 ];
603
604 let prefix = "/sys/devices/system/cpu/cpu0";
606 let mut raw_capacity = 0;
607 let mut suffix = sources[sources.len() - 1];
608 for src in sources {
609 let path_str = [prefix, src].join("/");
610 let path = Path::new(&path_str);
611 raw_capacity = read_from_file(&path).unwrap_or(0_usize);
612 if raw_capacity > 0 {
613 suffix = src;
614 break;
615 }
616 }
617 if raw_capacity == 0 {
618 return None;
619 }
620
621 let mut max_raw_capacity = 0;
623 let mut avg_raw_capacity = 0;
624 let mut nr_cpus = 0;
625 let cpu_paths = glob("/sys/devices/system/cpu/cpu[0-9]*").ok()?;
626 for cpu_path in cpu_paths.filter_map(Result::ok) {
627 let raw_capacity = read_from_file(&cpu_path.join(suffix)).unwrap_or(0_usize);
628 if max_raw_capacity < raw_capacity {
629 max_raw_capacity = raw_capacity;
630 }
631 avg_raw_capacity += raw_capacity;
632 nr_cpus += 1;
633 }
634 if max_raw_capacity == 0 {
635 return None;
636 }
637
638 Some((
639 suffix.to_string(),
640 avg_raw_capacity / nr_cpus,
641 max_raw_capacity,
642 ))
643}
644
645fn avg_cpu_freq() -> Option<(usize, usize)> {
647 let mut top_max_freq = 0;
648 let mut avg_base_freq = 0;
649 let mut nr_cpus = 0;
650 let cpu_paths = glob("/sys/devices/system/cpu/cpu[0-9]*").ok()?;
651 for cpu_path in cpu_paths.filter_map(Result::ok) {
652 let freq_path = cpu_path.join("cpufreq");
653 let max_freq = read_from_file(&freq_path.join("scaling_max_freq")).unwrap_or(0_usize);
654 let base_freq = read_from_file(&freq_path.join("base_frequency")).unwrap_or(max_freq);
655 if base_freq > 0 {
656 if max_freq > top_max_freq {
657 top_max_freq = max_freq;
658 }
659 avg_base_freq += base_freq;
660 nr_cpus += 1;
661 }
662 }
663 if avg_base_freq == 0 {
664 return None;
665 }
666 Some((avg_base_freq / nr_cpus, top_max_freq))
667}
668
669fn has_big_little() -> Option<bool> {
670 let mut clusters = std::collections::HashSet::new();
671
672 let cpu_paths = glob("/sys/devices/system/cpu/cpu[0-9]*").ok()?;
673 for cpu_path in cpu_paths.filter_map(Result::ok) {
674 let top_path = cpu_path.join("topology");
675 let cluster_id = read_from_file(&top_path.join("cluster_id")).unwrap_or(-1);
676 clusters.insert(cluster_id);
677 }
678
679 Some(clusters.len() > 1)
680}
681
682fn is_smt_active() -> Option<bool> {
683 let smt_on: u8 = read_from_file(Path::new("/sys/devices/system/cpu/smt/active")).ok()?;
684 Some(smt_on == 1)
685}
686
687fn create_default_node(
688 online_mask: &Cpumask,
689 topo_ctx: &mut TopoCtx,
690 flatten_llc: bool,
691) -> Result<BTreeMap<usize, Node>> {
692 let mut nodes = BTreeMap::<usize, Node>::new();
693
694 let mut node = Node {
695 id: 0,
696 distance: vec![],
697 llcs: BTreeMap::new(),
698 span: Cpumask::new(),
699 #[cfg(feature = "gpu-topology")]
700 gpus: BTreeMap::new(),
701 all_cores: BTreeMap::new(),
702 all_cpus: BTreeMap::new(),
703 };
704
705 #[cfg(feature = "gpu-topology")]
706 {
707 let system_gpus = create_gpus();
708 if let Some(gpus) = system_gpus.get(&0) {
709 for gpu in gpus {
710 node.gpus.insert(gpu.index, gpu.clone());
711 }
712 }
713 }
714
715 if !Path::new("/sys/devices/system/cpu").exists() {
716 bail!("/sys/devices/system/cpu sysfs node not found");
717 }
718
719 let capacity_src = cpu_capacity_source();
720 let avg_cpu_freq = avg_cpu_freq();
721 let big_little = has_big_little().unwrap_or(false);
722 let cpu_ids = read_cpu_ids()?;
723 for cpu_id in cpu_ids.iter() {
724 create_insert_cpu(
725 *cpu_id,
726 &mut node,
727 online_mask,
728 topo_ctx,
729 big_little,
730 avg_cpu_freq,
731 capacity_src.clone(),
732 flatten_llc,
733 )?;
734 }
735
736 nodes.insert(node.id, node);
737
738 Ok(nodes)
739}
740
741fn create_numa_nodes(
742 online_mask: &Cpumask,
743 topo_ctx: &mut TopoCtx,
744) -> Result<BTreeMap<usize, Node>> {
745 let mut nodes = BTreeMap::<usize, Node>::new();
746
747 #[cfg(feature = "gpu-topology")]
748 let system_gpus = create_gpus();
749
750 let numa_paths = glob("/sys/devices/system/node/node*")?;
751 for numa_path in numa_paths.filter_map(Result::ok) {
752 let numa_str = numa_path.to_str().unwrap().trim();
753 let node_id = match sscanf!(numa_str, "/sys/devices/system/node/node{usize}") {
754 Ok(val) => val,
755 Err(_) => {
756 bail!("Failed to parse NUMA node ID {}", numa_str);
757 }
758 };
759 let distance = read_file_usize_vec(
760 Path::new(&format!(
761 "/sys/devices/system/node/node{}/distance",
762 node_id
763 )),
764 ' ',
765 )?;
766 let mut node = Node {
767 id: node_id,
768 distance,
769 llcs: BTreeMap::new(),
770 span: Cpumask::new(),
771
772 all_cores: BTreeMap::new(),
773 all_cpus: BTreeMap::new(),
774
775 #[cfg(feature = "gpu-topology")]
776 gpus: BTreeMap::new(),
777 };
778
779 #[cfg(feature = "gpu-topology")]
780 {
781 if let Some(gpus) = system_gpus.get(&node_id) {
782 for gpu in gpus {
783 node.gpus.insert(gpu.index, gpu.clone());
784 }
785 }
786 }
787
788 let cpu_pattern = numa_path.join("cpu[0-9]*");
789 let cpu_paths = glob(cpu_pattern.to_string_lossy().as_ref())?;
790 let big_little = has_big_little().unwrap_or(false);
791 let capacity_src = cpu_capacity_source();
792 let avg_cpu_freq = avg_cpu_freq();
793 let mut cpu_ids = vec![];
794 for cpu_path in cpu_paths.filter_map(Result::ok) {
795 let cpu_str = cpu_path.to_str().unwrap().trim();
796 let cpu_id = match sscanf!(cpu_str, "/sys/devices/system/node/node{usize}/cpu{usize}") {
797 Ok((_, val)) => val,
798 Err(_) => {
799 bail!("Failed to parse cpu ID {}", cpu_str);
800 }
801 };
802 cpu_ids.push(cpu_id);
803 }
804 cpu_ids.sort();
805
806 for cpu_id in cpu_ids {
807 create_insert_cpu(
808 cpu_id,
809 &mut node,
810 online_mask,
811 topo_ctx,
812 big_little,
813 avg_cpu_freq,
814 capacity_src.clone(),
815 false,
816 )?;
817 }
818
819 nodes.insert(node.id, node);
820 }
821 Ok(nodes)
822}