From 1e63a38afbd1458aaa9e75c392beb106bb074467 Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Sat, 5 Apr 2025 15:02:02 +0800 Subject: [PATCH 1/7] vmstat: impl `delay`, `count,` `--one-heder`, `--no-first` --- Cargo.lock | 68 +-------- Cargo.toml | 2 +- src/uu/vmstat/Cargo.toml | 7 +- src/uu/vmstat/src/parser.rs | 116 +++++++++++++-- src/uu/vmstat/src/picker.rs | 196 +++++++++++++++++++++++++ src/uu/vmstat/src/vmstat.rs | 282 +++++++++++++----------------------- 6 files changed, 405 insertions(+), 266 deletions(-) create mode 100644 src/uu/vmstat/src/picker.rs diff --git a/Cargo.lock b/Cargo.lock index 1bc2a94c..c7268fc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - [[package]] name = "aho-corasick" version = "1.1.3" @@ -269,15 +263,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - [[package]] name = "crossterm" version = "0.28.1" @@ -520,16 +505,6 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" -[[package]] -name = "flate2" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - [[package]] name = "fnv" version = "1.0.7" @@ -594,12 +569,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - [[package]] name = "iana-time-zone" version = "0.1.63" @@ -762,15 +731,6 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "miniz_oxide" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" -dependencies = [ - "adler2", -] - [[package]] name = "mio" version = "1.0.3" @@ -988,31 +948,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "procfs" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f" -dependencies = [ - "bitflags", - "chrono", - "flate2", - "hex", - "procfs-core", - "rustix 0.38.44", -] - -[[package]] -name = "procfs-core" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" -dependencies = [ - "bitflags", - "chrono", - "hex", -] - [[package]] name = "procps" version = "0.0.1" @@ -1729,8 +1664,9 @@ dependencies = [ name = "uu_vmstat" version = "0.0.1" dependencies = [ + "bytesize", "clap", - "procfs", + "terminal_size", "uucore", ] diff --git a/Cargo.toml b/Cargo.toml index c1fc1dc0..3b29b1ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,12 +56,12 @@ nix = { version = "0.29", default-features = false, features = ["process"] } phf = "0.11.2" phf_codegen = "0.11.2" prettytable-rs = "0.10.0" -procfs = "0.17.0" rand = { version = "0.9.0", features = ["small_rng"] } ratatui = "0.29.0" regex = "1.10.4" sysinfo = "0.34.0" tempfile = "3.10.1" +terminal_size = "0.4.2" textwrap = { version = "0.16.1", features = ["terminal_size"] } thiserror = "2.0.4" uucore = "0.0.30" diff --git a/src/uu/vmstat/Cargo.toml b/src/uu/vmstat/Cargo.toml index 9bb7f866..5e6be4db 100644 --- a/src/uu/vmstat/Cargo.toml +++ b/src/uu/vmstat/Cargo.toml @@ -12,11 +12,10 @@ keywords = ["acl", "uutils", "cross-platform", "cli", "utility"] categories = ["command-line-utilities"] [dependencies] -uucore = { workspace = true } +bytesize = { workspace = true } clap = { workspace = true } - -[target.'cfg(target_os="linux")'.dependencies] -procfs = { workspace = true } +terminal_size = { workspace = true } +uucore = { workspace = true } [lib] path = "src/vmstat.rs" diff --git a/src/uu/vmstat/src/parser.rs b/src/uu/vmstat/src/parser.rs index 05f5a57a..4099edb2 100644 --- a/src/uu/vmstat/src/parser.rs +++ b/src/uu/vmstat/src/parser.rs @@ -4,21 +4,65 @@ // file that was distributed with this source code. #[cfg(target_os = "linux")] -pub fn parse_proc_file(path: &str) -> std::collections::HashMap { +use std::collections::HashMap; + +#[cfg(target_os = "linux")] +pub fn parse_proc_file(path: &str) -> HashMap { let file = std::fs::File::open(std::path::Path::new(path)).unwrap(); let content = std::io::read_to_string(file).unwrap(); - let mut map: std::collections::HashMap = std::collections::HashMap::new(); + let mut map: HashMap = HashMap::new(); for line in content.lines() { let parts = line.split_once(char::is_whitespace); if let Some(parts) = parts { - map.insert(parts.0.to_string(), parts.1.trim_start().to_string()); + map.insert( + parts.0.strip_suffix(":").unwrap_or(parts.0).to_string(), + parts.1.trim_start().to_string(), + ); } } map } +#[cfg(target_os = "linux")] +pub struct ProcData { + pub uptime: (f64, f64), + pub stat: HashMap, + pub meminfo: HashMap, + pub vmstat: HashMap, +} +#[cfg(target_os = "linux")] +impl Default for ProcData { + fn default() -> Self { + Self::new() + } +} +#[cfg(target_os = "linux")] +impl ProcData { + pub fn new() -> Self { + let uptime = Self::get_uptime(); + let stat = parse_proc_file("/proc/stat"); + let meminfo = parse_proc_file("/proc/meminfo"); + let vmstat = parse_proc_file("/proc/vmstat"); + Self { + uptime, + stat, + meminfo, + vmstat, + } + } + + fn get_uptime() -> (f64, f64) { + let file = std::fs::File::open(std::path::Path::new("/proc/uptime")).unwrap(); + let content = std::io::read_to_string(file).unwrap(); + let mut parts = content.split_whitespace(); + let uptime = parts.next().unwrap().parse::().unwrap(); + let idle_time = parts.next().unwrap().parse::().unwrap(); + (uptime, idle_time) + } +} + #[cfg(target_os = "linux")] pub struct CpuLoad { pub user: f64, @@ -35,18 +79,20 @@ pub struct CpuLoad { #[cfg(target_os = "linux")] impl CpuLoad { - pub fn current() -> CpuLoad { + 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(); - let load = content - .lines() - .next() - .unwrap() - .strip_prefix("cpu") - .unwrap() - .split(' ') - .filter(|s| !s.is_empty()) - .collect::>(); + let load_str = content.lines().next().unwrap().strip_prefix("cpu").unwrap(); + Self::from_str(load_str) + } + + pub fn from_proc_map(proc_map: &HashMap) -> Self { + let load_str = proc_map.get("cpu").unwrap(); + Self::from_str(load_str) + } + + 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(); @@ -81,3 +127,47 @@ impl CpuLoad { } } } + +#[cfg(target_os = "linux")] +pub struct Meminfo { + pub mem_total: bytesize::ByteSize, + pub mem_free: bytesize::ByteSize, + pub mem_available: bytesize::ByteSize, + pub buffers: bytesize::ByteSize, + pub cached: bytesize::ByteSize, + pub swap_cached: bytesize::ByteSize, + pub swap_total: bytesize::ByteSize, + pub swap_free: bytesize::ByteSize, +} +#[cfg(target_os = "linux")] +impl Meminfo { + pub fn current() -> Self { + let meminfo = parse_proc_file("/proc/meminfo"); + Self::from_proc_map(&meminfo) + } + + pub fn from_proc_map(proc_map: &HashMap) -> Self { + use std::str::FromStr; + + let mem_total = bytesize::ByteSize::from_str(proc_map.get("MemTotal").unwrap()).unwrap(); + let mem_free = bytesize::ByteSize::from_str(proc_map.get("MemFree").unwrap()).unwrap(); + let mem_available = + bytesize::ByteSize::from_str(proc_map.get("MemAvailable").unwrap()).unwrap(); + let buffers = bytesize::ByteSize::from_str(proc_map.get("Buffers").unwrap()).unwrap(); + let cached = bytesize::ByteSize::from_str(proc_map.get("Cached").unwrap()).unwrap(); + let swap_cached = + bytesize::ByteSize::from_str(proc_map.get("SwapCached").unwrap()).unwrap(); + let swap_total = bytesize::ByteSize::from_str(proc_map.get("SwapTotal").unwrap()).unwrap(); + let swap_free = bytesize::ByteSize::from_str(proc_map.get("SwapFree").unwrap()).unwrap(); + Self { + mem_total, + mem_free, + mem_available, + buffers, + cached, + swap_cached, + swap_total, + swap_free, + } + } +} diff --git a/src/uu/vmstat/src/picker.rs b/src/uu/vmstat/src/picker.rs new file mode 100644 index 00000000..8e57ff4c --- /dev/null +++ b/src/uu/vmstat/src/picker.rs @@ -0,0 +1,196 @@ +// This file is part of the uutils procps package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +#[cfg(target_os = "linux")] +use crate::{CpuLoad, Meminfo, ProcData}; + +#[cfg(target_os = "linux")] +pub type Picker = ( + (String, String), + Box, &mut Vec, &mut usize)>, +); + +#[cfg(target_os = "linux")] +pub fn get_pickers() -> Vec { + vec![ + concat_helper(("procs".into(), " r b".into()), get_process_info), + concat_helper( + ( + "-----------memory----------".into(), + " swpd free buff cache".into(), + ), + get_memory_info, + ), + concat_helper(("---swap--".into(), " si so".into()), get_swap_info), + concat_helper(("-----io----".into(), " bi bo".into()), get_io_info), + concat_helper(("-system--".into(), " in cs".into()), get_system_info), + concat_helper( + ("-------cpu-------".into(), "us sy id wa st gu".into()), + get_cpu_info, + ), + ] +} + +#[cfg(target_os = "linux")] +fn concat_helper( + title: (String, String), + func: impl Fn(&ProcData, Option<&ProcData>) -> Vec<(usize, String)> + 'static, +) -> Picker { + ( + title, + Box::from( + move |proc_data: &ProcData, + proc_data_before: Option<&ProcData>, + data: &mut Vec, + data_len_excess: &mut usize| { + let output = func(proc_data, proc_data_before); + output.iter().for_each(|(len, value)| { + let len = if *data_len_excess > *len { + 0 + } else { + len - *data_len_excess + }; + let formatted_value = format!("{:>width$}", value, width = len); + *data_len_excess = formatted_value.len() - len; + data.push(formatted_value); + }); + }, + ), + ) +} + +#[cfg(target_os = "linux")] +macro_rules! diff { + ($now:expr, $before:expr, $($property:tt)*) => { + if let Some(before) = &$before { + $now.$($property)* - before.$($property)* + } else { + $now.$($property)* + } + }; +} + +#[cfg(target_os = "linux")] +fn get_process_info( + proc_data: &ProcData, + _proc_data_before: Option<&ProcData>, +) -> Vec<(usize, String)> { + let runnable = proc_data.stat.get("procs_running").unwrap(); + let blocked = proc_data.stat.get("procs_blocked").unwrap(); + + vec![(2, runnable.to_string()), (2, blocked.to_string())] +} + +#[cfg(target_os = "linux")] +fn get_memory_info( + proc_data: &ProcData, + _proc_data_before: Option<&ProcData>, +) -> Vec<(usize, String)> { + use bytesize::*; + + let memory_info = Meminfo::from_proc_map(&proc_data.meminfo); + + let swap_used = (memory_info.swap_total - memory_info.swap_free).as_u64() / KB; + let free = memory_info.mem_free.as_u64() / KB; + let buffer = memory_info.buffers.as_u64() / KB; + let cache = memory_info.cached.as_u64() / KB; + + vec![ + (6, format!("{}", swap_used)), + (6, format!("{}", free)), + (6, format!("{}", buffer)), + (6, format!("{}", cache)), + ] +} + +#[cfg(target_os = "linux")] +fn get_swap_info( + proc_data: &ProcData, + proc_data_before: Option<&ProcData>, +) -> Vec<(usize, String)> { + let period = diff!(proc_data, proc_data_before, uptime.0); + let swap_in = diff!( + proc_data, + proc_data_before, + vmstat.get("pswpin").unwrap().parse::().unwrap() + ); + let swap_out = diff!( + proc_data, + proc_data_before, + vmstat.get("pswpout").unwrap().parse::().unwrap() + ); + + vec![ + (4, format!("{:.0}", swap_in as f64 / period)), + (4, format!("{:.0}", swap_out as f64 / period)), + ] +} + +#[cfg(target_os = "linux")] +fn get_io_info(proc_data: &ProcData, proc_data_before: Option<&ProcData>) -> Vec<(usize, String)> { + let period = diff!(proc_data, proc_data_before, uptime.0); + let read_bytes = diff!( + proc_data, + proc_data_before, + vmstat.get("pgpgin").unwrap().parse::().unwrap() + ); + let write_bytes = diff!( + proc_data, + proc_data_before, + vmstat.get("pgpgout").unwrap().parse::().unwrap() + ); + + vec![ + (5, format!("{:.0}", read_bytes as f64 / period)), + (5, format!("{:.0}", write_bytes as f64 / period)), + ] +} + +#[cfg(target_os = "linux")] +fn get_system_info( + proc_data: &ProcData, + proc_data_before: Option<&ProcData>, +) -> Vec<(usize, String)> { + let period = diff!(proc_data, proc_data_before, uptime.0); + + let interrupts = diff!( + proc_data, + proc_data_before, + stat.get("intr") + .unwrap() + .split_whitespace() + .next() + .unwrap() + .parse::() + .unwrap() + ); + let context_switches = diff!( + proc_data, + proc_data_before, + stat.get("ctxt").unwrap().parse::().unwrap() + ); + + vec![ + (4, format!("{:.0}", interrupts as f64 / period)), + (4, format!("{:.0}", context_switches as f64 / period)), + ] +} + +#[cfg(target_os = "linux")] +fn get_cpu_info( + proc_data: &ProcData, + _proc_data_before: Option<&ProcData>, +) -> Vec<(usize, String)> { + let cpu_load = CpuLoad::from_proc_map(&proc_data.stat); + + vec![ + (2, format!("{:.0}", cpu_load.user)), + (2, format!("{:.0}", cpu_load.system)), + (2, format!("{:.0}", cpu_load.idle)), + (2, format!("{:.0}", cpu_load.io_wait)), + (2, format!("{:.0}", cpu_load.steal_time)), + (2, format!("{:.0}", cpu_load.guest)), + ] +} diff --git a/src/uu/vmstat/src/vmstat.rs b/src/uu/vmstat/src/vmstat.rs index 79294bd5..60a6d6cb 100644 --- a/src/uu/vmstat/src/vmstat.rs +++ b/src/uu/vmstat/src/vmstat.rs @@ -4,13 +4,15 @@ // file that was distributed with this source code. mod parser; +mod picker; -use clap::{crate_version, Command}; +#[cfg(target_os = "linux")] +use crate::picker::{get_pickers, Picker}; +use clap::{arg, crate_version, Command}; #[allow(unused_imports)] pub use parser::*; -#[cfg(target_os = "linux")] -use procfs::{Current, CurrentSI}; -use uucore::error::UResult; +#[allow(unused_imports)] +use uucore::error::{UResult, USimpleError}; use uucore::{format_usage, help_about, help_usage}; const ABOUT: &str = help_about!("vmstat.md"); @@ -18,173 +20,91 @@ const USAGE: &str = help_usage!("vmstat.md"); #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let _matches = uu_app().try_get_matches_from(args)?; - - let mut section: Vec = vec![]; - let mut title: Vec = vec![]; - let mut data: Vec = vec![]; - let mut data_len_excess = 0; - + #[allow(unused)] + let matches = uu_app().try_get_matches_from(args)?; #[cfg(target_os = "linux")] - let func = [ - concat_helper(get_process_info), - concat_helper(get_memory_info), - concat_helper(get_swap_info), - concat_helper(get_io_info), - concat_helper(get_system_info), - concat_helper(get_cpu_info), - ]; - - #[cfg(not(target_os = "linux"))] - let func: [ConcatFunc; 0] = []; - - func.iter() - .for_each(|f| f(&mut section, &mut title, &mut data, &mut data_len_excess)); - - println!("{}", section.join(" ")); - println!("{}", title.join(" ")); - println!("{}", data.join(" ")); + { + let one_header = matches.get_flag("one-header"); + let no_first = matches.get_flag("no-first"); + let delay = matches.get_one::("delay"); + let mut delay = if let Some(delay) = delay { + let delay = delay.parse::().unwrap(); + if delay <= 0 { + return Err(USimpleError::new(-1, "delay must be greater than 0")); + } + delay + } else { + -1 + }; + let count = matches.get_one::("count"); + let mut count = if let Some(count) = count { + let mut count = count.parse::().unwrap(); + if count < 0 { + return Err(USimpleError::new(-1, "count must be greater than 0")); + } + if count == 0 { + count = 1; + } + count + } else { + -1 + }; + if count < 0 && delay < 0 { + count = 1; + if no_first { + delay = 1; + count = 1; + } + } + + let pickers = get_pickers(); + let mut proc_data = ProcData::new(); + + let mut line_count = 0; + print_header(&pickers); + if !no_first { + print_data(&pickers, &proc_data, None); + line_count += 1; + } + + let term_height = terminal_size::terminal_size() + .map(|size| size.1 .0) + .unwrap_or(0); + + while count < 0 || line_count < count { + std::thread::sleep(std::time::Duration::from_secs(delay as u64)); + let proc_data_now = ProcData::new(); + if !one_header && term_height > 0 && ((line_count + 3) % term_height as i64 == 0) { + print_header(&pickers); + } + print_data(&pickers, &proc_data_now, Some(&proc_data)); + line_count += 1; + proc_data = proc_data_now; + } + } Ok(()) } - -type ConcatFunc = Box, &mut Vec, &mut Vec, &mut usize)>; - -#[allow(dead_code)] -fn concat_helper( - func: impl Fn() -> (String, String, Vec<(usize, String)>) + 'static, -) -> ConcatFunc { - Box::from( - move |section: &mut Vec, - title: &mut Vec, - data: &mut Vec, - data_len_excess: &mut usize| { - let output = func(); - section.push(output.0); - title.push(output.1); - output.2.iter().for_each(|(len, value)| { - let len = len - *data_len_excess; - let formatted_value = format!("{:>width$}", value, width = len); - *data_len_excess = formatted_value.len() - len; - data.push(formatted_value); - }); - }, - ) -} - -#[cfg(target_os = "linux")] -fn up_secs() -> f64 { - let file = std::fs::File::open(std::path::Path::new("/proc/uptime")).unwrap(); - let content = std::io::read_to_string(file).unwrap(); - let mut parts = content.split_whitespace(); - parts.next().unwrap().parse::().unwrap() -} - -#[cfg(target_os = "linux")] -fn get_process_info() -> (String, String, Vec<(usize, String)>) { - let stat = procfs::KernelStats::current().unwrap(); - let runnable = stat.procs_running.unwrap_or_default(); - let blocked = stat.procs_blocked.unwrap_or_default(); - ( - "procs".into(), - " r b".into(), - vec![(2, format!("{}", runnable)), (2, format!("{}", blocked))], - ) -} - -#[cfg(target_os = "linux")] -fn get_memory_info() -> (String, String, Vec<(usize, String)>) { - let memory_info = procfs::Meminfo::current().unwrap(); - let swap_used = (memory_info.swap_total - memory_info.swap_free) / 1024; - let free = memory_info.mem_free / 1024; - let buffer = memory_info.buffers / 1024; - let cache = memory_info.cached / 1024; - - ( - "-----------memory----------".into(), - " swpd free buff cache".into(), - vec![ - (6, format!("{}", swap_used)), - (6, format!("{}", free)), - (6, format!("{}", buffer)), - (6, format!("{}", cache)), - ], - ) -} - #[cfg(target_os = "linux")] -fn get_swap_info() -> (String, String, Vec<(usize, String)>) { - let uptime = up_secs(); - let vmstat = procfs::vmstat().unwrap(); - let swap_in = vmstat.get("pswpin").unwrap(); - let swap_out = vmstat.get("pswpout").unwrap(); - ( - "---swap--".into(), - " si so".into(), - vec![ - (4, format!("{:.0}", *swap_in as f64 / uptime)), - (4, format!("{:.0}", *swap_out as f64 / uptime)), - ], - ) -} - -#[cfg(target_os = "linux")] -fn get_io_info() -> (String, String, Vec<(usize, String)>) { - let uptime = up_secs(); - let vmstat = procfs::vmstat().unwrap(); - let read_bytes = vmstat.get("pgpgin").unwrap(); - let write_bytes = vmstat.get("pgpgout").unwrap(); - ( - "-----io----".into(), - " bi bo".into(), - vec![ - (5, format!("{:.0}", *read_bytes as f64 / uptime)), - (5, format!("{:.0}", *write_bytes as f64 / uptime)), - ], - ) -} - -#[cfg(target_os = "linux")] -fn get_system_info() -> (String, String, Vec<(usize, String)>) { - let uptime = up_secs(); - let stat = parse_proc_file("/proc/stat"); - - let interrupts = stat - .get("intr") - .unwrap() - .split_whitespace() - .next() - .unwrap() - .parse::() - .unwrap(); - let context_switches = stat.get("ctxt").unwrap().parse::().unwrap(); - - ( - "-system--".into(), - " in cs".into(), - vec![ - (4, format!("{:.0}", interrupts as f64 / uptime)), - (4, format!("{:.0}", context_switches as f64 / uptime)), - ], - ) +fn print_header(pickers: &[Picker]) { + let mut section: Vec<&str> = vec![]; + let mut title: Vec<&str> = vec![]; + + pickers.iter().for_each(|p| { + section.push(p.0 .0.as_str()); + title.push(p.0 .1.as_str()); + }); + println!("{}", section.join(" ")); + println!("{}", title.join(" ")); } - #[cfg(target_os = "linux")] -fn get_cpu_info() -> (String, String, Vec<(usize, String)>) { - let cpu_load = CpuLoad::current(); - - ( - "-------cpu-------".into(), - "us sy id wa st gu".into(), - vec![ - (2, format!("{:.0}", cpu_load.user)), - (2, format!("{:.0}", cpu_load.system)), - (2, format!("{:.0}", cpu_load.idle)), - (2, format!("{:.0}", cpu_load.io_wait)), - (2, format!("{:.0}", cpu_load.steal_time)), - (2, format!("{:.0}", cpu_load.guest)), - ], - ) +fn print_data(pickers: &[Picker], proc_data: &ProcData, proc_data_before: Option<&ProcData>) { + let mut data: Vec = vec![]; + let mut data_len_excess = 0; + pickers.iter().for_each(|f| { + f.1(proc_data, proc_data_before, &mut data, &mut data_len_excess); + }); + println!("{}", data.join(" ")); } #[allow(clippy::cognitive_complexity)] @@ -194,22 +114,20 @@ pub fn uu_app() -> Command { .about(ABOUT) .override_usage(format_usage(USAGE)) .infer_long_args(true) - // .args([ - // arg!( "The delay between updates in seconds").required(false), - // arg!( "Number of updates").required(false), - // arg!(-a --active "Display active and inactive memory"), - // 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!(-d --disk "Report disk statistics"), - // arg!(-D --disk-sum "Report some summary statistics about disk activity"), - // arg!(-p --partition "Detailed statistics about partition"), - // arg!(-S --unit "Switches outputs between 1000 (k), 1024 (K), 1000000 (m), or 1048576 (M) bytes"), - // arg!(-t --timestamp "Append timestamp to each line"), - // arg!(-w --wide "Wide output mode"), - // arg!(-y --no-first "Omits first report with statistics since system boot"), - // arg!(-V --version "Display version information and exit"), - // arg!(-h --help "Display help and exit"), - // ]) + .args([ + arg!( "The delay between updates in seconds").required(false), + arg!( "Number of updates").required(false), + // arg!(-a --active "Display active and inactive memory"), + // 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!(-d --disk "Report disk statistics"), + // arg!(-D --"disk-sum" "Report some summary statistics about disk activity"), + // arg!(-p --partition "Detailed statistics about partition"), + // arg!(-S --unit "Switches outputs between 1000 (k), 1024 (K), 1000000 (m), or 1048576 (M) bytes"), + // arg!(-t --timestamp "Append timestamp to each line"), + // arg!(-w --wide "Wide output mode"), + arg!(-y --"no-first" "Omits first report with statistics since system boot"), + ]) } From a95ff2976b50a072effec04da305ad51c74bbd68 Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Mon, 14 Apr 2025 20:22:13 +0800 Subject: [PATCH 2/7] vmstat: impl `--active` `--wide` --- src/uu/vmstat/src/parser.rs | 6 +++ src/uu/vmstat/src/picker.rs | 102 ++++++++++++++++++++++++++++-------- src/uu/vmstat/src/vmstat.rs | 26 ++++++--- 3 files changed, 104 insertions(+), 30 deletions(-) diff --git a/src/uu/vmstat/src/parser.rs b/src/uu/vmstat/src/parser.rs index 4099edb2..6cff6ea0 100644 --- a/src/uu/vmstat/src/parser.rs +++ b/src/uu/vmstat/src/parser.rs @@ -136,6 +136,8 @@ pub struct Meminfo { pub buffers: bytesize::ByteSize, pub cached: bytesize::ByteSize, pub swap_cached: bytesize::ByteSize, + pub active: bytesize::ByteSize, + pub inactive: bytesize::ByteSize, pub swap_total: bytesize::ByteSize, pub swap_free: bytesize::ByteSize, } @@ -157,6 +159,8 @@ impl Meminfo { let cached = bytesize::ByteSize::from_str(proc_map.get("Cached").unwrap()).unwrap(); let swap_cached = bytesize::ByteSize::from_str(proc_map.get("SwapCached").unwrap()).unwrap(); + let active = bytesize::ByteSize::from_str(proc_map.get("Active").unwrap()).unwrap(); + let inactive = bytesize::ByteSize::from_str(proc_map.get("Inactive").unwrap()).unwrap(); let swap_total = bytesize::ByteSize::from_str(proc_map.get("SwapTotal").unwrap()).unwrap(); let swap_free = bytesize::ByteSize::from_str(proc_map.get("SwapFree").unwrap()).unwrap(); Self { @@ -166,6 +170,8 @@ impl Meminfo { buffers, cached, swap_cached, + active, + inactive, swap_total, swap_free, } diff --git a/src/uu/vmstat/src/picker.rs b/src/uu/vmstat/src/picker.rs index 8e57ff4c..c05b9397 100644 --- a/src/uu/vmstat/src/picker.rs +++ b/src/uu/vmstat/src/picker.rs @@ -5,29 +5,60 @@ #[cfg(target_os = "linux")] use crate::{CpuLoad, Meminfo, ProcData}; +use clap::ArgMatches; #[cfg(target_os = "linux")] pub type Picker = ( (String, String), - Box, &mut Vec, &mut usize)>, + Box, &ArgMatches, &mut Vec, &mut usize)>, ); #[cfg(target_os = "linux")] -pub fn get_pickers() -> Vec { +pub fn get_pickers(matches: &ArgMatches) -> Vec { + let wide = matches.get_flag("wide"); vec![ - concat_helper(("procs".into(), " r b".into()), get_process_info), concat_helper( - ( - "-----------memory----------".into(), - " swpd free buff cache".into(), - ), + if wide { + ("--procs--".into(), " r b".into()) + } else { + ("procs".into(), " r b".into()) + }, + get_process_info, + ), + concat_helper( + if wide { + ( + "-----------------------memory----------------------".into(), + if matches.get_flag("active") { + " swpd free inact active".into() + } else { + " swpd free buff cache".into() + }, + ) + } else { + ( + "-----------memory----------".into(), + if matches.get_flag("active") { + " swpd free inact active".into() + } else { + " swpd free buff cache".into() + }, + ) + }, get_memory_info, ), concat_helper(("---swap--".into(), " si so".into()), get_swap_info), concat_helper(("-----io----".into(), " bi bo".into()), get_io_info), concat_helper(("-system--".into(), " in cs".into()), get_system_info), concat_helper( - ("-------cpu-------".into(), "us sy id wa st gu".into()), + if wide { + ( + "----------cpu----------".into(), + " us sy id wa st gu".into(), + ) + } else { + ("-------cpu-------".into(), "us sy id wa st gu".into()) + }, get_cpu_info, ), ] @@ -36,16 +67,17 @@ pub fn get_pickers() -> Vec { #[cfg(target_os = "linux")] fn concat_helper( title: (String, String), - func: impl Fn(&ProcData, Option<&ProcData>) -> Vec<(usize, String)> + 'static, + func: impl Fn(&ProcData, Option<&ProcData>, &ArgMatches) -> Vec<(usize, String)> + 'static, ) -> Picker { ( title, Box::from( move |proc_data: &ProcData, proc_data_before: Option<&ProcData>, + matches: &ArgMatches, data: &mut Vec, data_len_excess: &mut usize| { - let output = func(proc_data, proc_data_before); + let output = func(proc_data, proc_data_before, matches); output.iter().for_each(|(len, value)| { let len = if *data_len_excess > *len { 0 @@ -76,32 +108,47 @@ macro_rules! diff { fn get_process_info( proc_data: &ProcData, _proc_data_before: Option<&ProcData>, + matches: &ArgMatches, ) -> Vec<(usize, String)> { let runnable = proc_data.stat.get("procs_running").unwrap(); let blocked = proc_data.stat.get("procs_blocked").unwrap(); + let len = if matches.get_flag("wide") { 4 } else { 2 }; - vec![(2, runnable.to_string()), (2, blocked.to_string())] + vec![(len, runnable.to_string()), (len, blocked.to_string())] } #[cfg(target_os = "linux")] fn get_memory_info( proc_data: &ProcData, _proc_data_before: Option<&ProcData>, + matches: &ArgMatches, ) -> Vec<(usize, String)> { use bytesize::*; - + let len = if matches.get_flag("wide") { 12 } else { 6 }; let memory_info = Meminfo::from_proc_map(&proc_data.meminfo); let swap_used = (memory_info.swap_total - memory_info.swap_free).as_u64() / KB; let free = memory_info.mem_free.as_u64() / KB; + + if matches.get_flag("active") { + let inactive = memory_info.inactive.as_u64() / KB; + let active = memory_info.active.as_u64() / KB; + return vec![ + (len, format!("{}", swap_used)), + (len, format!("{}", free)), + (len, format!("{}", inactive)), + (len, format!("{}", active)), + ]; + } + let buffer = memory_info.buffers.as_u64() / KB; let cache = memory_info.cached.as_u64() / KB; vec![ - (6, format!("{}", swap_used)), - (6, format!("{}", free)), - (6, format!("{}", buffer)), - (6, format!("{}", cache)), + (len, format!("{}", swap_used)), + (len, format!("{}", free)), + (len, format!("{}", buffer)), + (len, format!("{}", cache)), ] } @@ -109,6 +156,7 @@ fn get_memory_info( fn get_swap_info( proc_data: &ProcData, proc_data_before: Option<&ProcData>, + _matches: &ArgMatches, ) -> Vec<(usize, String)> { let period = diff!(proc_data, proc_data_before, uptime.0); let swap_in = diff!( @@ -129,7 +177,11 @@ fn get_swap_info( } #[cfg(target_os = "linux")] -fn get_io_info(proc_data: &ProcData, proc_data_before: Option<&ProcData>) -> Vec<(usize, String)> { +fn get_io_info( + proc_data: &ProcData, + proc_data_before: Option<&ProcData>, + _matches: &ArgMatches, +) -> Vec<(usize, String)> { let period = diff!(proc_data, proc_data_before, uptime.0); let read_bytes = diff!( proc_data, @@ -152,6 +204,7 @@ fn get_io_info(proc_data: &ProcData, proc_data_before: Option<&ProcData>) -> Vec fn get_system_info( proc_data: &ProcData, proc_data_before: Option<&ProcData>, + _matches: &ArgMatches, ) -> Vec<(usize, String)> { let period = diff!(proc_data, proc_data_before, uptime.0); @@ -182,15 +235,18 @@ fn get_system_info( fn get_cpu_info( proc_data: &ProcData, _proc_data_before: Option<&ProcData>, + matches: &ArgMatches, ) -> Vec<(usize, String)> { + let len = if matches.get_flag("wide") { 3 } else { 2 }; + let cpu_load = CpuLoad::from_proc_map(&proc_data.stat); vec![ - (2, format!("{:.0}", cpu_load.user)), - (2, format!("{:.0}", cpu_load.system)), - (2, format!("{:.0}", cpu_load.idle)), - (2, format!("{:.0}", cpu_load.io_wait)), - (2, format!("{:.0}", cpu_load.steal_time)), - (2, format!("{:.0}", cpu_load.guest)), + (len, format!("{:.0}", cpu_load.user)), + (len, format!("{:.0}", cpu_load.system)), + (len, format!("{:.0}", cpu_load.idle)), + (len, format!("{:.0}", cpu_load.io_wait)), + (len, format!("{:.0}", cpu_load.steal_time)), + (len, format!("{:.0}", cpu_load.guest)), ] } diff --git a/src/uu/vmstat/src/vmstat.rs b/src/uu/vmstat/src/vmstat.rs index 60a6d6cb..fecf1edd 100644 --- a/src/uu/vmstat/src/vmstat.rs +++ b/src/uu/vmstat/src/vmstat.rs @@ -8,7 +8,7 @@ mod picker; #[cfg(target_os = "linux")] use crate::picker::{get_pickers, Picker}; -use clap::{arg, crate_version, Command}; +use clap::{arg, crate_version, ArgMatches, Command}; #[allow(unused_imports)] pub use parser::*; #[allow(unused_imports)] @@ -57,13 +57,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } } - let pickers = get_pickers(); + let pickers = get_pickers(&matches); let mut proc_data = ProcData::new(); let mut line_count = 0; print_header(&pickers); if !no_first { - print_data(&pickers, &proc_data, None); + print_data(&pickers, &proc_data, None, &matches); line_count += 1; } @@ -77,7 +77,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { if !one_header && term_height > 0 && ((line_count + 3) % term_height as i64 == 0) { print_header(&pickers); } - print_data(&pickers, &proc_data_now, Some(&proc_data)); + print_data(&pickers, &proc_data_now, Some(&proc_data), &matches); line_count += 1; proc_data = proc_data_now; } @@ -98,11 +98,22 @@ fn print_header(pickers: &[Picker]) { println!("{}", title.join(" ")); } #[cfg(target_os = "linux")] -fn print_data(pickers: &[Picker], proc_data: &ProcData, proc_data_before: Option<&ProcData>) { +fn print_data( + pickers: &[Picker], + proc_data: &ProcData, + proc_data_before: Option<&ProcData>, + matches: &ArgMatches, +) { let mut data: Vec = vec![]; let mut data_len_excess = 0; pickers.iter().for_each(|f| { - f.1(proc_data, proc_data_before, &mut data, &mut data_len_excess); + f.1( + proc_data, + proc_data_before, + matches, + &mut data, + &mut data_len_excess, + ); }); println!("{}", data.join(" ")); } @@ -117,7 +128,7 @@ pub fn uu_app() -> Command { .args([ arg!( "The delay between updates in seconds").required(false), arg!( "Number of updates").required(false), - // arg!(-a --active "Display active and inactive memory"), + arg!(-a --active "Display active and inactive memory"), // 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"), @@ -128,6 +139,7 @@ pub fn uu_app() -> Command { // arg!(-S --unit "Switches outputs between 1000 (k), 1024 (K), 1000000 (m), or 1048576 (M) bytes"), // arg!(-t --timestamp "Append timestamp to each line"), // arg!(-w --wide "Wide output mode"), + arg!(-w --wide "Wide output mode"), arg!(-y --"no-first" "Omits first report with statistics since system boot"), ]) } From 995013e1e427137c105eff29e9ba3c5438a2c428 Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Mon, 14 Apr 2025 22:36:30 +0800 Subject: [PATCH 3/7] vmstat: impl `--unit` --- src/uu/vmstat/src/picker.rs | 33 ++++++++++++++++++++++++++------- src/uu/vmstat/src/vmstat.rs | 3 +-- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/uu/vmstat/src/picker.rs b/src/uu/vmstat/src/picker.rs index c05b9397..03846161 100644 --- a/src/uu/vmstat/src/picker.rs +++ b/src/uu/vmstat/src/picker.rs @@ -6,6 +6,7 @@ #[cfg(target_os = "linux")] use crate::{CpuLoad, Meminfo, ProcData}; use clap::ArgMatches; +use uucore::error::USimpleError; #[cfg(target_os = "linux")] pub type Picker = ( @@ -64,6 +65,22 @@ pub fn get_pickers(matches: &ArgMatches) -> Vec { ] } +fn with_unit(x: u64, arg: &ArgMatches) -> u64 { + if let Some(unit) = arg.get_one::("unit") { + return match unit.as_str() { + "k" => x / bytesize::KB, + "K" => x / bytesize::KIB, + "m" => x / bytesize::MB, + "M" => x / bytesize::MIB, + _ => panic!( + "{:?}", + USimpleError::new(1, "-S requires k, K, m or M (default is KiB)",) + ), + }; + } + x / bytesize::KIB +} + #[cfg(target_os = "linux")] fn concat_helper( title: (String, String), @@ -123,16 +140,18 @@ fn get_memory_info( _proc_data_before: Option<&ProcData>, matches: &ArgMatches, ) -> Vec<(usize, String)> { - use bytesize::*; let len = if matches.get_flag("wide") { 12 } else { 6 }; let memory_info = Meminfo::from_proc_map(&proc_data.meminfo); - let swap_used = (memory_info.swap_total - memory_info.swap_free).as_u64() / KB; - let free = memory_info.mem_free.as_u64() / KB; + let swap_used = with_unit( + (memory_info.swap_total - memory_info.swap_free).as_u64(), + matches, + ); + let free = with_unit(memory_info.mem_free.as_u64(), matches); if matches.get_flag("active") { - let inactive = memory_info.inactive.as_u64() / KB; - let active = memory_info.active.as_u64() / KB; + let inactive = with_unit(memory_info.inactive.as_u64(), matches); + let active = with_unit(memory_info.active.as_u64(), matches); return vec![ (len, format!("{}", swap_used)), (len, format!("{}", free)), @@ -141,8 +160,8 @@ fn get_memory_info( ]; } - let buffer = memory_info.buffers.as_u64() / KB; - let cache = memory_info.cached.as_u64() / KB; + let buffer = with_unit(memory_info.buffers.as_u64(), matches); + let cache = with_unit(memory_info.cached.as_u64(), matches); vec![ (len, format!("{}", swap_used)), diff --git a/src/uu/vmstat/src/vmstat.rs b/src/uu/vmstat/src/vmstat.rs index fecf1edd..5011574e 100644 --- a/src/uu/vmstat/src/vmstat.rs +++ b/src/uu/vmstat/src/vmstat.rs @@ -136,9 +136,8 @@ pub fn uu_app() -> Command { // arg!(-d --disk "Report disk statistics"), // arg!(-D --"disk-sum" "Report some summary statistics about disk activity"), // arg!(-p --partition "Detailed statistics about partition"), - // arg!(-S --unit "Switches outputs between 1000 (k), 1024 (K), 1000000 (m), or 1048576 (M) bytes"), + arg!(-S --unit "Switches outputs between 1000 (k), 1024 (K), 1000000 (m), or 1048576 (M) bytes"), // arg!(-t --timestamp "Append timestamp to each line"), - // arg!(-w --wide "Wide output mode"), arg!(-w --wide "Wide output mode"), arg!(-y --"no-first" "Omits first report with statistics since system boot"), ]) From 57cf05d19e80bba59a819b264f6ddd7ac34aadf8 Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Mon, 14 Apr 2025 22:42:41 +0800 Subject: [PATCH 4/7] vmstat: add tests --- src/uu/vmstat/src/picker.rs | 8 +++----- src/uu/vmstat/src/vmstat.rs | 11 +++++++++++ tests/by-util/test_vmstat.rs | 11 +++++++++++ 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/uu/vmstat/src/picker.rs b/src/uu/vmstat/src/picker.rs index 03846161..0e0ccdb2 100644 --- a/src/uu/vmstat/src/picker.rs +++ b/src/uu/vmstat/src/picker.rs @@ -5,8 +5,8 @@ #[cfg(target_os = "linux")] use crate::{CpuLoad, Meminfo, ProcData}; +#[cfg(target_os = "linux")] use clap::ArgMatches; -use uucore::error::USimpleError; #[cfg(target_os = "linux")] pub type Picker = ( @@ -65,6 +65,7 @@ pub fn get_pickers(matches: &ArgMatches) -> Vec { ] } +#[cfg(target_os = "linux")] fn with_unit(x: u64, arg: &ArgMatches) -> u64 { if let Some(unit) = arg.get_one::("unit") { return match unit.as_str() { @@ -72,10 +73,7 @@ fn with_unit(x: u64, arg: &ArgMatches) -> u64 { "K" => x / bytesize::KIB, "m" => x / bytesize::MB, "M" => x / bytesize::MIB, - _ => panic!( - "{:?}", - USimpleError::new(1, "-S requires k, K, m or M (default is KiB)",) - ), + _ => x, // impossible }; } x / bytesize::KIB diff --git a/src/uu/vmstat/src/vmstat.rs b/src/uu/vmstat/src/vmstat.rs index 5011574e..9002e92a 100644 --- a/src/uu/vmstat/src/vmstat.rs +++ b/src/uu/vmstat/src/vmstat.rs @@ -8,6 +8,7 @@ mod picker; #[cfg(target_os = "linux")] use crate::picker::{get_pickers, Picker}; +#[allow(unused_imports)] use clap::{arg, crate_version, ArgMatches, Command}; #[allow(unused_imports)] pub use parser::*; @@ -24,6 +25,16 @@ 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)", + ))?; + } + } + let one_header = matches.get_flag("one-header"); let no_first = matches.get_flag("no-first"); let delay = matches.get_one::("delay"); diff --git a/tests/by-util/test_vmstat.rs b/tests/by-util/test_vmstat.rs index 8bc02447..733ee30c 100644 --- a/tests/by-util/test_vmstat.rs +++ b/tests/by-util/test_vmstat.rs @@ -16,3 +16,14 @@ fn test_simple() { fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails().code_is(1); } + +#[test] +fn test_unit() { + new_ucmd!().args(&["-S", "M"]).succeeds(); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_invalid_unit() { + new_ucmd!().args(&["-S", "x"]).fails().code_is(1); +} From 11ce7faaac199ccbb786af94aa5e10b8e2d4241a Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Wed, 16 Apr 2025 23:02:09 +0800 Subject: [PATCH 5/7] vmstat: fix parsing --- src/uu/vmstat/src/vmstat.rs | 57 ++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/src/uu/vmstat/src/vmstat.rs b/src/uu/vmstat/src/vmstat.rs index 9002e92a..9f7da7e7 100644 --- a/src/uu/vmstat/src/vmstat.rs +++ b/src/uu/vmstat/src/vmstat.rs @@ -8,6 +8,7 @@ mod picker; #[cfg(target_os = "linux")] use crate::picker::{get_pickers, Picker}; +use clap::value_parser; #[allow(unused_imports)] use clap::{arg, crate_version, ArgMatches, Command}; #[allow(unused_imports)] @@ -37,36 +38,26 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let one_header = matches.get_flag("one-header"); let no_first = matches.get_flag("no-first"); - let delay = matches.get_one::("delay"); - let mut delay = if let Some(delay) = delay { - let delay = delay.parse::().unwrap(); - if delay <= 0 { - return Err(USimpleError::new(-1, "delay must be greater than 0")); - } - delay - } else { - -1 - }; - let count = matches.get_one::("count"); + + let delay = matches.get_one::("delay"); + let count = matches.get_one::("count"); let mut count = if let Some(count) = count { - let mut count = count.parse::().unwrap(); - if count < 0 { - return Err(USimpleError::new(-1, "count must be greater than 0")); - } - if count == 0 { - count = 1; + if *count == 0 { + Some(1) + } else { + Some(*count) } - count } else { - -1 + None }; - if count < 0 && delay < 0 { - count = 1; - if no_first { - delay = 1; - count = 1; + let delay = if let Some(delay) = delay { + *delay + } else { + if count.is_none() { + count = Some(1); } - } + 1 + }; let pickers = get_pickers(&matches); let mut proc_data = ProcData::new(); @@ -82,10 +73,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .map(|size| size.1 .0) .unwrap_or(0); - while count < 0 || line_count < count { - std::thread::sleep(std::time::Duration::from_secs(delay as u64)); + while count.is_none() || line_count < count.unwrap() { + std::thread::sleep(std::time::Duration::from_secs(delay)); let proc_data_now = ProcData::new(); - if !one_header && term_height > 0 && ((line_count + 3) % term_height as i64 == 0) { + if !one_header && term_height > 0 && ((line_count + 3) % term_height as u64 == 0) { print_header(&pickers); } print_data(&pickers, &proc_data_now, Some(&proc_data), &matches); @@ -96,6 +87,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { Ok(()) } + #[cfg(target_os = "linux")] fn print_header(pickers: &[Picker]) { let mut section: Vec<&str> = vec![]; @@ -108,6 +100,7 @@ fn print_header(pickers: &[Picker]) { println!("{}", section.join(" ")); println!("{}", title.join(" ")); } + #[cfg(target_os = "linux")] fn print_data( pickers: &[Picker], @@ -137,8 +130,12 @@ pub fn uu_app() -> Command { .override_usage(format_usage(USAGE)) .infer_long_args(true) .args([ - arg!( "The delay between updates in seconds").required(false), - arg!( "Number of updates").required(false), + arg!( "The delay between updates in seconds") + .required(false) + .value_parser(value_parser!(u64).range(1..)), + arg!( "Number of updates") + .required(false) + .value_parser(value_parser!(u64)), arg!(-a --active "Display active and inactive memory"), // arg!(-f --forks "switch displays the number of forks since boot"), // arg!(-m --slabs "Display slabinfo"), From 389a9f6dd2f465b9356b4b29d70db8dbeb1bba73 Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Wed, 16 Apr 2025 23:36:14 +0800 Subject: [PATCH 6/7] vmstat: more tests --- tests/by-util/test_vmstat.rs | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/by-util/test_vmstat.rs b/tests/by-util/test_vmstat.rs index 733ee30c..f26cf969 100644 --- a/tests/by-util/test_vmstat.rs +++ b/tests/by-util/test_vmstat.rs @@ -3,6 +3,8 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +#[cfg(target_os = "linux")] +use std::time::Duration; use uutests::new_ucmd; use uutests::util::TestScenario; use uutests::util_name; @@ -17,6 +19,12 @@ fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails().code_is(1); } +#[test] +fn test_invalid_number() { + new_ucmd!().arg("-1").fails().code_is(1); + new_ucmd!().arg("0").fails().code_is(1); +} + #[test] fn test_unit() { new_ucmd!().args(&["-S", "M"]).succeeds(); @@ -27,3 +35,37 @@ fn test_unit() { fn test_invalid_unit() { new_ucmd!().args(&["-S", "x"]).fails().code_is(1); } + +#[test] +#[cfg(target_os = "linux")] +fn test_header() { + let result = new_ucmd!().succeeds(); + assert!(result.stdout_str().starts_with("procs")); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_wide_mode() { + let result = new_ucmd!().arg("-w").succeeds(); + assert!(result.stdout_str().starts_with("--procs--")); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_no_first() { + let time = std::time::Instant::now(); + new_ucmd!().arg("-y").succeeds(); + assert!(time.elapsed() >= Duration::from_secs(1)); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_active() { + let result = new_ucmd!().arg("-a").succeeds(); + assert!(result + .stdout_str() + .lines() + .nth(1) + .unwrap() + .contains("active")); +} From b1601a0feef9024b287f0725ceab7cee527e9971 Mon Sep 17 00:00:00 2001 From: Bluemangoo Date: Mon, 21 Apr 2025 18:39:47 +0800 Subject: [PATCH 7/7] vmstat: optimize --- src/uu/vmstat/src/picker.rs | 2 +- src/uu/vmstat/src/vmstat.rs | 20 ++++---------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/uu/vmstat/src/picker.rs b/src/uu/vmstat/src/picker.rs index 0e0ccdb2..1228ead5 100644 --- a/src/uu/vmstat/src/picker.rs +++ b/src/uu/vmstat/src/picker.rs @@ -73,7 +73,7 @@ fn with_unit(x: u64, arg: &ArgMatches) -> u64 { "K" => x / bytesize::KIB, "m" => x / bytesize::MB, "M" => x / bytesize::MIB, - _ => x, // impossible + _ => unreachable!(), }; } x / bytesize::KIB diff --git a/src/uu/vmstat/src/vmstat.rs b/src/uu/vmstat/src/vmstat.rs index 9f7da7e7..0fc1c17c 100644 --- a/src/uu/vmstat/src/vmstat.rs +++ b/src/uu/vmstat/src/vmstat.rs @@ -41,23 +41,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let delay = matches.get_one::("delay"); let count = matches.get_one::("count"); - let mut count = if let Some(count) = count { - if *count == 0 { - Some(1) - } else { - Some(*count) - } - } else { - None - }; - let delay = if let Some(delay) = delay { - *delay - } else { - if count.is_none() { - count = Some(1); - } + let mut count = count.copied().map(|c| if c == 0 { 1 } else { c }); + let delay = delay.copied().unwrap_or_else(|| { + count.get_or_insert(1); 1 - }; + }); let pickers = get_pickers(&matches); let mut proc_data = ProcData::new();