1use crate::{AnalyticsSnapshot, EndpointMetrics, LatencyMetrics, RequestMetrics};
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum InsightType {
11 HighErrorRate,
12 HighLatency,
13 RateLimitPressure,
14 TrafficSpike,
15 SlowEndpoint,
16 ErrorSpike,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
21#[serde(rename_all = "lowercase")]
22pub enum Severity {
23 Info,
24 Warning,
25 Critical,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct Insight {
31 pub insight_type: InsightType,
32 pub severity: Severity,
33 pub title: String,
34 pub description: String,
35 pub value: f64,
36 pub threshold: f64,
37 pub timestamp: DateTime<Utc>,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub endpoint: Option<String>,
40 #[serde(skip_serializing_if = "Option::is_none")]
41 pub recommendation: Option<String>,
42}
43
44impl Insight {
45 pub fn new(
46 insight_type: InsightType,
47 severity: Severity,
48 title: impl Into<String>,
49 description: impl Into<String>,
50 ) -> Self {
51 Self {
52 insight_type,
53 severity,
54 title: title.into(),
55 description: description.into(),
56 value: 0.0,
57 threshold: 0.0,
58 timestamp: Utc::now(),
59 endpoint: None,
60 recommendation: None,
61 }
62 }
63
64 pub fn with_value(mut self, value: f64) -> Self {
65 self.value = value;
66 self
67 }
68
69 pub fn with_threshold(mut self, threshold: f64) -> Self {
70 self.threshold = threshold;
71 self
72 }
73
74 pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
75 self.endpoint = Some(endpoint.into());
76 self
77 }
78
79 pub fn with_recommendation(mut self, rec: impl Into<String>) -> Self {
80 self.recommendation = Some(rec.into());
81 self
82 }
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct InsightConfig {
88 pub error_rate_warning: f64,
90 pub error_rate_critical: f64,
91
92 pub p99_latency_warning_ms: f64,
94 pub p99_latency_critical_ms: f64,
95
96 pub rate_limit_warning: f64,
98 pub rate_limit_critical: f64,
99
100 pub traffic_spike_multiplier: f64,
102
103 pub min_requests: u64,
105}
106
107impl Default for InsightConfig {
108 fn default() -> Self {
109 Self {
110 error_rate_warning: 1.0, error_rate_critical: 5.0, p99_latency_warning_ms: 500.0,
113 p99_latency_critical_ms: 2000.0,
114 rate_limit_warning: 10.0, rate_limit_critical: 25.0, traffic_spike_multiplier: 3.0,
117 min_requests: 100,
118 }
119 }
120}
121
122pub struct InsightGenerator {
124 config: InsightConfig,
125 baseline_rps: Option<f64>,
126}
127
128impl InsightGenerator {
129 pub fn new(config: InsightConfig) -> Self {
130 Self {
131 config,
132 baseline_rps: None,
133 }
134 }
135
136 pub fn set_baseline_rps(&mut self, rps: f64) {
138 self.baseline_rps = Some(rps);
139 }
140
141 pub fn generate(&self, snapshot: &AnalyticsSnapshot) -> Vec<Insight> {
143 let mut insights = Vec::new();
144
145 if snapshot.requests.total < self.config.min_requests {
147 return insights;
148 }
149
150 if let Some(insight) = self.check_error_rate(&snapshot.requests) {
152 insights.push(insight);
153 }
154
155 if let Some(insight) = self.check_latency(&snapshot.latency) {
157 insights.push(insight);
158 }
159
160 if let Some(insight) = self.check_rate_limits(snapshot) {
162 insights.push(insight);
163 }
164
165 if let Some(insight) = self.check_traffic_spike(snapshot) {
167 insights.push(insight);
168 }
169
170 for insight in self.check_slow_endpoints(&snapshot.endpoints) {
172 insights.push(insight);
173 }
174
175 insights
176 }
177
178 fn check_error_rate(&self, requests: &RequestMetrics) -> Option<Insight> {
179 let error_rate = requests.error_rate();
180
181 if error_rate >= self.config.error_rate_critical {
182 Some(
183 Insight::new(
184 InsightType::HighErrorRate,
185 Severity::Critical,
186 "Critical Error Rate",
187 format!("Error rate is {:.2}%, above critical threshold of {:.2}%",
188 error_rate, self.config.error_rate_critical),
189 )
190 .with_value(error_rate)
191 .with_threshold(self.config.error_rate_critical)
192 .with_recommendation("Investigate error logs immediately. Check for deployment issues or upstream service failures."),
193 )
194 } else if error_rate >= self.config.error_rate_warning {
195 Some(
196 Insight::new(
197 InsightType::HighErrorRate,
198 Severity::Warning,
199 "Elevated Error Rate",
200 format!(
201 "Error rate is {:.2}%, above warning threshold of {:.2}%",
202 error_rate, self.config.error_rate_warning
203 ),
204 )
205 .with_value(error_rate)
206 .with_threshold(self.config.error_rate_warning)
207 .with_recommendation("Review error logs and monitor for further increase."),
208 )
209 } else {
210 None
211 }
212 }
213
214 fn check_latency(&self, latency: &LatencyMetrics) -> Option<Insight> {
215 if latency.p99_ms >= self.config.p99_latency_critical_ms {
216 Some(
217 Insight::new(
218 InsightType::HighLatency,
219 Severity::Critical,
220 "Critical Latency",
221 format!(
222 "P99 latency is {:.0}ms, above critical threshold of {:.0}ms",
223 latency.p99_ms, self.config.p99_latency_critical_ms
224 ),
225 )
226 .with_value(latency.p99_ms)
227 .with_threshold(self.config.p99_latency_critical_ms)
228 .with_recommendation(
229 "Check database queries, external service calls, and resource utilization.",
230 ),
231 )
232 } else if latency.p99_ms >= self.config.p99_latency_warning_ms {
233 Some(
234 Insight::new(
235 InsightType::HighLatency,
236 Severity::Warning,
237 "Elevated Latency",
238 format!(
239 "P99 latency is {:.0}ms, above warning threshold of {:.0}ms",
240 latency.p99_ms, self.config.p99_latency_warning_ms
241 ),
242 )
243 .with_value(latency.p99_ms)
244 .with_threshold(self.config.p99_latency_warning_ms)
245 .with_recommendation("Profile slow requests and consider caching or optimization."),
246 )
247 } else {
248 None
249 }
250 }
251
252 fn check_rate_limits(&self, snapshot: &AnalyticsSnapshot) -> Option<Insight> {
253 let rate_limits = &snapshot.rate_limits;
254 if rate_limits.total_checks == 0 {
255 return None;
256 }
257
258 let limited_rate = (rate_limits.limited as f64 / rate_limits.total_checks as f64) * 100.0;
259
260 if limited_rate >= self.config.rate_limit_critical {
261 Some(
262 Insight::new(
263 InsightType::RateLimitPressure,
264 Severity::Critical,
265 "Critical Rate Limit Pressure",
266 format!("{:.2}% of requests are being rate limited", limited_rate),
267 )
268 .with_value(limited_rate)
269 .with_threshold(self.config.rate_limit_critical)
270 .with_recommendation("Consider increasing rate limits, adding capacity, or implementing request queuing."),
271 )
272 } else if limited_rate >= self.config.rate_limit_warning {
273 Some(
274 Insight::new(
275 InsightType::RateLimitPressure,
276 Severity::Warning,
277 "Rate Limit Pressure",
278 format!("{:.2}% of requests are being rate limited", limited_rate),
279 )
280 .with_value(limited_rate)
281 .with_threshold(self.config.rate_limit_warning)
282 .with_recommendation("Monitor rate limit usage and consider adjusting limits for legitimate traffic."),
283 )
284 } else {
285 None
286 }
287 }
288
289 fn check_traffic_spike(&self, snapshot: &AnalyticsSnapshot) -> Option<Insight> {
290 let baseline = self.baseline_rps?;
291 let current_rps = snapshot.throughput.requests_per_second;
292
293 if current_rps > baseline * self.config.traffic_spike_multiplier {
294 Some(
295 Insight::new(
296 InsightType::TrafficSpike,
297 Severity::Warning,
298 "Traffic Spike Detected",
299 format!(
300 "Current RPS ({:.1}) is {:.1}x higher than baseline ({:.1})",
301 current_rps,
302 current_rps / baseline,
303 baseline
304 ),
305 )
306 .with_value(current_rps)
307 .with_threshold(baseline * self.config.traffic_spike_multiplier)
308 .with_recommendation(
309 "Investigate traffic source. Consider enabling auto-scaling if available.",
310 ),
311 )
312 } else {
313 None
314 }
315 }
316
317 fn check_slow_endpoints(&self, endpoints: &[EndpointMetrics]) -> Vec<Insight> {
318 let mut insights = Vec::new();
319
320 for endpoint in endpoints {
321 if endpoint.requests < 10 {
323 continue;
324 }
325
326 if endpoint.p99_latency_ms >= self.config.p99_latency_critical_ms {
328 insights.push(
329 Insight::new(
330 InsightType::SlowEndpoint,
331 Severity::Warning,
332 "Slow Endpoint",
333 format!(
334 "{} {} has P99 latency of {:.0}ms",
335 endpoint.method, endpoint.path, endpoint.p99_latency_ms
336 ),
337 )
338 .with_value(endpoint.p99_latency_ms)
339 .with_threshold(self.config.p99_latency_critical_ms)
340 .with_endpoint(format!("{} {}", endpoint.method, endpoint.path))
341 .with_recommendation(
342 "Profile this specific endpoint for optimization opportunities.",
343 ),
344 );
345 }
346
347 if endpoint.error_rate >= self.config.error_rate_critical {
349 insights.push(
350 Insight::new(
351 InsightType::ErrorSpike,
352 Severity::Warning,
353 "High Error Rate Endpoint",
354 format!(
355 "{} {} has error rate of {:.2}%",
356 endpoint.method, endpoint.path, endpoint.error_rate
357 ),
358 )
359 .with_value(endpoint.error_rate)
360 .with_threshold(self.config.error_rate_critical)
361 .with_endpoint(format!("{} {}", endpoint.method, endpoint.path))
362 .with_recommendation("Investigate errors specific to this endpoint."),
363 );
364 }
365 }
366
367 insights
368 }
369}
370
371impl Default for InsightGenerator {
372 fn default() -> Self {
373 Self::new(InsightConfig::default())
374 }
375}
376
377#[cfg(test)]
378mod tests {
379 use super::*;
380
381 #[test]
382 fn test_insight_creation() {
383 let insight = Insight::new(
384 InsightType::HighErrorRate,
385 Severity::Critical,
386 "Test",
387 "Description",
388 )
389 .with_value(5.0)
390 .with_threshold(1.0)
391 .with_recommendation("Fix it");
392
393 assert_eq!(insight.insight_type, InsightType::HighErrorRate);
394 assert_eq!(insight.severity, Severity::Critical);
395 assert_eq!(insight.value, 5.0);
396 }
397
398 #[test]
399 fn test_insight_config_defaults() {
400 let config = InsightConfig::default();
401 assert_eq!(config.error_rate_warning, 1.0);
402 assert_eq!(config.error_rate_critical, 5.0);
403 }
404}