Skip to content

Commit 1be5929

Browse files
authored
Merge pull request #477 from Bluemangoo/feature/vmstat-disk
vmstat: implement `--disk` `--disk-sum` `--partition`
2 parents fbbe063 + 19a1c37 commit 1be5929

File tree

4 files changed

+310
-15
lines changed

4 files changed

+310
-15
lines changed

src/uu/vmstat/src/parser.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
#[cfg(target_os = "linux")]
77
use std::collections::HashMap;
8+
#[cfg(target_os = "linux")]
9+
use std::fmt::{Debug, Display, Formatter};
810

911
#[cfg(target_os = "linux")]
1012
pub fn parse_proc_file(path: &str) -> HashMap<String, String> {
@@ -31,6 +33,7 @@ pub struct ProcData {
3133
pub stat: HashMap<String, String>,
3234
pub meminfo: HashMap<String, String>,
3335
pub vmstat: HashMap<String, String>,
36+
pub diskstat: Vec<String>,
3437
}
3538
#[cfg(target_os = "linux")]
3639
impl Default for ProcData {
@@ -45,11 +48,17 @@ impl ProcData {
4548
let stat = parse_proc_file("/proc/stat");
4649
let meminfo = parse_proc_file("/proc/meminfo");
4750
let vmstat = parse_proc_file("/proc/vmstat");
51+
let diskstat = std::fs::read_to_string("/proc/diskstats")
52+
.unwrap()
53+
.lines()
54+
.map(|line| line.to_string())
55+
.collect();
4856
Self {
4957
uptime,
5058
stat,
5159
meminfo,
5260
vmstat,
61+
diskstat,
5362
}
5463
}
5564

@@ -228,3 +237,104 @@ impl Meminfo {
228237
}
229238
}
230239
}
240+
241+
#[cfg(target_os = "linux")]
242+
#[derive(Debug)]
243+
pub struct DiskStatParseError;
244+
245+
#[cfg(target_os = "linux")]
246+
impl Display for DiskStatParseError {
247+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
248+
std::fmt::Debug::fmt("Failed to parse diskstat line", f)
249+
}
250+
}
251+
252+
#[cfg(target_os = "linux")]
253+
impl std::error::Error for DiskStatParseError {}
254+
255+
#[cfg(target_os = "linux")]
256+
pub struct DiskStat {
257+
// Name from https://www.kernel.org/doc/html/latest/admin-guide/iostats.html
258+
pub major: u64,
259+
pub minor: u64,
260+
pub device: String,
261+
pub reads_completed: u64,
262+
pub reads_merged: u64,
263+
pub sectors_read: u64,
264+
pub milliseconds_spent_reading: u64,
265+
pub writes_completed: u64,
266+
pub writes_merged: u64,
267+
pub sectors_written: u64,
268+
pub milliseconds_spent_writing: u64,
269+
pub ios_currently_in_progress: u64,
270+
pub milliseconds_spent_doing_ios: u64,
271+
pub weighted_milliseconds_spent_doing_ios: u64,
272+
pub discards_completed: u64,
273+
pub discards_merged: u64,
274+
pub sectors_discarded: u64,
275+
pub milliseconds_spent_discarding: u64,
276+
pub flush_requests_completed: u64,
277+
pub milliseconds_spent_flushing: u64,
278+
}
279+
280+
#[cfg(target_os = "linux")]
281+
impl std::str::FromStr for DiskStat {
282+
type Err = DiskStatParseError;
283+
284+
fn from_str(line: &str) -> Result<Self, Self::Err> {
285+
let parts: Vec<&str> = line.split_whitespace().collect();
286+
if parts.len() < 14 {
287+
Err(DiskStatParseError)?;
288+
}
289+
290+
let parse_value = |s: &str| s.parse::<u64>().map_err(|_| DiskStatParseError);
291+
let parse_optional_value = |s: Option<&&str>| match s {
292+
None => Ok(0),
293+
Some(value) => value.parse::<u64>().map_err(|_| DiskStatParseError),
294+
};
295+
296+
Ok(Self {
297+
major: parse_value(parts[0])?,
298+
minor: parse_value(parts[1])?,
299+
device: parts[2].to_string(),
300+
reads_completed: parse_value(parts[3])?,
301+
reads_merged: parse_value(parts[4])?,
302+
sectors_read: parse_value(parts[5])?,
303+
milliseconds_spent_reading: parse_value(parts[6])?,
304+
writes_completed: parse_value(parts[7])?,
305+
writes_merged: parse_value(parts[8])?,
306+
sectors_written: parse_value(parts[9])?,
307+
milliseconds_spent_writing: parse_value(parts[10])?,
308+
ios_currently_in_progress: parse_value(parts[11])?,
309+
milliseconds_spent_doing_ios: parse_value(parts[12])?,
310+
weighted_milliseconds_spent_doing_ios: parse_optional_value(parts.get(13))?,
311+
discards_completed: parse_optional_value(parts.get(14))?,
312+
discards_merged: parse_optional_value(parts.get(15))?,
313+
sectors_discarded: parse_optional_value(parts.get(16))?,
314+
milliseconds_spent_discarding: parse_optional_value(parts.get(17))?,
315+
flush_requests_completed: parse_optional_value(parts.get(18))?,
316+
milliseconds_spent_flushing: parse_optional_value(parts.get(19))?,
317+
})
318+
}
319+
}
320+
321+
#[cfg(target_os = "linux")]
322+
impl DiskStat {
323+
pub fn is_disk(&self) -> bool {
324+
std::path::Path::new(&format!("/sys/block/{}", self.device)).exists()
325+
}
326+
327+
pub fn current() -> Result<Vec<Self>, DiskStatParseError> {
328+
let diskstats =
329+
std::fs::read_to_string("/proc/diskstats").map_err(|_| DiskStatParseError)?;
330+
let lines = diskstats.lines();
331+
Self::from_proc_vec(&lines.map(|line| line.to_string()).collect::<Vec<_>>())
332+
}
333+
334+
pub fn from_proc_vec(proc_vec: &[String]) -> Result<Vec<Self>, DiskStatParseError> {
335+
proc_vec
336+
.iter()
337+
.map(|line| line.parse::<DiskStat>())
338+
.collect()
339+
}
340+
}

