use super::metrics::LinuxSystemMetrics;
use crate::MetricDetail;
use crate::PowerReserveMeterProvider;
use crate::error::PwrzvResult;
use crate::sigmoid::{SigmoidFn, get_sigmoid_config};
use std::collections::HashMap;
fn get_cpu_usage_config() -> SigmoidFn {
get_sigmoid_config("PWRZV_LINUX_CPU_USAGE", 0.65, 8.0)
}
fn get_cpu_iowait_config() -> SigmoidFn {
get_sigmoid_config("PWRZV_LINUX_CPU_IOWAIT", 0.20, 20.0)
}
fn get_cpu_load_config() -> SigmoidFn {
get_sigmoid_config("PWRZV_LINUX_CPU_LOAD", 1.2, 5.0)
}
fn get_memory_usage_config() -> SigmoidFn {
get_sigmoid_config("PWRZV_LINUX_MEMORY_USAGE", 0.85, 18.0)
}
fn get_memory_pressure_config() -> SigmoidFn {
get_sigmoid_config("PWRZV_LINUX_MEMORY_PRESSURE", 0.30, 12.0)
}
fn get_disk_io_config() -> SigmoidFn {
get_sigmoid_config("PWRZV_LINUX_DISK_IO", 0.70, 10.0)
}
fn get_network_dropped_config() -> SigmoidFn {
get_sigmoid_config("PWRZV_LINUX_NETWORK_DROPPED", 0.02, 100.0)
}
fn get_fd_config() -> SigmoidFn {
get_sigmoid_config("PWRZV_LINUX_FD", 0.90, 25.0)
}
fn get_process_config() -> SigmoidFn {
get_sigmoid_config("PWRZV_LINUX_PROCESS", 0.80, 12.0)
}
#[derive(Debug, Clone)]
pub(crate) struct LinuxProvider;
impl PowerReserveMeterProvider for LinuxProvider {
async fn get_power_reserve_level(&self) -> PwrzvResult<f32> {
let metrics = LinuxSystemMetrics::collect_system_metrics().await?;
let (level, _) = Self::calculate(&metrics)?;
Ok(level)
}
async fn get_power_reserve_level_with_details(
&self,
) -> PwrzvResult<(f32, HashMap<String, MetricDetail>)> {
let metrics = LinuxSystemMetrics::collect_system_metrics().await?;
let (level, details) = Self::calculate(&metrics)?;
Ok((level, details))
}
}
impl LinuxProvider {
fn calculate(
metrics: &LinuxSystemMetrics,
) -> PwrzvResult<(f32, HashMap<String, MetricDetail>)> {
let mut details = HashMap::new();
let mut available_scores = Vec::new();
if let Some(value) = metrics.cpu_usage_ratio {
let score = get_cpu_usage_config().evaluate(value);
let n = Self::five_point_scale_with_decimal(score);
details.insert("cpu_usage".to_string(), MetricDetail { value, score: n });
available_scores.push(n);
}
if let Some(value) = metrics.cpu_io_wait_ratio {
let score = get_cpu_iowait_config().evaluate(value);
let n = Self::five_point_scale_with_decimal(score);
details.insert("cpu_io_wait".to_string(), MetricDetail { value, score: n });
available_scores.push(n);
}
if let Some(value) = metrics.cpu_load_ratio {
let score = get_cpu_load_config().evaluate(value);
let n = Self::five_point_scale_with_decimal(score);
details.insert("cpu_load".to_string(), MetricDetail { value, score: n });
available_scores.push(n);
}
if let Some(value) = metrics.memory_usage_ratio {
let score = get_memory_usage_config().evaluate(value);
let n = Self::five_point_scale_with_decimal(score);
details.insert("memory_usage".to_string(), MetricDetail { value, score: n });
available_scores.push(n);
}
if let Some(value) = metrics.memory_pressure_ratio {
let score = get_memory_pressure_config().evaluate(value);
let n = Self::five_point_scale_with_decimal(score);
details.insert(
"memory_pressure".to_string(),
MetricDetail { value, score: n },
);
available_scores.push(n);
}
if let Some(value) = metrics.disk_io_utilization {
let score = get_disk_io_config().evaluate(value);
let n = Self::five_point_scale_with_decimal(score);
details.insert(
"disk_io_utilization".to_string(),
MetricDetail { value, score: n },
);
available_scores.push(n);
}
if let Some(value) = metrics.network_dropped_packets_ratio {
let score = get_network_dropped_config().evaluate(value);
let n = Self::five_point_scale_with_decimal(score);
details.insert(
"network_dropped_packets".to_string(),
MetricDetail { value, score: n },
);
available_scores.push(n);
}
if let Some(value) = metrics.fd_usage_ratio {
let score = get_fd_config().evaluate(value);
let n = Self::five_point_scale_with_decimal(score);
details.insert(
"file_descriptors".to_string(),
MetricDetail { value, score: n },
);
available_scores.push(n);
}
if let Some(value) = metrics.process_count_ratio {
let score = get_process_config().evaluate(value);
let n = Self::five_point_scale_with_decimal(score);
details.insert(
"process_count".to_string(),
MetricDetail { value, score: n },
);
available_scores.push(n);
}
if let Some(&final_score) = available_scores
.iter()
.min_by(|a, b| a.partial_cmp(b).unwrap())
{
return Ok((final_score, details));
}
Ok((3.0, details))
}
fn five_point_scale_with_decimal(score: f32) -> f32 {
let score = 5.0 * (1.0 - score);
let factor = 10_000f32;
(score * factor).round() / factor
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_five_point_scale_with_decimal() {
assert_eq!(LinuxProvider::five_point_scale_with_decimal(0.0), 5.0000); assert_eq!(LinuxProvider::five_point_scale_with_decimal(0.2), 4.0000); assert_eq!(LinuxProvider::five_point_scale_with_decimal(0.4), 3.0000); assert_eq!(LinuxProvider::five_point_scale_with_decimal(0.6), 2.0000); assert_eq!(LinuxProvider::five_point_scale_with_decimal(0.8), 1.0000); assert_eq!(LinuxProvider::five_point_scale_with_decimal(1.0), 0.0000);
let score = LinuxProvider::five_point_scale_with_decimal(0.1234);
assert!((score - 4.3830).abs() < 0.0001); }
#[test]
fn test_calculate_with_full_metrics() {
let metrics = LinuxSystemMetrics {
cpu_usage_ratio: Some(0.5),
cpu_io_wait_ratio: Some(0.1),
cpu_load_ratio: Some(0.8),
memory_usage_ratio: Some(0.7),
memory_pressure_ratio: Some(0.2),
disk_io_utilization: Some(0.6),
network_dropped_packets_ratio: Some(0.01),
fd_usage_ratio: Some(0.3),
process_count_ratio: Some(0.4),
};
let result = LinuxProvider::calculate(&metrics);
assert!(result.is_ok());
let (level, details) = result.unwrap();
assert!(!details.is_empty());
for detail in details.values() {
assert!(
detail.score >= 0.0 && detail.score <= 5.0,
"Score {} should be in range [0.0, 5.0]",
detail.score
);
}
assert!((0.0..=5.0).contains(&level));
}
#[test]
fn test_calculate_with_no_metrics() {
let metrics = LinuxSystemMetrics {
cpu_usage_ratio: None,
cpu_io_wait_ratio: None,
cpu_load_ratio: None,
memory_usage_ratio: None,
memory_pressure_ratio: None,
disk_io_utilization: None,
network_dropped_packets_ratio: None,
fd_usage_ratio: None,
process_count_ratio: None,
};
let result = LinuxProvider::calculate(&metrics);
assert!(result.is_ok());
let (level, details) = result.unwrap();
assert_eq!(
level, 3.0,
"Should default to 3.0 level when no metrics available"
);
assert!(details.is_empty(), "Should have no detailed scores");
}
#[test]
fn test_calculate_with_partial_metrics() {
let metrics = LinuxSystemMetrics {
cpu_usage_ratio: Some(0.3),
cpu_io_wait_ratio: None,
cpu_load_ratio: Some(0.5),
memory_usage_ratio: None,
memory_pressure_ratio: Some(0.1),
disk_io_utilization: None,
network_dropped_packets_ratio: None,
fd_usage_ratio: None,
process_count_ratio: None,
};
let result = LinuxProvider::calculate(&metrics);
assert!(result.is_ok());
let (level, details) = result.unwrap();
assert_eq!(details.len(), 3);
for detail in details.values() {
assert!(
detail.score >= 0.0 && detail.score <= 5.0,
"Score {} should be in range [0.0, 5.0]",
detail.score
);
}
assert!((0.0..=5.0).contains(&level));
}
#[test]
fn test_calculate_extreme_values() {
let metrics = LinuxSystemMetrics {
cpu_usage_ratio: Some(0.95), cpu_io_wait_ratio: Some(0.8), cpu_load_ratio: Some(3.0), memory_usage_ratio: Some(0.98), memory_pressure_ratio: Some(0.9), disk_io_utilization: Some(0.99), network_dropped_packets_ratio: Some(0.1), fd_usage_ratio: Some(0.95), process_count_ratio: Some(0.9), };
let result = LinuxProvider::calculate(&metrics);
assert!(result.is_ok());
let (level, details) = result.unwrap();
assert!(
level <= 2.0,
"High load should result in low power reserve (taking minimum score)"
);
let low_scores = details.values().filter(|d| d.score <= 2.0).count();
assert!(
low_scores > 0,
"Should have some low scores with high system load"
);
}
#[test]
fn test_calculate_low_values() {
let metrics = LinuxSystemMetrics {
cpu_usage_ratio: Some(0.05), cpu_io_wait_ratio: Some(0.01), cpu_load_ratio: Some(0.1), memory_usage_ratio: Some(0.1), memory_pressure_ratio: Some(0.01), disk_io_utilization: Some(0.05), network_dropped_packets_ratio: Some(0.001), fd_usage_ratio: Some(0.1), process_count_ratio: Some(0.1), };
let result = LinuxProvider::calculate(&metrics);
assert!(result.is_ok());
let (level, details) = result.unwrap();
assert!(level >= 4.0, "Low load should result in high power reserve");
let high_scores = details.values().filter(|d| d.score >= 4.0).count();
assert!(
high_scores > 0,
"Should have some high scores with low system load"
);
}
}