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 struct Cpu {
128 pub id: usize,
129 pub min_freq: usize,
130 pub max_freq: usize,
131 pub base_freq: usize,
134 pub cpu_capacity: usize,
136 pub smt_level: usize,
137 pub pm_qos_resume_latency_us: usize,
139 pub trans_lat_ns: usize,
140 pub l2_id: usize,
141 pub l3_id: usize,
142 pub cache_size: usize,
144 pub core_type: CoreType,
145
146 pub core_id: usize,
148 pub llc_id: usize,
149 pub node_id: usize,
150 pub package_id: usize,
151 pub cluster_id: isize,
152}
153
154#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
155pub struct Core {
156 pub id: usize,
158 pub kernel_id: usize,
160 pub cluster_id: isize,
161 pub cpus: BTreeMap<usize, Arc<Cpu>>,
162 pub span: Cpumask,
164 pub core_type: CoreType,
165
166 pub llc_id: usize,
168 pub node_id: usize,
169}
170
171#[derive(Debug, Clone)]
172pub struct Llc {
173 pub id: usize,
175 pub kernel_id: usize,
177 pub cores: BTreeMap<usize, Arc<Core>>,
178 pub span: Cpumask,
180
181 pub node_id: usize,
183
184 pub all_cpus: BTreeMap<usize, Arc<Cpu>>,
186}
187
188#[derive(Debug, Clone)]
189pub struct Node {
190 pub id: usize,
191 pub distance: Vec<usize>,
192 pub llcs: BTreeMap<usize, Arc<Llc>>,
193 pub span: Cpumask,
195
196 pub all_cores: BTreeMap<usize, Arc<Core>>,
198 pub all_cpus: BTreeMap<usize, Arc<Cpu>>,
199
200 #[cfg(feature = "gpu-topology")]
201 pub gpus: BTreeMap<GpuIndex, Gpu>,
202}
203
204#[derive(Debug)]
205pub struct Topology {
206 pub nodes: BTreeMap<usize, Node>,
207 pub span: Cpumask,
209 pub smt_enabled: bool,
211
212 pub all_llcs: BTreeMap<usize, Arc<Llc>>,
214 pub all_cores: BTreeMap<usize, Arc<Core>>,
215 pub all_cpus: BTreeMap<usize, Arc<Cpu>>,
216}
217
218impl Topology {
219 fn instantiate(span: Cpumask, mut nodes: BTreeMap<usize, Node>) -> Result<Self> {
220 let mut topo_llcs = BTreeMap::new();
224 let mut topo_cores = BTreeMap::new();
225 let mut topo_cpus = BTreeMap::new();
226
227 for (_node_id, node) in nodes.iter_mut() {
228 let mut node_cores = BTreeMap::new();
229 let mut node_cpus = BTreeMap::new();
230
231 for (&llc_id, llc) in node.llcs.iter_mut() {
232 let llc_mut = Arc::get_mut(llc).unwrap();
233 let mut llc_cpus = BTreeMap::new();
234
235 for (&core_id, core) in llc_mut.cores.iter_mut() {
236 let core_mut = Arc::get_mut(core).unwrap();
237 let smt_level = core_mut.cpus.len();
238
239 for (&cpu_id, cpu) in core_mut.cpus.iter_mut() {
240 let cpu_mut = Arc::get_mut(cpu).unwrap();
241 cpu_mut.smt_level = smt_level;
242
243 if topo_cpus
244 .insert(cpu_id, cpu.clone())
245 .or(node_cpus.insert(cpu_id, cpu.clone()))
246 .or(llc_cpus.insert(cpu_id, cpu.clone()))
247 .is_some()
248 {
249 bail!("Duplicate CPU ID {}", cpu_id);
250 }
251 }
252
253 topo_cores
256 .insert(core_id, core.clone())
257 .or(node_cores.insert(core_id, core.clone()));
258 }
259
260 llc_mut.all_cpus = llc_cpus;
261
262 if topo_llcs.insert(llc_id, llc.clone()).is_some() {
263 bail!("Duplicate LLC ID {}", llc_id);
264 }
265 }
266
267 node.all_cores = node_cores;
268 node.all_cpus = node_cpus;
269 }
270
271 Ok(Topology {
272 nodes,
273 span,
274 smt_enabled: is_smt_active().unwrap_or(false),
275 all_llcs: topo_llcs,
276 all_cores: topo_cores,
277 all_cpus: topo_cpus,
278 })
279 }
280
281 pub fn new() -> Result<Topology> {
283 Self::with_virt_llcs(None)
284 }
285
286 pub fn with_virt_llcs(nr_cores_per_vllc: Option<(usize, usize)>) -> Result<Topology> {
287 let span = cpus_online()?;
288 let mut topo_ctx = TopoCtx::new();
289
290 let path = format!("{}/sys/devices/system/node", *ROOT_PREFIX);
294 let nodes = if Path::new(&path).exists() {
295 create_numa_nodes(&span, &mut topo_ctx, nr_cores_per_vllc)?
296 } else {
297 create_default_node(&span, &mut topo_ctx, false, nr_cores_per_vllc)?
298 };
299
300 Self::instantiate(span, nodes)
301 }
302
303 pub fn with_flattened_llc_node() -> Result<Topology> {
304 let span = cpus_online()?;
305 let mut topo_ctx = TopoCtx::new();
306 let nodes = create_default_node(&span, &mut topo_ctx, true, None)?;
307 Self::instantiate(span, nodes)
308 }
309
310 pub fn with_args(topology_args: &crate::cli::TopologyArgs) -> Result<Topology> {
314 topology_args.validate()?;
316
317 let nr_cores_per_vllc = topology_args.get_nr_cores_per_vllc();
319
320 Self::with_virt_llcs(nr_cores_per_vllc)
322 }
323
324 #[cfg(feature = "gpu-topology")]
326 pub fn gpus(&self) -> BTreeMap<GpuIndex, &Gpu> {
327 let mut gpus = BTreeMap::new();
328 for node in self.nodes.values() {
329 for (idx, gpu) in &node.gpus {
330 gpus.insert(*idx, gpu);
331 }
332 }
333 gpus
334 }
335
336 pub fn has_little_cores(&self) -> bool {
338 self.all_cores
339 .values()
340 .any(|c| c.core_type == CoreType::Little)
341 }
342
343 pub fn sibling_cpus(&self) -> Vec<i32> {
350 let mut sibling_cpu = vec![-1i32; *NR_CPUS_POSSIBLE];
351 for core in self.all_cores.values() {
352 let mut first = -1i32;
353 for &cpu in core.cpus.keys() {
354 if first < 0 {
355 first = cpu as i32;
356 } else {
357 sibling_cpu[first as usize] = cpu as i32;
358 sibling_cpu[cpu] = first;
359 break;
360 }
361 }
362 }
363 sibling_cpu
364 }
365
366 pub fn cpumask_nr_cores(&self, cpumask: &Cpumask) -> usize {
368 let mut count = 0;
369 for core in self.all_cores.values() {
370 if core.cpus.keys().any(|&cpu_id| cpumask.test_cpu(cpu_id)) {
371 count += 1;
372 }
373 }
374 count
375 }
376
377 pub fn format_cpumask_grid<W: Write>(
389 &self,
390 w: &mut W,
391 cpumask: &Cpumask,
392 indent: &str,
393 max_width: usize,
394 ) -> Result<()> {
395 for node in self.nodes.values() {
396 let mut llc_segments: Vec<(usize, String)> = Vec::new();
399
400 for llc in node.llcs.values() {
401 let mut seg = String::new();
402 let nr_cores = llc.cores.len();
403 let nr_groups = (nr_cores + 7) / 8;
404 let base = nr_cores / nr_groups;
405 let rem = nr_cores % nr_groups;
406 let mut next_break = if rem > 0 { base + 1 } else { base };
408 let mut group_idx = 0;
409 for (i, core) in llc.cores.values().enumerate() {
410 if i > 0 && i == next_break {
411 seg.push(' ');
412 group_idx += 1;
413 next_break += if group_idx < rem { base + 1 } else { base };
414 }
415 let nr_cpus = core.cpus.len();
416 let cpu_ids: Vec<usize> = core.cpus.keys().copied().collect();
417 let nr_set: usize = cpu_ids.iter().filter(|&&c| cpumask.test_cpu(c)).count();
418
419 let ch = if nr_cpus == 1 {
420 if nr_set > 0 {
421 '█'
422 } else {
423 '░'
424 }
425 } else if nr_cpus == 2 {
426 let first_set = cpumask.test_cpu(cpu_ids[0]);
427 let second_set = cpumask.test_cpu(cpu_ids[1]);
428 match (first_set, second_set) {
429 (false, false) => '░',
430 (true, false) => '▀',
431 (false, true) => '▄',
432 (true, true) => '█',
433 }
434 } else {
435 if nr_set == 0 {
437 '░'
438 } else if nr_set == nr_cpus {
439 '█'
440 } else {
441 '▄'
442 }
443 };
444 seg.push(ch);
445 }
446 llc_segments.push((llc.id, seg));
447 }
448
449 if llc_segments.is_empty() {
450 continue;
451 }
452
453 let first_llc_id = llc_segments[0].0;
455 let prefix = format!("{}N{} L{:02}: ", indent, node.id, first_llc_id);
456 let prefix_width = prefix.chars().count();
457 let cont_indent = format!(
458 "{}{}",
459 indent,
460 " ".repeat(prefix_width - indent.chars().count())
461 );
462
463 let mut line = prefix.clone();
465 let mut first_llc = true;
466
467 for (_, seg) in &llc_segments {
468 let seg_width = seg.chars().count();
469 let separator = if first_llc { "" } else { "|" };
470 let sep_width = separator.chars().count();
471 let current_line_width = line.chars().count();
472
473 if !first_llc && current_line_width + sep_width + seg_width > max_width {
474 writeln!(w, "{}", line)?;
475 line = format!("{}{}", cont_indent, seg);
476 } else {
477 line = format!("{}{}{}", line, separator, seg);
478 }
479 first_llc = false;
480 }
481 writeln!(w, "{}", line)?;
482 }
483 Ok(())
484 }
485
486 pub fn format_cpumask_header(&self, cpumask: &Cpumask, min_cpus: u32, max_cpus: u32) -> String {
488 let nr_cpus = cpumask.weight();
489 let nr_cores = self.cpumask_nr_cores(cpumask);
490 format!(
491 "cpus={:3}({:3}c) [{:3},{:3}]",
492 nr_cpus, nr_cores, min_cpus, max_cpus
493 )
494 }
495}
496
497struct TopoCtx {
502 node_core_kernel_ids: BTreeMap<(usize, usize, usize), usize>,
504 node_llc_kernel_ids: BTreeMap<(usize, usize, usize), usize>,
506 l2_ids: BTreeMap<String, usize>,
508 l3_ids: BTreeMap<String, usize>,
510}
511
512impl TopoCtx {
513 fn new() -> TopoCtx {
514 let core_kernel_ids = BTreeMap::new();
515 let llc_kernel_ids = BTreeMap::new();
516 let l2_ids = BTreeMap::new();
517 let l3_ids = BTreeMap::new();
518 TopoCtx {
519 node_core_kernel_ids: core_kernel_ids,
520 node_llc_kernel_ids: llc_kernel_ids,
521 l2_ids,
522 l3_ids,
523 }
524 }
525}
526
527fn cpus_online() -> Result<Cpumask> {
528 let path = format!("{}/sys/devices/system/cpu/online", *ROOT_PREFIX);
529 let online = std::fs::read_to_string(path)?;
530 Cpumask::from_cpulist(&online)
531}
532
533fn get_cache_id(topo_ctx: &mut TopoCtx, cache_level_path: &Path, cache_level: usize) -> usize {
534 let id_map = match cache_level {
536 2 => &mut topo_ctx.l2_ids,
537 3 => &mut topo_ctx.l3_ids,
538 _ => return usize::MAX,
539 };
540
541 let path = &cache_level_path.join("shared_cpu_list");
542 let key = match std::fs::read_to_string(path) {
543 Ok(key) => key,
544 Err(_) => return usize::MAX,
545 };
546
547 let id = *id_map.get(&key).unwrap_or(&usize::MAX);
548 if id != usize::MAX {
549 return id;
550 }
551
552 let id = read_from_file(&cache_level_path.join("id")).unwrap_or(usize::MAX);
554 if id != usize::MAX {
555 id_map.insert(key, id);
557 return id;
558 }
559
560 let id = id_map.len();
562 id_map.insert(key, id);
563
564 id
565}
566
567fn get_per_cpu_cache_size(cache_path: &Path) -> Result<usize> {
568 let path_str = cache_path.to_str().unwrap();
569 let paths = glob(&(path_str.to_owned() + "/index[0-9]*"))?;
570 let mut tot_size = 0;
571
572 for index in paths.filter_map(Result::ok) {
573 let size = read_file_byte(&index.join("size")).unwrap_or(1024_usize);
578 let cpulist: String = read_from_file(&index.join("shared_cpu_list"))?;
579 let num_cpus = read_cpulist(&cpulist)?.len();
580 tot_size += size / num_cpus;
581 }
582
583 Ok(tot_size)
584}
585
586#[allow(clippy::too_many_arguments)]
587fn create_insert_cpu(
588 id: usize,
589 node: &mut Node,
590 online_mask: &Cpumask,
591 topo_ctx: &mut TopoCtx,
592 cs: &CapacitySource,
593 flatten_llc: bool,
594) -> Result<()> {
595 if !online_mask.test_cpu(id) {
599 return Ok(());
600 }
601
602 let cpu_str = format!("{}/sys/devices/system/cpu/cpu{}", *ROOT_PREFIX, id);
603 let cpu_path = Path::new(&cpu_str);
604
605 let top_path = cpu_path.join("topology");
607 let core_kernel_id = read_from_file(&top_path.join("core_id"))?;
608 let package_id = read_from_file(&top_path.join("physical_package_id"))?;
609 let cluster_id = read_from_file(&top_path.join("cluster_id"))?;
610
611 let cache_path = cpu_path.join("cache");
617 let l2_id = get_cache_id(topo_ctx, &cache_path.join(format!("index{}", 2)), 2);
618 let l3_id = get_cache_id(topo_ctx, &cache_path.join(format!("index{}", 3)), 3);
619 let llc_kernel_id = if flatten_llc {
620 0
621 } else if l3_id == usize::MAX {
622 l2_id
623 } else {
624 l3_id
625 };
626
627 let cache_size = get_per_cpu_cache_size(&cache_path).unwrap_or(0_usize);
629
630 let freq_path = cpu_path.join("cpufreq");
633 let min_freq = read_from_file(&freq_path.join("scaling_min_freq")).unwrap_or(0_usize);
634 let max_freq = read_from_file(&freq_path.join("scaling_max_freq")).unwrap_or(0_usize);
635 let base_freq = read_from_file(&freq_path.join("base_frequency")).unwrap_or(max_freq);
636 let trans_lat_ns =
637 read_from_file(&freq_path.join("cpuinfo_transition_latency")).unwrap_or(0_usize);
638
639 let cap_path = cpu_path.join(cs.suffix.clone());
641 let rcap = read_from_file(&cap_path).unwrap_or(cs.max_rcap);
642 let cpu_capacity = (rcap * 1024) / cs.max_rcap;
643
644 let power_path = cpu_path.join("power");
646 let pm_qos_resume_latency_us =
647 read_from_file(&power_path.join("pm_qos_resume_latency_us")).unwrap_or(0_usize);
648
649 let num_llcs = topo_ctx.node_llc_kernel_ids.len();
650 let llc_id = topo_ctx
651 .node_llc_kernel_ids
652 .entry((node.id, package_id, llc_kernel_id))
653 .or_insert(num_llcs);
654
655 let llc = node.llcs.entry(*llc_id).or_insert(Arc::new(Llc {
656 id: *llc_id,
657 cores: BTreeMap::new(),
658 span: Cpumask::new(),
659 all_cpus: BTreeMap::new(),
660
661 node_id: node.id,
662 kernel_id: llc_kernel_id,
663 }));
664 let llc_mut = Arc::get_mut(llc).unwrap();
665
666 let core_type = if cs.avg_rcap < cs.max_rcap && rcap == cs.max_rcap {
667 CoreType::Big { turbo: true }
668 } else if !cs.has_biglittle || rcap >= cs.avg_rcap {
669 CoreType::Big { turbo: false }
670 } else {
671 CoreType::Little
672 };
673
674 let num_cores = topo_ctx.node_core_kernel_ids.len();
675 let core_id = topo_ctx
676 .node_core_kernel_ids
677 .entry((node.id, package_id, core_kernel_id))
678 .or_insert(num_cores);
679
680 let core = llc_mut.cores.entry(*core_id).or_insert(Arc::new(Core {
681 id: *core_id,
682 cpus: BTreeMap::new(),
683 span: Cpumask::new(),
684 core_type: core_type.clone(),
685
686 llc_id: *llc_id,
687 node_id: node.id,
688 kernel_id: core_kernel_id,
689 cluster_id,
690 }));
691 let core_mut = Arc::get_mut(core).unwrap();
692
693 core_mut.cpus.insert(
694 id,
695 Arc::new(Cpu {
696 id,
697 min_freq,
698 max_freq,
699 base_freq,
700 cpu_capacity,
701 smt_level: 0, pm_qos_resume_latency_us,
703 trans_lat_ns,
704 l2_id,
705 l3_id,
706 cache_size,
707 core_type: core_type.clone(),
708
709 core_id: *core_id,
710 llc_id: *llc_id,
711 node_id: node.id,
712 package_id,
713 cluster_id,
714 }),
715 );
716
717 if node.span.test_cpu(id) {
718 bail!("Node {} already had CPU {}", node.id, id);
719 }
720
721 core_mut.span.set_cpu(id)?;
723 llc_mut.span.set_cpu(id)?;
724 node.span.set_cpu(id)?;
725
726 Ok(())
727}
728
729fn read_cpu_ids() -> Result<Vec<usize>> {
730 let mut cpu_ids = vec![];
731 let path = format!("{}/sys/devices/system/cpu/cpu[0-9]*", *ROOT_PREFIX);
732 let cpu_paths = glob(&path)?;
733 for cpu_path in cpu_paths.filter_map(Result::ok) {
734 let cpu_str = cpu_path.to_str().unwrap().trim();
735 if ROOT_PREFIX.is_empty() {
736 match sscanf!(cpu_str, "/sys/devices/system/cpu/cpu{usize}") {
737 Ok(val) => cpu_ids.push(val),
738 Err(_) => {
739 bail!("Failed to parse cpu ID {}", cpu_str);
740 }
741 }
742 } else {
743 match sscanf!(cpu_str, "{str}/sys/devices/system/cpu/cpu{usize}") {
744 Ok((_, val)) => cpu_ids.push(val),
745 Err(_) => {
746 bail!("Failed to parse cpu ID {}", cpu_str);
747 }
748 }
749 }
750 }
751 cpu_ids.sort();
752 Ok(cpu_ids)
753}
754
755struct CapacitySource {
756 suffix: String,
758 avg_rcap: usize,
760 max_rcap: usize,
762 has_biglittle: bool,
764}
765
766fn get_capacity_source() -> Option<CapacitySource> {
767 let sources = [
770 "cpufreq/amd_pstate_prefcore_ranking",
771 "cpufreq/amd_pstate_highest_perf",
772 "acpi_cppc/highest_perf",
773 "cpu_capacity",
774 "cpufreq/cpuinfo_max_freq",
775 ];
776
777 let prefix = format!("{}/sys/devices/system/cpu/cpu0", *ROOT_PREFIX);
779 let mut raw_capacity;
780 let mut suffix = sources[sources.len() - 1];
781 'outer: for src in sources {
782 let path_str = [prefix.clone(), src.to_string()].join("/");
783 let path = Path::new(&path_str);
784 raw_capacity = read_from_file(&path).unwrap_or(0_usize);
785 if raw_capacity > 0 {
786 suffix = src;
788 let path = format!("{}/sys/devices/system/cpu/cpu[0-9]*", *ROOT_PREFIX);
790 let cpu_paths = glob(&path).ok()?;
791 for cpu_path in cpu_paths.filter_map(Result::ok) {
792 let raw_capacity2 = read_from_file(&cpu_path.join(suffix)).unwrap_or(0_usize);
793 if raw_capacity != raw_capacity2 {
794 break 'outer;
795 }
796 }
797 }
802 }
803
804 let mut max_rcap = 0;
806 let mut min_rcap = usize::MAX;
807 let mut avg_rcap = 0;
808 let mut nr_cpus = 0;
809 let mut has_biglittle = false;
810 let path = format!("{}/sys/devices/system/cpu/cpu[0-9]*", *ROOT_PREFIX);
811 let cpu_paths = glob(&path).ok()?;
812 for cpu_path in cpu_paths.filter_map(Result::ok) {
813 let rcap = read_from_file(&cpu_path.join(suffix)).unwrap_or(0_usize);
814 if max_rcap < rcap {
815 max_rcap = rcap;
816 }
817 if min_rcap > rcap {
818 min_rcap = rcap;
819 }
820 avg_rcap += rcap;
821 nr_cpus += 1;
822 }
823
824 if nr_cpus == 0 || max_rcap == 0 {
825 suffix = "";
826 avg_rcap = 1024;
827 max_rcap = 1024;
828 warn!("CPU capacity information is not available under sysfs.");
829 } else {
830 avg_rcap /= nr_cpus;
831 has_biglittle = max_rcap as f32 >= (1.3 * min_rcap as f32);
840 }
841
842 Some(CapacitySource {
843 suffix: suffix.to_string(),
844 avg_rcap,
845 max_rcap,
846 has_biglittle,
847 })
848}
849
850fn is_smt_active() -> Option<bool> {
851 let path = format!("{}/sys/devices/system/cpu/smt/active", *ROOT_PREFIX);
852 let smt_on: u8 = read_from_file(Path::new(&path)).ok()?;
853 Some(smt_on == 1)
854}
855
856fn replace_with_virt_llcs(
857 node: &mut Node,
858 min_cores: usize,
859 max_cores: usize,
860 start_id: usize,
861) -> Result<usize> {
862 let mut next_id = start_id;
863 let mut core_to_partition: BTreeMap<usize, usize> = BTreeMap::new();
864 let mut partition_to_kernel_id: BTreeMap<usize, usize> = BTreeMap::new();
865 let num_orig_llcs = node.llcs.len();
866
867 for (_llc_id, llc) in node.llcs.iter() {
870 let mut cores_by_type: BTreeMap<bool, Vec<usize>> = BTreeMap::new();
872
873 for (core_id, core) in llc.cores.iter() {
874 let core_type = core.core_type == CoreType::Little;
875 cores_by_type
876 .entry(core_type)
877 .or_insert(Vec::new())
878 .push(*core_id);
879 }
880
881 for (_core_type, core_ids) in cores_by_type.iter() {
882 let num_cores_in_bucket = core_ids.len();
883
884 let best_split = find_best_split_size(num_cores_in_bucket, min_cores, max_cores);
886 let num_partitions = num_cores_in_bucket / best_split;
887
888 for (bucket_idx, &core_id) in core_ids.iter().enumerate() {
890 let partition_idx = min(bucket_idx / best_split, num_partitions - 1);
891 let current_partition_id = next_id + partition_idx;
892 core_to_partition.insert(core_id, current_partition_id);
893 partition_to_kernel_id.insert(current_partition_id, llc.kernel_id);
894 }
895
896 next_id += num_partitions;
897 }
898 }
899
900 let mut virt_llcs: BTreeMap<usize, Arc<Llc>> = BTreeMap::new();
902
903 for vllc_id in start_id..next_id {
904 let kernel_id = partition_to_kernel_id.get(&vllc_id).copied().unwrap();
905 virt_llcs.insert(
906 vllc_id,
907 Arc::new(Llc {
908 id: vllc_id,
909 kernel_id,
910 cores: BTreeMap::new(),
911 span: Cpumask::new(),
912 node_id: node.id,
913 all_cpus: BTreeMap::new(),
914 }),
915 );
916 }
917
918 for (_llc_id, llc) in node.llcs.iter_mut() {
920 for (core_id, core) in llc.cores.iter() {
921 if let Some(&target_partition_id) = core_to_partition.get(core_id) {
922 if let Some(target_llc) = virt_llcs.get_mut(&target_partition_id) {
923 let target_llc_mut = Arc::get_mut(target_llc).unwrap();
924
925 let mut new_core = (**core).clone();
927 new_core.llc_id = target_partition_id;
928
929 let mut updated_cpus = BTreeMap::new();
931 for (cpu_id, cpu) in new_core.cpus.iter() {
932 let mut new_cpu = (**cpu).clone();
933 new_cpu.llc_id = target_partition_id;
934
935 target_llc_mut.span.set_cpu(*cpu_id)?;
937
938 updated_cpus.insert(*cpu_id, Arc::new(new_cpu));
939 }
940 new_core.cpus = updated_cpus;
941
942 target_llc_mut.cores.insert(*core_id, Arc::new(new_core));
944 }
945 }
946 }
947 }
948
949 node.llcs = virt_llcs;
951
952 let num_virt_llcs = next_id - start_id;
953 let vllc_sizes: Vec<usize> = node.llcs.values().map(|llc| llc.cores.len()).collect();
954
955 if vllc_sizes.is_empty() {
956 return Ok(next_id);
957 }
958
959 let common_size = vllc_sizes[0];
961 let last_size = *vllc_sizes.last().unwrap();
962
963 if common_size == last_size {
964 info!(
965 "Node {}: split {} LLC(s) into {} virtual LLCs with {} cores each",
966 node.id, num_orig_llcs, num_virt_llcs, common_size
967 );
968 } else {
969 info!(
970 "Node {}: split {} LLC(s) into {} virtual LLCs with {} cores each (last with {})",
971 node.id, num_orig_llcs, num_virt_llcs, common_size, last_size
972 );
973 }
974
975 Ok(next_id)
976}
977
978fn create_default_node(
979 online_mask: &Cpumask,
980 topo_ctx: &mut TopoCtx,
981 flatten_llc: bool,
982 nr_cores_per_vllc: Option<(usize, usize)>,
983) -> Result<BTreeMap<usize, Node>> {
984 let mut nodes = BTreeMap::<usize, Node>::new();
985
986 let mut node = Node {
987 id: 0,
988 distance: vec![],
989 llcs: BTreeMap::new(),
990 span: Cpumask::new(),
991 #[cfg(feature = "gpu-topology")]
992 gpus: BTreeMap::new(),
993 all_cores: BTreeMap::new(),
994 all_cpus: BTreeMap::new(),
995 };
996
997 #[cfg(feature = "gpu-topology")]
998 {
999 let system_gpus = create_gpus();
1000 if let Some(gpus) = system_gpus.get(&0) {
1001 for gpu in gpus {
1002 node.gpus.insert(gpu.index, gpu.clone());
1003 }
1004 }
1005 }
1006
1007 let path = format!("{}/sys/devices/system/cpu", *ROOT_PREFIX);
1008 if !Path::new(&path).exists() {
1009 bail!("/sys/devices/system/cpu sysfs node not found");
1010 }
1011
1012 let cs = get_capacity_source().unwrap();
1013 let cpu_ids = read_cpu_ids()?;
1014 for cpu_id in cpu_ids.iter() {
1015 create_insert_cpu(*cpu_id, &mut node, online_mask, topo_ctx, &cs, flatten_llc)?;
1016 }
1017
1018 if let Some((min_cores_val, max_cores_val)) = nr_cores_per_vllc {
1019 replace_with_virt_llcs(&mut node, min_cores_val, max_cores_val, 0)?;
1020 }
1021
1022 nodes.insert(node.id, node);
1023
1024 Ok(nodes)
1025}
1026
1027fn create_numa_nodes(
1028 online_mask: &Cpumask,
1029 topo_ctx: &mut TopoCtx,
1030 nr_cores_per_vllc: Option<(usize, usize)>,
1031) -> Result<BTreeMap<usize, Node>> {
1032 let mut nodes = BTreeMap::<usize, Node>::new();
1033 let mut next_virt_llc_id = 0;
1034
1035 #[cfg(feature = "gpu-topology")]
1036 let system_gpus = create_gpus();
1037
1038 let path = format!("{}/sys/devices/system/node/node*", *ROOT_PREFIX);
1039 let numa_paths = glob(&path)?;
1040 for numa_path in numa_paths.filter_map(Result::ok) {
1041 let numa_str = numa_path.to_str().unwrap().trim();
1042 let node_id = if ROOT_PREFIX.is_empty() {
1043 match sscanf!(numa_str, "/sys/devices/system/node/node{usize}") {
1044 Ok(val) => val,
1045 Err(_) => {
1046 bail!("Failed to parse NUMA node ID {}", numa_str);
1047 }
1048 }
1049 } else {
1050 match sscanf!(numa_str, "{str}/sys/devices/system/node/node{usize}") {
1051 Ok((_, val)) => val,
1052 Err(_) => {
1053 bail!("Failed to parse NUMA node ID {}", numa_str);
1054 }
1055 }
1056 };
1057
1058 let distance = read_file_usize_vec(
1059 Path::new(&format!(
1060 "{}/sys/devices/system/node/node{}/distance",
1061 *ROOT_PREFIX, node_id
1062 )),
1063 ' ',
1064 )?;
1065 let mut node = Node {
1066 id: node_id,
1067 distance,
1068 llcs: BTreeMap::new(),
1069 span: Cpumask::new(),
1070
1071 all_cores: BTreeMap::new(),
1072 all_cpus: BTreeMap::new(),
1073
1074 #[cfg(feature = "gpu-topology")]
1075 gpus: BTreeMap::new(),
1076 };
1077
1078 #[cfg(feature = "gpu-topology")]
1079 {
1080 if let Some(gpus) = system_gpus.get(&node_id) {
1081 for gpu in gpus {
1082 node.gpus.insert(gpu.index, gpu.clone());
1083 }
1084 }
1085 }
1086
1087 let cpu_pattern = numa_path.join("cpu[0-9]*");
1088 let cpu_paths = glob(cpu_pattern.to_string_lossy().as_ref())?;
1089 let cs = get_capacity_source().unwrap();
1090 let mut cpu_ids = vec![];
1091 for cpu_path in cpu_paths.filter_map(Result::ok) {
1092 let cpu_str = cpu_path.to_str().unwrap().trim();
1093 let cpu_id = if ROOT_PREFIX.is_empty() {
1094 match sscanf!(cpu_str, "/sys/devices/system/node/node{usize}/cpu{usize}") {
1095 Ok((_, val)) => val,
1096 Err(_) => {
1097 bail!("Failed to parse cpu ID {}", cpu_str);
1098 }
1099 }
1100 } else {
1101 match sscanf!(
1102 cpu_str,
1103 "{str}/sys/devices/system/node/node{usize}/cpu{usize}"
1104 ) {
1105 Ok((_, _, val)) => val,
1106 Err(_) => {
1107 bail!("Failed to parse cpu ID {}", cpu_str);
1108 }
1109 }
1110 };
1111 cpu_ids.push(cpu_id);
1112 }
1113 cpu_ids.sort();
1114
1115 for cpu_id in cpu_ids {
1116 create_insert_cpu(cpu_id, &mut node, online_mask, topo_ctx, &cs, false)?;
1117 }
1118
1119 if let Some((min_cores_val, max_cores_val)) = nr_cores_per_vllc {
1120 next_virt_llc_id =
1121 replace_with_virt_llcs(&mut node, min_cores_val, max_cores_val, next_virt_llc_id)?;
1122 }
1123
1124 nodes.insert(node.id, node);
1125 }
1126 Ok(nodes)
1127}
1128
1129#[cfg(any(test, feature = "testutils"))]
1136pub mod testutils {
1137 use super::*;
1138 use crate::set_cpumask_test_width;
1139
1140 pub fn test_cpu(id: usize, core_id: usize, llc_id: usize, node_id: usize) -> Cpu {
1142 Cpu {
1143 id,
1144 core_id,
1145 llc_id,
1146 node_id,
1147 min_freq: 0,
1148 max_freq: 0,
1149 base_freq: 0,
1150 cpu_capacity: 1024,
1151 smt_level: 0, pm_qos_resume_latency_us: 0,
1153 trans_lat_ns: 0,
1154 l2_id: 0,
1155 l3_id: llc_id,
1156 cache_size: 0,
1157 core_type: CoreType::Big { turbo: false },
1158 package_id: node_id,
1159 cluster_id: 0,
1160 }
1161 }
1162
1163 pub fn test_core(
1165 id: usize,
1166 cpus: BTreeMap<usize, Arc<Cpu>>,
1167 llc_id: usize,
1168 node_id: usize,
1169 ) -> Core {
1170 let mut span = Cpumask::new();
1171 for &cpu_id in cpus.keys() {
1172 span.set_cpu(cpu_id).unwrap();
1173 }
1174 Core {
1175 id,
1176 kernel_id: id,
1177 cluster_id: 0,
1178 cpus,
1179 span,
1180 core_type: CoreType::Big { turbo: false },
1181 llc_id,
1182 node_id,
1183 }
1184 }
1185
1186 pub fn test_llc(id: usize, cores: BTreeMap<usize, Arc<Core>>, node_id: usize) -> Llc {
1188 let mut span = Cpumask::new();
1189 for core in cores.values() {
1190 for &cpu_id in core.cpus.keys() {
1191 span.set_cpu(cpu_id).unwrap();
1192 }
1193 }
1194 Llc {
1195 id,
1196 kernel_id: id,
1197 cores,
1198 span,
1199 node_id,
1200 all_cpus: BTreeMap::new(), }
1202 }
1203
1204 pub fn test_node(id: usize, llcs: BTreeMap<usize, Arc<Llc>>, nr_nodes: usize) -> Node {
1206 let mut span = Cpumask::new();
1207 for llc in llcs.values() {
1208 for core in llc.cores.values() {
1209 for &cpu_id in core.cpus.keys() {
1210 span.set_cpu(cpu_id).unwrap();
1211 }
1212 }
1213 }
1214 Node {
1215 id,
1216 distance: vec![10; nr_nodes],
1217 llcs,
1218 span,
1219 all_cores: BTreeMap::new(), all_cpus: BTreeMap::new(), #[cfg(feature = "gpu-topology")]
1222 gpus: BTreeMap::new(),
1223 }
1224 }
1225
1226 pub fn make_test_topo(
1235 nr_nodes: usize,
1236 llcs_per_node: usize,
1237 cores_per_llc: usize,
1238 hts_per_core: usize,
1239 ) -> (Topology, usize) {
1240 let total_cpus = nr_nodes * llcs_per_node * cores_per_llc * hts_per_core;
1241 set_cpumask_test_width(total_cpus);
1242
1243 let mut cpu_id = 0usize;
1244 let mut core_id = 0usize;
1245 let mut llc_id = 0usize;
1246 let mut nodes = BTreeMap::new();
1247
1248 for node_idx in 0..nr_nodes {
1249 let mut llcs = BTreeMap::new();
1250 for _ in 0..llcs_per_node {
1251 let mut cores = BTreeMap::new();
1252 for _ in 0..cores_per_llc {
1253 let mut cpus = BTreeMap::new();
1254 for _ in 0..hts_per_core {
1255 cpus.insert(
1256 cpu_id,
1257 Arc::new(test_cpu(cpu_id, core_id, llc_id, node_idx)),
1258 );
1259 cpu_id += 1;
1260 }
1261 cores.insert(
1262 core_id,
1263 Arc::new(test_core(core_id, cpus, llc_id, node_idx)),
1264 );
1265 core_id += 1;
1266 }
1267 llcs.insert(llc_id, Arc::new(test_llc(llc_id, cores, node_idx)));
1268 llc_id += 1;
1269 }
1270 nodes.insert(node_idx, test_node(node_idx, llcs, nr_nodes));
1271 }
1272
1273 let mut span = Cpumask::new();
1274 for i in 0..total_cpus {
1275 span.set_cpu(i).unwrap();
1276 }
1277
1278 (Topology::instantiate(span, nodes).unwrap(), total_cpus)
1279 }
1280
1281 pub fn mask_from_bits(_total: usize, bits: &[usize]) -> Cpumask {
1283 let mut mask = Cpumask::new();
1284 for &b in bits {
1285 mask.set_cpu(b).unwrap();
1286 }
1287 mask
1288 }
1289}
1290
1291#[cfg(test)]
1292mod tests {
1293 use super::testutils::*;
1294 use super::*;
1295
1296 fn grid_output(topo: &Topology, cpumask: &Cpumask) -> String {
1297 let mut buf = Vec::new();
1298 topo.format_cpumask_grid(&mut buf, cpumask, " ", 80)
1299 .unwrap();
1300 String::from_utf8(buf).unwrap()
1301 }
1302
1303 #[test]
1304 fn test_grid_2node_2llc_3core_2ht() {
1305 let (topo, total) = make_test_topo(2, 2, 3, 2);
1307 assert_eq!(total, 24);
1308
1309 let cpumask = mask_from_bits(total, &[1, 2, 3, 12]);
1317
1318 let output = grid_output(&topo, &cpumask);
1319 assert!(output.contains("N0 L00:"));
1322 assert!(output.contains("N1 L02:"));
1323 assert!(output.contains("▄█░|░░░"));
1325 assert!(output.contains("▀░░|░░░"));
1327
1328 assert_eq!(topo.cpumask_nr_cores(&cpumask), 3);
1330 }
1331
1332 #[test]
1333 fn test_grid_empty_cpumask() {
1334 let (topo, total) = make_test_topo(1, 2, 3, 2);
1335 let cpumask = mask_from_bits(total, &[]);
1336 let output = grid_output(&topo, &cpumask);
1337 assert!(!output.contains('█'));
1339 assert!(!output.contains('▀'));
1340 assert!(!output.contains('▄'));
1341 assert!(output.contains('░'));
1342 assert_eq!(topo.cpumask_nr_cores(&cpumask), 0);
1343 }
1344
1345 #[test]
1346 fn test_grid_full_cpumask() {
1347 let (topo, total) = make_test_topo(1, 2, 3, 2);
1348 let cpumask = mask_from_bits(total, &(0..total).collect::<Vec<_>>());
1349 let output = grid_output(&topo, &cpumask);
1350 assert!(!output.contains('░'));
1352 assert!(!output.contains('▀'));
1353 assert!(!output.contains('▄'));
1354 assert!(output.contains('█'));
1355 assert_eq!(topo.cpumask_nr_cores(&cpumask), 6);
1356 }
1357
1358 #[test]
1359 fn test_grid_mixed_ht() {
1360 let (topo, total) = make_test_topo(1, 1, 4, 2);
1362 let cpumask = mask_from_bits(total, &[0, 3, 4, 5]);
1365 let output = grid_output(&topo, &cpumask);
1366 assert!(output.contains('▀'));
1367 assert!(output.contains('▄'));
1368 assert!(output.contains('█'));
1369 assert!(output.contains('░'));
1370 }
1371
1372 #[test]
1373 fn test_grid_single_node() {
1374 let (topo, total) = make_test_topo(1, 1, 2, 2);
1375 let cpumask = mask_from_bits(total, &[0, 1]);
1376 let output = grid_output(&topo, &cpumask);
1377 assert!(output.contains("N0 L00:"));
1378 assert!(!output.contains("N1"));
1379 }
1380
1381 #[test]
1382 fn test_grid_overflow_wrap() {
1383 let (topo, total) = make_test_topo(1, 12, 4, 2);
1386 let cpumask = mask_from_bits(total, &[0]);
1387 let mut buf = Vec::new();
1388 topo.format_cpumask_grid(&mut buf, &cpumask, " ", 60)
1389 .unwrap();
1390 let output = String::from_utf8(buf).unwrap();
1391 let lines: Vec<&str> = output.lines().collect();
1393 assert!(
1394 lines.len() > 1,
1395 "Expected wrapping with narrow width, got {} lines",
1396 lines.len()
1397 );
1398 }
1399
1400 #[test]
1401 fn test_grid_smt_off() {
1402 let (topo, total) = make_test_topo(1, 1, 4, 1);
1404 let cpumask = mask_from_bits(total, &[0, 2]);
1406 let output = grid_output(&topo, &cpumask);
1407 assert!(output.contains('█'));
1409 assert!(output.contains('░'));
1410 assert!(!output.contains('▀'));
1411 assert!(!output.contains('▄'));
1412 }
1413
1414 #[test]
1415 fn test_grid_4way_smt() {
1416 let (topo, total) = make_test_topo(1, 1, 2, 4);
1418 let cpumask = mask_from_bits(total, &[0, 1, 2, 3, 4, 5]);
1421 let output = grid_output(&topo, &cpumask);
1422 assert!(output.contains('█')); assert!(output.contains('▄')); }
1425
1426 #[test]
1427 fn test_cpumask_header() {
1428 let (topo, total) = make_test_topo(1, 1, 4, 2);
1429 let cpumask = mask_from_bits(total, &[0, 1, 2]);
1431 let header = topo.format_cpumask_header(&cpumask, 5, 10);
1432 assert!(header.contains("cpus= 3( 2c)"));
1433 assert!(header.contains("[ 5, 10]"));
1434 }
1435}