1use serde::{Deserialize, Serialize};
4use std::collections::VecDeque;
5
6pub struct CapacityPlanner {
8 historical_data: VecDeque<SystemMetrics>,
9 max_history: usize,
10}
11
12impl CapacityPlanner {
13 pub fn new(max_history: usize) -> Self {
14 Self {
15 historical_data: VecDeque::with_capacity(max_history),
16 max_history,
17 }
18 }
19
20 pub fn add_metrics(&mut self, metrics: SystemMetrics) {
22 if self.historical_data.len() >= self.max_history {
23 self.historical_data.pop_front();
24 }
25 self.historical_data.push_back(metrics);
26 }
27
28 pub fn generate_report(&self) -> CapacityPlanningReport {
30 let current_metrics = self.get_current_metrics();
31 let growth_trends = self.calculate_growth_trends();
32 let predictions = self.generate_predictions(&growth_trends);
33 let scaling_triggers = self.identify_scaling_triggers(&predictions);
34 let recommendations = self.generate_scaling_recommendations(&predictions);
35
36 CapacityPlanningReport {
37 timestamp: chrono::Utc::now(),
38 current_metrics,
39 growth_trends,
40 predictions,
41 recommendations,
42 scaling_triggers,
43 }
44 }
45
46 fn get_current_metrics(&self) -> SystemMetrics {
48 self.historical_data
49 .back()
50 .cloned()
51 .unwrap_or_else(SystemMetrics::default)
52 }
53
54 fn calculate_growth_trends(&self) -> GrowthTrends {
56 if self.historical_data.len() < 2 {
57 return GrowthTrends::default();
58 }
59
60 let first = self.historical_data.front().unwrap();
61 let last = self.historical_data.back().unwrap();
62 let duration_hours = (last.timestamp - first.timestamp).num_hours() as f64;
63
64 if duration_hours == 0.0 {
65 return GrowthTrends::default();
66 }
67
68 GrowthTrends {
69 memory_growth_rate: (last.memory_usage as f64 - first.memory_usage as f64)
70 / duration_hours,
71 cpu_growth_rate: (last.cpu_usage - first.cpu_usage) / duration_hours,
72 request_growth_rate: (last.requests_per_second - first.requests_per_second)
73 / duration_hours,
74 storage_growth_rate: (last.storage_usage as f64 - first.storage_usage as f64)
75 / duration_hours,
76 connection_growth_rate: (last.active_connections as f64
77 - first.active_connections as f64)
78 / duration_hours,
79 }
80 }
81
82 fn generate_predictions(&self, trends: &GrowthTrends) -> ScalingPredictions {
84 let current = self.get_current_metrics();
85
86 let memory_exhaustion_hours = if trends.memory_growth_rate > 0.0 {
88 let remaining_memory = (current.max_memory - current.memory_usage) as f64;
89 Some((remaining_memory / trends.memory_growth_rate) as u32)
90 } else {
91 None
92 };
93
94 let cpu_exhaustion_hours = if trends.cpu_growth_rate > 0.0 {
95 let remaining_cpu = 100.0 - current.cpu_usage;
96 Some((remaining_cpu / trends.cpu_growth_rate) as u32)
97 } else {
98 None
99 };
100
101 let storage_exhaustion_hours = if trends.storage_growth_rate > 0.0 {
102 let remaining_storage = (current.max_storage - current.storage_usage) as f64;
103 Some((remaining_storage / trends.storage_growth_rate) as u32)
104 } else {
105 None
106 };
107
108 let memory_needed_7d =
110 current.memory_usage as f64 + (trends.memory_growth_rate * 24.0 * 7.0);
111 let cpu_needed_7d = current.cpu_usage + (trends.cpu_growth_rate * 24.0 * 7.0);
112 let storage_needed_7d =
113 current.storage_usage as f64 + (trends.storage_growth_rate * 24.0 * 7.0);
114
115 let memory_needed_30d =
116 current.memory_usage as f64 + (trends.memory_growth_rate * 24.0 * 30.0);
117 let cpu_needed_30d = current.cpu_usage + (trends.cpu_growth_rate * 24.0 * 30.0);
118 let storage_needed_30d =
119 current.storage_usage as f64 + (trends.storage_growth_rate * 24.0 * 30.0);
120
121 ScalingPredictions {
122 memory_exhaustion_hours,
123 cpu_exhaustion_hours,
124 storage_exhaustion_hours,
125 memory_needed_7d: memory_needed_7d as u64,
126 cpu_needed_7d,
127 storage_needed_7d: storage_needed_7d as u64,
128 memory_needed_30d: memory_needed_30d as u64,
129 cpu_needed_30d,
130 storage_needed_30d: storage_needed_30d as u64,
131 predicted_peak_rps: current.requests_per_second * 1.5, }
133 }
134
135 fn generate_scaling_recommendations(
137 &self,
138 predictions: &ScalingPredictions,
139 ) -> Vec<ScalingRecommendation> {
140 let mut recommendations = Vec::new();
141
142 if let Some(hours) = predictions.memory_exhaustion_hours {
144 if hours < 24 {
145 recommendations.push(ScalingRecommendation {
146 resource: ResourceType::Memory,
147 urgency: Urgency::Critical,
148 action: "Immediately increase memory allocation".to_string(),
149 reason: format!("Memory will be exhausted in {hours} hours"),
150 suggested_value: predictions.memory_needed_7d,
151 });
152 } else if hours < 168 {
153 recommendations.push(ScalingRecommendation {
155 resource: ResourceType::Memory,
156 urgency: Urgency::High,
157 action: "Plan memory upgrade within this week".to_string(),
158 reason: format!("Memory will be exhausted in {} days", hours / 24),
159 suggested_value: predictions.memory_needed_30d,
160 });
161 }
162 }
163
164 if let Some(hours) = predictions.cpu_exhaustion_hours {
166 if hours < 24 {
167 recommendations.push(ScalingRecommendation {
168 resource: ResourceType::CPU,
169 urgency: Urgency::Critical,
170 action: "Add more CPU cores or scale horizontally".to_string(),
171 reason: format!("CPU will be saturated in {hours} hours"),
172 suggested_value: (predictions.cpu_needed_7d as u64).min(100),
173 });
174 }
175 }
176
177 if let Some(hours) = predictions.storage_exhaustion_hours {
179 if hours < 168 {
180 recommendations.push(ScalingRecommendation {
182 resource: ResourceType::Storage,
183 urgency: if hours < 24 {
184 Urgency::Critical
185 } else {
186 Urgency::High
187 },
188 action: "Increase storage capacity".to_string(),
189 reason: format!("Storage will be full in {} days", hours / 24),
190 suggested_value: predictions.storage_needed_30d,
191 });
192 }
193 }
194
195 let current = self.get_current_metrics();
197 if current.cpu_usage > 70.0 && current.memory_usage > current.max_memory * 70 / 100 {
198 recommendations.push(ScalingRecommendation {
199 resource: ResourceType::Instances,
200 urgency: Urgency::Medium,
201 action: "Consider horizontal scaling".to_string(),
202 reason: "Both CPU and memory usage are high".to_string(),
203 suggested_value: 2, });
205 }
206
207 recommendations
208 }
209
210 fn identify_scaling_triggers(&self, predictions: &ScalingPredictions) -> Vec<ScalingTrigger> {
212 let mut triggers = Vec::new();
213
214 triggers.push(ScalingTrigger {
216 metric: "memory_usage_percent".to_string(),
217 threshold: 80.0,
218 action: ScalingAction::VerticalScale,
219 cooldown_minutes: 30,
220 });
221
222 triggers.push(ScalingTrigger {
224 metric: "cpu_usage_percent".to_string(),
225 threshold: 75.0,
226 action: ScalingAction::HorizontalScale,
227 cooldown_minutes: 15,
228 });
229
230 triggers.push(ScalingTrigger {
232 metric: "requests_per_second".to_string(),
233 threshold: predictions.predicted_peak_rps * 0.8,
234 action: ScalingAction::HorizontalScale,
235 cooldown_minutes: 10,
236 });
237
238 triggers
239 }
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct SystemMetrics {
245 pub timestamp: chrono::DateTime<chrono::Utc>,
246 pub cpu_usage: f64,
247 pub memory_usage: u64,
248 pub max_memory: u64,
249 pub storage_usage: u64,
250 pub max_storage: u64,
251 pub requests_per_second: f64,
252 pub active_connections: u32,
253 pub error_rate: f64,
254}
255
256impl Default for SystemMetrics {
257 fn default() -> Self {
258 Self {
259 timestamp: chrono::Utc::now(),
260 cpu_usage: 0.0,
261 memory_usage: 0,
262 max_memory: 8_589_934_592, storage_usage: 0,
264 max_storage: 107_374_182_400, requests_per_second: 0.0,
266 active_connections: 0,
267 error_rate: 0.0,
268 }
269 }
270}
271
272#[derive(Debug, Clone, Serialize, Deserialize, Default)]
274pub struct GrowthTrends {
275 pub memory_growth_rate: f64, pub cpu_growth_rate: f64, pub request_growth_rate: f64, pub storage_growth_rate: f64, pub connection_growth_rate: f64, }
281
282#[derive(Debug, Clone, Serialize, Deserialize)]
284pub struct ScalingPredictions {
285 pub memory_exhaustion_hours: Option<u32>,
286 pub cpu_exhaustion_hours: Option<u32>,
287 pub storage_exhaustion_hours: Option<u32>,
288 pub memory_needed_7d: u64,
289 pub cpu_needed_7d: f64,
290 pub storage_needed_7d: u64,
291 pub memory_needed_30d: u64,
292 pub cpu_needed_30d: f64,
293 pub storage_needed_30d: u64,
294 pub predicted_peak_rps: f64,
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize)]
299pub struct ScalingRecommendation {
300 pub resource: ResourceType,
301 pub urgency: Urgency,
302 pub action: String,
303 pub reason: String,
304 pub suggested_value: u64,
305}
306
307#[derive(Debug, Clone, Serialize, Deserialize)]
309pub enum ResourceType {
310 CPU,
311 Memory,
312 Storage,
313 Instances,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
318pub enum Urgency {
319 Low,
320 Medium,
321 High,
322 Critical,
323}
324
325#[derive(Debug, Clone, Serialize, Deserialize)]
327pub struct ScalingTrigger {
328 pub metric: String,
329 pub threshold: f64,
330 pub action: ScalingAction,
331 pub cooldown_minutes: u32,
332}
333
334#[derive(Debug, Clone, Serialize, Deserialize)]
336pub enum ScalingAction {
337 HorizontalScale,
338 VerticalScale,
339 Alert,
340}
341
342#[derive(Debug, Clone, Serialize, Deserialize)]
344pub struct CapacityPlanningReport {
345 pub timestamp: chrono::DateTime<chrono::Utc>,
346 pub current_metrics: SystemMetrics,
347 pub growth_trends: GrowthTrends,
348 pub predictions: ScalingPredictions,
349 pub recommendations: Vec<ScalingRecommendation>,
350 pub scaling_triggers: Vec<ScalingTrigger>,
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356 use chrono::Utc;
357
358 #[test]
359 fn test_capacity_planner() {
360 let mut planner = CapacityPlanner::new(100);
361
362 let base_time = Utc::now();
364
365 planner.add_metrics(SystemMetrics {
366 timestamp: base_time,
367 cpu_usage: 30.0,
368 memory_usage: 2_000_000_000,
369 max_memory: 8_000_000_000,
370 storage_usage: 10_000_000_000,
371 max_storage: 100_000_000_000,
372 requests_per_second: 100.0,
373 active_connections: 20,
374 error_rate: 0.1,
375 });
376
377 planner.add_metrics(SystemMetrics {
378 timestamp: base_time + chrono::Duration::hours(1),
379 cpu_usage: 35.0,
380 memory_usage: 2_100_000_000,
381 max_memory: 8_000_000_000,
382 storage_usage: 10_500_000_000,
383 max_storage: 100_000_000_000,
384 requests_per_second: 110.0,
385 active_connections: 22,
386 error_rate: 0.15,
387 });
388
389 let report = planner.generate_report();
390
391 assert!(report.growth_trends.cpu_growth_rate > 0.0);
392 assert!(report.growth_trends.memory_growth_rate > 0.0);
393 assert!(!report.recommendations.is_empty());
394 assert!(!report.scaling_triggers.is_empty());
395 }
396
397 #[test]
398 fn test_exhaustion_prediction() {
399 let planner = CapacityPlanner::new(100);
400
401 let trends = GrowthTrends {
402 memory_growth_rate: 100_000_000.0, cpu_growth_rate: 1.0, request_growth_rate: 10.0,
405 storage_growth_rate: 1_000_000_000.0, connection_growth_rate: 1.0,
407 };
408
409 let predictions = planner.generate_predictions(&trends);
410
411 assert!(predictions.memory_exhaustion_hours.is_some());
412 assert!(predictions.cpu_exhaustion_hours.is_some());
413 assert!(predictions.storage_exhaustion_hours.is_some());
414 assert!(predictions.memory_needed_7d > 0);
415 assert!(predictions.cpu_needed_7d > 0.0);
416 }
417}