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/>
6 //! See also: <https://www.kernel.org/doc/Documentation/accounting/psi.txt>
8 use crate::{ProcError, ProcResult};
9 use std::collections::HashMap;
11 #[cfg(feature = "serde1")]
12 use serde::{Deserialize, Serialize};
14 /// Pressure stall information for either CPU, memory, or IO.
16 /// See also: <https://www.kernel.org/doc/Documentation/accounting/psi.txt>
17 #[derive(Debug, Clone)]
18 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
19 pub struct PressureRecord {
22 /// The percentage of time, over a 10 second window, that either some or all tasks were stalled
23 /// waiting for a resource.
27 /// The percentage of time, over a 60 second window, that either some or all tasks were stalled
28 /// waiting for a resource.
32 /// The percentage of time, over a 300 second window, that either some or all tasks were stalled
33 /// waiting for a resource.
35 /// Total stall time (in microseconds).
39 /// CPU pressure information
40 #[derive(Debug, Clone)]
41 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
42 pub struct CpuPressure {
43 pub some: PressureRecord,
46 impl 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)?;
52 some: parse_pressure_record(&some)?,
57 /// Memory pressure information
58 #[derive(Debug, Clone)]
59 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
60 pub 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
65 pub full: PressureRecord,
68 impl 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 })
75 /// IO pressure information
76 #[derive(Debug, Clone)]
77 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
78 pub 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
83 pub full: PressureRecord,
86 impl 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 })
93 fn 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)),
100 fn 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)),
107 fn parse_pressure_record(line: &str) -> ProcResult<PressureRecord> {
108 let mut parsed = HashMap::new();
110 if !line.starts_with("some") && !line.starts_with("full") {
111 return Err(ProcError::Incomplete(None));
114 let values = &line[5..];
116 for kv_str in values.split_whitespace() {
117 let kv_split = kv_str.split('=');
118 let vec: Vec<&str> = kv_split.collect();
120 parsed.insert(vec[0], vec[1]);
125 avg10: get_f32(&parsed, "avg10")?,
126 avg60: get_f32(&parsed, "avg60")?,
127 avg300: get_f32(&parsed, "avg300")?,
128 total: get_total(&parsed)?,
132 fn 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)?))
143 use std::f32::EPSILON;
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();
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);
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());