summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClement Tsang <34804052+ClementTsang@users.noreply.github.com>2021-12-21 15:17:30 -0800
committerGitHub <noreply@github.com>2021-12-21 18:17:30 -0500
commitd32a74ec7e5eb283a78bac0d8c56c115c193cdce (patch)
tree2f4f6f42fc85c81645fdc8d81a7e2522b906f2ff
parentd6a112bee48c938db9b070d461e365f226b275c9 (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.md6
-rw-r--r--src/app/data_harvester/processes/linux.rs92
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"
+ );
+ }
+}