Skip to content

Commit d32a74e

Browse files
authored
bug: Fix process CPU calculation if /proc/stat CPU line has less values than expected (#637)
Addresses a potential case where processing would fail if there were missing values from the CPU line of `/proc/stat`, and allows it to successfully return.
1 parent d6a112b commit d32a74e

File tree

2 files changed

+74
-24
lines changed

2 files changed

+74
-24
lines changed

CHANGELOG.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [0.6.6]/[0.7.0] - Unreleased
8+
## [0.6.6] - Unreleased
9+
10+
## Bug Fixes
11+
12+
- [#637](https://github.com/ClementTsang/bottom/pull/637): Fix process CPU calculation if /proc/stat CPU line has less values than expected
913

1014
## [0.6.5] - 2021-12-19
1115

src/app/data_harvester/processes/linux.rs

+69-23
Original file line numberDiff line numberDiff line change
@@ -36,40 +36,42 @@ impl PrevProcDetails {
3636
}
3737
}
3838

39+
fn calculate_idle_values(line: String) -> (f64, f64) {
40+
/// Converts a `Option<&str>` value to an f64. If it fails to parse or is `None`, then it will return `0_f64`.
41+
fn str_to_f64(val: Option<&str>) -> f64 {
42+
val.and_then(|v| v.parse::<f64>().ok()).unwrap_or(0_f64)
43+
}
44+
45+
let mut val = line.split_whitespace();
46+
let user = str_to_f64(val.next());
47+
let nice: f64 = str_to_f64(val.next());
48+
let system: f64 = str_to_f64(val.next());
49+
let idle: f64 = str_to_f64(val.next());
50+
let iowait: f64 = str_to_f64(val.next());
51+
let irq: f64 = str_to_f64(val.next());
52+
let softirq: f64 = str_to_f64(val.next());
53+
let steal: f64 = str_to_f64(val.next());
54+
55+
// Note we do not get guest/guest_nice, as they are calculated as part of user/nice respectively
56+
57+
let idle = idle + iowait;
58+
let non_idle = user + nice + system + irq + softirq + steal;
59+
60+
(idle, non_idle)
61+
}
62+
3963
fn cpu_usage_calculation(
4064
prev_idle: &mut f64, prev_non_idle: &mut f64,
4165
) -> error::Result<(f64, f64)> {
4266
use std::io::prelude::*;
4367
use std::io::BufReader;
4468

4569
// From SO answer: https://stackoverflow.com/a/23376195
46-
4770
let mut reader = BufReader::new(std::fs::File::open("/proc/stat")?);
4871
let mut first_line = String::new();
4972
reader.read_line(&mut first_line)?;
5073

51-
let val = first_line.split_whitespace().collect::<Vec<&str>>();
52-
53-
// SC in case that the parsing will fail due to length:
54-
if val.len() <= 10 {
55-
return Err(error::BottomError::InvalidIo(format!(
56-
"CPU parsing will fail due to too short of a return value; saw {} values, expected 10 values.",
57-
val.len()
58-
)));
59-
}
60-
61-
let user: f64 = val[1].parse::<_>().unwrap_or(0_f64);
62-
let nice: f64 = val[2].parse::<_>().unwrap_or(0_f64);
63-
let system: f64 = val[3].parse::<_>().unwrap_or(0_f64);
64-
let idle: f64 = val[4].parse::<_>().unwrap_or(0_f64);
65-
let iowait: f64 = val[5].parse::<_>().unwrap_or(0_f64);
66-
let irq: f64 = val[6].parse::<_>().unwrap_or(0_f64);
67-
let softirq: f64 = val[7].parse::<_>().unwrap_or(0_f64);
68-
let steal: f64 = val[8].parse::<_>().unwrap_or(0_f64);
69-
let guest: f64 = val[9].parse::<_>().unwrap_or(0_f64);
70-
71-
let idle = idle + iowait;
72-
let non_idle = user + nice + system + irq + softirq + steal + guest;
74+
let (idle, non_idle) = calculate_idle_values(first_line);
7375

7476
let total = idle + non_idle;
7577
let prev_total = *prev_idle + *prev_non_idle;
@@ -294,3 +296,47 @@ pub fn get_process_data(
294296
))
295297
}
296298
}
299+
300+
#[cfg(test)]
301+
mod tests {
302+
use super::*;
303+
304+
#[test]
305+
fn test_proc_cpu_parse() {
306+
assert_eq!(
307+
(100_f64, 200_f64),
308+
calculate_idle_values("100 0 100 100".to_string()),
309+
"Failed to properly calculate idle/non-idle for /proc/stat CPU with 4 values"
310+
);
311+
assert_eq!(
312+
(120_f64, 200_f64),
313+
calculate_idle_values("100 0 100 100 20".to_string()),
314+
"Failed to properly calculate idle/non-idle for /proc/stat CPU with 5 values"
315+
);
316+
assert_eq!(
317+
(120_f64, 230_f64),
318+
calculate_idle_values("100 0 100 100 20 30".to_string()),
319+
"Failed to properly calculate idle/non-idle for /proc/stat CPU with 6 values"
320+
);
321+
assert_eq!(
322+
(120_f64, 270_f64),
323+
calculate_idle_values("100 0 100 100 20 30 40".to_string()),
324+
"Failed to properly calculate idle/non-idle for /proc/stat CPU with 7 values"
325+
);
326+
assert_eq!(
327+
(120_f64, 320_f64),
328+
calculate_idle_values("100 0 100 100 20 30 40 50".to_string()),
329+
"Failed to properly calculate idle/non-idle for /proc/stat CPU with 8 values"
330+
);
331+
assert_eq!(
332+
(120_f64, 320_f64),
333+
calculate_idle_values("100 0 100 100 20 30 40 50 100".to_string()),
334+
"Failed to properly calculate idle/non-idle for /proc/stat CPU with 9 values"
335+
);
336+
assert_eq!(
337+
(120_f64, 320_f64),
338+
calculate_idle_values("100 0 100 100 20 30 40 50 100 200".to_string()),
339+
"Failed to properly calculate idle/non-idle for /proc/stat CPU with 10 values"
340+
);
341+
}
342+
}

0 commit comments

Comments
 (0)