Merge branch 'fix/cpu_usage_overflow' into dev

This commit is contained in:
Jack Wills
2024-11-15 15:49:07 +00:00
+217 -80
View File
@@ -71,24 +71,30 @@ pub struct DockerData {
impl DockerData { impl DockerData {
/// Use docker stats to calculate current cpu usage /// Use docker stats to calculate current cpu usage
#[allow(clippy::cast_precision_loss)] #[allow(clippy::cast_precision_loss)]
// TODO FIX: this can overflow
fn calculate_usage(stats: &Stats) -> f64 { fn calculate_usage(stats: &Stats) -> f64 {
let mut cpu_percentage = 0.0; let mut cpu_percentage = 0.0;
let previous_cpu = stats.precpu_stats.cpu_usage.total_usage; let cpu_delta = stats
let cpu_delta = stats.cpu_stats.cpu_usage.total_usage as f64 - previous_cpu as f64; .cpu_stats
.cpu_usage
.total_usage
.saturating_sub(stats.precpu_stats.cpu_usage.total_usage)
as f64;
if let (Some(cpu_stats_usage), Some(precpu_stats_usage)) = ( if let (Some(cpu_stats_usage), Some(precpu_stats_usage)) = (
stats.cpu_stats.system_cpu_usage, stats.cpu_stats.system_cpu_usage,
stats.precpu_stats.system_cpu_usage, stats.precpu_stats.system_cpu_usage,
) { ) {
let system_delta = (cpu_stats_usage - precpu_stats_usage) as f64; let system_delta = cpu_stats_usage.saturating_sub(precpu_stats_usage) as f64;
let online_cpus = stats.cpu_stats.online_cpus.unwrap_or_else(|| { let online_cpus = stats.cpu_stats.online_cpus.unwrap_or_else(|| {
stats u64::try_from(
.cpu_stats stats
.cpu_usage .cpu_stats
.percpu_usage .cpu_usage
.as_ref() .percpu_usage
.map_or(0, std::vec::Vec::len) as u64 .as_ref()
.map_or(0, std::vec::Vec::len),
)
.unwrap_or_default()
}) as f64; }) as f64;
if system_delta > 0.0 && cpu_delta > 0.0 { if system_delta > 0.0 && cpu_delta > 0.0 {
cpu_percentage = (cpu_delta / system_delta) * online_cpus * 100.0; cpu_percentage = (cpu_delta / system_delta) * online_cpus * 100.0;
@@ -460,19 +466,19 @@ impl DockerData {
// tests, use redis-test container, check logs exists, and selector of logs, and that it increases, and matches end, when you run restart on the docker containers // tests, use redis-test container, check logs exists, and selector of logs, and that it increases, and matches end, when you run restart on the docker containers
#[cfg(test)] #[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests { mod tests {
use bollard::container::{ use bollard::container::{
BlkioStats, CPUStats, CPUUsage, MemoryStats, PidsStats, StorageStats, ThrottlingData, BlkioStats, CPUStats, CPUUsage, MemoryStats, PidsStats, Stats, StorageStats, ThrottlingData,
}; };
use super::*; use super::*;
#[allow(clippy::too_many_lines)] fn gen_stats() -> Stats {
fn gen_stats(x: u64, y: u64) -> Stats {
Stats { Stats {
read: String::new(), read: String::new(),
preread: String::new(), preread: String::new(),
num_procs: 0, num_procs: 1,
pids_stats: PidsStats { pids_stats: PidsStats {
current: None, current: None,
limit: None, limit: None,
@@ -503,33 +509,12 @@ mod tests {
}, },
cpu_stats: CPUStats { cpu_stats: CPUStats {
cpu_usage: CPUUsage { cpu_usage: CPUUsage {
percpu_usage: Some(vec![ percpu_usage: Some(vec![50]),
291_593_800, usage_in_usermode: 10,
182_192_900, total_usage: 100,
195_048_700, usage_in_kernelmode: 20,
23_032_300,
132_928_700,
235_555_600,
120_225_700,
175_752_000,
213_060_300,
95_321_600,
226_821_000,
0,
109_151_300,
0,
86_240_200,
1_884_400,
59_077_300,
23_224_900,
95_386_300,
144_987_400,
]),
total_usage: 250_000_000,
usage_in_usermode: 1_020_000_000,
usage_in_kernelmode: 1_030_000_000,
}, },
system_cpu_usage: Some(x), system_cpu_usage: Some(400),
online_cpus: Some(1), online_cpus: Some(1),
throttling_data: ThrottlingData { throttling_data: ThrottlingData {
periods: 0, periods: 0,
@@ -539,33 +524,12 @@ mod tests {
}, },
precpu_stats: CPUStats { precpu_stats: CPUStats {
cpu_usage: CPUUsage { cpu_usage: CPUUsage {
percpu_usage: Some(vec![ percpu_usage: Some(vec![50]),
291_593_800, usage_in_usermode: 10,
182_192_900, total_usage: 100,
195_048_700, usage_in_kernelmode: 20,
23_032_300,
132_928_700,
235_555_600,
120_225_700,
175_752_000,
213_060_300,
95_321_600,
226_821_000,
0,
109_151_300,
0,
86_240_200,
1_884_400,
59_077_300,
23_224_900,
93_831_100,
144_987_400,
]),
total_usage: 200_000_000,
usage_in_usermode: 1_020_000_000,
usage_in_kernelmode: 1_020_000_000,
}, },
system_cpu_usage: Some(y), system_cpu_usage: Some(400),
online_cpus: Some(1), online_cpus: Some(1),
throttling_data: ThrottlingData { throttling_data: ThrottlingData {
periods: 0, periods: 0,
@@ -579,25 +543,198 @@ mod tests {
write_count_normalized: None, write_count_normalized: None,
write_size_bytes: None, write_size_bytes: None,
}, },
name: "/container_1".to_owned(), name: String::new(),
id: "1".to_owned(), id: String::new(),
} }
} }
#[test] #[test]
#[allow(clippy::float_cmp)] fn test_calculate_usage_50() {
/// Test the stats calculator, had to cheat here to get round input/outputs let mut stats = gen_stats();
fn test_calculate_usage_no_previous_cpu() { stats.precpu_stats = CPUStats {
let stats = gen_stats(1_000_000_000, 900_000_000); cpu_usage: CPUUsage {
let result = DockerData::calculate_usage(&stats); percpu_usage: Some(vec![50]),
assert_eq!(result, 50.0); usage_in_usermode: 10,
total_usage: 100,
usage_in_kernelmode: 20,
},
system_cpu_usage: Some(400),
online_cpus: Some(1),
throttling_data: ThrottlingData {
periods: 0,
throttled_periods: 0,
throttled_time: 0,
},
};
stats.cpu_stats = CPUStats {
cpu_usage: CPUUsage {
percpu_usage: Some(vec![150]),
usage_in_usermode: 20,
total_usage: 150,
usage_in_kernelmode: 30,
},
system_cpu_usage: Some(500),
online_cpus: Some(1),
throttling_data: ThrottlingData {
periods: 0,
throttled_periods: 0,
throttled_time: 0,
},
};
let cpu_percentage = DockerData::calculate_usage(&stats);
assert_eq!(50.0, cpu_percentage);
}
let stats = gen_stats(1_000_000_000, 800_000_000); #[test]
let result = DockerData::calculate_usage(&stats); fn test_calculate_usage_25() {
assert_eq!(result, 25.0); let mut stats = gen_stats();
stats.precpu_stats = CPUStats {
cpu_usage: CPUUsage {
percpu_usage: Some(vec![50]),
usage_in_usermode: 10,
total_usage: 100,
usage_in_kernelmode: 20,
},
system_cpu_usage: Some(400),
online_cpus: Some(1),
throttling_data: ThrottlingData {
periods: 0,
throttled_periods: 0,
throttled_time: 0,
},
};
stats.cpu_stats = CPUStats {
cpu_usage: CPUUsage {
percpu_usage: Some(vec![75]),
usage_in_usermode: 20,
total_usage: 125,
usage_in_kernelmode: 30,
},
system_cpu_usage: Some(500),
online_cpus: Some(1),
throttling_data: ThrottlingData {
periods: 0,
throttled_periods: 0,
throttled_time: 0,
},
};
let stats = gen_stats(1_000_000_000, 750_000_000); let cpu_percentage = DockerData::calculate_usage(&stats);
let result = DockerData::calculate_usage(&stats); assert_eq!(25.0, cpu_percentage);
assert_eq!(result, 20.00); }
#[test]
fn test_calculate_usage_75() {
let mut stats = gen_stats();
stats.precpu_stats = CPUStats {
cpu_usage: CPUUsage {
percpu_usage: Some(vec![50]),
usage_in_usermode: 10,
total_usage: 100,
usage_in_kernelmode: 20,
},
system_cpu_usage: Some(400),
online_cpus: Some(1),
throttling_data: ThrottlingData {
periods: 0,
throttled_periods: 0,
throttled_time: 0,
},
};
stats.cpu_stats = CPUStats {
cpu_usage: CPUUsage {
percpu_usage: Some(vec![175]),
usage_in_usermode: 20,
total_usage: 175,
usage_in_kernelmode: 30,
},
system_cpu_usage: Some(500),
online_cpus: Some(1),
throttling_data: ThrottlingData {
periods: 0,
throttled_periods: 0,
throttled_time: 0,
},
};
let cpu_percentage = DockerData::calculate_usage(&stats);
assert_eq!(75.0, cpu_percentage);
}
#[test]
fn test_calculate_usage_100() {
let mut stats = gen_stats();
stats.precpu_stats = CPUStats {
cpu_usage: CPUUsage {
percpu_usage: Some(vec![50]),
usage_in_usermode: 10,
total_usage: 100,
usage_in_kernelmode: 20,
},
system_cpu_usage: Some(400),
online_cpus: Some(1),
throttling_data: ThrottlingData {
periods: 0,
throttled_periods: 0,
throttled_time: 0,
},
};
stats.cpu_stats = CPUStats {
cpu_usage: CPUUsage {
percpu_usage: Some(vec![200]),
usage_in_usermode: 20,
total_usage: 200,
usage_in_kernelmode: 30,
},
system_cpu_usage: Some(500),
online_cpus: Some(1),
throttling_data: ThrottlingData {
periods: 0,
throttled_periods: 0,
throttled_time: 0,
},
};
let cpu_percentage = DockerData::calculate_usage(&stats);
assert_eq!(100.0, cpu_percentage);
}
#[test]
fn test_calculate_usage_175() {
let mut stats = gen_stats();
stats.precpu_stats = CPUStats {
cpu_usage: CPUUsage {
percpu_usage: Some(vec![50]),
usage_in_usermode: 10,
total_usage: 100,
usage_in_kernelmode: 20,
},
system_cpu_usage: Some(400),
online_cpus: Some(1),
throttling_data: ThrottlingData {
periods: 0,
throttled_periods: 0,
throttled_time: 0,
},
};
stats.cpu_stats = CPUStats {
cpu_usage: CPUUsage {
percpu_usage: Some(vec![275]),
usage_in_usermode: 20,
total_usage: 275,
usage_in_kernelmode: 30,
},
system_cpu_usage: Some(500),
online_cpus: Some(1),
throttling_data: ThrottlingData {
periods: 0,
throttled_periods: 0,
throttled_time: 0,
},
};
let cpu_percentage = DockerData::calculate_usage(&stats);
assert_eq!(175.0, cpu_percentage);
} }
} }