Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 82 additions & 31 deletions src/uu/vmstat/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,30 @@ impl ProcData {
let idle_time = parts.next().unwrap().parse::<f64>().unwrap();
(uptime, idle_time)
}

pub fn get_one<T>(table: &HashMap<String, String>, 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")]
Expand All @@ -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();
Expand All @@ -93,37 +117,64 @@ impl CpuLoad {

fn from_str(s: &str) -> Self {
let load = s.split(' ').filter(|s| !s.is_empty()).collect::<Vec<_>>();
let user = load[0].parse::<f64>().unwrap();
let nice = load[1].parse::<f64>().unwrap();
let system = load[2].parse::<f64>().unwrap();
let idle = load[3].parse::<f64>().unwrap_or_default(); // since 2.5.41
let io_wait = load[4].parse::<f64>().unwrap_or_default(); // since 2.5.41
let hardware_interrupt = load[5].parse::<f64>().unwrap_or_default(); // since 2.6.0
let software_interrupt = load[6].parse::<f64>().unwrap_or_default(); // since 2.6.0
let steal_time = load[7].parse::<f64>().unwrap_or_default(); // since 2.6.11
let guest = load[8].parse::<f64>().unwrap_or_default(); // since 2.6.24
let guest_nice = load[9].parse::<f64>().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::<u64>().unwrap();
let nice = load[1].parse::<u64>().unwrap();
let system = load[2].parse::<u64>().unwrap();
let idle = load[3].parse::<u64>().unwrap_or_default(); // since 2.5.41
let io_wait = load[4].parse::<u64>().unwrap_or_default(); // since 2.5.41
let hardware_interrupt = load[5].parse::<u64>().unwrap_or_default(); // since 2.6.0
let software_interrupt = load[6].parse::<u64>().unwrap_or_default(); // since 2.6.0
let steal_time = load[7].parse::<u64>().unwrap_or_default(); // since 2.6.11
let guest = load[8].parse::<u64>().unwrap_or_default(); // since 2.6.24
let guest_nice = load[9].parse::<u64>().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<String, String>) -> 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,
}
}
}
Expand Down
129 changes: 128 additions & 1 deletion src/uu/vmstat/src/picker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -76,6 +76,133 @@ pub fn get_pickers(matches: &ArgMatches) -> Vec<Picker> {
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::<u64>()
.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::<String>("unit") {
Expand Down
34 changes: 24 additions & 10 deletions src/uu/vmstat/src/vmstat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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::<String>("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");
Expand All @@ -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::<String>("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::<u64>("delay");
let count = matches.get_one::<u64>("count");
let mut count = count.copied().map(|c| if c == 0 { 1 } else { c });
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 <device> "Detailed statistics about partition"),
Expand Down
6 changes: 6 additions & 0 deletions tests/by-util/test_vmstat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,9 @@ fn test_timestamp() {
.unwrap()
.contains("timestamp"));
}

#[test]
#[cfg(target_os = "linux")]
fn test_stats() {
new_ucmd!().arg("-s").succeeds();
}
Loading