-
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
169 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,168 @@ | ||
//! Disk I/O statistics | ||
//! | ||
//! # Configuration | ||
//! | ||
//! Key | Values | Default | ||
//! ----|--------|-------- | ||
//! `device` | Block device name to monitor (as specified in `/dev/`) | If not set, device will be automatically selected every `interval` | ||
//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $icon $available "` | ||
//! `interval` | Update interval in seconds | `2` | ||
//! | ||
//! Placeholder | Value | Type | Unit | ||
//! ------------|-------|--------|------- | ||
//! `icon` | A static icon | Icon | - | ||
//! `device` | The name of device | Text | - | ||
//! `speed_read` | Read speed | Number | Bytes per second | ||
//! `speed_write` | Write speed | Number | Bytes per second | ||
//! | ||
//! # Examples | ||
//! | ||
//! ```toml | ||
//! [[block]] | ||
//! block = "disk_stats" | ||
//! device = "sda" | ||
//! format = " $icon $speed_write.eng(prefix:K) " | ||
//! ``` | ||
//! | ||
//! # Icons Used | ||
//! | ||
//! - `disk_drive` | ||
use 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; | ||
|
||
/// 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 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[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") | ||
} |