-
Notifications
You must be signed in to change notification settings - Fork 476
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Reads disk stats from sysfs. See: https://docs.kernel.org/admin-guide/iostats.html
- Loading branch information
Showing
2 changed files
with
131 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -156,6 +156,7 @@ define_blocks!( | |
cpu, | ||
custom, | ||
custom_dbus, | ||
disk_stats, | ||
disk_space, | ||
#[deprecated( | ||
since = "0.33.0", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
use super::prelude::*; | ||
use tokio::fs::read_dir; | ||
use crate::util::read_file; | ||
use std::path::Path; | ||
use std::time::Instant; | ||
use std::ops; | ||
use libc::c_ulong; | ||
|
||
/// Path for block devices | ||
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 read /sys/class/block directory")?; | ||
while let Some(dir) = sysfs_dir | ||
.next_entry() | ||
.await | ||
.error("failed to read /sys/class/power_supply 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[2].parse().error("Failed to parse sectors read")?, | ||
sectors_written: fields[6].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") | ||
} |