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..6cff6ea0 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,53 @@ 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 active: bytesize::ByteSize, + pub inactive: 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 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 { + mem_total, + mem_free, + mem_available, + 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 new file mode 100644 index 00000000..1228ead5 --- /dev/null +++ b/src/uu/vmstat/src/picker.rs @@ -0,0 +1,269 @@ +// 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")] +use clap::ArgMatches; + +#[cfg(target_os = "linux")] +pub type Picker = ( + (String, String), + Box, &ArgMatches, &mut Vec, &mut usize)>, +); + +#[cfg(target_os = "linux")] +pub fn get_pickers(matches: &ArgMatches) -> Vec { + let wide = matches.get_flag("wide"); + vec![ + concat_helper( + 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( + if wide { + ( + "----------cpu----------".into(), + " us sy id wa st gu".into(), + ) + } else { + ("-------cpu-------".into(), "us sy id wa st gu".into()) + }, + get_cpu_info, + ), + ] +} + +#[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() { + "k" => x / bytesize::KB, + "K" => x / bytesize::KIB, + "m" => x / bytesize::MB, + "M" => x / bytesize::MIB, + _ => unreachable!(), + }; + } + x / bytesize::KIB +} + +#[cfg(target_os = "linux")] +fn concat_helper( + title: (String, String), + 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, matches); + 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>, + 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![(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)> { + let len = if matches.get_flag("wide") { 12 } else { 6 }; + let memory_info = Meminfo::from_proc_map(&proc_data.meminfo); + + 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 = 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)), + (len, format!("{}", inactive)), + (len, format!("{}", active)), + ]; + } + + 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)), + (len, format!("{}", free)), + (len, format!("{}", buffer)), + (len, format!("{}", cache)), + ] +} + +#[cfg(target_os = "linux")] +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!( + 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>, + _matches: &ArgMatches, +) -> 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>, + _matches: &ArgMatches, +) -> 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>, + 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![ + (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 79294bd5..0fc1c17c 100644 --- a/src/uu/vmstat/src/vmstat.rs +++ b/src/uu/vmstat/src/vmstat.rs @@ -4,13 +4,17 @@ // 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::value_parser; +#[allow(unused_imports)] +use clap::{arg, crate_version, ArgMatches, 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 +22,92 @@ 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(" ")); + { + // 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"); + let count = matches.get_one::("count"); + 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(); + + let mut line_count = 0; + print_header(&pickers); + if !no_first { + print_data(&pickers, &proc_data, None, &matches); + line_count += 1; + } + + let term_height = terminal_size::terminal_size() + .map(|size| size.1 .0) + .unwrap_or(0); + + 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 u64 == 0) { + print_header(&pickers); + } + print_data(&pickers, &proc_data_now, Some(&proc_data), &matches); + 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>, + 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, + matches, + &mut data, + &mut data_len_excess, + ); + }); + println!("{}", data.join(" ")); } #[allow(clippy::cognitive_complexity)] @@ -194,22 +117,24 @@ 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) + .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"), + 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"), + ]) } diff --git a/tests/by-util/test_vmstat.rs b/tests/by-util/test_vmstat.rs index 8bc02447..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; @@ -16,3 +18,54 @@ fn test_simple() { 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(); +} + +#[test] +#[cfg(target_os = "linux")] +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")); +}