diff --git a/src/uu/vmstat/src/parser.rs b/src/uu/vmstat/src/parser.rs index 6cff6ea0..831023e3 100644 --- a/src/uu/vmstat/src/parser.rs +++ b/src/uu/vmstat/src/parser.rs @@ -61,6 +61,30 @@ impl ProcData { let idle_time = parts.next().unwrap().parse::().unwrap(); (uptime, idle_time) } + + pub fn get_one(table: &HashMap, name: &str) -> T + where + T: Default + std::str::FromStr, + { + table + .get(name) + .and_then(|v| v.parse().ok()) + .unwrap_or_default() + } +} + +#[cfg(target_os = "linux")] +pub struct CpuLoadRaw { + pub user: u64, + pub nice: u64, + pub system: u64, + pub idle: u64, + pub io_wait: u64, + pub hardware_interrupt: u64, + pub software_interrupt: u64, + pub steal_time: u64, + pub guest: u64, + pub guest_nice: u64, } #[cfg(target_os = "linux")] @@ -78,7 +102,7 @@ pub struct CpuLoad { } #[cfg(target_os = "linux")] -impl CpuLoad { +impl CpuLoadRaw { pub fn current() -> Self { let file = std::fs::File::open(std::path::Path::new("/proc/stat")).unwrap(); // do not use `parse_proc_file` here because only one line is used let content = std::io::read_to_string(file).unwrap(); @@ -93,37 +117,64 @@ impl CpuLoad { fn from_str(s: &str) -> Self { let load = s.split(' ').filter(|s| !s.is_empty()).collect::>(); - let user = load[0].parse::().unwrap(); - let nice = load[1].parse::().unwrap(); - let system = load[2].parse::().unwrap(); - let idle = load[3].parse::().unwrap_or_default(); // since 2.5.41 - let io_wait = load[4].parse::().unwrap_or_default(); // since 2.5.41 - let hardware_interrupt = load[5].parse::().unwrap_or_default(); // since 2.6.0 - let software_interrupt = load[6].parse::().unwrap_or_default(); // since 2.6.0 - let steal_time = load[7].parse::().unwrap_or_default(); // since 2.6.11 - let guest = load[8].parse::().unwrap_or_default(); // since 2.6.24 - let guest_nice = load[9].parse::().unwrap_or_default(); // since 2.6.33 - let total = user - + nice - + system - + idle - + io_wait - + hardware_interrupt - + software_interrupt - + steal_time - + guest - + guest_nice; + let user = load[0].parse::().unwrap(); + let nice = load[1].parse::().unwrap(); + let system = load[2].parse::().unwrap(); + let idle = load[3].parse::().unwrap_or_default(); // since 2.5.41 + let io_wait = load[4].parse::().unwrap_or_default(); // since 2.5.41 + let hardware_interrupt = load[5].parse::().unwrap_or_default(); // since 2.6.0 + let software_interrupt = load[6].parse::().unwrap_or_default(); // since 2.6.0 + let steal_time = load[7].parse::().unwrap_or_default(); // since 2.6.11 + let guest = load[8].parse::().unwrap_or_default(); // since 2.6.24 + let guest_nice = load[9].parse::().unwrap_or_default(); // since 2.6.33 + + Self { + user, + system, + nice, + idle, + io_wait, + hardware_interrupt, + software_interrupt, + steal_time, + guest, + guest_nice, + } + } +} + +#[cfg(target_os = "linux")] +impl CpuLoad { + pub fn current() -> Self { + Self::from_raw(CpuLoadRaw::current()) + } + + pub fn from_proc_map(proc_map: &HashMap) -> Self { + Self::from_raw(CpuLoadRaw::from_proc_map(proc_map)) + } + + pub fn from_raw(raw_data: CpuLoadRaw) -> Self { + let total = (raw_data.user + + raw_data.nice + + raw_data.system + + raw_data.idle + + raw_data.io_wait + + raw_data.hardware_interrupt + + raw_data.software_interrupt + + raw_data.steal_time + + raw_data.guest + + raw_data.guest_nice) as f64; Self { - user: user / total * 100.0, - system: system / total * 100.0, - nice: nice / total * 100.0, - idle: idle / total * 100.0, - io_wait: io_wait / total * 100.0, - hardware_interrupt: hardware_interrupt / total * 100.0, - software_interrupt: software_interrupt / total * 100.0, - steal_time: steal_time / total * 100.0, - guest: guest / total * 100.0, - guest_nice: guest_nice / total * 100.0, + user: raw_data.user as f64 / total * 100.0, + system: raw_data.system as f64 / total * 100.0, + nice: raw_data.nice as f64 / total * 100.0, + idle: raw_data.idle as f64 / total * 100.0, + io_wait: raw_data.io_wait as f64 / total * 100.0, + hardware_interrupt: raw_data.hardware_interrupt as f64 / total * 100.0, + software_interrupt: raw_data.software_interrupt as f64 / total * 100.0, + steal_time: raw_data.steal_time as f64 / total * 100.0, + guest: raw_data.guest as f64 / total * 100.0, + guest_nice: raw_data.guest_nice as f64 / total * 100.0, } } } diff --git a/src/uu/vmstat/src/picker.rs b/src/uu/vmstat/src/picker.rs index 5e5362b9..e2b4026a 100644 --- a/src/uu/vmstat/src/picker.rs +++ b/src/uu/vmstat/src/picker.rs @@ -4,7 +4,7 @@ // file that was distributed with this source code. #[cfg(target_os = "linux")] -use crate::{CpuLoad, Meminfo, ProcData}; +use crate::{CpuLoad, CpuLoadRaw, Meminfo, ProcData}; #[cfg(target_os = "linux")] use clap::ArgMatches; @@ -76,6 +76,133 @@ pub fn get_pickers(matches: &ArgMatches) -> Vec { pickers } +#[cfg(target_os = "linux")] +pub fn get_stats() -> Vec<(String, u64)> { + let proc_data = ProcData::new(); + let memory_info = Meminfo::from_proc_map(&proc_data.meminfo); + let cpu_load = CpuLoadRaw::from_proc_map(&proc_data.stat); + + vec![ + ( + "K total memory".to_string(), + memory_info.mem_total.0 / bytesize::KB, + ), + ( + "K used memory".to_string(), + (memory_info.mem_total - memory_info.mem_available).0 / bytesize::KB, + ), + ( + "K active memory".to_string(), + memory_info.active.0 / bytesize::KB, + ), + ( + "K inactive memory".to_string(), + memory_info.inactive.0 / bytesize::KB, + ), + ( + "K free memory".to_string(), + memory_info.mem_free.0 / bytesize::KB, + ), + ( + "K buffer memory".to_string(), + memory_info.buffers.0 / bytesize::KB, + ), + ( + "K swap cache".to_string(), + memory_info.cached.0 / bytesize::KB, + ), + ( + "K total swap".to_string(), + memory_info.swap_total.0 / bytesize::KB, + ), + ( + "K used swap".to_string(), + (memory_info.swap_total - memory_info.swap_free).0 / bytesize::KB, + ), + ( + "K free swap".to_string(), + memory_info.swap_free.0 / bytesize::KB, + ), + ( + "non-nice user cpu ticks".to_string(), + cpu_load.user - cpu_load.nice, + ), + ("nice user cpu ticks".to_string(), cpu_load.nice), + ("system cpu ticks".to_string(), cpu_load.system), + ("idle cpu ticks".to_string(), cpu_load.idle), + ("IO-wait cpu ticks".to_string(), cpu_load.io_wait), + ("IRQ cpu ticks".to_string(), cpu_load.hardware_interrupt), + ("softirq cpu ticks".to_string(), cpu_load.software_interrupt), + ("stolen cpu ticks".to_string(), cpu_load.steal_time), + ("non-nice guest cpu ticks".to_string(), cpu_load.guest), + ("nice guest cpu ticks".to_string(), cpu_load.guest_nice), + ( + "K paged in".to_string(), + ProcData::get_one(&proc_data.vmstat, "pgpgin"), + ), + ( + "K paged out".to_string(), + ProcData::get_one(&proc_data.vmstat, "pgpgout"), + ), + ( + "pages swapped in".to_string(), + ProcData::get_one(&proc_data.vmstat, "pswpin"), + ), + ( + "pages swapped out".to_string(), + ProcData::get_one(&proc_data.vmstat, "pswpout"), + ), + ( + "pages alloc in dma".to_string(), + ProcData::get_one(&proc_data.vmstat, "pgalloc_dma"), + ), + ( + "pages alloc in dma32".to_string(), + ProcData::get_one(&proc_data.vmstat, "pgalloc_dma32"), + ), + ( + "pages alloc in high".to_string(), + ProcData::get_one(&proc_data.vmstat, "pgalloc_high"), + ), + ( + "pages alloc in movable".to_string(), + ProcData::get_one(&proc_data.vmstat, "pgalloc_movable"), + ), + ( + "pages alloc in normal".to_string(), + ProcData::get_one(&proc_data.vmstat, "pgalloc_normal"), + ), + ( + "pages free".to_string(), + ProcData::get_one(&proc_data.vmstat, "pgfree"), + ), + ( + "interrupts".to_string(), + proc_data + .stat + .get("intr") + .unwrap() + .split_whitespace() + .next() + .unwrap() + .parse::() + .unwrap(), + ), + ( + "CPU context switches".to_string(), + ProcData::get_one(&proc_data.stat, "ctxt"), + ), + ( + "boot time".to_string(), + ProcData::get_one(&proc_data.stat, "btime"), + ), + ( + "forks".to_string(), + ProcData::get_one(&proc_data.stat, "processes"), + ), + ] +} + #[cfg(target_os = "linux")] fn with_unit(x: u64, arg: &ArgMatches) -> u64 { if let Some(unit) = arg.get_one::("unit") { diff --git a/src/uu/vmstat/src/vmstat.rs b/src/uu/vmstat/src/vmstat.rs index 8b943f0b..38619a79 100644 --- a/src/uu/vmstat/src/vmstat.rs +++ b/src/uu/vmstat/src/vmstat.rs @@ -7,7 +7,7 @@ mod parser; mod picker; #[cfg(target_os = "linux")] -use crate::picker::{get_pickers, Picker}; +use crate::picker::{get_pickers, get_stats, Picker}; use clap::value_parser; #[allow(unused_imports)] use clap::{arg, crate_version, ArgMatches, Command}; @@ -26,14 +26,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uu_app().try_get_matches_from(args)?; #[cfg(target_os = "linux")] { - // validate unit - if let Some(unit) = matches.get_one::("unit") { - if !["k", "K", "m", "M"].contains(&unit.as_str()) { - Err(USimpleError::new( - 1, - "-S requires k, K, m or M (default is KiB)", - ))?; - } + if matches.get_flag("stats") { + return print_stats(); } let one_header = matches.get_flag("one-header"); @@ -46,6 +40,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { return print_slabs(one_header, term_height); } + // validate unit + if let Some(unit) = matches.get_one::("unit") { + if !["k", "K", "m", "M"].contains(&unit.as_str()) { + Err(USimpleError::new( + 1, + "-S requires k, K, m or M (default is KiB)", + ))?; + } + } + let delay = matches.get_one::("delay"); let count = matches.get_one::("count"); let mut count = count.copied().map(|c| if c == 0 { 1 } else { c }); @@ -79,6 +83,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { Ok(()) } +#[cfg(target_os = "linux")] +fn print_stats() -> UResult<()> { + let data = get_stats(); + + data.iter() + .for_each(|(name, value)| println!("{value:>13} {name}")); + + Ok(()) +} + #[cfg(target_os = "linux")] fn print_slabs(one_header: bool, term_height: u16) -> UResult<()> { let mut slab_data = uu_slabtop::SlabInfo::new()?.data; @@ -166,7 +180,7 @@ pub fn uu_app() -> Command { // arg!(-f --forks "switch displays the number of forks since boot"), arg!(-m --slabs "Display slabinfo"), arg!(-n --"one-header" "Display the header only once rather than periodically"), - // arg!(-s --stats "Displays a table of various event counters and memory statistics"), + arg!(-s --stats "Displays a table of various event counters and memory statistics"), // arg!(-d --disk "Report disk statistics"), // arg!(-D --"disk-sum" "Report some summary statistics about disk activity"), // arg!(-p --partition "Detailed statistics about partition"), diff --git a/tests/by-util/test_vmstat.rs b/tests/by-util/test_vmstat.rs index a37a790d..b48c2408 100644 --- a/tests/by-util/test_vmstat.rs +++ b/tests/by-util/test_vmstat.rs @@ -81,3 +81,9 @@ fn test_timestamp() { .unwrap() .contains("timestamp")); } + +#[test] +#[cfg(target_os = "linux")] +fn test_stats() { + new_ucmd!().arg("-s").succeeds(); +}