diff options
author | Clement Tsang <34804052+ClementTsang@users.noreply.github.com> | 2021-12-21 15:17:30 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-21 18:17:30 -0500 |
commit | d32a74ec7e5eb283a78bac0d8c56c115c193cdce (patch) | |
tree | 2f4f6f42fc85c81645fdc8d81a7e2522b906f2ff | |
parent | d6a112bee48c938db9b070d461e365f226b275c9 (diff) |
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.
-rw-r--r-- | CHANGELOG.md | 6 | ||||
-rw-r--r-- | src/app/data_harvester/processes/linux.rs | 92 |
2 files changed, 74 insertions, 24 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a0aadae..57e86637 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.6.6]/[0.7.0] - Unreleased +## [0.6.6] - Unreleased + +## Bug Fixes + +- [#637](https://github.com/ClementTsang/bottom/pull/637): Fix process CPU calculation if /proc/stat CPU line has less values than expected ## [0.6.5] - 2021-12-19 diff --git a/src/app/data_harvester/processes/linux.rs b/src/app/data_harvester/processes/linux.rs index d7d621dc..53c93e05 100644 --- a/src/app/data_harvester/processes/linux.rs +++ b/src/app/data_harvester/processes/linux.rs @@ -36,6 +36,30 @@ impl PrevProcDetails { } } +fn calculate_idle_values(line: String) -> (f64, f64) { + /// Converts a `Option<&str>` value to an f64. If it fails to parse or is `None`, then it will return `0_f64`. + fn str_to_f64(val: Option<&str>) -> f64 { + val.and_then(|v| v.parse::<f64>().ok()).unwrap_or(0_f64) + } + + let mut val = line.split_whitespace(); + let user = str_to_f64(val.next()); + let nice: f64 = str_to_f64(val.next()); + let system: f64 = str_to_f64(val.next()); + let idle: f64 = str_to_f64(val.next()); + let iowait: f64 = str_to_f64(val.next()); + let irq: f64 = str_to_f64(val.next()); + let softirq: f64 = str_to_f64(val.next()); + let steal: f64 = str_to_f64(val.next()); + + // Note we do not get guest/guest_nice, as they are calculated as part of user/nice respectively + + let idle = idle + iowait; + let non_idle = user + nice + system + irq + softirq + steal; + + (idle, non_idle) +} + fn cpu_usage_calculation( prev_idle: &mut f64, prev_non_idle: &mut f64, ) -> error::Result<(f64, f64)> { @@ -43,33 +67,11 @@ fn cpu_usage_calculation( use std::io::BufReader; // From SO answer: https://stackoverflow.com/a/23376195 - let mut reader = BufReader::new(std::fs::File::open("/proc/stat")?); let mut first_line = String::new(); reader.read_line(&mut first_line)?; - let val = first_line.split_whitespace().collect::<Vec<&str>>(); - - // SC in case that the parsing will fail due to length: - if val.len() <= 10 { - return Err(error::BottomError::InvalidIo(format!( - "CPU parsing will fail due to too short of a return value; saw {} values, expected 10 values.", - val.len() - ))); - } - - let user: f64 = val[1].parse::<_>().unwrap_or(0_f64); - let nice: f64 = val[2].parse::<_>().unwrap_or(0_f64); - let system: f64 = val[3].parse::<_>().unwrap_or(0_f64); - let idle: f64 = val[4].parse::<_>().unwrap_or(0_f64); - let iowait: f64 = val[5].parse::<_>().unwrap_or(0_f64); - let irq: f64 = val[6].parse::<_>().unwrap_or(0_f64); - let softirq: f64 = val[7].parse::<_>().unwrap_or(0_f64); - let steal: f64 = val[8].parse::<_>().unwrap_or(0_f64); - let guest: f64 = val[9].parse::<_>().unwrap_or(0_f64); - - let idle = idle + iowait; - let non_idle = user + nice + system + irq + softirq + steal + guest; + let (idle, non_idle) = calculate_idle_values(first_line); let total = idle + non_idle; let prev_total = *prev_idle + *prev_non_idle; @@ -294,3 +296,47 @@ pub fn get_process_data( )) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_proc_cpu_parse() { + assert_eq!( + (100_f64, 200_f64), + calculate_idle_values("100 0 100 100".to_string()), + "Failed to properly calculate idle/non-idle for /proc/stat CPU with 4 values" + ); + assert_eq!( + (120_f64, 200_f64), + calculate_idle_values("100 0 100 100 20".to_string()), + "Failed to properly calculate idle/non-idle for /proc/stat CPU with 5 values" + ); + assert_eq!( + (120_f64, 230_f64), + calculate_idle_values("100 0 100 100 20 30".to_string()), + "Failed to properly calculate idle/non-idle for /proc/stat CPU with 6 values" + ); + assert_eq!( + (120_f64, 270_f64), + calculate_idle_values("100 0 100 100 20 30 40".to_string()), + "Failed to properly calculate idle/non-idle for /proc/stat CPU with 7 values" + ); + assert_eq!( + (120_f64, 320_f64), + calculate_idle_values("100 0 100 100 20 30 40 50".to_string()), + "Failed to properly calculate idle/non-idle for /proc/stat CPU with 8 values" + ); + assert_eq!( + (120_f64, 320_f64), + calculate_idle_values("100 0 100 100 20 30 40 50 100".to_string()), + "Failed to properly calculate idle/non-idle for /proc/stat CPU with 9 values" + ); + assert_eq!( + (120_f64, 320_f64), + calculate_idle_values("100 0 100 100 20 30 40 50 100 200".to_string()), + "Failed to properly calculate idle/non-idle for /proc/stat CPU with 10 values" + ); + } +} |