Skip to main content

rullst_core/
devops.rs

1use serde::{Deserialize, Serialize};
2
3/// Recommendation generated by the Agentic DevOps Engine
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct InfrastructureRecommendation {
6    /// Name of the monitored metric (e.g. tokio_tick_latency_us)
7    pub metric_name: String,
8    /// Current measured value of the metric
9    pub current_value: f64,
10    /// Recommended target value for optimal execution
11    pub target_value: f64,
12    /// Actionable recommendation for developer or autoscaler
13    pub recommended_action: String,
14    /// Urgency level ("LOW", "MEDIUM", "HIGH", "CRITICAL")
15    pub urgency: String,
16}
17
18/// Agentic DevOps Engine analyzing system telemetry and proposing infrastructure tuning (Milestone 22)
19#[derive(Debug, Default)]
20pub struct DevOpsAgent;
21
22impl DevOpsAgent {
23    /// Create a new instance of the Agentic DevOps Engine
24    pub fn new() -> Self {
25        Self
26    }
27
28    /// Analyze runtime telemetry metrics and generate autonomous recommendations
29    pub fn analyze_telemetry(
30        &self,
31        tokio_latency_us: u64,
32        memory_rss_bytes: u64,
33        db_pool_active_connections: u32,
34        db_pool_max_connections: u32,
35    ) -> Vec<InfrastructureRecommendation> {
36        let mut recommendations = Vec::new();
37
38        // 1. Analyze Tokio Runtime Event Loop Latency
39        if tokio_latency_us > 10_000 {
40            recommendations.push(InfrastructureRecommendation {
41                metric_name: "tokio_tick_latency_us".to_string(),
42                current_value: tokio_latency_us as f64,
43                target_value: 1_000.0,
44                recommended_action: "Increase Tokio worker threads or offload CPU-bound blocking tasks to tokio::task::spawn_blocking".to_string(),
45                urgency: "HIGH".to_string(),
46            });
47        }
48
49        // 2. Analyze RSS Memory Usage (Threshold > 500MB)
50        let memory_mb = memory_rss_bytes as f64 / (1024.0 * 1024.0);
51        if memory_mb > 500.0 {
52            recommendations.push(InfrastructureRecommendation {
53                metric_name: "memory_rss_mb".to_string(),
54                current_value: memory_mb,
55                target_value: 256.0,
56                recommended_action: "Audit response payload buffer allocations or enable Brotli asset compression level 11".to_string(),
57                urgency: "MEDIUM".to_string(),
58            });
59        }
60
61        // 3. Analyze SQLx Database Connection Pool Saturation
62        if db_pool_max_connections > 0 {
63            let pool_utilization =
64                db_pool_active_connections as f64 / db_pool_max_connections as f64;
65            if pool_utilization > 0.85 {
66                recommendations.push(InfrastructureRecommendation {
67                    metric_name: "db_pool_utilization".to_string(),
68                    current_value: pool_utilization * 100.0,
69                    target_value: 60.0,
70                    recommended_action: format!(
71                        "Scale SQLx connection pool max_connections from {} to {}",
72                        db_pool_max_connections,
73                        db_pool_max_connections * 2
74                    ),
75                    urgency: "HIGH".to_string(),
76                });
77            }
78        }
79
80        recommendations
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn test_devops_agent_recommendations() {
90        let agent = DevOpsAgent::new();
91
92        // Test below/at boundary (no recommendations)
93        let recs_clean = agent.analyze_telemetry(10_000, 500 * 1024 * 1024, 17, 20);
94        assert_eq!(recs_clean.len(), 0);
95
96        // Test high Tokio latency (> 10_000)
97        let recs = agent.analyze_telemetry(15_000, 100 * 1024 * 1024, 5, 20);
98        assert_eq!(recs.len(), 1);
99        assert_eq!(recs[0].metric_name, "tokio_tick_latency_us");
100
101        // Test high memory (> 500 MB)
102        let recs_mem = agent.analyze_telemetry(500, 600 * 1024 * 1024, 5, 20);
103        assert_eq!(recs_mem.len(), 1);
104        assert_eq!(recs_mem[0].metric_name, "memory_rss_mb");
105
106        // Test saturated DB pool (> 85%)
107        let recs_db = agent.analyze_telemetry(500, 100 * 1024 * 1024, 18, 20);
108        assert_eq!(recs_db.len(), 1);
109        assert_eq!(recs_db[0].metric_name, "db_pool_utilization");
110    }
111}