summaryrefslogtreecommitdiffstats
path: root/src/data_collection/disks/unix/linux/counters.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/data_collection/disks/unix/linux/counters.rs')
-rw-r--r--src/data_collection/disks/unix/linux/counters.rs95
1 files changed, 95 insertions, 0 deletions
diff --git a/src/data_collection/disks/unix/linux/counters.rs b/src/data_collection/disks/unix/linux/counters.rs
new file mode 100644
index 00000000..bc2cb076
--- /dev/null
+++ b/src/data_collection/disks/unix/linux/counters.rs
@@ -0,0 +1,95 @@
+//! Based on [heim's implementation](https://github.com/heim-rs/heim/blob/master/heim-disk/src/sys/linux/counters.rs).
+
+use std::{
+ fs::File,
+ io::{self, BufRead, BufReader},
+ num::ParseIntError,
+ str::FromStr,
+};
+
+use crate::data_collection::disks::IoCounters;
+
+/// Copied from the `psutil` sources:
+///
+/// "man iostat" states that sectors are equivalent with blocks and have
+/// a size of 512 bytes. Despite this value can be queried at runtime
+/// via /sys/block/{DISK}/queue/hw_sector_size and results may vary
+/// between 1k, 2k, or 4k... 512 appears to be a magic constant used
+/// throughout Linux source code:
+/// * <https://stackoverflow.com/a/38136179/376587>
+/// * <https://lists.gt.net/linux/kernel/2241060>
+/// * <https://github.com/giampaolo/psutil/issues/1305>
+/// * <https://github.com/torvalds/linux/blob/4f671fe2f9523a1ea206f63fe60a7c7b3a56d5c7/include/linux/bio.h#L99>
+/// * <https://lkml.org/lkml/2015/8/17/234>
+const DISK_SECTOR_SIZE: u64 = 512;
+
+impl FromStr for IoCounters {
+ type Err = anyhow::Error;
+
+ /// Converts a `&str` to an [`IoCounters`].
+ ///
+ /// Follows the format used in Linux 2.6+. Note that this completely ignores the following stats:
+ /// - Discard stats from 4.18+
+ /// - Flush stats from 5.5+
+ ///
+ /// <https://www.kernel.org/doc/Documentation/iostats.txt>
+ /// <https://www.kernel.org/doc/Documentation/ABI/testing/procfs-diskstats>
+ fn from_str(s: &str) -> anyhow::Result<IoCounters> {
+ fn next_part<'a>(iter: &mut impl Iterator<Item = &'a str>) -> Result<&'a str, io::Error> {
+ iter.next()
+ .ok_or_else(|| io::Error::from(io::ErrorKind::InvalidData))
+ }
+
+ fn next_part_to_u64<'a>(iter: &mut impl Iterator<Item = &'a str>) -> anyhow::Result<u64> {
+ next_part(iter)?
+ .parse()
+ .map_err(|err: ParseIntError| err.into())
+ }
+
+ // Skip the major and minor numbers.
+ let mut parts = s.split_whitespace().skip(2);
+
+ let name = next_part(&mut parts)?.to_string();
+
+ // Skip read count, read merged count.
+ let mut parts = parts.skip(2);
+ let read_bytes = next_part_to_u64(&mut parts)? * DISK_SECTOR_SIZE;
+
+ // Skip read time seconds, write count, and write merged count.
+ let mut parts = parts.skip(3);
+ let write_bytes = next_part_to_u64(&mut parts)? * DISK_SECTOR_SIZE;
+
+ Ok(IoCounters::new(name, read_bytes, write_bytes))
+ }
+}
+
+/// Returns an iterator of disk I/O stats. Pulls data from `/proc/diskstats`.
+pub fn io_stats() -> anyhow::Result<Vec<IoCounters>> {
+ const PROC_DISKSTATS: &str = "/proc/diskstats";
+
+ let mut results = vec![];
+ let mut reader = BufReader::new(File::open(PROC_DISKSTATS)?);
+ let mut line = String::new();
+
+ // This saves us from doing a string allocation on each iteration compared to `lines()`.
+ while let Ok(bytes) = reader.read_line(&mut line) {
+ if bytes > 0 {
+ if let Ok(counters) = IoCounters::from_str(&line) {
+ results.push(counters);
+ }
+ line.clear();
+ } else {
+ break;
+ }
+ }
+
+ #[cfg(feature = "zfs")]
+ {
+ use crate::data_collection::disks::zfs_io_counters;
+ if let Ok(mut zfs_io) = zfs_io_counters::zfs_io_stats() {
+ results.append(&mut zfs_io);
+ }
+ }
+
+ Ok(results)
+}