1use crate::compat::ROOT_PREFIX;
73use crate::cpumask::read_cpulist;
74use crate::misc::find_best_split_size;
75use crate::misc::read_file_byte;
76use crate::misc::read_file_usize_vec;
77use crate::misc::read_from_file;
78use crate::Cpumask;
79use anyhow::bail;
80use anyhow::Result;
81use glob::glob;
82use log::info;
83use log::warn;
84use sscanf::sscanf;
85use std::cmp::min;
86use std::collections::BTreeMap;
87use std::io::Write;
88use std::path::Path;
89use std::sync::Arc;
90
91#[cfg(feature = "gpu-topology")]
92use crate::gpu::{create_gpus, Gpu, GpuIndex};
93
94lazy_static::lazy_static! {
95 pub static ref NR_CPU_IDS: usize = read_cpu_ids().unwrap().last().unwrap() + 1;
102
103 pub static ref NR_CPUS_POSSIBLE: usize = libbpf_rs::num_possible_cpus().unwrap();
110
111 pub static ref NR_PARTITION_MIN_CORES: usize = 2;
117 pub static ref NR_PARTITION_MAX_CORES: usize = 8;
118}
119
120#[derive(Debug, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
121pub enum CoreType {
122 Big { turbo: bool },
123 Little,
124}
125
126#[derive(Debug, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
127pub enum Powermode {
128 Turbo,
129 Performance,
130 Powersave,
131 Any,
132}
133
134pub fn get_primary_cpus(mode: Powermode) -> std::io::Result<Vec<usize>> {
142 let topo = Topology::new().unwrap();
143
144 let cpus: Vec<usize> = topo
145 .all_cores
146 .values()
147 .flat_map(|core| &core.cpus)
148 .filter_map(|(cpu_id, cpu)| match (&mode, &cpu.core_type) {
149 (Powermode::Turbo, CoreType::Big { turbo: true })
150 | (Powermode::Performance, CoreType::Big { .. })
151 | (Powermode::Powersave, CoreType::Little) => Some(*cpu_id),
152 (Powermode::Any, ..) => Some(*cpu_id),
153 _ => None,
154 })
155 .collect();
156
157 Ok(cpus)
158}
159
160#[derive(Debug, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
161pub struct Cpu {
162 pub id: usize,
163 pub min_freq: usize,
164 pub max_freq: usize,
165 pub base_freq: usize,
168 pub cpu_capacity: usize,
170 pub smt_level: usize,
171 pub pm_qos_resume_latency_us: usize,
173 pub trans_lat_ns: usize,
174 pub l2_id: usize,
175 pub l3_id: usize,
176 pub cache_size: usize,
178 pub core_type: CoreType,
179
180 pub core_id: usize,
182 pub llc_id: usize,
183 pub node_id: usize,
184 pub package_id: usize,
185 pub cluster_id: isize,
186}
187
188#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
189pub struct Core {
190 pub id: usize,
192 pub kernel_id: usize,
194 pub cluster_id: isize,
195 pub cpus: BTreeMap<usize, Arc<Cpu>>,
196 pub span: Cpumask,
198 pub core_type: CoreType,
199
200 pub llc_id: usize,
202 pub node_id: usize,
203}
204
205#[derive(Debug, Clone)]
206pub struct Llc {
207 pub id: usize,
209 pub kernel_id: usize,
211 pub cores: BTreeMap<usize, Arc<Core>>,
212 pub span: Cpumask,
214
215 pub node_id: usize,
217
218 pub all_cpus: BTreeMap<usize, Arc<Cpu>>,
220}
221
222#[derive(Debug, Clone)]
223pub struct Node {
224 pub id: usize,
225 pub distance: Vec<usize>,
226 pub llcs: BTreeMap<usize, Arc<Llc>>,
227 pub span: Cpumask,
229
230 pub all_cores: BTreeMap<usize, Arc<Core>>,
232 pub all_cpus: BTreeMap<usize, Arc<Cpu>>,
233
234 #[cfg(feature = "gpu-topology")]
235 pub gpus: BTreeMap<GpuIndex, Gpu>,
236}
237
238#[derive(Debug)]
239pub struct Topology {
240 pub nodes: BTreeMap<usize, Node>,
241 pub span: Cpumask,
243 pub smt_enabled: bool,
245
246 pub all_llcs: BTreeMap<usize, Arc<Llc>>,
248 pub all_cores: BTreeMap<usize, Arc<Core>>,
249 pub all_cpus: BTreeMap<usize, Arc<Cpu>>,
250}
251
252impl Topology {
253 fn instantiate(span: Cpumask, mut nodes: BTreeMap<usize, Node>) -> Result<Self> {
254 let mut topo_llcs = BTreeMap::new();
258 let mut topo_cores = BTreeMap::new();
259 let mut topo_cpus = BTreeMap::new();
260
261 for (_node_id, node) in nodes.iter_mut() {
262 let mut node_cores = BTreeMap::new();
263 let mut node_cpus = BTreeMap::new();
264
265 for (&llc_id, llc) in node.llcs.iter_mut() {
266 let llc_mut = Arc::get_mut(llc).unwrap();
267 let mut llc_cpus = BTreeMap::new();
268
269 for (&core_id, core) in llc_mut.cores.iter_mut() {
270 let core_mut = Arc::get_mut(core).unwrap();
271 let smt_level = core_mut.cpus.len();
272
273 for (&cpu_id, cpu) in core_mut.cpus.iter_mut() {
274 let cpu_mut = Arc::get_mut(cpu).unwrap();
275 cpu_mut.smt_level = smt_level;
276
277 if topo_cpus
278 .insert(cpu_id, cpu.clone())
279 .or(node_cpus.insert(cpu_id, cpu.clone()))
280 .or(llc_cpus.insert(cpu_id, cpu.clone()))
281 .is_some()
282 {
283 bail!("Duplicate CPU ID {}", cpu_id);
284 }
285 }
286
287 topo_cores
290 .insert(core_id, core.clone())
291 .or(node_cores.insert(core_id, core.clone()));
292 }
293
294 llc_mut.all_cpus = llc_cpus;
295
296 if topo_llcs.insert(llc_id, llc.clone()).is_some() {
297 bail!("Duplicate LLC ID {}", llc_id);
298 }
299 }
300
301 node.all_cores = node_cores;
302 node.all_cpus = node_cpus;
303 }
304
305 Ok(Topology {
306 nodes,
307 span,
308 smt_enabled: is_smt_active().unwrap_or(false),
309 all_llcs: topo_llcs,
310 all_cores: topo_cores,
311 all_cpus: topo_cpus,
312 })
313 }
314
315 pub fn new() -> Result<Topology> {
317 Self::with_virt_llcs(None)
318 }
319
320 pub fn with_virt_llcs(nr_cores_per_vllc: Option<(usize, usize)>) -> Result<Topology> {
321 let span = cpus_online()?;
322 let mut topo_ctx = TopoCtx::new();
323
324 let path = format!("{}/sys/devices/system/node", *ROOT_PREFIX);
328 let nodes = if Path::new(&path).exists() {
329 create_numa_nodes(&span, &mut topo_ctx, nr_cores_per_vllc)?
330 } else {
331 create_default_node(&span, &mut topo_ctx, false, nr_cores_per_vllc)?
332 };
333
334 Self::instantiate(span, nodes)
335 }
336
337 pub fn with_flattened_llc_node() -> Result<Topology> {
338 let span = cpus_online()?;
339 let mut topo_ctx = TopoCtx::new();
340 let nodes = create_default_node(&span, &mut topo_ctx, true, None)?;
341 Self::instantiate(span, nodes)
342 }
343
344 pub fn with_args(topology_args: &crate::cli::TopologyArgs) -> Result<Topology> {
348 topology_args.validate()?;
350
351 let nr_cores_per_vllc = topology_args.get_nr_cores_per_vllc();
353
354 Self::with_virt_llcs(nr_cores_per_vllc)
356 }
357
358 #[cfg(feature = "gpu-topology")]
360 pub fn gpus(&self) -> BTreeMap<GpuIndex, &Gpu> {
361 let mut gpus = BTreeMap::new();
362 for node in self.nodes.values() {
363 for (idx, gpu) in &node.gpus {
364 gpus.insert(*idx, gpu);
365 }
366 }
367 gpus
368 }
369
370 pub fn has_little_cores(&self) -> bool {
372 self.all_cores
373 .values()
374 .any(|c| c.core_type == CoreType::Little)
375 }
376
377 pub fn sibling_cpus(&self) -> Vec<i32> {
384 let mut sibling_cpu = vec![-1i32; *NR_CPUS_POSSIBLE];
385 for core in self.all_cores.values() {
386 let mut first = -1i32;
387 for &cpu in core.cpus.keys() {
388 if first < 0 {
389 first = cpu as i32;
390 } else {
391 sibling_cpu[first as usize] = cpu as i32;
392 sibling_cpu[cpu] = first;
393 break;
394 }
395 }
396 }
397 sibling_cpu
398 }
399
400 pub fn cpumask_nr_cores(&self, cpumask: &Cpumask) -> usize {
402 let mut count = 0;
403 for core in self.all_cores.values() {
404 if core.cpus.keys().any(|&cpu_id| cpumask.test_cpu(cpu_id)) {
405 count += 1;
406 }
407 }
408 count
409 }
410
411 pub fn format_cpumask_grid<W: Write>(
423 &self,
424 w: &mut W,
425 cpumask: &Cpumask,
426 indent: &str,
427 max_width: usize,
428 ) -> Result<()> {
429 for node in self.nodes.values() {
430 let mut llc_segments: Vec<(usize, String)> = Vec::new();
433
434 for llc in node.llcs.values() {
435 let mut seg = String::new();
436 let nr_cores = llc.cores.len();
437 let nr_groups = (nr_cores + 7) / 8;
438 let base = nr_cores / nr_groups;
439 let rem = nr_cores % nr_groups;
440 let mut next_break = if rem > 0 { base + 1 } else { base };
442 let mut group_idx = 0;
443 for (i, core) in llc.cores.values().enumerate() {
444 if i > 0 && i == next_break {
445 seg.push(' ');
446 group_idx += 1;
447 next_break += if group_idx < rem { base + 1 } else { base };
448 }
449 let nr_cpus = core.cpus.len();
450 let cpu_ids: Vec<usize> = core.cpus.keys().copied().collect();
451 let nr_set: usize = cpu_ids.iter().filter(|&&c| cpumask.test_cpu(c)).count();
452
453 let ch = if nr_cpus == 1 {
454 if nr_set > 0 {
455 '█'
456 } else {
457 '░'
458 }
459 } else if nr_cpus == 2 {
460 let first_set = cpumask.test_cpu(cpu_ids[0]);
461 let second_set = cpumask.test_cpu(cpu_ids[1]);
462 match (first_set, second_set) {
463 (false, false) => '░',
464 (true, false) => '▀',
465 (false, true) => '▄',
466 (true, true) => '█',
467 }
468 } else {
469 if nr_set == 0 {
471 '░'
472 } else if nr_set == nr_cpus {
473 '█'
474 } else {
475 '▄'
476 }
477 };
478 seg.push(ch);
479 }
480 llc_segments.push((llc.id, seg));
481 }
482
483 if llc_segments.is_empty() {
484 continue;
485 }
486
487 let first_llc_id = llc_segments[0].0;
489 let prefix = format!("{}N{} L{:02}: ", indent, node.id, first_llc_id);
490 let prefix_width = prefix.chars().count();
491 let cont_indent = format!(
492 "{}{}",
493 indent,
494 " ".repeat(prefix_width - indent.chars().count())
495 );
496
497 let mut line = prefix.clone();
499 let mut first_llc = true;
500
501 for (_, seg) in &llc_segments {
502 let seg_width = seg.chars().count();
503 let separator = if first_llc { "" } else { "|" };
504 let sep_width = separator.chars().count();
505 let current_line_width = line.chars().count();
506
507 if !first_llc && current_line_width + sep_width + seg_width > max_width {
508 writeln!(w, "{}", line)?;
509 line = format!("{}{}", cont_indent, seg);
510 } else {
511 line = format!("{}{}{}", line, separator, seg);
512 }
513 first_llc = false;
514 }
515 writeln!(w, "{}", line)?;
516 }
517 Ok(())
518 }
519
520 pub fn format_cpumask_header(&self, cpumask: &Cpumask, min_cpus: u32, max_cpus: u32) -> String {
522 let nr_cpus = cpumask.weight();
523 let nr_cores = self.cpumask_nr_cores(cpumask);
524 format!(
525 "cpus={:3}({:3}c) [{:3},{:3}]",
526 nr_cpus, nr_cores, min_cpus, max_cpus
527 )
528 }
529}
530
531struct TopoCtx {
536 node_core_kernel_ids: BTreeMap<(usize, usize, usize), usize>,
538 node_llc_kernel_ids: BTreeMap<(usize, usize, usize), usize>,
540 l2_ids: BTreeMap<String, usize>,
542 l3_ids: BTreeMap<String, usize>,
544}
545
546impl TopoCtx {
547 fn new() -> TopoCtx {
548 let core_kernel_ids = BTreeMap::new();
549 let llc_kernel_ids = BTreeMap::new();
550 let l2_ids = BTreeMap::new();
551 let l3_ids = BTreeMap::new();
552 TopoCtx {
553 node_core_kernel_ids: core_kernel_ids,
554 node_llc_kernel_ids: llc_kernel_ids,
555 l2_ids,
556 l3_ids,
557 }
558 }
559}
560
561fn cpus_online() -> Result<Cpumask> {
562 let path = format!("{}/sys/devices/system/cpu/online", *ROOT_PREFIX);
563 let online = std::fs::read_to_string(path)?;
564 Cpumask::from_cpulist(&online)
565}
566
567fn get_cache_id(topo_ctx: &mut TopoCtx, cache_level_path: &Path, cache_level: usize) -> usize {
568 let id_map = match cache_level {
570 2 => &mut topo_ctx.l2_ids,
571 3 => &mut topo_ctx.l3_ids,
572 _ => return usize::MAX,
573 };
574
575 let path = &cache_level_path.join("shared_cpu_list");
576 let key = match std::fs::read_to_string(path) {
577 Ok(key) => key,
578 Err(_) => return usize::MAX,
579 };
580
581 let id = *id_map.get(&key).unwrap_or(&usize::MAX);
582 if id != usize::MAX {
583 return id;
584 }
585
586 let id = read_from_file(&cache_level_path.join("id")).unwrap_or(usize::MAX);
588 if id != usize::MAX {
589 id_map.insert(key, id);
591 return id;
592 }
593
594 let id = id_map.len();
596 id_map.insert(key, id);
597
598 id
599}
600
601fn get_per_cpu_cache_size(cache_path: &Path) -> Result<usize> {
602 let path_str = cache_path.to_str().unwrap();
603 let paths = glob(&(path_str.to_owned() + "/index[0-9]*"))?;
604 let mut tot_size = 0;
605
606 for index in paths.filter_map(Result::ok) {
607 let size = read_file_byte(&index.join("size")).unwrap_or(1024_usize);
612 let cpulist: String = read_from_file(&index.join("shared_cpu_list"))?;
613 let num_cpus = read_cpulist(&cpulist)?.len();
614 tot_size += size / num_cpus;
615 }
616
617 Ok(tot_size)
618}
619
620#[allow(clippy::too_many_arguments)]
621fn create_insert_cpu(
622 id: usize,
623 node: &mut Node,
624 online_mask: &Cpumask,
625 topo_ctx: &mut TopoCtx,
626 cs: &CapacitySource,
627 flatten_llc: bool,
628) -> Result<()> {
629 if !online_mask.test_cpu(id) {
633 return Ok(());
634 }
635
636 let cpu_str = format!("{}/sys/devices/system/cpu/cpu{}", *ROOT_PREFIX, id);
637 let cpu_path = Path::new(&cpu_str);
638
639 let top_path = cpu_path.join("topology");
641 let core_kernel_id = read_from_file(&top_path.join("core_id"))?;
642 let package_id = read_from_file(&top_path.join("physical_package_id"))?;
643 let cluster_id = read_from_file(&top_path.join("cluster_id"))?;
644
645 let cache_path = cpu_path.join("cache");
651 let l2_id = get_cache_id(topo_ctx, &cache_path.join(format!("index{}", 2)), 2);
652 let l3_id = get_cache_id(topo_ctx, &cache_path.join(format!("index{}", 3)), 3);
653 let llc_kernel_id = if flatten_llc {
654 0
655 } else if l3_id == usize::MAX {
656 l2_id
657 } else {
658 l3_id
659 };
660
661 let cache_size = get_per_cpu_cache_size(&cache_path).unwrap_or(0_usize);
663
664 let freq_path = cpu_path.join("cpufreq");
667 let min_freq = read_from_file(&freq_path.join("scaling_min_freq")).unwrap_or(0_usize);
668 let max_freq = read_from_file(&freq_path.join("scaling_max_freq")).unwrap_or(0_usize);
669 let base_freq = read_from_file(&freq_path.join("base_frequency")).unwrap_or(max_freq);
670 let trans_lat_ns =
671 read_from_file(&freq_path.join("cpuinfo_transition_latency")).unwrap_or(0_usize);
672
673 let cap_path = cpu_path.join(cs.suffix.clone());
675 let rcap = read_from_file(&cap_path).unwrap_or(cs.max_rcap);
676 let cpu_capacity = (rcap * 1024) / cs.max_rcap;
677
678 let power_path = cpu_path.join("power");
680 let pm_qos_resume_latency_us =
681 read_from_file(&power_path.join("pm_qos_resume_latency_us")).unwrap_or(0_usize);
682
683 let num_llcs = topo_ctx.node_llc_kernel_ids.len();
684 let llc_id = topo_ctx
685 .node_llc_kernel_ids
686 .entry((node.id, package_id, llc_kernel_id))
687 .or_insert(num_llcs);
688
689 let llc = node.llcs.entry(*llc_id).or_insert(Arc::new(Llc {
690 id: *llc_id,
691 cores: BTreeMap::new(),
692 span: Cpumask::new(),
693 all_cpus: BTreeMap::new(),
694
695 node_id: node.id,
696 kernel_id: llc_kernel_id,
697 }));
698 let llc_mut = Arc::get_mut(llc).unwrap();
699
700 let core_type = if cs.avg_rcap < cs.max_rcap && rcap == cs.max_rcap {
701 CoreType::Big { turbo: true }
702 } else if !cs.has_biglittle || rcap >= cs.avg_rcap {
703 CoreType::Big { turbo: false }
704 } else {
705 CoreType::Little
706 };
707
708 let num_cores = topo_ctx.node_core_kernel_ids.len();
709 let core_id = topo_ctx
710 .node_core_kernel_ids
711 .entry((node.id, package_id, core_kernel_id))
712 .or_insert(num_cores);
713
714 let core = llc_mut.cores.entry(*core_id).or_insert(Arc::new(Core {
715 id: *core_id,
716 cpus: BTreeMap::new(),
717 span: Cpumask::new(),
718 core_type: core_type.clone(),
719
720 llc_id: *llc_id,
721 node_id: node.id,
722 kernel_id: core_kernel_id,
723 cluster_id,
724 }));
725 let core_mut = Arc::get_mut(core).unwrap();
726
727 core_mut.cpus.insert(
728 id,
729 Arc::new(Cpu {
730 id,
731 min_freq,
732 max_freq,
733 base_freq,
734 cpu_capacity,
735 smt_level: 0, pm_qos_resume_latency_us,
737 trans_lat_ns,
738 l2_id,
739 l3_id,
740 cache_size,
741 core_type: core_type.clone(),
742
743 core_id: *core_id,
744 llc_id: *llc_id,
745 node_id: node.id,
746 package_id,
747 cluster_id,
748 }),
749 );
750
751 if node.span.test_cpu(id) {
752 bail!("Node {} already had CPU {}", node.id, id);
753 }
754
755 core_mut.span.set_cpu(id)?;
757 llc_mut.span.set_cpu(id)?;
758 node.span.set_cpu(id)?;
759
760 Ok(())
761}
762
763fn read_cpu_ids() -> Result<Vec<usize>> {
764 let mut cpu_ids = vec![];
765 let path = format!("{}/sys/devices/system/cpu/cpu[0-9]*", *ROOT_PREFIX);
766 let cpu_paths = glob(&path)?;
767 for cpu_path in cpu_paths.filter_map(Result::ok) {
768 let cpu_str = cpu_path.to_str().unwrap().trim();
769 if ROOT_PREFIX.is_empty() {
770 match sscanf!(cpu_str, "/sys/devices/system/cpu/cpu{usize}") {
771 Some(val) => cpu_ids.push(val),
772 None => {
773 bail!("Failed to parse cpu ID {}", cpu_str);
774 }
775 }
776 } else {
777 match sscanf!(cpu_str, "{str}/sys/devices/system/cpu/cpu{usize}") {
778 Some((_, val)) => cpu_ids.push(val),
779 None => {
780 bail!("Failed to parse cpu ID {}", cpu_str);
781 }
782 }
783 }
784 }
785 cpu_ids.sort();
786 Ok(cpu_ids)
787}
788
789struct CapacitySource {
790 suffix: String,
792 avg_rcap: usize,
794 max_rcap: usize,
796 has_biglittle: bool,
798}
799
800fn get_capacity_source() -> Option<CapacitySource> {
801 let sources = [
804 "cpufreq/amd_pstate_prefcore_ranking",
805 "cpufreq/amd_pstate_highest_perf",
806 "acpi_cppc/highest_perf",
807 "cpu_capacity",
808 "cpufreq/cpuinfo_max_freq",
809 ];
810
811 let prefix = format!("{}/sys/devices/system/cpu/cpu0", *ROOT_PREFIX);
813 let mut raw_capacity;
814 let mut suffix = sources[sources.len() - 1];
815 'outer: for src in sources {
816 let path_str = [prefix.clone(), src.to_string()].join("/");
817 let path = Path::new(&path_str);
818 raw_capacity = read_from_file(&path).unwrap_or(0_usize);
819 if raw_capacity > 0 {
820 suffix = src;
822 let path = format!("{}/sys/devices/system/cpu/cpu[0-9]*", *ROOT_PREFIX);
824 let cpu_paths = glob(&path).ok()?;
825 for cpu_path in cpu_paths.filter_map(Result::ok) {
826 let raw_capacity2 = read_from_file(&cpu_path.join(suffix)).unwrap_or(0_usize);
827 if raw_capacity != raw_capacity2 {
828 break 'outer;
829 }
830 }
831 }
836 }
837
838 let mut max_rcap = 0;
840 let mut min_rcap = usize::MAX;
841 let mut avg_rcap = 0;
842 let mut nr_cpus = 0;
843 let mut has_biglittle = false;
844 let path = format!("{}/sys/devices/system/cpu/cpu[0-9]*", *ROOT_PREFIX);
845 let cpu_paths = glob(&path).ok()?;
846 for cpu_path in cpu_paths.filter_map(Result::ok) {
847 let rcap = read_from_file(&cpu_path.join(suffix)).unwrap_or(0_usize);
848 if max_rcap < rcap {
849 max_rcap = rcap;
850 }
851 if min_rcap > rcap {
852 min_rcap = rcap;
853 }
854 avg_rcap += rcap;
855 nr_cpus += 1;
856 }
857
858 if nr_cpus == 0 || max_rcap == 0 {
859 suffix = "";
860 avg_rcap = 1024;
861 max_rcap = 1024;
862 warn!("CPU capacity information is not available under sysfs.");
863 } else {
864 avg_rcap /= nr_cpus;
865 has_biglittle = max_rcap as f32 >= (1.3 * min_rcap as f32);
874 }
875
876 Some(CapacitySource {
877 suffix: suffix.to_string(),
878 avg_rcap,
879 max_rcap,
880 has_biglittle,
881 })
882}
883
884fn is_smt_active() -> Option<bool> {
885 let path = format!("{}/sys/devices/system/cpu/smt/active", *ROOT_PREFIX);
886 let smt_on: u8 = read_from_file(Path::new(&path)).ok()?;
887 Some(smt_on == 1)
888}
889
890fn replace_with_virt_llcs(
891 node: &mut Node,
892 min_cores: usize,
893 max_cores: usize,
894 start_id: usize,
895) -> Result<usize> {
896 let mut next_id = start_id;
897 let mut core_to_partition: BTreeMap<usize, usize> = BTreeMap::new();
898 let mut partition_to_kernel_id: BTreeMap<usize, usize> = BTreeMap::new();
899 let num_orig_llcs = node.llcs.len();
900
901 for (_llc_id, llc) in node.llcs.iter() {
904 let mut cores_by_type: BTreeMap<bool, Vec<usize>> = BTreeMap::new();
906
907 for (core_id, core) in llc.cores.iter() {
908 let core_type = core.core_type == CoreType::Little;
909 cores_by_type
910 .entry(core_type)
911 .or_insert(Vec::new())
912 .push(*core_id);
913 }
914
915 for (_core_type, core_ids) in cores_by_type.iter() {
916 let num_cores_in_bucket = core_ids.len();
917
918 let best_split = find_best_split_size(num_cores_in_bucket, min_cores, max_cores);
920 let num_partitions = num_cores_in_bucket / best_split;
921
922 for (bucket_idx, &core_id) in core_ids.iter().enumerate() {
924 let partition_idx = min(bucket_idx / best_split, num_partitions - 1);
925 let current_partition_id = next_id + partition_idx;
926 core_to_partition.insert(core_id, current_partition_id);
927 partition_to_kernel_id.insert(current_partition_id, llc.kernel_id);
928 }
929
930 next_id += num_partitions;
931 }
932 }
933
934 let mut virt_llcs: BTreeMap<usize, Arc<Llc>> = BTreeMap::new();
936
937 for vllc_id in start_id..next_id {
938 let kernel_id = partition_to_kernel_id.get(&vllc_id).copied().unwrap();
939 virt_llcs.insert(
940 vllc_id,
941 Arc::new(Llc {
942 id: vllc_id,
943 kernel_id,
944 cores: BTreeMap::new(),
945 span: Cpumask::new(),
946 node_id: node.id,
947 all_cpus: BTreeMap::new(),
948 }),
949 );
950 }
951
952 for (_llc_id, llc) in node.llcs.iter_mut() {
954 for (core_id, core) in llc.cores.iter() {
955 if let Some(&target_partition_id) = core_to_partition.get(core_id) {
956 if let Some(target_llc) = virt_llcs.get_mut(&target_partition_id) {
957 let target_llc_mut = Arc::get_mut(target_llc).unwrap();
958
959 let mut new_core = (**core).clone();
961 new_core.llc_id = target_partition_id;
962
963 let mut updated_cpus = BTreeMap::new();
965 for (cpu_id, cpu) in new_core.cpus.iter() {
966 let mut new_cpu = (**cpu).clone();
967 new_cpu.llc_id = target_partition_id;
968
969 target_llc_mut.span.set_cpu(*cpu_id)?;
971
972 updated_cpus.insert(*cpu_id, Arc::new(new_cpu));
973 }
974 new_core.cpus = updated_cpus;
975
976 target_llc_mut.cores.insert(*core_id, Arc::new(new_core));
978 }
979 }
980 }
981 }
982
983 node.llcs = virt_llcs;
985
986 let num_virt_llcs = next_id - start_id;
987 let vllc_sizes: Vec<usize> = node.llcs.values().map(|llc| llc.cores.len()).collect();
988
989 if vllc_sizes.is_empty() {
990 return Ok(next_id);
991 }
992
993 let common_size = vllc_sizes[0];
995 let last_size = *vllc_sizes.last().unwrap();
996
997 if common_size == last_size {
998 info!(
999 "Node {}: split {} LLC(s) into {} virtual LLCs with {} cores each",
1000 node.id, num_orig_llcs, num_virt_llcs, common_size
1001 );
1002 } else {
1003 info!(
1004 "Node {}: split {} LLC(s) into {} virtual LLCs with {} cores each (last with {})",
1005 node.id, num_orig_llcs, num_virt_llcs, common_size, last_size
1006 );
1007 }
1008
1009 Ok(next_id)
1010}
1011
1012fn create_default_node(
1013 online_mask: &Cpumask,
1014 topo_ctx: &mut TopoCtx,
1015 flatten_llc: bool,
1016 nr_cores_per_vllc: Option<(usize, usize)>,
1017) -> Result<BTreeMap<usize, Node>> {
1018 let mut nodes = BTreeMap::<usize, Node>::new();
1019
1020 let mut node = Node {
1021 id: 0,
1022 distance: vec![],
1023 llcs: BTreeMap::new(),
1024 span: Cpumask::new(),
1025 #[cfg(feature = "gpu-topology")]
1026 gpus: BTreeMap::new(),
1027 all_cores: BTreeMap::new(),
1028 all_cpus: BTreeMap::new(),
1029 };
1030
1031 #[cfg(feature = "gpu-topology")]
1032 {
1033 let system_gpus = create_gpus();
1034 if let Some(gpus) = system_gpus.get(&0) {
1035 for gpu in gpus {
1036 node.gpus.insert(gpu.index, gpu.clone());
1037 }
1038 }
1039 }
1040
1041 let path = format!("{}/sys/devices/system/cpu", *ROOT_PREFIX);
1042 if !Path::new(&path).exists() {
1043 bail!("/sys/devices/system/cpu sysfs node not found");
1044 }
1045
1046 let cs = get_capacity_source().unwrap();
1047 let cpu_ids = read_cpu_ids()?;
1048 for cpu_id in cpu_ids.iter() {
1049 create_insert_cpu(*cpu_id, &mut node, online_mask, topo_ctx, &cs, flatten_llc)?;
1050 }
1051
1052 if let Some((min_cores_val, max_cores_val)) = nr_cores_per_vllc {
1053 replace_with_virt_llcs(&mut node, min_cores_val, max_cores_val, 0)?;
1054 }
1055
1056 nodes.insert(node.id, node);
1057
1058 Ok(nodes)
1059}
1060
1061fn create_numa_nodes(
1062 online_mask: &Cpumask,
1063 topo_ctx: &mut TopoCtx,
1064 nr_cores_per_vllc: Option<(usize, usize)>,
1065) -> Result<BTreeMap<usize, Node>> {
1066 let mut nodes = BTreeMap::<usize, Node>::new();
1067 let mut next_virt_llc_id = 0;
1068
1069 #[cfg(feature = "gpu-topology")]
1070 let system_gpus = create_gpus();
1071
1072 let path = format!("{}/sys/devices/system/node/node*", *ROOT_PREFIX);
1073 let numa_paths = glob(&path)?;
1074 for numa_path in numa_paths.filter_map(Result::ok) {
1075 let numa_str = numa_path.to_str().unwrap().trim();
1076 let node_id = if ROOT_PREFIX.is_empty() {
1077 match sscanf!(numa_str, "/sys/devices/system/node/node{usize}") {
1078 Some(val) => val,
1079 None => {
1080 bail!("Failed to parse NUMA node ID {}", numa_str);
1081 }
1082 }
1083 } else {
1084 match sscanf!(numa_str, "{str}/sys/devices/system/node/node{usize}") {
1085 Some((_, val)) => val,
1086 None => {
1087 bail!("Failed to parse NUMA node ID {}", numa_str);
1088 }
1089 }
1090 };
1091
1092 let distance = read_file_usize_vec(
1093 Path::new(&format!(
1094 "{}/sys/devices/system/node/node{}/distance",
1095 *ROOT_PREFIX, node_id
1096 )),
1097 ' ',
1098 )?;
1099 let mut node = Node {
1100 id: node_id,
1101 distance,
1102 llcs: BTreeMap::new(),
1103 span: Cpumask::new(),
1104
1105 all_cores: BTreeMap::new(),
1106 all_cpus: BTreeMap::new(),
1107
1108 #[cfg(feature = "gpu-topology")]
1109 gpus: BTreeMap::new(),
1110 };
1111
1112 #[cfg(feature = "gpu-topology")]
1113 {
1114 if let Some(gpus) = system_gpus.get(&node_id) {
1115 for gpu in gpus {
1116 node.gpus.insert(gpu.index, gpu.clone());
1117 }
1118 }
1119 }
1120
1121 let cpu_pattern = numa_path.join("cpu[0-9]*");
1122 let cpu_paths = glob(cpu_pattern.to_string_lossy().as_ref())?;
1123 let cs = get_capacity_source().unwrap();
1124 let mut cpu_ids = vec![];
1125 for cpu_path in cpu_paths.filter_map(Result::ok) {
1126 let cpu_str = cpu_path.to_str().unwrap().trim();
1127 let cpu_id = if ROOT_PREFIX.is_empty() {
1128 match sscanf!(cpu_str, "/sys/devices/system/node/node{usize}/cpu{usize}") {
1129 Some((_, val)) => val,
1130 None => {
1131 bail!("Failed to parse cpu ID {}", cpu_str);
1132 }
1133 }
1134 } else {
1135 match sscanf!(
1136 cpu_str,
1137 "{str}/sys/devices/system/node/node{usize}/cpu{usize}"
1138 ) {
1139 Some((_, _, val)) => val,
1140 None => {
1141 bail!("Failed to parse cpu ID {}", cpu_str);
1142 }
1143 }
1144 };
1145 cpu_ids.push(cpu_id);
1146 }
1147 cpu_ids.sort();
1148
1149 for cpu_id in cpu_ids {
1150 create_insert_cpu(cpu_id, &mut node, online_mask, topo_ctx, &cs, false)?;
1151 }
1152
1153 if let Some((min_cores_val, max_cores_val)) = nr_cores_per_vllc {
1154 next_virt_llc_id =
1155 replace_with_virt_llcs(&mut node, min_cores_val, max_cores_val, next_virt_llc_id)?;
1156 }
1157
1158 nodes.insert(node.id, node);
1159 }
1160 Ok(nodes)
1161}
1162
1163#[cfg(any(test, feature = "testutils"))]
1170pub mod testutils {
1171 use super::*;
1172 use crate::set_cpumask_test_width;
1173
1174 pub fn test_cpu(id: usize, core_id: usize, llc_id: usize, node_id: usize) -> Cpu {
1176 Cpu {
1177 id,
1178 core_id,
1179 llc_id,
1180 node_id,
1181 min_freq: 0,
1182 max_freq: 0,
1183 base_freq: 0,
1184 cpu_capacity: 1024,
1185 smt_level: 0, pm_qos_resume_latency_us: 0,
1187 trans_lat_ns: 0,
1188 l2_id: 0,
1189 l3_id: llc_id,
1190 cache_size: 0,
1191 core_type: CoreType::Big { turbo: false },
1192 package_id: node_id,
1193 cluster_id: 0,
1194 }
1195 }
1196
1197 pub fn test_core(
1199 id: usize,
1200 cpus: BTreeMap<usize, Arc<Cpu>>,
1201 llc_id: usize,
1202 node_id: usize,
1203 ) -> Core {
1204 let mut span = Cpumask::new();
1205 for &cpu_id in cpus.keys() {
1206 span.set_cpu(cpu_id).unwrap();
1207 }
1208 Core {
1209 id,
1210 kernel_id: id,
1211 cluster_id: 0,
1212 cpus,
1213 span,
1214 core_type: CoreType::Big { turbo: false },
1215 llc_id,
1216 node_id,
1217 }
1218 }
1219
1220 pub fn test_llc(id: usize, cores: BTreeMap<usize, Arc<Core>>, node_id: usize) -> Llc {
1222 let mut span = Cpumask::new();
1223 for core in cores.values() {
1224 for &cpu_id in core.cpus.keys() {
1225 span.set_cpu(cpu_id).unwrap();
1226 }
1227 }
1228 Llc {
1229 id,
1230 kernel_id: id,
1231 cores,
1232 span,
1233 node_id,
1234 all_cpus: BTreeMap::new(), }
1236 }
1237
1238 pub fn test_node(id: usize, llcs: BTreeMap<usize, Arc<Llc>>, nr_nodes: usize) -> Node {
1240 let mut span = Cpumask::new();
1241 for llc in llcs.values() {
1242 for core in llc.cores.values() {
1243 for &cpu_id in core.cpus.keys() {
1244 span.set_cpu(cpu_id).unwrap();
1245 }
1246 }
1247 }
1248 Node {
1249 id,
1250 distance: vec![10; nr_nodes],
1251 llcs,
1252 span,
1253 all_cores: BTreeMap::new(), all_cpus: BTreeMap::new(), #[cfg(feature = "gpu-topology")]
1256 gpus: BTreeMap::new(),
1257 }
1258 }
1259
1260 pub fn make_test_topo(
1269 nr_nodes: usize,
1270 llcs_per_node: usize,
1271 cores_per_llc: usize,
1272 hts_per_core: usize,
1273 ) -> (Topology, usize) {
1274 let total_cpus = nr_nodes * llcs_per_node * cores_per_llc * hts_per_core;
1275 set_cpumask_test_width(total_cpus);
1276
1277 let mut cpu_id = 0usize;
1278 let mut core_id = 0usize;
1279 let mut llc_id = 0usize;
1280 let mut nodes = BTreeMap::new();
1281
1282 for node_idx in 0..nr_nodes {
1283 let mut llcs = BTreeMap::new();
1284 for _ in 0..llcs_per_node {
1285 let mut cores = BTreeMap::new();
1286 for _ in 0..cores_per_llc {
1287 let mut cpus = BTreeMap::new();
1288 for _ in 0..hts_per_core {
1289 cpus.insert(
1290 cpu_id,
1291 Arc::new(test_cpu(cpu_id, core_id, llc_id, node_idx)),
1292 );
1293 cpu_id += 1;
1294 }
1295 cores.insert(
1296 core_id,
1297 Arc::new(test_core(core_id, cpus, llc_id, node_idx)),
1298 );
1299 core_id += 1;
1300 }
1301 llcs.insert(llc_id, Arc::new(test_llc(llc_id, cores, node_idx)));
1302 llc_id += 1;
1303 }
1304 nodes.insert(node_idx, test_node(node_idx, llcs, nr_nodes));
1305 }
1306
1307 let mut span = Cpumask::new();
1308 for i in 0..total_cpus {
1309 span.set_cpu(i).unwrap();
1310 }
1311
1312 (Topology::instantiate(span, nodes).unwrap(), total_cpus)
1313 }
1314
1315 pub fn mask_from_bits(_total: usize, bits: &[usize]) -> Cpumask {
1317 let mut mask = Cpumask::new();
1318 for &b in bits {
1319 mask.set_cpu(b).unwrap();
1320 }
1321 mask
1322 }
1323}
1324
1325#[cfg(test)]
1326mod tests {
1327 use super::testutils::*;
1328 use super::*;
1329
1330 fn grid_output(topo: &Topology, cpumask: &Cpumask) -> String {
1331 let mut buf = Vec::new();
1332 topo.format_cpumask_grid(&mut buf, cpumask, " ", 80)
1333 .unwrap();
1334 String::from_utf8(buf).unwrap()
1335 }
1336
1337 #[test]
1338 fn test_grid_2node_2llc_3core_2ht() {
1339 let (topo, total) = make_test_topo(2, 2, 3, 2);
1341 assert_eq!(total, 24);
1342
1343 let cpumask = mask_from_bits(total, &[1, 2, 3, 12]);
1351
1352 let output = grid_output(&topo, &cpumask);
1353 assert!(output.contains("N0 L00:"));
1356 assert!(output.contains("N1 L02:"));
1357 assert!(output.contains("▄█░|░░░"));
1359 assert!(output.contains("▀░░|░░░"));
1361
1362 assert_eq!(topo.cpumask_nr_cores(&cpumask), 3);
1364 }
1365
1366 #[test]
1367 fn test_grid_empty_cpumask() {
1368 let (topo, total) = make_test_topo(1, 2, 3, 2);
1369 let cpumask = mask_from_bits(total, &[]);
1370 let output = grid_output(&topo, &cpumask);
1371 assert!(!output.contains('█'));
1373 assert!(!output.contains('▀'));
1374 assert!(!output.contains('▄'));
1375 assert!(output.contains('░'));
1376 assert_eq!(topo.cpumask_nr_cores(&cpumask), 0);
1377 }
1378
1379 #[test]
1380 fn test_grid_full_cpumask() {
1381 let (topo, total) = make_test_topo(1, 2, 3, 2);
1382 let cpumask = mask_from_bits(total, &(0..total).collect::<Vec<_>>());
1383 let output = grid_output(&topo, &cpumask);
1384 assert!(!output.contains('░'));
1386 assert!(!output.contains('▀'));
1387 assert!(!output.contains('▄'));
1388 assert!(output.contains('█'));
1389 assert_eq!(topo.cpumask_nr_cores(&cpumask), 6);
1390 }
1391
1392 #[test]
1393 fn test_grid_mixed_ht() {
1394 let (topo, total) = make_test_topo(1, 1, 4, 2);
1396 let cpumask = mask_from_bits(total, &[0, 3, 4, 5]);
1399 let output = grid_output(&topo, &cpumask);
1400 assert!(output.contains('▀'));
1401 assert!(output.contains('▄'));
1402 assert!(output.contains('█'));
1403 assert!(output.contains('░'));
1404 }
1405
1406 #[test]
1407 fn test_grid_single_node() {
1408 let (topo, total) = make_test_topo(1, 1, 2, 2);
1409 let cpumask = mask_from_bits(total, &[0, 1]);
1410 let output = grid_output(&topo, &cpumask);
1411 assert!(output.contains("N0 L00:"));
1412 assert!(!output.contains("N1"));
1413 }
1414
1415 #[test]
1416 fn test_grid_overflow_wrap() {
1417 let (topo, total) = make_test_topo(1, 12, 4, 2);
1420 let cpumask = mask_from_bits(total, &[0]);
1421 let mut buf = Vec::new();
1422 topo.format_cpumask_grid(&mut buf, &cpumask, " ", 60)
1423 .unwrap();
1424 let output = String::from_utf8(buf).unwrap();
1425 let lines: Vec<&str> = output.lines().collect();
1427 assert!(
1428 lines.len() > 1,
1429 "Expected wrapping with narrow width, got {} lines",
1430 lines.len()
1431 );
1432 }
1433
1434 #[test]
1435 fn test_grid_smt_off() {
1436 let (topo, total) = make_test_topo(1, 1, 4, 1);
1438 let cpumask = mask_from_bits(total, &[0, 2]);
1440 let output = grid_output(&topo, &cpumask);
1441 assert!(output.contains('█'));
1443 assert!(output.contains('░'));
1444 assert!(!output.contains('▀'));
1445 assert!(!output.contains('▄'));
1446 }
1447
1448 #[test]
1449 fn test_grid_4way_smt() {
1450 let (topo, total) = make_test_topo(1, 1, 2, 4);
1452 let cpumask = mask_from_bits(total, &[0, 1, 2, 3, 4, 5]);
1455 let output = grid_output(&topo, &cpumask);
1456 assert!(output.contains('█')); assert!(output.contains('▄')); }
1459
1460 #[test]
1461 fn test_cpumask_header() {
1462 let (topo, total) = make_test_topo(1, 1, 4, 2);
1463 let cpumask = mask_from_bits(total, &[0, 1, 2]);
1465 let header = topo.format_cpumask_header(&cpumask, 5, 10);
1466 assert!(header.contains("cpus= 3( 2c)"));
1467 assert!(header.contains("[ 5, 10]"));
1468 }
1469}