1use 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#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
27pub struct Stat {
28 pub cpu_totals: StatCpu,
30 pub cpus: Vec<StatCpu>,
32 pub context_switches: u64,
34 pub boot_time: u64,
36 pub processes: u64,
38 pub procs_running: u64,
40 pub procs_blocked: u64,
42 }
44
45impl Stat {
46 const PATH: &'static str = "/proc/stat";
47
48 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 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#[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 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 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 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}