lscpu: Show number of physical CPU sockets, add unit test

This commit is contained in:
alxndrv 2025-02-14 14:57:43 +02:00
parent 99c751bd1d
commit 41bff4fbd6
2 changed files with 86 additions and 51 deletions
src/uu/lscpu/src

@ -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);
}

@ -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<CpuCache>,
}
@ -32,6 +32,41 @@ pub enum CacheType {
Unified,
}
impl CpuTopology {
pub fn new() -> Self {
let mut out: Vec<Cpu> = 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::<usize>()
.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<usize> = 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<usize> {
let mut out: Vec<usize> = vec![];
for part in list.trim().split(",") {
if part.contains("-") {
let bounds: Vec<_> = part.split("-").flat_map(|x| x.parse::<usize>()).collect();
assert_eq!(bounds.len(), 2);
for idx in bounds[0]..bounds[1] + 1 {
out.push(idx)
}
} else {
let idx = part.parse::<usize>().expect("Invalid CPU index value");
out.push(idx);
}
}
out
}
pub fn read_cpu_topology() -> CpuTopology {
let mut out: Vec<Cpu> = 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::<usize>()
.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<CpuCache> {
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<usize> {
let mut out: Vec<usize> = 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::<usize>()).collect();
assert_eq!(bounds.len(), 2);
for idx in bounds[0]..bounds[1] + 1 {
out.push(idx)
}
} else {
let idx = part.parse::<usize>().expect("Invalid CPU index value");
out.push(idx);
}
}
out
}
#[test]
fn test_parse_cpu_list() {
assert_eq!(parse_cpu_list(""), Vec::<usize>::new());
assert_eq!(parse_cpu_list("1-3"), Vec::<usize>::from([1, 2, 3]));
assert_eq!(parse_cpu_list("1,2,3"), Vec::<usize>::from([1, 2, 3]));
assert_eq!(
parse_cpu_list("1,3-6,8"),
Vec::<usize>::from([1, 3, 4, 5, 6, 8])
);
assert_eq!(
parse_cpu_list("1-2,3-5,7"),
Vec::<usize>::from([1, 2, 3, 4, 5, 7])
);
}