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