codex_memory/performance/
capacity_planning.rs

1//! Capacity planning and scaling predictions
2
3use serde::{Deserialize, Serialize};
4use std::collections::VecDeque;
5
6/// Capacity planner for predicting system scaling needs
7pub 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    /// Add system metrics data point
21    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    /// Generate capacity planning report
29    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    /// Get current system metrics
47    fn get_current_metrics(&self) -> SystemMetrics {
48        self.historical_data
49            .back()
50            .cloned()
51            .unwrap_or_else(SystemMetrics::default)
52    }
53
54    /// Calculate growth trends from historical data
55    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    /// Generate predictions based on growth trends
83    fn generate_predictions(&self, trends: &GrowthTrends) -> ScalingPredictions {
84        let current = self.get_current_metrics();
85
86        // Predict when resources will be exhausted
87        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        // Predict future resource needs
109        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, // Simple peak prediction
132        }
133    }
134
135    /// Generate scaling recommendations
136    fn generate_scaling_recommendations(
137        &self,
138        predictions: &ScalingPredictions,
139    ) -> Vec<ScalingRecommendation> {
140        let mut recommendations = Vec::new();
141
142        // Check memory exhaustion
143        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                // 7 days
154                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        // Check CPU exhaustion
165        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        // Check storage exhaustion
178        if let Some(hours) = predictions.storage_exhaustion_hours {
179            if hours < 168 {
180                // 7 days
181                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        // Check if horizontal scaling is needed
196        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, // Suggest doubling instances
204            });
205        }
206
207        recommendations
208    }
209
210    /// Identify scaling triggers
211    fn identify_scaling_triggers(&self, predictions: &ScalingPredictions) -> Vec<ScalingTrigger> {
212        let mut triggers = Vec::new();
213
214        // Memory trigger
215        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        // CPU trigger
223        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        // Request rate trigger
231        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/// System metrics snapshot
243#[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, // 8 GB
263            storage_usage: 0,
264            max_storage: 107_374_182_400, // 100 GB
265            requests_per_second: 0.0,
266            active_connections: 0,
267            error_rate: 0.0,
268        }
269    }
270}
271
272/// Growth trends analysis
273#[derive(Debug, Clone, Serialize, Deserialize, Default)]
274pub struct GrowthTrends {
275    pub memory_growth_rate: f64,     // bytes per hour
276    pub cpu_growth_rate: f64,        // percent per hour
277    pub request_growth_rate: f64,    // requests/sec per hour
278    pub storage_growth_rate: f64,    // bytes per hour
279    pub connection_growth_rate: f64, // connections per hour
280}
281
282/// Scaling predictions
283#[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/// Scaling recommendation
298#[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/// Resource type
308#[derive(Debug, Clone, Serialize, Deserialize)]
309pub enum ResourceType {
310    CPU,
311    Memory,
312    Storage,
313    Instances,
314}
315
316/// Urgency level
317#[derive(Debug, Clone, Serialize, Deserialize)]
318pub enum Urgency {
319    Low,
320    Medium,
321    High,
322    Critical,
323}
324
325/// Scaling trigger
326#[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/// Scaling action type
335#[derive(Debug, Clone, Serialize, Deserialize)]
336pub enum ScalingAction {
337    HorizontalScale,
338    VerticalScale,
339    Alert,
340}
341
342/// Capacity planning report
343#[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        // Add some test metrics
363        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, // 100 MB per hour
403            cpu_growth_rate: 1.0,              // 1% per hour
404            request_growth_rate: 10.0,
405            storage_growth_rate: 1_000_000_000.0, // 1 GB per hour
406            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}