linux_proc/
stat.rs

1//! Bindings to `/proc/stat`.
2use crate::{util, Error};
3use std::{fs::File, io};
4
5macro_rules! parse_single {
6    ($name:expr) => {
7        |input| {
8            let (input, name) = util::parse_token(input).ok_or(Error::from("cannot read name"))?;
9            if name != $name {
10                return Err(Error::from(format!(
11                    "incorrect name, expected: {}, actual: {}",
12                    $name, name
13                )));
14            }
15            let (input, value) = util::parse_u64(input).ok_or(Error::from("cannot read value"))?;
16            let input = util::consume_space(input);
17            if !input.is_empty() {
18                return Err(Error::from("trailing content"));
19            }
20            Ok(value)
21        }
22    };
23}
24
25/// The stats from `/proc/stat`.
26#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
27pub struct Stat {
28    /// Total stats, sum of all cpus.
29    pub cpu_totals: StatCpu,
30    /// For each cpu, the number of *units* spent in different contexts.
31    pub cpus: Vec<StatCpu>,
32    /// Number of context switches since the system booted.
33    pub context_switches: u64,
34    /// Timestamp (in seconds since epoch) that system booted.
35    pub boot_time: u64,
36    /// The total number of processes and threads created since system booted.
37    pub processes: u64,
38    /// The total number of processes running on the cpu.
39    pub procs_running: u64,
40    /// The total number of processes waiting to run on the cpu.
41    pub procs_blocked: u64,
42    // todo `softirq`
43}
44
45impl Stat {
46    const PATH: &'static str = "/proc/stat";
47
48    /// Parse the contents of `/proc/stat`.
49    pub fn from_system() -> io::Result<Self> {
50        Stat::from_reader(File::open(Self::PATH)?)
51    }
52
53    fn from_reader(reader: impl io::Read) -> io::Result<Self> {
54        let mut reader = util::LineParser::new(reader);
55        let cpu_totals = reader.parse_line(StatCpu::from_str)?;
56        let mut cpus = Vec::new();
57        loop {
58            if let Ok(cpu_info) = reader.parse_line(StatCpu::from_str) {
59                cpus.push(cpu_info);
60            } else {
61                break;
62            }
63        }
64        reader.parse_line(util::parse_dummy)?;
65        let context_switches = reader.parse_line(parse_single!("ctxt"))?;
66        let boot_time = reader.parse_line(parse_single!("btime"))?;
67        let processes = reader.parse_line(parse_single!("processes"))?;
68        let procs_running = reader.parse_line(parse_single!("procs_running"))?;
69        let procs_blocked = reader.parse_line(parse_single!("procs_blocked"))?;
70        // todo softirq
71        Ok(Stat {
72            cpu_totals,
73            cpus,
74            context_switches,
75            boot_time,
76            processes,
77            procs_running,
78            procs_blocked,
79        })
80    }
81}
82
83/// Info about the number of *units* in the various cpu contexts.
84///
85/// *units* could be anything, for example cpu cycles, or hundredths of a second. The numbers only
86/// really make sense as a proportion of the total.
87#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
88pub struct StatCpu {
89    pub user: u64,
90    pub nice: u64,
91    pub system: u64,
92    pub idle: u64,
93    pub iowait: u64,
94    pub irq: u64,
95    pub softirq: u64,
96    pub steal: Option<u64>,
97    pub guest: Option<u64>,
98    pub guest_nice: Option<u64>,
99}
100
101macro_rules! err_msg {
102    ($inner:expr, $msg:expr) => {
103        $inner.ok_or_else(|| Error::from($msg))
104    };
105}
106
107impl StatCpu {
108    fn from_str(input: &str) -> Result<StatCpu, Error> {
109        let (input, cpunum) = err_msg!(util::parse_token(input), "first token")?;
110        if !cpunum.starts_with("cpu") {
111            return Err("starts with cpu<x>".into());
112        }
113
114        let (input, user) = err_msg!(util::parse_u64(input), "user")?;
115        let (input, nice) = err_msg!(util::parse_u64(input), "nice")?;
116        let (input, system) = err_msg!(util::parse_u64(input), "system")?;
117        let (input, idle) = err_msg!(util::parse_u64(input), "idle")?;
118        let (input, iowait) = err_msg!(util::parse_u64(input), "iowait")?;
119        let (input, irq) = err_msg!(util::parse_u64(input), "irq")?;
120        let (input, softirq) = err_msg!(util::parse_u64(input), "softirq")?;
121        // Following are optional fields
122        let (input, steal) = match util::parse_u64(input) {
123            Some((i, steal)) => (i, Some(steal)),
124            None => (input, None),
125        };
126        let (input, guest) = match util::parse_u64(input) {
127            Some((i, guest)) => (i, Some(guest)),
128            None => (input, None),
129        };
130        let (_, guest_nice) = match util::parse_u64(input) {
131            Some((i, guest_nice)) => (i, Some(guest_nice)),
132            None => (input, None),
133        };
134        // We don't check remaining content as future linux may add extra columns.
135        Ok(StatCpu {
136            user,
137            nice,
138            system,
139            idle,
140            iowait,
141            irq,
142            softirq,
143            steal,
144            guest,
145            guest_nice,
146        })
147    }
148
149    /// Convenience function to add up all cpu values.
150    pub fn total(&self) -> u64 {
151        self.user
152            .checked_add(self.nice)
153            .unwrap()
154            .checked_add(self.system)
155            .unwrap()
156            .checked_add(self.idle)
157            .unwrap()
158            .checked_add(self.iowait)
159            .unwrap()
160            .checked_add(self.irq)
161            .unwrap()
162            .checked_add(self.softirq)
163            .unwrap()
164            .checked_add(self.steal.unwrap_or(0))
165            .unwrap()
166            .checked_add(self.guest.unwrap_or(0))
167            .unwrap()
168            .checked_add(self.guest_nice.unwrap_or(0))
169            .unwrap()
170    }
171}
172
173#[test]
174fn test_stat() {
175    let raw = "\
176cpu  17501 2 6293 8212469 20141 1955 805 0 0 0
177cpu0 4713 0 1720 2049410 8036 260 255 0 0 0
178cpu1 3866 0 1325 2054893 3673 928 307 0 0 0
179cpu2 4966 1 1988 2051243 5596 516 141 0 0 0
180cpu3 3955 0 1258 2056922 2835 250 100 0 0 0
181intr 1015182 8 8252 0 0 0 0 0 0 1 113449 0 0 198907 0 0 0 18494 0 0 1 0 0 0 29 22 7171 46413 13 0 413 167 528 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
182ctxt 2238717
183btime 1535128607
184processes 2453
185procs_running 1
186procs_blocked 0
187softirq 4257581 64 299604 69 2986 36581 0 3497229 283111 0 137937
188";
189    let _stat = Stat::from_reader(io::Cursor::new(raw)).unwrap();
190}