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    pub some: PressureRecord,
44}
45
46impl super::FromBufRead for CpuPressure {
47    fn from_buf_read<R: std::io::BufRead>(mut r: R) -> ProcResult<Self> {
48        let mut some = String::new();
49        r.read_line(&mut some)?;
50
51        Ok(CpuPressure {
52            some: parse_pressure_record(&some)?,
53        })
54    }
55}
56
57/// Memory pressure information
58#[derive(Debug, Clone)]
59#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
60pub struct MemoryPressure {
61    /// This record indicates the share of time in which at least some tasks are stalled
62    pub some: PressureRecord,
63    /// This record indicates this share of time in which all non-idle tasks are stalled
64    /// simultaneously.
65    pub full: PressureRecord,
66}
67
68impl super::FromBufRead for MemoryPressure {
69    fn from_buf_read<R: std::io::BufRead>(r: R) -> ProcResult<Self> {
70        let (some, full) = get_pressure(r)?;
71        Ok(MemoryPressure { some, full })
72    }
73}
74
75/// IO pressure information
76#[derive(Debug, Clone)]
77#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
78pub struct IoPressure {
79    /// This record indicates the share of time in which at least some tasks are stalled
80    pub some: PressureRecord,
81    /// This record indicates this share of time in which all non-idle tasks are stalled
82    /// simultaneously.
83    pub full: PressureRecord,
84}
85
86impl super::FromBufRead for IoPressure {
87    fn from_buf_read<R: std::io::BufRead>(r: R) -> ProcResult<Self> {
88        let (some, full) = get_pressure(r)?;
89        Ok(IoPressure { some, full })
90    }
91}
92
93fn get_f32(map: &HashMap<&str, &str>, value: &str) -> ProcResult<f32> {
94    map.get(value).map_or_else(
95        || Err(ProcError::Incomplete(None)),
96        |v| v.parse::<f32>().map_err(|_| ProcError::Incomplete(None)),
97    )
98}
99
100fn get_total(map: &HashMap<&str, &str>) -> ProcResult<u64> {
101    map.get("total").map_or_else(
102        || Err(ProcError::Incomplete(None)),
103        |v| v.parse::<u64>().map_err(|_| ProcError::Incomplete(None)),
104    )
105}
106
107fn parse_pressure_record(line: &str) -> ProcResult<PressureRecord> {
108    let mut parsed = HashMap::new();
109
110    if !line.starts_with("some") && !line.starts_with("full") {
111        return Err(ProcError::Incomplete(None));
112    }
113
114    let values = &line[5..];
115
116    for kv_str in values.split_whitespace() {
117        let kv_split = kv_str.split('=');
118        let vec: Vec<&str> = kv_split.collect();
119        if vec.len() == 2 {
120            parsed.insert(vec[0], vec[1]);
121        }
122    }
123
124    Ok(PressureRecord {
125        avg10: get_f32(&parsed, "avg10")?,
126        avg60: get_f32(&parsed, "avg60")?,
127        avg300: get_f32(&parsed, "avg300")?,
128        total: get_total(&parsed)?,
129    })
130}
131
132fn get_pressure<R: std::io::BufRead>(mut r: R) -> ProcResult<(PressureRecord, PressureRecord)> {
133    let mut some = String::new();
134    r.read_line(&mut some)?;
135    let mut full = String::new();
136    r.read_line(&mut full)?;
137    Ok((parse_pressure_record(&some)?, parse_pressure_record(&full)?))
138}
139
140#[cfg(test)]
141mod test {
142    use super::*;
143    use std::f32::EPSILON;
144
145    #[test]
146    fn test_parse_pressure_record() {
147        let record = parse_pressure_record("full avg10=2.10 avg60=0.12 avg300=0.00 total=391926").unwrap();
148
149        assert!(record.avg10 - 2.10 < EPSILON);
150        assert!(record.avg60 - 0.12 < EPSILON);
151        assert!(record.avg300 - 0.00 < EPSILON);
152        assert_eq!(record.total, 391_926);
153    }
154
155    #[test]
156    fn test_parse_pressure_record_errs() {
157        assert!(parse_pressure_record("avg10=2.10 avg60=0.12 avg300=0.00 total=391926").is_err());
158        assert!(parse_pressure_record("some avg10=2.10 avg300=0.00 total=391926").is_err());
159        assert!(parse_pressure_record("some avg10=2.10 avg60=0.00 avg300=0.00").is_err());
160    }
161}