syncable_cli/analyzer/k8s_optimize/rules/
k8s_opt_010.rs

1//! K8S-OPT-010: Unbalanced resource allocation for workload type.
2
3use super::{OptimizationRule, RuleContext, codes};
4use crate::analyzer::k8s_optimize::config::K8sOptimizeConfig;
5use crate::analyzer::k8s_optimize::parser::{parse_cpu_to_millicores, parse_memory_to_bytes};
6use crate::analyzer::k8s_optimize::types::{
7    OptimizationIssue, ResourceRecommendation, ResourceSpec, RuleCode, Severity, WorkloadType,
8};
9
10/// Rule: Unbalanced resource allocation.
11pub struct UnbalancedResourcesRule;
12
13impl OptimizationRule for UnbalancedResourcesRule {
14    fn code(&self) -> &'static str {
15        codes::UNBALANCED_RESOURCES
16    }
17
18    fn description(&self) -> &'static str {
19        "Resource allocation is unbalanced for workload type"
20    }
21
22    fn default_severity(&self) -> Severity {
23        Severity::Low
24    }
25
26    fn check(
27        &self,
28        ctx: &RuleContext,
29        config: &K8sOptimizeConfig,
30    ) -> Option<ResourceRecommendation> {
31        // Only report if include_info is set (this is a low-severity check)
32        if !config.include_info {
33            return None;
34        }
35
36        // Need both CPU and memory requests to check balance
37        let cpu_request = ctx.current.cpu_request.as_ref()?;
38        let memory_request = ctx.current.memory_request.as_ref()?;
39
40        let cpu_millicores = parse_cpu_to_millicores(cpu_request)?;
41        let memory_bytes = parse_memory_to_bytes(memory_request)?;
42
43        // Calculate CPU to memory ratio (millicores per GB)
44        let memory_gb = memory_bytes as f64 / (1024.0 * 1024.0 * 1024.0);
45        if memory_gb < 0.1 {
46            return None; // Too small to calculate meaningful ratio
47        }
48
49        let ratio = cpu_millicores as f64 / memory_gb;
50
51        // Expected ratios vary by workload type
52        let (expected_min, expected_max) = match ctx.workload_type {
53            WorkloadType::Web => (200.0, 2000.0), // Web: 200m-2000m per GB
54            WorkloadType::Worker => (500.0, 3000.0), // Worker: higher CPU
55            WorkloadType::Database => (100.0, 1000.0), // DB: lower CPU per GB
56            WorkloadType::Cache => (100.0, 500.0), // Cache: memory-heavy
57            WorkloadType::MessageBroker => (200.0, 1000.0),
58            WorkloadType::MachineLearning => (500.0, 4000.0), // ML: high CPU
59            WorkloadType::Batch => (500.0, 4000.0),           // Batch: high CPU
60            WorkloadType::General => (100.0, 2000.0),         // Wide range
61        };
62
63        // Check if ratio is within expected range
64        if ratio >= expected_min && ratio <= expected_max {
65            return None;
66        }
67
68        let defaults = ctx.workload_type.default_resources();
69        let recommended = ResourceSpec {
70            cpu_request: Some(defaults.cpu_request.to_string()),
71            cpu_limit: Some(defaults.cpu_limit.to_string()),
72            memory_request: Some(defaults.memory_request.to_string()),
73            memory_limit: Some(defaults.memory_limit.to_string()),
74        };
75
76        let direction = if ratio < expected_min {
77            "CPU-heavy for memory"
78        } else {
79            "Memory-heavy for CPU"
80        };
81
82        Some(ResourceRecommendation {
83            resource_kind: ctx.resource_kind.clone(),
84            resource_name: ctx.resource_name.clone(),
85            namespace: ctx.namespace.clone(),
86            container: ctx.container_name.clone(),
87            file_path: ctx.file_path.clone(),
88            line: ctx.line,
89            issue: OptimizationIssue::UnbalancedResources,
90            severity: self.default_severity(),
91            message: format!(
92                "Resource allocation is unbalanced for {} workload: {} (ratio: {:.0} millicores/GB, expected: {:.0}-{:.0}).",
93                ctx.workload_type, direction, ratio, expected_min, expected_max
94            ),
95            workload_type: ctx.workload_type,
96            current: ctx.current.clone(),
97            actual_usage: None,
98            recommended: recommended.clone(),
99            savings: None,
100            fix_yaml: recommended.to_yaml(),
101            rule_code: RuleCode::new(self.code()),
102        })
103    }
104}