Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_sys/src/disk.rs

4.9 KiB, 1 run

created by r1870400018:10580, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! Block-device I/O accounting from `/proc/diskstats`.
2//!
3//! The kernel exposes one line per block device with cumulative
4//! counters. Two samples separated in time let you compute
5//! throughput, IOPS, and utilisation; see [`DiskStats::deltas`].
6
7use crate::parse::{
8 parse_num,
9 read_to_string,
10 tokens,
11};
12
13use oxedyne_fe2o3_core::prelude::*;
14
15/// Sector size assumed when translating diskstats "sectors"
16/// counters to bytes. The kernel interface has used a fixed
17/// 512-byte sector forever; the on-disk physical sector size is
18/// a separate concept.
19pub const SECTOR_BYTES: u64 = 512;
20
21/// One block device's cumulative counters, extracted from a
22/// single line of `/proc/diskstats`.
23#[derive(Clone, Debug, Default, PartialEq)]
24pub struct DiskLine {
25 /// Device name, e.g. `sda`, `nvme0n1`, `dm-0`.
26 pub name: String,
27 /// Cumulative reads completed successfully.
28 pub reads: u64,
29 /// Cumulative sectors read.
30 pub read_sectors: u64,
31 /// Cumulative time spent on reads (ms).
32 pub read_ms: u64,
33 /// Cumulative writes completed successfully.
34 pub writes: u64,
35 /// Cumulative sectors written.
36 pub write_sectors:u64,
37 /// Cumulative time spent on writes (ms).
38 pub write_ms: u64,
39 /// Cumulative time spent on all I/O (ms). Handy for a
40 /// rough utilisation figure.
41 pub io_ms: u64,
42}
43
44/// A collection of block-device counters. Keeps them in the
45/// order seen on disk so downstream renderers can display them
46/// consistently.
47#[derive(Clone, Debug, Default, PartialEq)]
48pub struct DiskStats {
49 pub devices: Vec<DiskLine>,
50}
51
52impl DiskStats {
53 /// Read `/proc/diskstats`.
54 pub fn sample() -> Outcome<Self> {
55 let content = res!(read_to_string("/proc/diskstats"));
56 Self::from_diskstats(&content)
57 }
58
59 /// Parse a `/proc/diskstats` body. Lines that do not have the
60 /// expected column count are silently skipped (the kernel
61 /// version has grown the format over time; we are liberal in
62 /// what we accept).
63 pub fn from_diskstats(content: &str) -> Outcome<Self> {
64 let mut out = Self::default();
65 for line in content.lines() {
66 let toks = tokens(line);
67 // Format: major minor name reads reads_merged read_sectors
68 // read_ms writes writes_merged write_sectors write_ms
69 // io_in_progress io_ms weighted_io_ms ...
70 // Need at least 14 columns for the fields we use.
71 if toks.len() < 14 {
72 continue;
73 }
74 let name = toks[2].to_string();
75 out.devices.push(DiskLine {
76 name,
77 reads: res!(parse_num::<u64>(toks[3], "disk.reads")),
78 read_sectors: res!(parse_num::<u64>(toks[5], "disk.read_sectors")),
79 read_ms: res!(parse_num::<u64>(toks[6], "disk.read_ms")),
80 writes: res!(parse_num::<u64>(toks[7], "disk.writes")),
81 write_sectors: res!(parse_num::<u64>(toks[9], "disk.write_sectors")),
82 write_ms: res!(parse_num::<u64>(toks[10], "disk.write_ms")),
83 io_ms: res!(parse_num::<u64>(toks[12], "disk.io_ms")),
84 });
85 }
86 Ok(out)
87 }
88
89 /// Compute per-device rate deltas between `prev` and `self`.
90 /// Returns read/write bytes per second and an I/O utilisation
91 /// fraction over the elapsed wall-clock interval in seconds.
92 pub fn deltas(&self, prev: &Self, elapsed_s: f64) -> Vec<DiskDelta> {
93 let mut out = Vec::with_capacity(self.devices.len());
94 for dev in &self.devices {
95 let p = prev.devices.iter().find(|d| d.name == dev.name);
96 let p = match p {
97 Some(p) => p,
98 None => continue,
99 };
100 let dr = dev.read_sectors.saturating_sub(p.read_sectors) * SECTOR_BYTES;
101 let dw = dev.write_sectors.saturating_sub(p.write_sectors) * SECTOR_BYTES;
102 let dio = dev.io_ms.saturating_sub(p.io_ms);
103 let (rps, wps, util) = if elapsed_s > 0.0 {
104 (
105 dr as f64 / elapsed_s,
106 dw as f64 / elapsed_s,
107 ((dio as f64) / 1000.0 / elapsed_s).min(1.0),
108 )
109 } else {
110 (0.0, 0.0, 0.0)
111 };
112 out.push(DiskDelta {
113 name: dev.name.clone(),
114 read_bps: rps,
115 write_bps: wps,
116 utilisation: util,
117 });
118 }
119 out
120 }
121}
122
123/// Per-device rate between two samples.
124#[derive(Clone, Debug, Default, PartialEq)]
125pub struct DiskDelta {
126 /// Device name (as in `DiskLine::name`).
127 pub name: String,
128 /// Read bytes per second over the interval.
129 pub read_bps: f64,
130 /// Write bytes per second over the interval.
131 pub write_bps: f64,
132 /// Fraction (0..=1) of the interval the device was busy with
133 /// any I/O.
134 pub utilisation: f64,
135}