psutil/disk/
disk_io_counters.rs

1#[cfg(feature = "serde")]
2use serde::{Deserialize, Serialize};
3
4use std::collections::HashMap;
5use std::time::Duration;
6
7use derive_more::{Add, Sub, Sum};
8
9use crate::disk::disk_io_counters_per_partition;
10use crate::{Bytes, Count, Result};
11
12#[cfg_attr(feature = "serde", serde(crate = "renamed_serde"))]
13#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
14#[derive(Clone, Debug, Add, Sum, Default, Sub)]
15pub struct DiskIoCounters {
16	pub(crate) read_count: Count,
17	pub(crate) write_count: Count,
18	pub(crate) read_bytes: Bytes,
19	pub(crate) write_bytes: Bytes,
20
21	#[cfg(not(any(target_os = "netbsd", target_os = "openbsd")))]
22	pub(crate) read_time: Duration,
23	#[cfg(not(any(target_os = "netbsd", target_os = "openbsd")))]
24	pub(crate) write_time: Duration,
25
26	#[cfg(any(target_os = "linux", target_os = "freebsd"))]
27	pub(crate) busy_time: Duration,
28
29	#[cfg(target_os = "linux")]
30	pub(crate) read_merged_count: Count,
31	#[cfg(target_os = "linux")]
32	pub(crate) write_merged_count: Count,
33}
34
35impl DiskIoCounters {
36	/// Number of reads.
37	pub fn read_count(&self) -> Count {
38		self.read_count
39	}
40
41	/// Number of writes.
42	pub fn write_count(&self) -> Count {
43		self.write_count
44	}
45
46	/// Number of bytes read.
47	pub fn read_bytes(&self) -> Bytes {
48		self.read_bytes
49	}
50
51	/// Number of bytes written.
52	pub fn write_bytes(&self) -> Bytes {
53		self.write_bytes
54	}
55}
56
57fn nowrap(prev: u64, current: u64, corrected: u64) -> u64 {
58	if current >= prev {
59		corrected + (current - prev)
60	} else {
61		corrected + current + ((u32::MAX as u64) - prev)
62	}
63}
64
65fn nowrap_struct(
66	prev: &DiskIoCounters,
67	current: &DiskIoCounters,
68	corrected: &DiskIoCounters,
69) -> DiskIoCounters {
70	DiskIoCounters {
71		read_count: nowrap(prev.read_count, current.read_count, corrected.read_count),
72		write_count: nowrap(prev.write_count, current.write_count, corrected.write_count),
73		read_bytes: nowrap(prev.read_bytes, current.read_bytes, corrected.read_bytes),
74		write_bytes: nowrap(prev.write_bytes, current.write_bytes, corrected.write_bytes),
75
76		#[cfg(not(any(target_os = "netbsd", target_os = "openbsd")))]
77		read_time: Duration::from_millis(nowrap(
78			prev.read_time.as_millis() as u64,
79			current.read_time.as_millis() as u64,
80			corrected.read_time.as_millis() as u64,
81		)),
82		#[cfg(not(any(target_os = "netbsd", target_os = "openbsd")))]
83		write_time: Duration::from_millis(nowrap(
84			prev.write_time.as_millis() as u64,
85			current.write_time.as_millis() as u64,
86			corrected.write_time.as_millis() as u64,
87		)),
88
89		#[cfg(any(target_os = "linux", target_os = "freebsd"))]
90		busy_time: Duration::from_millis(nowrap(
91			prev.busy_time.as_millis() as u64,
92			current.busy_time.as_millis() as u64,
93			corrected.busy_time.as_millis() as u64,
94		)),
95
96		#[cfg(target_os = "linux")]
97		read_merged_count: nowrap(
98			prev.read_merged_count,
99			current.read_merged_count,
100			corrected.read_merged_count,
101		),
102		#[cfg(target_os = "linux")]
103		write_merged_count: nowrap(
104			prev.write_merged_count,
105			current.write_merged_count,
106			corrected.write_merged_count,
107		),
108	}
109}
110
111fn fix_io_counter_overflow(
112	prev: &HashMap<String, DiskIoCounters>,
113	current: &HashMap<String, DiskIoCounters>,
114	corrected: &HashMap<String, DiskIoCounters>,
115) -> HashMap<String, DiskIoCounters> {
116	current
117		.iter()
118		.map(|(name, current_counters)| {
119			if !prev.contains_key(name) || !corrected.contains_key(name) {
120				(name.clone(), current_counters.clone())
121			} else {
122				let prev_counters = &prev[name];
123				let corrected_counters = &corrected[name];
124
125				(
126					name.clone(),
127					nowrap_struct(prev_counters, current_counters, corrected_counters),
128				)
129			}
130		})
131		.collect()
132}
133
134/// Used to persist data between calls to detect data overflow by the kernel and fix the result.
135/// Requires a minimum kernel version of 2.5.69 due to the usage of `/proc/diskstats`.
136#[derive(Clone, Debug, Default)]
137pub struct DiskIoCountersCollector {
138	prev_disk_io_counters_per_partition: Option<HashMap<String, DiskIoCounters>>,
139	corrected_disk_io_counters_per_partition: Option<HashMap<String, DiskIoCounters>>,
140}
141
142impl DiskIoCountersCollector {
143	pub fn disk_io_counters(&mut self) -> Result<DiskIoCounters> {
144		let sum = self.disk_io_counters_per_partition()?.into_values().sum();
145
146		Ok(sum)
147	}
148
149	pub fn disk_io_counters_per_partition(&mut self) -> Result<HashMap<String, DiskIoCounters>> {
150		let io_counters = disk_io_counters_per_partition()?;
151
152		let corrected_counters = match (
153			&self.prev_disk_io_counters_per_partition,
154			&self.corrected_disk_io_counters_per_partition,
155		) {
156			(Some(prev), Some(corrected)) => fix_io_counter_overflow(prev, &io_counters, corrected),
157			_ => io_counters.clone(),
158		};
159
160		self.prev_disk_io_counters_per_partition = Some(io_counters);
161		self.corrected_disk_io_counters_per_partition = Some(corrected_counters.clone());
162
163		Ok(corrected_counters)
164	}
165}