Skip to content

Commit 263e16e

Browse files
authored
Merge pull request #470 from Bluemangoo/feature/vmstat-stats
vmstat: implement `--stats`
2 parents a8f314b + ad8598e commit 263e16e

File tree

4 files changed

+240
-42
lines changed

4 files changed

+240
-42
lines changed

src/uu/vmstat/src/parser.rs

Lines changed: 82 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,30 @@ impl ProcData {
6161
let idle_time = parts.next().unwrap().parse::<f64>().unwrap();
6262
(uptime, idle_time)
6363
}
64+
65+
pub fn get_one<T>(table: &HashMap<String, String>, name: &str) -> T
66+
where
67+
T: Default + std::str::FromStr,
68+
{
69+
table
70+
.get(name)
71+
.and_then(|v| v.parse().ok())
72+
.unwrap_or_default()
73+
}
74+
}
75+
76+
#[cfg(target_os = "linux")]
77+
pub struct CpuLoadRaw {
78+
pub user: u64,
79+
pub nice: u64,
80+
pub system: u64,
81+
pub idle: u64,
82+
pub io_wait: u64,
83+
pub hardware_interrupt: u64,
84+
pub software_interrupt: u64,
85+
pub steal_time: u64,
86+
pub guest: u64,
87+
pub guest_nice: u64,
6488
}
6589

6690
#[cfg(target_os = "linux")]
@@ -78,7 +102,7 @@ pub struct CpuLoad {
78102
}
79103

