From c6a36590296c6dd549fd0a74085fbee01b9f181c Mon Sep 17 00:00:00 2001 From: alxndrv <> Date: Fri, 14 Feb 2025 20:38:12 +0200 Subject: [PATCH] `lscpu`: Print cache sizes in human-readable form --- src/uu/lscpu/src/lscpu.rs | 13 +++-- src/uu/lscpu/src/sysfs.rs | 108 +++++++++++++++++++++++++++----------- 2 files changed, 86 insertions(+), 35 deletions(-) diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs index 19cce4e..6b43bc8 100644 --- a/src/uu/lscpu/src/lscpu.rs +++ b/src/uu/lscpu/src/lscpu.rs @@ -7,6 +7,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use regex::RegexBuilder; use serde::Serialize; use std::{cmp, collections::HashMap, fs}; +use sysfs::CacheSize; use uucore::{error::UResult, format_usage, help_about, help_usage}; mod options { @@ -196,11 +197,17 @@ fn calculate_cache_totals(cpus: Vec<sysfs::Cpu>) -> Option<CpuInfo> { caches.dedup_by_key(|c| &c.shared_cpu_map); let count = caches.len(); - let size_total = caches.iter().fold(0_u64, |acc, c| acc + c.size); + let size_total = caches + .iter() + .fold(0_u64, |acc, c| acc + c.size.size_bytes()); + cache_info.add_child(CpuInfo::new( level, - // TODO: Format sizes using `KiB`, `MiB` etc. - &format!("{} bytes ({} instances)", size_total, count), + &format!( + "{} ({} instances)", + CacheSize::new(size_total).human_readable(), + count + ), None, )); } diff --git a/src/uu/lscpu/src/sysfs.rs b/src/uu/lscpu/src/sysfs.rs index 2497f23..2e1409d 100644 --- a/src/uu/lscpu/src/sysfs.rs +++ b/src/uu/lscpu/src/sysfs.rs @@ -21,10 +21,13 @@ pub struct Cpu { pub struct CpuCache { pub typ: CacheType, pub level: usize, - pub size: u64, + pub size: CacheSize, pub shared_cpu_map: String, } +#[derive(Debug)] +pub struct CacheSize(u64); + #[derive(Debug)] pub enum CacheType { Data, @@ -79,6 +82,51 @@ impl CpuTopology { } } +impl CacheSize { + pub fn new(size: u64) -> Self { + Self(size) + } + + fn parse(s: &str) -> Self { + // Yes, this will break if we ever reach a point where caches exceed terabytes in size... + const EXPONENTS: [(char, u32); 4] = [('K', 1), ('M', 2), ('G', 3), ('T', 4)]; + + // If we only have numbers, treat it as a raw amount of bytes and parse as-is + if s.chars().all(|c| c.is_numeric()) { + return Self(s.parse::<u64>().expect("Could not parse cache size")); + }; + + for (suffix, exponent) in EXPONENTS { + if s.ends_with(suffix) { + let nums = s.strip_suffix(suffix).unwrap(); + let value = nums.parse::<u64>().expect("Could not parse cache size"); + let multiplier = 1024_u64.pow(exponent); + + return Self(value * multiplier); + } + } + + panic!("No known suffix in cache size string"); + } + + pub fn size_bytes(&self) -> u64 { + self.0 + } + + pub fn human_readable(&self) -> String { + let (unit, denominator) = match self.0 { + x if x < 1024_u64.pow(1) => ("B", 1024_u64.pow(0)), + x if x < 1024_u64.pow(2) => ("KiB", 1024_u64.pow(1)), + x if x < 1024_u64.pow(3) => ("MiB", 1024_u64.pow(2)), + x if x < 1024_u64.pow(4) => ("GiB", 1024_u64.pow(3)), + x if x < 1024_u64.pow(5) => ("TiB", 1024_u64.pow(4)), + _ => return format!("{} bytes", self.0), + }; + let scaled_size = self.0 / denominator; + format!("{} {}", scaled_size, unit) + } +} + // 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") @@ -112,7 +160,7 @@ fn read_cpu_caches(cpu_index: usize) -> Vec<CpuCache> { .unwrap(); let size_string = fs::read_to_string(cache_path.join("size")).unwrap(); - let c_size = parse_cache_size(size_string.trim()); + let c_size = CacheSize::parse(size_string.trim()); let shared_cpu_map = fs::read_to_string(cache_path.join("shared_cpu_map")) .unwrap() @@ -201,40 +249,36 @@ fn parse_cpu_list(list: &str) -> Vec<usize> { out } -fn parse_cache_size(s: &str) -> u64 { - // Yes, this will break if we ever reach a point where caches exceed terabytes in size... - const EXPONENTS: [(char, u32); 4] = [('K', 1), ('M', 2), ('G', 3), ('T', 4)]; - - // If we only have numbers, treat it as a raw amount of bytes and parse as-is - if s.chars().all(|c| c.is_numeric()) { - return s.parse::<u64>().expect("Could not parse cache size"); - }; - - for (suffix, exponent) in EXPONENTS { - if s.ends_with(suffix) { - let nums = s.strip_suffix(suffix).unwrap(); - let value = nums.parse::<u64>().expect("Could not parse cache size"); - let multiplier = 1024_u64.pow(exponent); - - return value * multiplier; - } - } - - panic!("No known suffix in cache size string"); +#[test] +fn test_parse_cache_size() { + assert_eq!(CacheSize::parse("512").size_bytes(), 512); + assert_eq!(CacheSize::parse("1K").size_bytes(), 1024); + assert_eq!(CacheSize::parse("1M").size_bytes(), 1024 * 1024); + assert_eq!(CacheSize::parse("1G").size_bytes(), 1024 * 1024 * 1024); + assert_eq!( + CacheSize::parse("1T").size_bytes(), + 1024 * 1024 * 1024 * 1024 + ); + assert_eq!(CacheSize::parse("123K").size_bytes(), 123 * 1024); + assert_eq!(CacheSize::parse("32M").size_bytes(), 32 * 1024 * 1024); + assert_eq!( + CacheSize::parse("345G").size_bytes(), + 345 * 1024 * 1024 * 1024 + ); } #[test] -fn test_parse_cache_size() { - assert_eq!(parse_cache_size("512"), 512); +fn test_print_cache_size() { + assert_eq!(CacheSize::new(1023).human_readable(), "1023 B"); + assert_eq!(CacheSize::new(1024).human_readable(), "1 KiB"); + assert_eq!(CacheSize::new(1024 * 1024).human_readable(), "1 MiB"); + assert_eq!(CacheSize::new(1024 * 1024 * 1024).human_readable(), "1 GiB"); - assert_eq!(parse_cache_size("1K"), 1024); - assert_eq!(parse_cache_size("1M"), 1024 * 1024); - assert_eq!(parse_cache_size("1G"), 1024 * 1024 * 1024); - assert_eq!(parse_cache_size("1T"), 1024 * 1024 * 1024 * 1024); - - assert_eq!(parse_cache_size("123K"), 123 * 1024); - assert_eq!(parse_cache_size("32M"), 32 * 1024 * 1024); - assert_eq!(parse_cache_size("345G"), 345 * 1024 * 1024 * 1024); + assert_eq!(CacheSize::new(3 * 1024).human_readable(), "3 KiB"); + assert_eq!( + CacheSize::new((7.6 * 1024.0 * 1024.0) as u64).human_readable(), + "7 MiB" + ); } #[test]