diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs index 71bbe1d..1f32a1c 100644 --- a/src/uu/lscpu/src/lscpu.rs +++ b/src/uu/lscpu/src/lscpu.rs @@ -94,7 +94,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { cpu_infos.push(arch_info); - let cpu_topology = sysfs::read_cpu_topology(); + let cpu_topology = sysfs::CpuTopology::new(); let mut cores_info = CpuInfo::new("CPU(s)", &format!("{}", cpu_topology.cpus.len()), None); cores_info.add_child(CpuInfo::new( @@ -131,8 +131,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { model_name_info.add_child(CpuInfo::new("Frequency boost", s, None)); } + model_name_info.add_child(CpuInfo::new( + "Socket(s)", + &cpu_topology.socket_count().to_string(), + None, + )); + vendor_info.add_child(model_name_info); } + cpu_infos.push(vendor_info); } diff --git a/src/uu/lscpu/src/sysfs.rs b/src/uu/lscpu/src/sysfs.rs index f4b1102..f710a65 100644 --- a/src/uu/lscpu/src/sysfs.rs +++ b/src/uu/lscpu/src/sysfs.rs @@ -1,5 +1,5 @@ use parse_size::parse_size; -use std::{fs, path::PathBuf}; +use std::{collections::HashSet, fs, path::PathBuf}; pub struct CpuVulnerability { pub name: String, @@ -13,7 +13,7 @@ pub struct CpuTopology { #[derive(Debug)] pub struct Cpu { _index: usize, - pub _pkg_id: usize, + pub pkg_id: usize, pub caches: Vec, } @@ -32,6 +32,41 @@ pub enum CacheType { Unified, } +impl CpuTopology { + pub fn new() -> Self { + let mut out: Vec = vec![]; + + let online_cpus = parse_cpu_list(&read_online_cpus()); + + for cpu_index in online_cpus { + let cpu_dir = PathBuf::from(format!("/sys/devices/system/cpu/cpu{}/", cpu_index)); + + let physical_pkg_id = fs::read_to_string(cpu_dir.join("topology/physical_package_id")) + .unwrap() + .trim() + .parse::() + .unwrap(); + + let caches = read_cpu_caches(cpu_index); + + out.push(Cpu { + _index: cpu_index, + pkg_id: physical_pkg_id, + caches, + }) + } + Self { cpus: out } + } + + pub fn socket_count(&self) -> usize { + // Each physical socket is represented as its own package_id, so amount of unique pkg_ids = sockets + // https://www.kernel.org/doc/html/latest/admin-guide/abi-stable.html#abi-sys-devices-system-cpu-cpux-topology-physical-package-id + let physical_sockets: HashSet = self.cpus.iter().map(|cpu| cpu.pkg_id).collect(); + + physical_sockets.len() + } +} + // TODO: respect `--hex` option and output the bitmask instead of human-readable range pub fn read_online_cpus() -> String { fs::read_to_string("/sys/devices/system/cpu/online") @@ -40,54 +75,6 @@ pub fn read_online_cpus() -> String { .to_string() } -// Takes in a human-readable list of CPUs, and returns a list of indices parsed from that list -// These can come in the form of a plain range like `X-Y`, or a comma-separated ranges and indices ie. `1,3-4,7-8,10` -// Kernel docs with examples: https://www.kernel.org/doc/html/latest/admin-guide/cputopology.html -fn parse_cpu_list(list: &str) -> Vec { - let mut out: Vec = vec![]; - - for part in list.trim().split(",") { - if part.contains("-") { - let bounds: Vec<_> = part.split("-").flat_map(|x| x.parse::()).collect(); - assert_eq!(bounds.len(), 2); - for idx in bounds[0]..bounds[1] + 1 { - out.push(idx) - } - } else { - let idx = part.parse::().expect("Invalid CPU index value"); - out.push(idx); - } - } - - out -} - -pub fn read_cpu_topology() -> CpuTopology { - let mut out: Vec = vec![]; - - let online_cpus = parse_cpu_list(&read_online_cpus()); - - for cpu_index in online_cpus { - let cpu_dir = PathBuf::from(format!("/sys/devices/system/cpu/cpu{}/", cpu_index)); - - let physical_pkg_id = fs::read_to_string(cpu_dir.join("topology/physical_package_id")) - .unwrap() - .trim() - .parse::() - .unwrap(); - - let caches = read_cpu_caches(cpu_index); - - out.push(Cpu { - _index: cpu_index, - _pkg_id: physical_pkg_id, - caches, - }) - } - - CpuTopology { cpus: out } -} - fn read_cpu_caches(cpu_index: usize) -> Vec { let cpu_dir = PathBuf::from(format!("/sys/devices/system/cpu/cpu{}/", cpu_index)); let cache_dir = fs::read_dir(cpu_dir.join("cache")).unwrap(); @@ -175,3 +162,44 @@ pub fn read_cpu_byte_order() -> Option<&'static str> { } None } + +// Takes in a human-readable list of CPUs, and returns a list of indices parsed from that list +// These can come in the form of a plain range like `X-Y`, or a comma-separated ranges and indices ie. `1,3-4,7-8,10` +// Kernel docs with examples: https://www.kernel.org/doc/html/latest/admin-guide/cputopology.html +fn parse_cpu_list(list: &str) -> Vec { + let mut out: Vec = vec![]; + + if list.is_empty() { + return out; + } + + for part in list.trim().split(",") { + if part.contains("-") { + let bounds: Vec<_> = part.split("-").flat_map(|x| x.parse::()).collect(); + assert_eq!(bounds.len(), 2); + for idx in bounds[0]..bounds[1] + 1 { + out.push(idx) + } + } else { + let idx = part.parse::().expect("Invalid CPU index value"); + out.push(idx); + } + } + + out +} + +#[test] +fn test_parse_cpu_list() { + assert_eq!(parse_cpu_list(""), Vec::::new()); + assert_eq!(parse_cpu_list("1-3"), Vec::::from([1, 2, 3])); + assert_eq!(parse_cpu_list("1,2,3"), Vec::::from([1, 2, 3])); + assert_eq!( + parse_cpu_list("1,3-6,8"), + Vec::::from([1, 3, 4, 5, 6, 8]) + ); + assert_eq!( + parse_cpu_list("1-2,3-5,7"), + Vec::::from([1, 2, 3, 4, 5, 7]) + ); +}