1use nix::unistd::{sysconf, SysconfVar};
55use std::fs::read_to_string;
56use crate::ProcSysParserError;
57use log::warn;
58
59
60#[derive(Debug, PartialEq, Default)]
62pub struct CpuStat {
63 pub name: String,
65 pub user: u64,
67 pub nice: u64,
69 pub system: u64,
71 pub idle: u64,
73 pub iowait: Option<u64>,
75 pub irq: Option<u64>,
77 pub softirq: Option<u64>,
79 pub steal: Option<u64>,
82 pub guest: Option<u64>,
85 pub guest_nice: Option<u64>,
88}
89
90#[derive(Default)]
92pub struct Builder {
93 pub proc_path : String,
94 pub proc_file : String,
95}
96
97impl Builder {
98 pub fn new() -> Builder {
99 Builder {
100 proc_path: "/proc".to_string(),
101 proc_file: "stat".to_string(),
102 }
103 }
104
105 pub fn path(mut self, proc_path: &str) -> Builder {
106 self.proc_path = proc_path.to_string();
107 self
108 }
109 pub fn file(mut self, proc_file: &str) -> Builder {
110 self.proc_file = proc_file.to_string();
111 self
112 }
113 pub fn read(self) -> Result<ProcStat, ProcSysParserError> {
114 ProcStat::read_proc_stat(format!("{}/{}", &self.proc_path, &self.proc_file).as_str())
115 }
116}
117
118pub fn read() -> Result<ProcStat, ProcSysParserError> {
121 Builder::new().read()
122}
123
124#[derive(Debug, PartialEq, Default)]
126pub struct ProcStat {
127 pub cpu_total: CpuStat,
128 pub cpu_individual: Vec<CpuStat>,
129 pub interrupts: Vec<u64>,
130 pub context_switches: u64,
131 pub boot_time: u64,
132 pub processes: u64,
133 pub processes_running: u64,
134 pub processes_blocked: u64,
135 pub softirq: Vec<u64>,
136}
137
138impl ProcStat {
139 pub fn new() -> ProcStat {
140 ProcStat::default()
141 }
142 pub fn parse_proc_stat_output(proc_stat: &str,) -> Result<ProcStat, ProcSysParserError> {
143 let mut procstat = ProcStat::new();
144 for line in proc_stat.lines() {
145 match line {
146 line if line.starts_with("cpu ") => {
147 procstat.cpu_total = CpuStat::generate_cpu_times(line)?;
148 },
149 line if line.starts_with("cpu") && line.chars().nth(3) != Some(' ') => {
150 procstat.cpu_individual.push(CpuStat::generate_cpu_times(line)?);
151 },
152 line if line.starts_with("intr ") => {
153 procstat.interrupts = ProcStat::generate_number_vector(line)?;
154 },
155 line if line.starts_with("ctxt ") => {
156 procstat.context_switches = ProcStat::generate_number_unsigned(line)?;
157 },
158 line if line.starts_with("btime ") => {
159 procstat.boot_time = ProcStat::generate_number_unsigned(line)?;
160 },
161 line if line.starts_with("processes ") => {
162 procstat.processes = ProcStat::generate_number_unsigned(line)?;
163 },
164 line if line.starts_with("procs_running ") => {
165 procstat.processes_running = ProcStat::generate_number_unsigned(line)?;
166 },
167 line if line.starts_with("procs_blocked ") => {
168 procstat.processes_blocked = ProcStat::generate_number_unsigned(line)?;
169 },
170 line if line.starts_with("softirq ") => {
171 procstat.softirq = ProcStat::generate_number_vector(line)?;
172 },
173 _ => warn!("stat: unknown entry found: {}", line),
174 }
175 }
176 Ok(procstat)
177 }
178 fn generate_number_vector(proc_stat_line: &str) -> Result<Vec<u64>, ProcSysParserError> {
179 proc_stat_line.split_whitespace()
180 .skip(1)
181 .map(|row| row.parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError))
182 .collect::<Vec<_>>()
183 .into_iter()
184 .collect::<Result<Vec<_>, _>>()
185 }
186 fn generate_number_unsigned(proc_stat_line: &str) -> Result<u64, ProcSysParserError> {
187 proc_stat_line.split_whitespace()
188 .nth(1)
189 .ok_or(ProcSysParserError::IteratorItemError {item: "stat generate_number_unsigned".to_string() })?
190 .parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)
191 }
192 pub fn read_proc_stat(proc_stat_file: &str) -> Result<ProcStat, ProcSysParserError> {
193 let proc_stat_output = read_to_string(proc_stat_file)
194 .map_err(|error| ProcSysParserError::FileReadError { file: proc_stat_file.to_string(), error })?;
195 ProcStat::parse_proc_stat_output(&proc_stat_output)
196 }
197}
198
199impl CpuStat {
200 pub fn generate_cpu_times(proc_stat_cpu_line: &str) -> Result<CpuStat, ProcSysParserError> {
201 let clock_time = sysconf(SysconfVar::CLK_TCK).unwrap_or(Some(100)).unwrap_or(100) as u64;
204
205 let parse_next_and_conversion_into_option_milliseconds = |result: Option<&str>, clock_time: u64 | -> Option<u64> {
206 match result {
207 None => None,
208 Some(value) => {
209 match value.parse::<u64>() {
210 Err(_) => None,
211 Ok(number) => Some((number*1000_u64)/clock_time),
212 }
213 },
214 }
215 };
216
217 let mut splitted = proc_stat_cpu_line.split_whitespace();
218 Ok(CpuStat {
219 name: splitted.next()
220 .ok_or(ProcSysParserError::IteratorItemError {item: "stat generate_cpu_times name".to_string() })?
221 .to_string(),
222 user: ((splitted.next()
223 .ok_or(ProcSysParserError::IteratorItemError {item: "stat generate_cpu_times user".to_string() })?
224 .parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)? *1000_u64)/clock_time),
225 nice: ((splitted.next()
226 .ok_or(ProcSysParserError::IteratorItemError {item: "stat generate_cpu_times nice".to_string() })?
227 .parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)? *1000_u64)/clock_time),
228 system: ((splitted.next()
229 .ok_or(ProcSysParserError::IteratorItemError {item: "stat generate_cpu_times system".to_string() })?
230 .parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)? *1000_u64)/clock_time),
231 idle: ((splitted.next()
232 .ok_or(ProcSysParserError::IteratorItemError {item: "stat generate_cpu_times idle".to_string() })?
233 .parse::<u64>().map_err(ProcSysParserError::ParseToIntegerError)? *1000_u64)/clock_time),
234 iowait: parse_next_and_conversion_into_option_milliseconds(splitted.next(), clock_time),
235 irq: parse_next_and_conversion_into_option_milliseconds(splitted.next(), clock_time),
236 softirq: parse_next_and_conversion_into_option_milliseconds(splitted.next(), clock_time),
237 steal: parse_next_and_conversion_into_option_milliseconds(splitted.next(), clock_time),
238 guest: parse_next_and_conversion_into_option_milliseconds(splitted.next(), clock_time),
239 guest_nice: parse_next_and_conversion_into_option_milliseconds(splitted.next(), clock_time),
240 })
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use std::fs::{write, create_dir_all, remove_dir_all};
247 use rand::{thread_rng, Rng};
248 use rand::distributions::Alphanumeric;
249 use super::*;
250
251 #[test]
256 fn parse_cpu_line() {
257 let cpu_line = "cpu 101521 47 66467 43586274 7651 0 1367 0 0 0";
258 let result = CpuStat::generate_cpu_times(&cpu_line).unwrap();
259 assert_eq!(result, CpuStat { name:"cpu".to_string(), user:1015210, nice:470, system:664670, idle:435862740, iowait:Some(76510), irq:Some(0), softirq:Some(13670), steal:Some(0), guest:Some(0), guest_nice:Some(0) });
260 }
261
262 #[test]
265 fn parse_cpu_line_with_less_statistics() {
266 let cpu_line = "cpu 101521 47 66467 43586274";
267 let result = CpuStat::generate_cpu_times(&cpu_line).unwrap();
268 assert_eq!(result, CpuStat { name:"cpu".to_string(), user:1015210, nice:470, system:664670, idle:435862740, iowait:None, irq:None, softirq:None, steal:None, guest:None, guest_nice:None });
269 }
270
271
272 #[test]
273 fn parse_interrupt_line() {
274 let interrupt_line = "intr 21965856 0 520030 7300523 0 0 0 2 0 0 0 12267292 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 644 0 0 0 0 0 2 0 77822 81889 80164 70697 68349 79207 0 0 0 6172 6117 6131 5983 6483 6062 0 588204 437602 0 0 1202 0 0 0 0 0 0 0 0 0 0 0 355279 0 0";
275 let result = ProcStat::generate_number_vector(&interrupt_line).unwrap();
276 assert_eq!(result, vec![21965856, 0, 520030, 7300523, 0, 0, 0, 2, 0, 0, 0, 12267292, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 644, 0, 0, 0, 0, 0, 2, 0, 77822, 81889, 80164, 70697, 68349, 79207, 0, 0, 0, 6172, 6117, 6131, 5983, 6483, 6062, 0, 588204, 437602, 0, 0, 1202, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 355279, 0, 0]);
277 }
278
279 #[test]
280 fn parse_context_switches_line() {
281 let context_switches_line = "ctxt 36432936";
282 let result = ProcStat::generate_number_unsigned(context_switches_line).unwrap();
283 assert_eq!(result, 36432936);
284
285 }
286
287 #[test]
288 fn parse_full_proc_stat_file() {
289 let proc_stat = "cpu 101521 47 66467 43586274 7651 0 1367 0 0 0
290cpu0 16298 0 11590 7259262 1213 0 846 0 0 0
291cpu1 16272 0 11291 7265615 1289 0 110 0 0 0
292cpu2 16121 47 10986 7266358 1251 0 111 0 0 0
293cpu3 17786 0 11023 7264715 1350 0 116 0 0 0
294cpu4 17426 0 10736 7265491 1195 0 79 0 0 0
295cpu5 17616 0 10840 7264832 1351 0 103 0 0 0
296intr 21965856 0 520030 7300523 0 0 0 2 0 0 0 12267292 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 644 0 0 0 0 0 2 0 77822 81889 80164 70697 68349 79207 0 0 0 6172 6117 6131 5983 6483 6062 0 588204 437602 0 0 1202 0 0 0 0 0 0 0 0 0 0 0 355279 0 0
297ctxt 36432936
298btime 1701783048
299processes 345159
300procs_running 1
301procs_blocked 0
302softirq 7616206 32 1416021 213 1102885 11 0 1409 2270709 0 2824926";
303 let result = ProcStat::parse_proc_stat_output(proc_stat).unwrap();
304 assert_eq!(result, ProcStat { cpu_total: CpuStat { name: "cpu".to_string(), user: 1015210, nice: 470, system: 664670, idle: 435862740, iowait: Some(76510), irq: Some(0), softirq: Some(13670), steal: Some(0), guest: Some(0), guest_nice: Some(0) },
305 cpu_individual: vec![CpuStat { name: "cpu0".to_string(), user: 162980, nice: 0, system: 115900, idle: 72592620, iowait: Some(12130), irq: Some(0), softirq: Some(8460), steal: Some(0), guest: Some(0), guest_nice: Some(0) },
306 CpuStat { name: "cpu1".to_string(), user: 162720, nice: 0, system: 112910, idle: 72656150, iowait: Some(12890), irq: Some(0), softirq: Some(1100), steal: Some(0), guest: Some(0), guest_nice: Some(0) },
307 CpuStat { name: "cpu2".to_string(), user: 161210, nice: 470, system: 109860, idle: 72663580, iowait: Some(12510), irq: Some(0), softirq: Some(1110), steal: Some(0), guest: Some(0), guest_nice: Some(0) },
308 CpuStat { name: "cpu3".to_string(), user: 177860, nice: 0, system: 110230, idle: 72647150, iowait: Some(13500), irq: Some(0), softirq: Some(1160), steal: Some(0), guest: Some(0), guest_nice: Some(0) },
309 CpuStat { name: "cpu4".to_string(), user: 174260, nice: 0, system: 107360, idle: 72654910, iowait: Some(11950), irq: Some(0), softirq: Some(790), steal: Some(0), guest: Some(0), guest_nice: Some(0) },
310 CpuStat { name: "cpu5".to_string(), user: 176160, nice: 0, system: 108400, idle: 72648320, iowait: Some(13510), irq: Some(0), softirq: Some(1030), steal: Some(0), guest: Some(0), guest_nice: Some(0) }],
311 interrupts: vec![21965856, 0, 520030, 7300523, 0, 0, 0, 2, 0, 0, 0, 12267292, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 644, 0, 0, 0, 0, 0, 2, 0, 77822, 81889, 80164, 70697, 68349, 79207, 0, 0, 0, 6172, 6117, 6131, 5983, 6483, 6062, 0, 588204, 437602, 0, 0, 1202, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 355279, 0, 0],
312 context_switches: 36432936,
313 boot_time: 1701783048,
314 processes: 345159,
315 processes_running: 1,
316 processes_blocked: 0,
317 softirq: vec![7616206, 32, 1416021, 213, 1102885, 11, 0, 1409, 2270709, 0, 2824926],
318 });
319 }
320
321 #[test]
322 fn create_proc_stat_file_and_read()
323 {
324 let proc_stat = "cpu 1 1 1 1 1 0 1 0 0 0
325cpu0 1 1 1 1 1 0 1 0 0 0
326intr 100 0 1 1
327ctxt 100
328btime 100
329processes 10
330procs_running 1
331procs_blocked 0
332softirq 100 0 1 1";
333 let directory_suffix: String = thread_rng().sample_iter(&Alphanumeric).take(8).map(char::from).collect();
334 let test_path = format!("/tmp/test.{}", directory_suffix);
335 create_dir_all(test_path.clone()).expect("Error creating mock sysfs directories.");
336
337 write(format!("{}/stat", test_path), proc_stat).expect(format!("Error writing to {}/stat", test_path).as_str());
338 let result = Builder::new().path(&test_path).read().unwrap();
339 remove_dir_all(test_path).unwrap();
340
341 assert_eq!(result, ProcStat { cpu_total: CpuStat { name: "cpu".to_string(), user: 10, nice: 10, system: 10, idle: 10, iowait: Some(10), irq: Some(0), softirq: Some(10), steal: Some(0), guest: Some(0), guest_nice: Some(0) },
342 cpu_individual: vec![CpuStat { name: "cpu0".to_string(),user: 10, nice: 10, system: 10, idle: 10, iowait: Some(10), irq: Some(0), softirq: Some(10), steal: Some(0), guest: Some(0), guest_nice: Some(0) }],
343 interrupts: vec![100, 0, 1, 1],
344 context_switches: 100,
345 boot_time: 100,
346 processes: 10,
347 processes_running: 1,
348 processes_blocked: 0,
349 softirq: vec![100, 0, 1, 1],
350 });
351 }
352}