mockforge_analytics/
models.rs

1//! Data models for analytics
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7/// Granularity level for aggregated metrics
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "lowercase")]
10pub enum Granularity {
11    Minute,
12    Hour,
13    Day,
14}
15
16/// Aggregated metrics for a specific time window
17#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
18pub struct MetricsAggregate {
19    pub id: Option<i64>,
20    pub timestamp: i64,
21    pub protocol: String,
22    pub method: Option<String>,
23    pub endpoint: Option<String>,
24    pub status_code: Option<i32>,
25    pub workspace_id: Option<String>,
26    pub environment: Option<String>,
27    pub request_count: i64,
28    pub error_count: i64,
29    pub latency_sum: f64,
30    pub latency_min: Option<f64>,
31    pub latency_max: Option<f64>,
32    pub latency_p50: Option<f64>,
33    pub latency_p95: Option<f64>,
34    pub latency_p99: Option<f64>,
35    pub bytes_sent: i64,
36    pub bytes_received: i64,
37    pub active_connections: Option<i64>,
38    pub created_at: Option<i64>,
39}
40
41/// Hour-level aggregated metrics
42#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
43pub struct HourMetricsAggregate {
44    pub id: Option<i64>,
45    pub timestamp: i64,
46    pub protocol: String,
47    pub method: Option<String>,
48    pub endpoint: Option<String>,
49    pub status_code: Option<i32>,
50    pub workspace_id: Option<String>,
51    pub environment: Option<String>,
52    pub request_count: i64,
53    pub error_count: i64,
54    pub latency_sum: f64,
55    pub latency_min: Option<f64>,
56    pub latency_max: Option<f64>,
57    pub latency_p50: Option<f64>,
58    pub latency_p95: Option<f64>,
59    pub latency_p99: Option<f64>,
60    pub bytes_sent: i64,
61    pub bytes_received: i64,
62    pub active_connections_avg: Option<f64>,
63    pub active_connections_max: Option<i64>,
64    pub created_at: Option<i64>,
65}
66
67/// Day-level aggregated metrics
68#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
69pub struct DayMetricsAggregate {
70    pub id: Option<i64>,
71    pub date: String,
72    pub timestamp: i64,
73    pub protocol: String,
74    pub method: Option<String>,
75    pub endpoint: Option<String>,
76    pub status_code: Option<i32>,
77    pub workspace_id: Option<String>,
78    pub environment: Option<String>,
79    pub request_count: i64,
80    pub error_count: i64,
81    pub latency_sum: f64,
82    pub latency_min: Option<f64>,
83    pub latency_max: Option<f64>,
84    pub latency_p50: Option<f64>,
85    pub latency_p95: Option<f64>,
86    pub latency_p99: Option<f64>,
87    pub bytes_sent: i64,
88    pub bytes_received: i64,
89    pub active_connections_avg: Option<f64>,
90    pub active_connections_max: Option<i64>,
91    pub unique_clients: Option<i64>,
92    pub peak_hour: Option<i32>,
93    pub created_at: Option<i64>,
94}
95
96/// Statistics for a specific endpoint
97#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
98pub struct EndpointStats {
99    pub id: Option<i64>,
100    pub endpoint: String,
101    pub protocol: String,
102    pub method: Option<String>,
103    pub workspace_id: Option<String>,
104    pub environment: Option<String>,
105    pub total_requests: i64,
106    pub total_errors: i64,
107    pub avg_latency_ms: Option<f64>,
108    pub min_latency_ms: Option<f64>,
109    pub max_latency_ms: Option<f64>,
110    pub p95_latency_ms: Option<f64>,
111    pub status_codes: Option<String>, // JSON
112    pub total_bytes_sent: i64,
113    pub total_bytes_received: i64,
114    pub first_seen: i64,
115    pub last_seen: i64,
116    pub updated_at: Option<i64>,
117}
118
119/// Parsed status code breakdown from endpoint stats
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct StatusCodeBreakdown {
122    pub status_codes: HashMap<u16, i64>,
123}
124
125impl EndpointStats {
126    /// Parse the status codes JSON field
127    pub fn get_status_code_breakdown(&self) -> Result<StatusCodeBreakdown, serde_json::Error> {
128        if let Some(ref json) = self.status_codes {
129            let map: HashMap<String, i64> = serde_json::from_str(json)?;
130            let status_codes = map
131                .into_iter()
132                .filter_map(|(k, v)| k.parse::<u16>().ok().map(|code| (code, v)))
133                .collect();
134            Ok(StatusCodeBreakdown { status_codes })
135        } else {
136            Ok(StatusCodeBreakdown {
137                status_codes: HashMap::new(),
138            })
139        }
140    }
141
142    /// Set the status codes from a breakdown
143    pub fn set_status_code_breakdown(
144        &mut self,
145        breakdown: &StatusCodeBreakdown,
146    ) -> Result<(), serde_json::Error> {
147        let map: HashMap<String, i64> =
148            breakdown.status_codes.iter().map(|(k, v)| (k.to_string(), *v)).collect();
149        self.status_codes = Some(serde_json::to_string(&map)?);
150        Ok(())
151    }
152}
153
154/// Individual error event
155#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
156pub struct ErrorEvent {
157    pub id: Option<i64>,
158    pub timestamp: i64,
159    pub protocol: String,
160    pub method: Option<String>,
161    pub endpoint: Option<String>,
162    pub status_code: Option<i32>,
163    pub error_type: Option<String>,
164    pub error_message: Option<String>,
165    pub error_category: Option<String>,
166    pub request_id: Option<String>,
167    pub trace_id: Option<String>,
168    pub span_id: Option<String>,
169    pub client_ip: Option<String>,
170    pub user_agent: Option<String>,
171    pub workspace_id: Option<String>,
172    pub environment: Option<String>,
173    pub metadata: Option<String>, // JSON
174    pub created_at: Option<i64>,
175}
176
177/// Error category enumeration
178#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
179#[serde(rename_all = "snake_case")]
180pub enum ErrorCategory {
181    ClientError, // 4xx
182    ServerError, // 5xx
183    NetworkError,
184    TimeoutError,
185    Other,
186}
187
188impl ErrorCategory {
189    /// Get the category from a status code
190    pub fn from_status_code(status_code: u16) -> Self {
191        match status_code {
192            400..=499 => ErrorCategory::ClientError,
193            500..=599 => ErrorCategory::ServerError,
194            _ => ErrorCategory::Other,
195        }
196    }
197
198    /// Convert to string representation
199    pub fn as_str(&self) -> &'static str {
200        match self {
201            ErrorCategory::ClientError => "client_error",
202            ErrorCategory::ServerError => "server_error",
203            ErrorCategory::NetworkError => "network_error",
204            ErrorCategory::TimeoutError => "timeout_error",
205            ErrorCategory::Other => "other",
206        }
207    }
208}
209
210/// Client analytics data
211#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
212pub struct ClientAnalytics {
213    pub id: Option<i64>,
214    pub timestamp: i64,
215    pub client_ip: String,
216    pub user_agent: Option<String>,
217    pub user_agent_family: Option<String>,
218    pub user_agent_version: Option<String>,
219    pub protocol: String,
220    pub workspace_id: Option<String>,
221    pub environment: Option<String>,
222    pub request_count: i64,
223    pub error_count: i64,
224    pub avg_latency_ms: Option<f64>,
225    pub bytes_sent: i64,
226    pub bytes_received: i64,
227    pub top_endpoints: Option<String>, // JSON array
228    pub created_at: Option<i64>,
229}
230
231/// Traffic pattern data for heatmap visualization
232#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
233pub struct TrafficPattern {
234    pub id: Option<i64>,
235    pub date: String,
236    pub hour: i32,
237    pub day_of_week: i32,
238    pub protocol: String,
239    pub workspace_id: Option<String>,
240    pub environment: Option<String>,
241    pub request_count: i64,
242    pub error_count: i64,
243    pub avg_latency_ms: Option<f64>,
244    pub unique_clients: Option<i64>,
245    pub created_at: Option<i64>,
246}
247
248/// Analytics snapshot for comparison and trending
249#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
250pub struct AnalyticsSnapshot {
251    pub id: Option<i64>,
252    pub timestamp: i64,
253    pub snapshot_type: String,
254    pub total_requests: i64,
255    pub total_errors: i64,
256    pub avg_latency_ms: Option<f64>,
257    pub active_connections: Option<i64>,
258    pub protocol_stats: Option<String>, // JSON
259    pub top_endpoints: Option<String>,  // JSON array
260    pub memory_usage_bytes: Option<i64>,
261    pub cpu_usage_percent: Option<f64>,
262    pub thread_count: Option<i32>,
263    pub uptime_seconds: Option<i64>,
264    pub workspace_id: Option<String>,
265    pub environment: Option<String>,
266    pub created_at: Option<i64>,
267}
268
269/// Query filter for analytics queries
270#[derive(Debug, Clone, Default, Serialize, Deserialize)]
271pub struct AnalyticsFilter {
272    pub start_time: Option<i64>,
273    pub end_time: Option<i64>,
274    pub protocol: Option<String>,
275    pub endpoint: Option<String>,
276    pub method: Option<String>,
277    pub status_code: Option<i32>,
278    pub workspace_id: Option<String>,
279    pub environment: Option<String>,
280    pub limit: Option<i64>,
281}
282
283/// Overview metrics for the dashboard
284#[derive(Debug, Clone, Serialize, Deserialize)]
285pub struct OverviewMetrics {
286    pub total_requests: i64,
287    pub total_errors: i64,
288    pub error_rate: f64,
289    pub avg_latency_ms: f64,
290    pub p95_latency_ms: f64,
291    pub p99_latency_ms: f64,
292    pub active_connections: i64,
293    pub total_bytes_sent: i64,
294    pub total_bytes_received: i64,
295    pub requests_per_second: f64,
296    pub top_protocols: Vec<ProtocolStat>,
297    pub top_endpoints: Vec<EndpointStat>,
298}
299
300/// Protocol statistics
301#[derive(Debug, Clone, Serialize, Deserialize)]
302pub struct ProtocolStat {
303    pub protocol: String,
304    pub request_count: i64,
305    pub error_count: i64,
306    pub avg_latency_ms: f64,
307}
308
309/// Endpoint statistics summary
310#[derive(Debug, Clone, Serialize, Deserialize)]
311pub struct EndpointStat {
312    pub endpoint: String,
313    pub protocol: String,
314    pub method: Option<String>,
315    pub request_count: i64,
316    pub error_count: i64,
317    pub error_rate: f64,
318    pub avg_latency_ms: f64,
319    pub p95_latency_ms: f64,
320}
321
322/// Time series data point
323#[derive(Debug, Clone, Serialize, Deserialize)]
324pub struct TimeSeriesPoint {
325    pub timestamp: i64,
326    pub value: f64,
327}
328
329/// Time series with metadata
330#[derive(Debug, Clone, Serialize, Deserialize)]
331pub struct TimeSeries {
332    pub label: String,
333    pub data: Vec<TimeSeriesPoint>,
334}
335
336/// Latency percentiles over time
337#[derive(Debug, Clone, Serialize, Deserialize)]
338pub struct LatencyTrend {
339    pub timestamp: i64,
340    pub p50: f64,
341    pub p95: f64,
342    pub p99: f64,
343    pub avg: f64,
344    pub min: f64,
345    pub max: f64,
346}
347
348/// Error summary
349#[derive(Debug, Clone, Serialize, Deserialize)]
350pub struct ErrorSummary {
351    pub error_type: String,
352    pub error_category: String,
353    pub count: i64,
354    pub endpoints: Vec<String>,
355    pub last_occurrence: DateTime<Utc>,
356}
357
358/// Export format
359#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
360#[serde(rename_all = "lowercase")]
361pub enum ExportFormat {
362    Csv,
363    Json,
364}