vmstat: implement --stats

This commit is contained in:
Bluemangoo
2025-07-22 18:57:39 +08:00
parent a8f314bc53
commit 05127ea2ec
3 changed files with 234 additions and 42 deletions
+82 -31
View File
@@ -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")]
@@ -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::<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: 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,
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: 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,
}
}
}
+128 -1
View File
@@ -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<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") {
+24 -10
View File
@@ -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::<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");
@@ -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 });
@@ -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 <device> "Detailed statistics about partition"),