procfs_core/
pressure.rs

1//! Pressure stall information retreived from `/proc/pressure/cpu`,
2//! `/proc/pressure/memory` and `/proc/pressure/io`
3//! may not be available on kernels older than 4.20.0
4//! For reference: <https://lwn.net/Articles/759781/>
5//!
6//! See also: <https://www.kernel.org/doc/Documentation/accounting/psi.txt>
7
8use crate::{ProcError, ProcResult};
9use std::collections::HashMap;
10
11#[cfg(feature = "serde1")]
12use serde::{Deserialize, Serialize};
13
14/// Pressure stall information for either CPU, memory, or IO.
15///
16/// See also: <https://www.kernel.org/doc/Documentation/accounting/psi.txt>
17#[derive(Debug, Clone)]
18#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
19pub struct PressureRecord {
20    /// 10 second window
21    ///
22    /// The percentage of time, over a 10 second window, that either some or all tasks were stalled
23    /// waiting for a resource.
24    pub avg10: f32,
25    /// 60 second window
26    ///
27    /// The percentage of time, over a 60 second window, that either some or all tasks were stalled
28    /// waiting for a resource.
29    pub avg60: f32,
30    /// 300 second window
31    ///
32    /// The percentage of time, over a 300 second window, that either some or all tasks were stalled
33    /// waiting for a resource.
34    pub avg300: f32,
35    /// Total stall time (in microseconds).
36    pub total: u64,
37}
38
39/// CPU pressure information
40#[derive(Debug, Clone)]
41#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
42pub struct CpuPressure {
43    /// This record indicates the share of time in which at least some tasks are stalled.
44    pub some: PressureRecord,
45    /// This record indicates this share of time in which all non-idle tasks are stalled
46    /// simultaneously.
47    ///
48    /// At the system level CPU full is set to zero.
49    pub full: PressureRecord,
50}
51
52impl super::FromBufRead for CpuPressure {
53    fn from_buf_read<R: std::io::BufRead>(mut r: R) -> ProcResult<Self> {
54        let (some, full) = get_pressure(r)?;
55        Ok(CpuPressure { some, full })
56    }
57}
58
59/// Memory pressure information
60#[derive(Debug, Clone)]
61#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
62pub struct MemoryPressure {
63    /// This record indicates the share of time in which at least some tasks are stalled.
64    pub some: PressureRecord,
65    /// This record indicates this share of time in which all non-idle tasks are stalled
66    /// simultaneously.
67    pub full: PressureRecord,
68}
69
70impl super::FromBufRead for MemoryPressure {
71    fn from_buf_read<R: std::io::BufRead>(r: R) -> ProcResult<Self> {
72        let (some, full) = get_pressure(r)?;
73        Ok(MemoryPressure { some, full })
74    }
75}
76
77/// IO pressure information
78#[derive(Debug, Clone)]
79#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
80pub struct IoPressure {
81    /// This record indicates the share of time in which at least some tasks are stalled.
82    pub some: PressureRecord,
83    /// This record indicates this share of time in which all non-idle tasks are stalled
84    /// simultaneously.
85    pub full: PressureRecord,
86}
87
88impl super::FromBufRead for IoPressure {
89    fn from_buf_read<R: std::io::BufRead>(r: R) -> ProcResult<Self> {
90        let (some, full) = get_pressure(r)?;
91        Ok(IoPressure { some, full })
92    }
93}
94
95fn get_f32(map: &HashMap<&str, &str>, value: &str) -> ProcResult<f32> {
96    map.get(value).map_or_else(
97        || Err(ProcError::Incomplete(None)),
98        |v| v.parse::<f32>().map_err(|_| ProcError::Incomplete(None)),
99    )
100}
101
102fn get_total(map: &HashMap<&str, &str>) -> ProcResult<u64> {
103    map.get("total").map_or_else(
104        || Err(ProcError::Incomplete(None)),
105        |v| v.parse::<u64>().map_err(|_| ProcError::Incomplete(None)),
106    )
107}
108
109/// Parse a single `some`/`full` pressure record in the following format:
110///
111/// ```text
112/// full avg10=2.10 avg60=0.12 avg300=0.00 total=391926
113/// ```
114///
115/// See also [`get_pressure`].
116pub fn parse_pressure_record(line: &str) -> ProcResult<PressureRecord> {
117    let mut parsed = HashMap::new();
118
119    if !line.starts_with("some") && !line.starts_with("full") {
120        return Err(ProcError::Incomplete(None));
121    }
122
123    let values = &line[5..];
124
125    for kv_str in values.split_whitespace() {
126        let kv_split = kv_str.split('=');
127        let vec: Vec<&str> = kv_split.collect();
128        if vec.len() == 2 {
129            parsed.insert(vec[0], vec[1]);
130        }
131    }
132
133    Ok(PressureRecord {
134        avg10: get_f32(&parsed, "avg10")?,
135        avg60: get_f32(&parsed, "avg60")?,
136        avg300: get_f32(&parsed, "avg300")?,
137        total: get_total(&parsed)?,
138    })
139}
140
141/// Get the pressure records from a reader. The first line should be a `some` record, and the second line a `full` record.
142/// The records are returned in the same order.
143///
144/// ```text
145/// some avg10=4.50 avg60=0.91 avg300=0.00 total=681245
146/// full avg10=2.10 avg60=0.12 avg300=0.00 total=391926
147/// ```
148///
149/// See also [`parse_pressure_record`].
150pub fn get_pressure<R: std::io::BufRead>(mut r: R) -> ProcResult<(PressureRecord, PressureRecord)> {
151    let mut some = String::new();
152    r.read_line(&mut some)?;
153    let mut full = String::new();
154    r.read_line(&mut full)?;
155    Ok((parse_pressure_record(&some)?, parse_pressure_record(&full)?))
156}
157
158#[cfg(test)]
159mod test {
160    use super::*;
161    use std::f32::EPSILON;
162
163    #[test]
164    fn test_parse_pressure_record() {
165        let record = parse_pressure_record("full avg10=2.10 avg60=0.12 avg300=0.00 total=391926").unwrap();
166
167        assert!(record.avg10 - 2.10 < EPSILON);
168        assert!(record.avg60 - 0.12 < EPSILON);
169        assert!(record.avg300 - 0.00 < EPSILON);
170        assert_eq!(record.total, 391_926);
171    }
172
173    #[test]
174    fn test_parse_pressure_record_errs() {
175        assert!(parse_pressure_record("avg10=2.10 avg60=0.12 avg300=0.00 total=391926").is_err());
176        assert!(parse_pressure_record("some avg10=2.10 avg300=0.00 total=391926").is_err());
177        assert!(parse_pressure_record("some avg10=2.10 avg60=0.00 avg300=0.00").is_err());
178    }
179}