i3status_rs/blocks/
disk_iostats.rsuse super::prelude::*;
use crate::util::read_file;
use libc::c_ulong;
use std::ops;
use std::path::Path;
use std::time::Instant;
use tokio::fs::read_dir;
const BLOCK_DEVICES_PATH: &str = "/sys/class/block";
#[derive(Deserialize, Debug, SmartDefault)]
#[serde(deny_unknown_fields, default)]
pub struct Config {
pub device: Option<String>,
#[default(2.into())]
pub interval: Seconds,
pub format: FormatConfig,
pub missing_format: FormatConfig,
}
pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
let format = config
.format
.with_default(" $icon $speed_read.eng(prefix:K) $speed_write.eng(prefix:K) ")?;
let missing_format = config.missing_format.with_default(" × ")?;
let mut timer = config.interval.timer();
let mut old_stats = None;
let mut stats_timer = Instant::now();
loop {
let mut device = config.device.clone();
if device.is_none() {
device = find_device().await?;
}
match device {
None => {
api.set_widget(Widget::new().with_format(missing_format.clone()))?;
}
Some(device) => {
let mut widget = Widget::new();
widget.set_format(format.clone());
let new_stats = read_stats(&device).await?;
let sector_size = read_sector_size(&device).await?;
let mut speed_read = 0.0;
let mut speed_write = 0.0;
if let Some(old_stats) = old_stats {
let diff = new_stats - old_stats;
let elapsed = stats_timer.elapsed().as_secs_f64();
stats_timer = Instant::now();
let size_read = diff.sectors_read as u64 * sector_size;
let size_written = diff.sectors_written as u64 * sector_size;
speed_read = size_read as f64 / elapsed;
speed_write = size_written as f64 / elapsed;
};
old_stats = Some(new_stats);
widget.set_values(map! {
"icon" => Value::icon("disk_drive"),
"speed_read" => Value::bytes(speed_read),
"speed_write" => Value::bytes(speed_write),
"device" => Value::text(device),
});
api.set_widget(widget)?;
}
}
select! {
_ = timer.tick() => continue,
_ = api.wait_for_update_request() => continue,
}
}
}
async fn find_device() -> Result<Option<String>> {
let mut sysfs_dir = read_dir(BLOCK_DEVICES_PATH)
.await
.error("Failed to open /sys/class/block directory")?;
while let Some(dir) = sysfs_dir
.next_entry()
.await
.error("Failed to read /sys/class/block directory")?
{
let path = dir.path();
if path.join("device").exists() {
return Ok(Some(
dir.file_name()
.into_string()
.map_err(|_| Error::new("Invalid device filename"))?,
));
}
}
Ok(None)
}
#[derive(Debug, Default, Clone, Copy)]
struct Stats {
sectors_read: c_ulong,
sectors_written: c_ulong,
}
impl ops::Sub for Stats {
type Output = Self;
fn sub(mut self, rhs: Self) -> Self::Output {
self.sectors_read = self.sectors_read.wrapping_sub(rhs.sectors_read);
self.sectors_written = self.sectors_written.wrapping_sub(rhs.sectors_written);
self
}
}
async fn read_stats(device: &str) -> Result<Stats> {
let raw = read_file(Path::new(BLOCK_DEVICES_PATH).join(device).join("stat"))
.await
.error("Failed to read stat file")?;
let fields: Vec<&str> = raw.split_whitespace().collect();
Ok(Stats {
sectors_read: fields
.get(2)
.error("Missing sectors read field")?
.parse()
.error("Failed to parse sectors read")?,
sectors_written: fields
.get(6)
.error("Missing sectors written field")?
.parse()
.error("Failed to parse sectors written")?,
})
}
async fn read_sector_size(device: &str) -> Result<u64> {
let raw = read_file(
Path::new(BLOCK_DEVICES_PATH)
.join(device)
.join("queue/hw_sector_size"),
)
.await
.error("Failed to read HW sector size")?;
raw.parse::<u64>().error("Failed to parse HW sector size")
}