i3status_rs/blocks/
disk_iostats.rs1use super::prelude::*;
33use crate::util::read_file;
34use libc::c_ulong;
35use std::ops;
36use std::path::Path;
37use std::time::Instant;
38use tokio::fs::read_dir;
39
40const BLOCK_DEVICES_PATH: &str = "/sys/class/block";
42
43#[derive(Deserialize, Debug, SmartDefault)]
44#[serde(deny_unknown_fields, default)]
45pub struct Config {
46 pub device: Option<String>,
47 #[default(2.into())]
48 pub interval: Seconds,
49 pub format: FormatConfig,
50 pub missing_format: FormatConfig,
51}
52
53pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
54 let format = config
55 .format
56 .with_default(" $icon $speed_read.eng(prefix:K) $speed_write.eng(prefix:K) ")?;
57 let missing_format = config.missing_format.with_default(" × ")?;
58
59 let mut timer = config.interval.timer();
60 let mut old_stats = None;
61 let mut stats_timer = Instant::now();
62
63 loop {
64 let mut device = config.device.clone();
65 if device.is_none() {
66 device = find_device().await?;
67 }
68 match device {
69 None => {
70 api.set_widget(Widget::new().with_format(missing_format.clone()))?;
71 }
72 Some(device) => {
73 let mut widget = Widget::new();
74
75 widget.set_format(format.clone());
76
77 let new_stats = read_stats(&device).await?;
78 let sector_size = read_sector_size(&device).await?;
79
80 let mut speed_read = 0.0;
81 let mut speed_write = 0.0;
82 if let Some(old_stats) = old_stats {
83 let diff = new_stats - old_stats;
84 let elapsed = stats_timer.elapsed().as_secs_f64();
85 stats_timer = Instant::now();
86 let size_read = diff.sectors_read as u64 * sector_size;
87 let size_written = diff.sectors_written as u64 * sector_size;
88 speed_read = size_read as f64 / elapsed;
89 speed_write = size_written as f64 / elapsed;
90 };
91 old_stats = Some(new_stats);
92
93 widget.set_values(map! {
94 "icon" => Value::icon("disk_drive"),
95 "speed_read" => Value::bytes(speed_read),
96 "speed_write" => Value::bytes(speed_write),
97 "device" => Value::text(device),
98 });
99
100 api.set_widget(widget)?;
101 }
102 }
103
104 select! {
105 _ = timer.tick() => continue,
106 _ = api.wait_for_update_request() => continue,
107 }
108 }
109}
110
111async fn find_device() -> Result<Option<String>> {
112 let mut sysfs_dir = read_dir(BLOCK_DEVICES_PATH)
113 .await
114 .error("Failed to open /sys/class/block directory")?;
115 while let Some(dir) = sysfs_dir
116 .next_entry()
117 .await
118 .error("Failed to read /sys/class/block directory")?
119 {
120 let path = dir.path();
121 if path.join("device").exists() {
122 return Ok(Some(
123 dir.file_name()
124 .into_string()
125 .map_err(|_| Error::new("Invalid device filename"))?,
126 ));
127 }
128 }
129
130 Ok(None)
131}
132
133#[derive(Debug, Default, Clone, Copy)]
134struct Stats {
135 sectors_read: c_ulong,
136 sectors_written: c_ulong,
137}
138
139impl ops::Sub for Stats {
140 type Output = Self;
141
142 fn sub(mut self, rhs: Self) -> Self::Output {
143 self.sectors_read = self.sectors_read.wrapping_sub(rhs.sectors_read);
144 self.sectors_written = self.sectors_written.wrapping_sub(rhs.sectors_written);
145 self
146 }
147}
148
149async fn read_stats(device: &str) -> Result<Stats> {
150 let raw = read_file(Path::new(BLOCK_DEVICES_PATH).join(device).join("stat"))
151 .await
152 .error("Failed to read stat file")?;
153 let fields: Vec<&str> = raw.split_whitespace().collect();
154 Ok(Stats {
155 sectors_read: fields
156 .get(2)
157 .error("Missing sectors read field")?
158 .parse()
159 .error("Failed to parse sectors read")?,
160 sectors_written: fields
161 .get(6)
162 .error("Missing sectors written field")?
163 .parse()
164 .error("Failed to parse sectors written")?,
165 })
166}
167
168async fn read_sector_size(device: &str) -> Result<u64> {
169 let raw = read_file(
170 Path::new(BLOCK_DEVICES_PATH)
171 .join(device)
172 .join("queue/hw_sector_size"),
173 )
174 .await
175 .error("Failed to read HW sector size")?;
176 raw.parse::<u64>().error("Failed to parse HW sector size")
177}