80104
#[cfg(target_os = "linux")]
81-
impl CpuLoad {
105+
impl CpuLoadRaw {
82106
pub fn current() -> Self {
83107
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
84108
let content = std::io::read_to_string(file).unwrap();
@@ -93,37 +117,64 @@ impl CpuLoad {
93117

94118
fn from_str(s: &str) -> Self {
95119
let load = s.split(' ').filter(|s| !s.is_empty()).collect::<Vec<_>>();
96-
let user = load[0].parse::<f64>().unwrap();
97-
let nice = load[1].parse::<f64>().unwrap();
98-
let system = load[2].parse::<f64>().unwrap();
99-
let idle = load[3].parse::<f64>().unwrap_or_default(); // since 2.5.41
100-
let io_wait = load[4].parse::<f64>().unwrap_or_default(); // since 2.5.41
101-
let hardware_interrupt = load[5].parse::<f64>().unwrap_or_default(); // since 2.6.0
102-
let software_interrupt = load[6].parse::<f64>().unwrap_or_default(); // since 2.6.0
103-
let steal_time = load[7].parse::<f64>().unwrap_or_default(); // since 2.6.11
104-
let guest = load[8].parse::<f64>().unwrap_or_default(); // since 2.6.24
105-
let guest_nice = load[9].parse::<f64>().unwrap_or_default(); // since 2.6.33
106-
let total = user
107-
+ nice
108-
+ system
109-
+ idle
110-
+ io_wait
111-
+ hardware_interrupt
112-
+ software_interrupt
113-
+ steal_time
114-
+ guest
115-
+ guest_nice;
120+
let user = load[0].parse::<u64>().unwrap();
121+
let nice = load[1].parse::<u64>().unwrap();
122+
let system = load[2].parse::<u64>().unwrap();
123+
let idle = load[3].parse::<u64>().unwrap_or_default(); // since 2.5.41
124+
let io_wait = load[4].parse::<u64>().unwrap_or_default(); // since 2.5.41
125+
let hardware_interrupt = load[5].parse::<u64>().unwrap_or_default(); // since 2.6.0
126+
let software_interrupt = load[6].parse::<u64>().unwrap_or_default(); // since 2.6.0
127+
let steal_time = load[7].parse::<u64>().unwrap_or_default(); // since 2.6.11
128+
let guest = load[8].parse::<u64>().unwrap_or_default(); // since 2.6.24
129+
let guest_nice = load[9].parse::<u64>().unwrap_or_default(); // since 2.6.33
130+
131+
Self {
132+
user,
133+
system,
134+
nice,
135+
idle,
136+
io_wait,
137+
hardware_interrupt,
138+
software_interrupt,
139+
steal_time,
140+
guest,
141+
guest_nice,
142+
}
143+
}
144+
}
145+
146+
#[cfg(target_os = "linux")]
147+
impl CpuLoad {
148+
pub fn current() -> Self {
149+
Self::from_raw(CpuLoadRaw::current())
150+
}
151+
152+
pub fn from_proc_map(proc_map: &HashMap<String, String>) -> Self {
153+
Self::from_raw(CpuLoadRaw::from_proc_map(proc_map))
154+
}
155+
156+
pub fn from_raw(raw_data: CpuLoadRaw) -> Self {
157+
let total = (raw_data.user
158+
+ raw_data.nice
159+
+ raw_data.system
160+
+ raw_data.idle
161+
+ raw_data.io_wait
162+
+ raw_data.hardware_interrupt
163+
+ raw_data.software_interrupt
164+
+ raw_data.steal_time
165+
+ raw_data.guest
166+
+ raw_data.guest_nice) as f64;
116167
Self {
117-
user: user / total * 100.0,
118-
system: system / total * 100.0,
119-
nice: nice / total * 100.0,
120-
idle: idle / total * 100.0,
121-
io_wait: io_wait / total * 100.0,
122-
hardware_interrupt: hardware_interrupt / total * 100.0,
123-
software_interrupt: software_interrupt / total * 100.0,
124-
steal_time: steal_time / total * 100.0,
125-
guest: guest / total * 100.0,
126-
guest_nice: guest_nice / total * 100.0,
168+
user: raw_data.user as f64 / total * 100.0,
169+
system: raw_data.system as f64 / total * 100.0,
170+
nice: raw_data.nice as f64 / total * 100.0,
171+
idle: raw_data.idle as f64 / total * 100.0,
172+
io_wait: raw_data.io_wait as f64 / total * 100.0,
173+
hardware_interrupt: raw_data.hardware_interrupt as f64 / total * 100.0,
174+
software_interrupt: raw_data.software_interrupt as f64 / total * 100.0,
175+
steal_time: raw_data.steal_time as f64 / total * 100.0,
176+
guest: raw_data.guest as f64 / total * 100.0,
177+
guest_nice: raw_data.guest_nice as f64 / total * 100.0,
127178
}
128179
}
129180
}

src/uu/vmstat/src/picker.rs

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// file that was distributed with this source code.
55

66
#[cfg(target_os = "linux")]
7-
use crate::{CpuLoad, Meminfo, ProcData};
7+
use crate::{CpuLoad, CpuLoadRaw, Meminfo, ProcData};
88
#[cfg(target_os = "linux")]
99
use clap::ArgMatches;
1010

@@ -76,6 +76,133 @@ pub fn get_pickers(matches: &ArgMatches) -> Vec<Picker> {
7676
pickers
7777
}
7878

79+
#[cfg(target_os = "linux")]
80+
pub fn get_stats() -> Vec<(String, u64)> {
81+
let proc_data = ProcData::new();
82+
let memory_info = Meminfo::from_proc_map(&proc_data.meminfo);
83+
let cpu_load = CpuLoadRaw::from_proc_map(&proc_data.stat);
84+
85+
vec![
86+
(
87+
"K total memory".to_string(),
88+
memory_info.mem_total.0 / bytesize::KB,
89+
),
90+
(
91+
"K used memory".to_string(),
92+
(memory_info.mem_total - memory_info.mem_available).0 / bytesize::KB,
93+
),
94+
(
95+
"K active memory".to_string(),
96+
memory_info.active.0 / bytesize::KB,
97+
),
98+
(
99+
"K inactive memory".to_string(),
100+
memory_info.inactive.0 / bytesize::KB,
101+
),
102+
(
103+
"K free memory".to_string(),
104+
memory_info.mem_free.0 / bytesize::KB,
105+
),
106+
(
107+
"K buffer memory".to_string(),
108+
memory_info.buffers.0 / bytesize::KB,
109+
),
110+
(
111+
"K swap cache".to_string(),
112+
memory_info.cached.0 / bytesize::KB,
113+
),
114+
(
115+
"K total swap".to_string(),
116+
memory_info.swap_total.0 / bytesize::KB,
117+
),
118+
(
119+
"K used swap".to_string(),
120+
(memory_info.swap_total - memory_info.swap_free).0 / bytesize::KB,
121+
),
122+
(
123+
"K free swap".to_string(),
124+
memory_info.swap_free.0 / bytesize::KB,
125+
),
126+
(
127+
"non-nice user cpu ticks".to_string(),
128+
cpu_load.user - cpu_load.nice,
129+
),
130+
("nice user cpu ticks".to_string(), cpu_load.nice),
131+
("system cpu ticks".to_string(), cpu_load.system),
132+
("idle cpu ticks".to_string(), cpu_load.idle),
133+
("IO-wait cpu ticks".to_string(), cpu_load.io_wait),
134+
("IRQ cpu ticks".to_string(), cpu_load.hardware_interrupt),
135+
("softirq cpu ticks".to_string(), cpu_load.software_interrupt),
136+
("stolen cpu ticks".to_string(), cpu_load.steal_time),
137+
("non-nice guest cpu ticks".to_string(), cpu_load.guest),
138+
("nice guest cpu ticks".to_string(), cpu_load.guest_nice),
139+
(
140+
"K paged in".to_string(),
141+
ProcData::get_one(&proc_data.vmstat, "pgpgin"),
142+
),
143+
(
144+
"K paged out".to_string(),
145+
ProcData::get_one(&proc_data.vmstat, "pgpgout"),
146+
),
147+
(
148+
"pages swapped in".to_string(),
149+
ProcData::get_one(&proc_data.vmstat, "pswpin"),
150+
),
151+
(
152+
"pages swapped out".to_string(),
153+
ProcData::get_one(&proc_data.vmstat, "pswpout"),
154+
),
155+
(
156+
"pages alloc in dma".to_string(),
157+
ProcData::get_one(&proc_data.vmstat, "pgalloc_dma"),
158+
),
159+
(
160+
"pages alloc in dma32".to_string(),
161+
ProcData::get_one(&proc_data.vmstat, "pgalloc_dma32"),
162+
),
163+
(
164+
"pages alloc in high".to_string(),
165+
ProcData::get_one(&proc_data.vmstat, "pgalloc_high"),
166+
),
167+
(
168+
"pages alloc in movable".to_string(),
169+
ProcData::get_one(&proc_data.vmstat, "pgalloc_movable"),
170+
),
171+
(
172+
"pages alloc in normal".to_string(),
173+
ProcData::get_one(&proc_data.vmstat, "pgalloc_normal"),
174+
),
175+
(
176+
"pages free".to_string(),
177+
ProcData::get_one(&proc_data.vmstat, "pgfree"),
178+
),
179+
(
180+
"interrupts".to_string(),
181+
proc_data
182+
.stat
183+
.get("intr")
184+
.unwrap()
185+
.split_whitespace()
186+
.next()
187+
.unwrap()
188+
.parse::<u64>()
189+
.unwrap(),
190+
),
191+
(
192+
"CPU context switches".to_string(),
193+
ProcData::get_one(&proc_data.stat, "ctxt"),
194+
),
195+
(
196+
"boot time".to_string(),
197+
ProcData::get_one(&proc_data.stat, "btime"),
198+
),
199+
(
200+
"forks".to_string(),
201+
ProcData::get_one(&proc_data.stat, "processes"),
202+
),
203+
]
204+
}
205+
79206
#[cfg(target_os = "linux")]
80207
fn with_unit(x: u64, arg: &ArgMatches) -> u64 {
81208
if let Some(unit) = arg.get_one::<String>("unit") {

src/uu/vmstat/src/vmstat.rs

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ mod parser;
77
mod picker;
88

99
#[cfg(target_os = "linux")]
10-
use crate::picker::{get_pickers, Picker};
10+
use crate::picker::{get_pickers, get_stats, Picker};
1111
use clap::value_parser;
1212
#[allow(unused_imports)]
1313
use clap::{arg, crate_version, ArgMatches, Command};
@@ -26,14 +26,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
2626
let matches = uu_app().try_get_matches_from(args)?;
2727
#[cfg(target_os = "linux")]
2828
{
29-
// validate unit
30-
if let Some(unit) = matches.get_one::<String>("unit") {
31-
if !["k", "K", "m", "M"].contains(&unit.as_str()) {
32-
Err(USimpleError::new(
33-
1,
34-
"-S requires k, K, m or M (default is KiB)",
35-
))?;
36-
}
29+
if matches.get_flag("stats") {
30+
return print_stats();
3731
}
3832

3933
let one_header = matches.get_flag("one-header");
@@ -46,6 +40,16 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
4640
return print_slabs(one_header, term_height);
4741
}
4842

43+
// validate unit
44+
if let Some(unit) = matches.get_one::<String>("unit") {
45+
if !["k", "K", "m", "M"].contains(&unit.as_str()) {
46+
Err(USimpleError::new(
47+
1,
48+
"-S requires k, K, m or M (default is KiB)",
49+
))?;
50+
}
51+
}
52+
4953
let delay = matches.get_one::<u64>("delay");
5054
let count = matches.get_one::<u64>("count");
5155
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<()> {
7983
Ok(())
8084
}
8185

86+
#[cfg(target_os = "linux")]
87+
fn print_stats() -> UResult<()> {
88+
let data = get_stats();
89+
90+
data.iter()
91+
.for_each(|(name, value)| println!("{value:>13} {name}"));
92+
93+
Ok(())
94+
}
95+
8296
#[cfg(target_os = "linux")]
8397
fn print_slabs(one_header: bool, term_height: u16) -> UResult<()> {
8498
let mut slab_data = uu_slabtop::SlabInfo::new()?.data;
@@ -166,7 +180,7 @@ pub fn uu_app() -> Command {
166180
// arg!(-f --forks "switch displays the number of forks since boot"),
167181
arg!(-m --slabs "Display slabinfo"),
168182
arg!(-n --"one-header" "Display the header only once rather than periodically"),
169-
// arg!(-s --stats "Displays a table of various event counters and memory statistics"),
183+
arg!(-s --stats "Displays a table of various event counters and memory statistics"),
170184
// arg!(-d --disk "Report disk statistics"),
171185
// arg!(-D --"disk-sum" "Report some summary statistics about disk activity"),
172186
// arg!(-p --partition <device> "Detailed statistics about partition"),

tests/by-util/test_vmstat.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,9 @@ fn test_timestamp() {
8181
.unwrap()
8282
.contains("timestamp"));
8383
}
84+
85+
#[test]
86+
#[cfg(target_os = "linux")]
87+
fn test_stats() {
88+
new_ucmd!().arg("-s").succeeds();
89+
}

0 commit comments

Comments
 (0)