use std::path::Path;
use std::time::Instant;
#[derive(Debug, Clone)]
pub struct ContainerStats {
pub cpu_usage_usec: u64,
pub memory_bytes: u64,
pub memory_limit: u64,
pub timestamp: Instant,
}
impl ContainerStats {
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn memory_percent(&self) -> f64 {
if self.memory_limit == u64::MAX || self.memory_limit == 0 {
0.0
} else {
(self.memory_bytes as f64 / self.memory_limit as f64) * 100.0
}
}
}
pub async fn read_container_stats(cgroup_path: &Path) -> std::io::Result<ContainerStats> {
let cpu_stat_path = cgroup_path.join("cpu.stat");
let cpu_stat = tokio::fs::read_to_string(&cpu_stat_path).await?;
let memory_current_path = cgroup_path.join("memory.current");
let memory_current = tokio::fs::read_to_string(&memory_current_path).await?;
let memory_max_path = cgroup_path.join("memory.max");
let memory_max = tokio::fs::read_to_string(&memory_max_path).await?;
let cpu_usage_usec = cpu_stat
.lines()
.find(|line| line.starts_with("usage_usec"))
.and_then(|line| line.split_whitespace().nth(1))
.and_then(|value| value.parse::<u64>().ok())
.unwrap_or(0);
let memory_bytes = memory_current.trim().parse::<u64>().unwrap_or(0);
let memory_limit = memory_max.trim().parse::<u64>().unwrap_or(u64::MAX);
Ok(ContainerStats {
cpu_usage_usec,
memory_bytes,
memory_limit,
timestamp: Instant::now(),
})
}
#[must_use]
#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
pub fn calculate_cpu_percent(prev: &ContainerStats, curr: &ContainerStats) -> f64 {
let usage_delta_usec = curr.cpu_usage_usec.saturating_sub(prev.cpu_usage_usec);
let time_delta = curr.timestamp.duration_since(prev.timestamp);
let time_delta_usec = time_delta.as_micros() as u64;
if time_delta_usec == 0 {
return 0.0;
}
let num_cpus = num_cpus::get() as u64;
(usage_delta_usec as f64 / (time_delta_usec * num_cpus) as f64) * 100.0
}
#[must_use]
#[allow(clippy::cast_precision_loss, clippy::cast_possible_truncation)]
pub fn calculate_cpu_percent_with_cpus(
prev: &ContainerStats,
curr: &ContainerStats,
num_cpus: u64,
) -> f64 {
let usage_delta_usec = curr.cpu_usage_usec.saturating_sub(prev.cpu_usage_usec);
let time_delta = curr.timestamp.duration_since(prev.timestamp);
let time_delta_usec = time_delta.as_micros() as u64;
if time_delta_usec == 0 || num_cpus == 0 {
return 0.0;
}
(usage_delta_usec as f64 / (time_delta_usec * num_cpus) as f64) * 100.0
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn test_memory_percent() {
let stats = ContainerStats {
cpu_usage_usec: 0,
memory_bytes: 512,
memory_limit: 1024,
timestamp: Instant::now(),
};
assert!((stats.memory_percent() - 50.0).abs() < 0.01);
}
#[test]
fn test_memory_percent_unlimited() {
let stats = ContainerStats {
cpu_usage_usec: 0,
memory_bytes: 512,
memory_limit: u64::MAX,
timestamp: Instant::now(),
};
assert!(
(stats.memory_percent() - 0.0).abs() < f64::EPSILON,
"memory_percent should be 0.0"
);
}
#[test]
fn test_memory_percent_zero_limit() {
let stats = ContainerStats {
cpu_usage_usec: 0,
memory_bytes: 512,
memory_limit: 0,
timestamp: Instant::now(),
};
assert!(
(stats.memory_percent() - 0.0).abs() < f64::EPSILON,
"memory_percent should be 0.0"
);
}
#[test]
fn test_calculate_cpu_percent_with_cpus() {
let now = Instant::now();
let prev = ContainerStats {
cpu_usage_usec: 1_000_000, memory_bytes: 1024,
memory_limit: 2048,
timestamp: now,
};
let later = now + Duration::from_secs(1);
let curr = ContainerStats {
cpu_usage_usec: 1_500_000, memory_bytes: 1024,
memory_limit: 2048,
timestamp: later,
};
let cpu_pct = calculate_cpu_percent_with_cpus(&prev, &curr, 1);
assert!((cpu_pct - 50.0).abs() < 1.0);
let cpu_pct_2 = calculate_cpu_percent_with_cpus(&prev, &curr, 2);
assert!((cpu_pct_2 - 25.0).abs() < 1.0);
}
#[test]
fn test_calculate_cpu_percent_zero_time() {
let now = Instant::now();
let stats = ContainerStats {
cpu_usage_usec: 1_000_000,
memory_bytes: 1024,
memory_limit: 2048,
timestamp: now,
};
let cpu_pct = calculate_cpu_percent_with_cpus(&stats, &stats, 1);
assert!(
(cpu_pct - 0.0).abs() < f64::EPSILON,
"cpu_pct should be 0.0"
);
}
#[test]
fn test_calculate_cpu_percent_zero_cpus() {
let now = Instant::now();
let prev = ContainerStats {
cpu_usage_usec: 1_000_000,
memory_bytes: 1024,
memory_limit: 2048,
timestamp: now,
};
let later = now + Duration::from_secs(1);
let curr = ContainerStats {
cpu_usage_usec: 1_500_000,
memory_bytes: 1024,
memory_limit: 2048,
timestamp: later,
};
let cpu_pct = calculate_cpu_percent_with_cpus(&prev, &curr, 0);
assert!(
(cpu_pct - 0.0).abs() < f64::EPSILON,
"cpu_pct should be 0.0"
);
}
#[test]
fn test_stats_clone() {
let stats = ContainerStats {
cpu_usage_usec: 1000,
memory_bytes: 2000,
memory_limit: 4000,
timestamp: Instant::now(),
};
let cloned = stats.clone();
assert_eq!(cloned.cpu_usage_usec, stats.cpu_usage_usec);
assert_eq!(cloned.memory_bytes, stats.memory_bytes);
assert_eq!(cloned.memory_limit, stats.memory_limit);
}
}