use std::collections::HashMap;
use std::fs::DirEntry;
use std::str::FromStr;
use anyhow::{Context, Result};
pub fn stat_all() -> Result<HashMap<String, Stat>> {
let mut ret = HashMap::default();
let devices =
std::fs::read_dir("/sys/block").context("read_dir call failed")?;
for device in devices {
let device = device.context("reading dir entry failed")?;
let (device_name, stat) = stat(&device)?;
ret.insert(device_name, stat);
}
Ok(ret)
}
fn stat(device: &DirEntry) -> Result<(String, Stat)> {
let device_name = device.file_name().to_string_lossy().into();
let stat_file = device.path().join("stat");
let stat = std::fs::read_to_string(&stat_file)
.with_context(|| {
format!("reading stat file: {}", stat_file.display())
})?
.parse()?;
Ok((device_name, stat))
}
#[derive(
Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default,
)]
pub struct Stat {
pub read_ios: u64,
pub read_merges: u64,
pub read_sectors: u64,
pub read_ticks: u64,
pub write_ios: u64,
pub write_merges: u64,
pub write_sectors: u64,
pub write_ticks: u64,
pub in_flight: u64,
pub io_ticks: u64,
pub time_in_queue: u64,
}
impl Stat {
#[must_use]
pub const fn read_bytes(&self) -> u64 {
self.read_sectors * 512
}
#[must_use]
pub const fn write_bytes(&self) -> u64 {
self.write_sectors * 512
}
}
impl FromStr for Stat {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self> {
let mut stat = Self::default();
let mut tokens =
s.trim().split(' ').filter(|token| !token.trim().is_empty());
stat.read_ios = tokens
.next()
.context("no read I/Os value")?
.parse()
.context("parsing read I/Os value")?;
stat.read_merges = tokens
.next()
.context("no read merges value")?
.parse()
.context("parsing read merges value")?;
stat.read_sectors = tokens
.next()
.context("no read sectors value")?
.parse()
.context("parsing read sectors value")?;
stat.read_ticks = tokens
.next()
.context("no read ticks value")?
.parse()
.context("parsing read ticks value")?;
stat.write_ios = tokens
.next()
.context("no write I/Os value")?
.parse()
.context("parsing write I/Os value")?;
stat.write_merges = tokens
.next()
.context("no write merges value")?
.parse()
.context("parsing write merges value")?;
stat.write_sectors = tokens
.next()
.context("no write sectors value")?
.parse()
.context("parsing write sectors value")?;
stat.write_ticks = tokens
.next()
.context("no write ticks value")?
.parse()
.context("parsing write ticks value")?;
stat.in_flight = tokens
.next()
.context("no in flight value")?
.parse()
.context("parsing in flight value")?;
stat.io_ticks = tokens
.next()
.context("no I/O ticks value")?
.parse()
.context("parsing I/O ticks value")?;
stat.time_in_queue = tokens
.next()
.context("no time in queue value")?
.parse()
.context("parsing time in queue value")?;
Ok(stat)
}
}
impl std::ops::Add for Stat {
type Output = Self;
fn add(self, other: Self) -> Self {
Self {
read_ios: self.read_ios + other.read_ios,
read_merges: self.read_merges + other.read_merges,
read_sectors: self.read_sectors + other.read_sectors,
read_ticks: self.read_ticks + other.read_ticks,
write_ios: self.write_ios + other.write_ios,
write_merges: self.write_merges + other.write_merges,
write_sectors: self.write_sectors + other.write_sectors,
write_ticks: self.write_ticks + other.write_sectors,
in_flight: self.in_flight + other.in_flight,
io_ticks: self.io_ticks + other.io_ticks,
time_in_queue: self.time_in_queue + other.time_in_queue,
}
}
}
impl std::ops::AddAssign for Stat {
fn add_assign(&mut self, other: Self) {
*self = Self {
read_ios: self.read_ios + other.read_ios,
read_merges: self.read_merges + other.read_merges,
read_sectors: self.read_sectors + other.read_sectors,
read_ticks: self.read_ticks + other.read_ticks,
write_ios: self.write_ios + other.write_ios,
write_merges: self.write_merges + other.write_merges,
write_sectors: self.write_sectors + other.write_sectors,
write_ticks: self.write_ticks + other.write_sectors,
in_flight: self.in_flight + other.in_flight,
io_ticks: self.io_ticks + other.io_ticks,
time_in_queue: self.time_in_queue + other.time_in_queue,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_new() {
let input = include_str!("block-example-new.in");
let stat: Stat = input.parse().unwrap();
assert_eq!(36290, stat.read_ios);
assert_eq!(29495, stat.read_merges);
assert_eq!(3_809_274, stat.read_sectors);
assert_eq!(13845, stat.read_ticks);
assert_eq!(128_563, stat.write_ios);
assert_eq!(160_891, stat.write_merges);
assert_eq!(8_436_842, stat.write_sectors);
assert_eq!(99801, stat.write_ticks);
assert_eq!(0, stat.in_flight);
assert_eq!(125_014, stat.io_ticks);
assert_eq!(154_343, stat.time_in_queue);
}
#[test]
fn parse_old() {
let input = include_str!("block-example-old.in");
let stat: Stat = input.parse().unwrap();
assert_eq!(79630, stat.read_ios);
assert_eq!(116, stat.read_merges);
assert_eq!(6_466_654, stat.read_sectors);
assert_eq!(416_960, stat.read_ticks);
assert_eq!(6_744_190, stat.write_ios);
assert_eq!(8_022_528, stat.write_merges);
assert_eq!(216_108_928, stat.write_sectors);
assert_eq!(164_633_155, stat.write_ticks);
assert_eq!(0, stat.in_flight);
assert_eq!(33_293_921, stat.io_ticks);
assert_eq!(165_047_391, stat.time_in_queue);
}
}