src/uu/vmstat/src/picker.rs

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

66
#[cfg(target_os = "linux")]
7-
use crate::{CpuLoad, CpuLoadRaw, Meminfo, ProcData};
7+
use crate::{CpuLoad, CpuLoadRaw, DiskStat, Meminfo, ProcData};
88
#[cfg(target_os = "linux")]
99
use clap::ArgMatches;
10+
#[cfg(target_os = "linux")]
11+
use uucore::error::{UResult, USimpleError};
1012

1113
#[cfg(target_os = "linux")]
1214
pub type Picker = (
@@ -203,6 +205,61 @@ pub fn get_stats() -> Vec<(String, u64)> {
203205
]
204206
}
205207

208+
#[cfg(target_os = "linux")]
209+
pub fn get_disk_sum() -> UResult<Vec<(String, u64)>> {
210+
let disk_data = DiskStat::current()
211+
.map_err(|_| USimpleError::new(1, "Unable to retrieve disk statistics"))?;
212+
213+
let mut disks = 0;
214+
let mut partitions = 0;
215+
let mut total_reads = 0;
216+
let mut merged_reads = 0;
217+
let mut read_sectors = 0;
218+
let mut milli_reading = 0;
219+
let mut writes = 0;
220+
let mut merged_writes = 0;
221+
let mut written_sectors = 0;
222+
let mut milli_writing = 0;
223+
let mut inprogress_io = 0;
224+
let mut milli_spent_io = 0;
225+
let mut milli_weighted_io = 0;
226+
227+
for disk in disk_data.iter() {
228+
if disk.is_disk() {
229+
disks += 1;
230+
total_reads += disk.reads_completed;
231+
merged_reads += disk.reads_merged;
232+
read_sectors += disk.sectors_read;
233+
milli_reading += disk.milliseconds_spent_reading;
234+
writes += disk.writes_completed;
235+
merged_writes += disk.writes_merged;
236+
written_sectors += disk.sectors_written;
237+
milli_writing += disk.milliseconds_spent_writing;
238+
inprogress_io += disk.ios_currently_in_progress;
239+
milli_spent_io += disk.milliseconds_spent_doing_ios / 1000;
240+
milli_weighted_io += disk.weighted_milliseconds_spent_doing_ios / 1000;
241+
} else {
242+
partitions += 1;
243+
}
244+
}
245+
246+
Ok(vec![
247+
("disks".to_string(), disks),
248+
("partitions".to_string(), partitions),
249+
("total reads".to_string(), total_reads),
250+
("merged reads".to_string(), merged_reads),
251+
("read sectors".to_string(), read_sectors),
252+
("milli reading".to_string(), milli_reading),
253+
("writes".to_string(), writes),
254+
("merged writes".to_string(), merged_writes),
255+
("written sectors".to_string(), written_sectors),
256+
("milli writing".to_string(), milli_writing),
257+
("in progress IO".to_string(), inprogress_io),
258+
("milli spent IO".to_string(), milli_spent_io),
259+
("milli weighted IO".to_string(), milli_weighted_io),
260+
])
261+
}
262+
206263
#[cfg(target_os = "linux")]
207264
fn with_unit(x: u64, arg: &ArgMatches) -> u64 {
208265
if let Some(unit) = arg.get_one::<String>("unit") {

src/uu/vmstat/src/vmstat.rs

Lines changed: 130 additions & 14 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, get_stats, Picker};
10+
use crate::picker::{get_disk_sum, get_pickers, get_stats, Picker};
1111
use clap::value_parser;
1212
#[allow(unused_imports)]
1313
use clap::{arg, crate_version, ArgMatches, Command};
@@ -26,22 +26,31 @@ 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-
if matches.get_flag("forks") {
30-
return print_forks();
31-
}
32-
if matches.get_flag("stats") {
33-
return print_stats();
34-
}
35-
29+
let wide = matches.get_flag("wide");
3630
let one_header = matches.get_flag("one-header");
3731
let no_first = matches.get_flag("no-first");
3832
let term_height = terminal_size::terminal_size()
3933
.map(|size| size.1 .0)
4034
.unwrap_or(0);
4135

36+
if matches.get_flag("forks") {
37+
return print_forks();
38+
}
4239
if matches.get_flag("slabs") {
4340
return print_slabs(one_header, term_height);
4441
}
42+
if matches.get_flag("stats") {
43+
return print_stats();
44+
}
45+
if matches.get_flag("disk") {
46+
return print_disk(wide, one_header, term_height);
47+
}
48+
if matches.get_flag("disk-sum") {
49+
return print_disk_sum();
50+
}
51+
if let Some(device) = matches.get_one::<String>("partition") {
52+
return print_partition(device);
53+
}
4554

4655
// validate unit
4756
if let Some(unit) = matches.get_one::<String>("unit") {
@@ -141,6 +150,110 @@ fn print_slab_header() {
141150
);
142151
}
143152

153+
#[cfg(target_os = "linux")]
154+
fn print_disk_header(wide: bool) {
155+
if wide {
156+
println!("disk- -------------------reads------------------- -------------------writes------------------ ------IO-------");
157+
println!(
158+
"{:>15} {:>9} {:>11} {:>11} {:>9} {:>9} {:>11} {:>11} {:>7} {:>7}",
159+
"total", "merged", "sectors", "ms", "total", "merged", "sectors", "ms", "cur", "sec"
160+
);
161+
} else {
162+
println!("disk- ------------reads------------ ------------writes----------- -----IO------");
163+
println!(
164+
"{:>12} {:>6} {:>7} {:>7} {:>6} {:>6} {:>7} {:>7} {:>6} {:>6}",
165+
"total", "merged", "sectors", "ms", "total", "merged", "sectors", "ms", "cur", "sec"
166+
);
167+
}
168+
}
169+
170+
#[cfg(target_os = "linux")]
171+
fn print_disk(wide: bool, one_header: bool, term_height: u16) -> UResult<()> {
172+
let disk_data = DiskStat::current()
173+
.map_err(|_| USimpleError::new(1, "Unable to retrieve disk statistics"))?;
174+
175+
let mut line_count = 0;
176+
177+
print_disk_header(wide);
178+
179+
for disk in disk_data {
180+
if !disk.is_disk() {
181+
continue;
182+
}
183+
184+
if needs_header(one_header, term_height, line_count) {
185+
print_disk_header(wide);
186+
}
187+
line_count += 1;
188+
189+
if wide {
190+
println!(
191+
"{:<5} {:>9} {:>9} {:>11} {:>11} {:>9} {:>9} {:>11} {:>11} {:>7} {:>7}",
192+
disk.device,
193+
disk.reads_completed,
194+
disk.reads_merged,
195+
disk.sectors_read,
196+
disk.milliseconds_spent_reading,
197+
disk.writes_completed,
198+
disk.writes_merged,
199+
disk.sectors_written,
200+
disk.milliseconds_spent_writing,
201+
disk.ios_currently_in_progress / 1000,
202+
disk.milliseconds_spent_doing_ios / 1000
203+
);
204+
} else {
205+
println!(
206+
"{:<5} {:>6} {:>6} {:>7} {:>7} {:>6} {:>6} {:>7} {:>7} {:>6} {:>6}",
207+
disk.device,
208+
disk.reads_completed,
209+
disk.reads_merged,
210+
disk.sectors_read,
211+
disk.milliseconds_spent_reading,
212+
disk.writes_completed,
213+
disk.writes_merged,
214+
disk.sectors_written,
215+
disk.milliseconds_spent_writing,
216+
disk.ios_currently_in_progress / 1000,
217+
disk.milliseconds_spent_doing_ios / 1000
218+
);
219+
}
220+
}
221+
222+
Ok(())
223+
}
224+
225+
#[cfg(target_os = "linux")]
226+
fn print_disk_sum() -> UResult<()> {
227+
let data = get_disk_sum()?;
228+
229+
data.iter()
230+
.for_each(|(name, value)| println!("{value:>13} {name}"));
231+
232+
Ok(())
233+
}
234+
235+
#[cfg(target_os = "linux")]
236+
fn print_partition(device: &str) -> UResult<()> {
237+
let disk_data = DiskStat::current()
238+
.map_err(|_| USimpleError::new(1, "Unable to retrieve disk statistics"))?;
239+
240+
let disk = disk_data
241+
.iter()
242+
.find(|disk| disk.device == device)
243+
.ok_or_else(|| USimpleError::new(1, format!("Disk/Partition {device} not found")))?;
244+
245+
println!(
246+
"{device:<9} {:>11} {:>17} {:>11} {:>17}",
247+
"reads", "read sectors", "writes", "requested writes"
248+
);
249+
println!(
250+
"{:>21} {:>17} {:>11} {:>17}",
251+
disk.reads_completed, disk.sectors_read, disk.writes_completed, disk.sectors_written
252+
);
253+
254+
Ok(())
255+
}
256+
144257
#[cfg(target_os = "linux")]
145258
fn print_header(pickers: &[Picker]) {
146259
let mut section: Vec<&str> = vec![];
@@ -191,15 +304,18 @@ pub fn uu_app() -> Command {
191304
.value_parser(value_parser!(u64)),
192305
arg!(-a --active "Display active and inactive memory"),
193306
arg!(-f --forks "switch displays the number of forks since boot")
194-
.conflicts_with_all(["slabs", "stats", /*"disk", "disk-sum", "partition"*/]),
307+
.conflicts_with_all(["slabs", "stats", "disk", "disk-sum", "partition"]),
195308
arg!(-m --slabs "Display slabinfo")
196-
.conflicts_with_all(["forks", "stats", /*"disk", "disk-sum", "partition"*/]),
309+
.conflicts_with_all(["forks", "stats", "disk", "disk-sum", "partition"]),
197310
arg!(-n --"one-header" "Display the header only once rather than periodically"),
198311
arg!(-s --stats "Displays a table of various event counters and memory statistics")
199-
.conflicts_with_all(["forks", "slabs", /*"disk", "disk-sum", "partition"*/]),
200-
// arg!(-d --disk "Report disk statistics"),
201-
// arg!(-D --"disk-sum" "Report some summary statistics about disk activity"),
202-
// arg!(-p --partition <device> "Detailed statistics about partition"),
312+
.conflicts_with_all(["forks", "slabs", "disk", "disk-sum", "partition"]),
313+
arg!(-d --disk "Report disk statistics")
314+
.conflicts_with_all(["forks", "slabs", "stats", "disk-sum", "partition"]),
315+
arg!(-D --"disk-sum" "Report some summary statistics about disk activity")
316+
.conflicts_with_all(["forks", "slabs", "stats", "disk", "partition"]),
317+
arg!(-p --partition <device> "Detailed statistics about partition")
318+
.conflicts_with_all(["forks", "slabs", "stats", "disk", "disk-sum"]),
203319
arg!(-S --unit <character> "Switches outputs between 1000 (k), 1024 (K), 1000000 (m), or 1048576 (M) bytes"),
204320
arg!(-t --timestamp "Append timestamp to each line"),
205321
arg!(-w --wide "Wide output mode"),

0 commit comments

Comments
 (0)