syncable_cli/analyzer/k8s_optimize/
trend_analyzer.rs1use super::live_analyzer::LiveRecommendation;
6use super::types::{TrendAnalysis, TrendDirection, WasteMetrics, WorkloadTrend};
7
8pub fn analyze_trends_from_live(current_recs: &[LiveRecommendation]) -> TrendAnalysis {
12 let current = calculate_current_waste(current_recs);
14
15 let workload_trends = calculate_workload_trends(current_recs);
17
18 TrendAnalysis {
19 period: "current".to_string(),
20 current: current.clone(),
21 historical: WasteMetrics {
22 cpu_waste_millicores: 0,
23 memory_waste_bytes: 0,
24 waste_percentage: 0.0,
25 over_provisioned_count: 0,
26 },
27 trend: TrendDirection {
28 direction: if current.over_provisioned_count > 5 {
30 "needs_attention"
31 } else if current.waste_percentage > 50.0 {
32 "high_waste"
33 } else if current.waste_percentage > 20.0 {
34 "moderate_waste"
35 } else {
36 "acceptable"
37 }
38 .to_string(),
39 change_percent: 0.0,
40 },
41 workload_trends,
42 }
43}
44
45fn calculate_current_waste(recs: &[LiveRecommendation]) -> WasteMetrics {
47 let mut total_cpu_waste: u64 = 0;
48 let mut total_mem_waste: u64 = 0;
49 let mut over_provisioned = 0;
50 let mut total_waste_pct = 0.0;
51
52 for rec in recs {
53 if rec.cpu_waste_pct > 0.0 || rec.memory_waste_pct > 0.0 {
54 over_provisioned += 1;
55
56 let cpu_waste = if rec.cpu_waste_pct > 0.0 {
57 let current = rec.current_cpu_millicores.unwrap_or(0);
58 current.saturating_sub(rec.recommended_cpu_millicores)
59 } else {
60 0
61 };
62
63 let mem_waste = if rec.memory_waste_pct > 0.0 {
64 let current = rec.current_memory_bytes.unwrap_or(0);
65 current.saturating_sub(rec.recommended_memory_bytes)
66 } else {
67 0
68 };
69
70 total_cpu_waste += cpu_waste;
71 total_mem_waste += mem_waste;
72 total_waste_pct += rec.cpu_waste_pct.max(rec.memory_waste_pct);
73 }
74 }
75
76 let avg_waste_pct = if over_provisioned > 0 {
77 total_waste_pct / over_provisioned as f32
78 } else {
79 0.0
80 };
81
82 WasteMetrics {
83 cpu_waste_millicores: total_cpu_waste,
84 memory_waste_bytes: total_mem_waste,
85 waste_percentage: avg_waste_pct,
86 over_provisioned_count: over_provisioned,
87 }
88}
89
90fn calculate_workload_trends(recs: &[LiveRecommendation]) -> Vec<WorkloadTrend> {
92 recs.iter()
93 .filter(|rec| rec.cpu_waste_pct > 10.0 || rec.memory_waste_pct > 10.0)
94 .map(|rec| {
95 let cpu_change = if rec.cpu_waste_pct > 0.0 {
96 let current = rec.current_cpu_millicores.unwrap_or(0) as i64;
97 let recommended = rec.recommended_cpu_millicores as i64;
98 current - recommended
99 } else {
100 0
101 };
102
103 let mem_change = if rec.memory_waste_pct > 0.0 {
104 let current = rec.current_memory_bytes.unwrap_or(0) as i64;
105 let recommended = rec.recommended_memory_bytes as i64;
106 current - recommended
107 } else {
108 0
109 };
110
111 let direction = if cpu_change > 0 || mem_change > 0 {
112 "over-provisioned"
113 } else if cpu_change < 0 || mem_change < 0 {
114 "under-provisioned"
115 } else {
116 "optimal"
117 };
118
119 WorkloadTrend {
120 namespace: rec.namespace.clone(),
121 workload_name: rec.workload_name.clone(),
122 cpu_change_millicores: cpu_change,
123 memory_change_bytes: mem_change,
124 direction: direction.to_string(),
125 }
126 })
127 .collect()
128}
129
130pub fn analyze_trends_static(
132 current_waste_pct: f32,
133 over_provisioned_count: usize,
134) -> TrendAnalysis {
135 TrendAnalysis {
137 period: "current".to_string(),
138 current: WasteMetrics {
139 cpu_waste_millicores: 0,
140 memory_waste_bytes: 0,
141 waste_percentage: current_waste_pct,
142 over_provisioned_count,
143 },
144 historical: WasteMetrics {
145 cpu_waste_millicores: 0,
146 memory_waste_bytes: 0,
147 waste_percentage: 0.0,
148 over_provisioned_count: 0,
149 },
150 trend: TrendDirection {
151 direction: if over_provisioned_count > 5 {
152 "needs_attention"
153 } else if current_waste_pct > 50.0 {
154 "high_waste"
155 } else if current_waste_pct > 20.0 {
156 "moderate_waste"
157 } else {
158 "acceptable"
159 }
160 .to_string(),
161 change_percent: 0.0,
162 },
163 workload_trends: vec![],
164 }
165}