1use serde::{Deserialize, Serialize};
4use std::collections::VecDeque;
5use std::time::Instant;
6
7use crate::profiler::Distribution;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct SizeStats {
12 pub count: u64,
14 pub total_bytes: u64,
16 pub min_bytes: u64,
18 pub max_bytes: u64,
20 pub p50_bytes: f64,
22 pub p95_bytes: f64,
24 pub p99_bytes: f64,
26}
27
28impl Default for SizeStats {
29 fn default() -> Self {
30 Self {
31 count: 0,
32 total_bytes: 0,
33 min_bytes: u64::MAX,
34 max_bytes: 0,
35 p50_bytes: 0.0,
36 p95_bytes: 0.0,
37 p99_bytes: 0.0,
38 }
39 }
40}
41
42impl SizeStats {
43 pub fn avg_bytes(&self) -> f64 {
45 if self.count == 0 {
46 0.0
47 } else {
48 self.total_bytes as f64 / self.count as f64
49 }
50 }
51
52 pub fn from_distribution(dist: &Distribution, total_bytes: u64) -> Self {
54 let (p50, p95, p99) = dist.percentiles();
55 Self {
56 count: dist.count() as u64,
57 total_bytes,
58 min_bytes: dist.min() as u64,
59 max_bytes: dist.max() as u64,
60 p50_bytes: p50,
61 p95_bytes: p95,
62 p99_bytes: p99,
63 }
64 }
65}
66
67#[derive(Debug, Clone)]
69pub struct PayloadWindow {
70 pub start: Instant,
72 pub end: Instant,
74 pub request_bytes: u64,
76 pub response_bytes: u64,
78 pub request_count: u64,
80}
81
82impl PayloadWindow {
83 pub fn new(duration_ms: u64) -> Self {
85 let now = Instant::now();
86 Self {
87 start: now,
88 end: now + std::time::Duration::from_millis(duration_ms),
89 request_bytes: 0,
90 response_bytes: 0,
91 request_count: 0,
92 }
93 }
94
95 pub fn is_expired(&self) -> bool {
97 Instant::now() >= self.end
98 }
99
100 pub fn record(&mut self, request_bytes: u64, response_bytes: u64) {
102 self.request_bytes += request_bytes;
103 self.response_bytes += response_bytes;
104 self.request_count += 1;
105 }
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct PayloadWindowSnapshot {
111 pub start_ms: i64,
112 pub end_ms: i64,
113 pub request_bytes: u64,
114 pub response_bytes: u64,
115 pub request_count: u64,
116}
117
118pub struct EndpointPayloadStats {
120 pub template: String,
122 pub request_dist: Distribution,
124 pub response_dist: Distribution,
126 pub total_request_bytes: u64,
128 pub total_response_bytes: u64,
130 pub windows: VecDeque<PayloadWindow>,
132 pub current_window: PayloadWindow,
134 window_duration_ms: u64,
136 max_windows: usize,
138 pub first_seen: Instant,
140 pub last_seen: Instant,
142 pub access_count: u64,
144}
145
146impl EndpointPayloadStats {
147 pub fn new(template: String, window_duration_ms: u64, max_windows: usize) -> Self {
149 let now = Instant::now();
150 Self {
151 template,
152 request_dist: Distribution::new(),
153 response_dist: Distribution::new(),
154 total_request_bytes: 0,
155 total_response_bytes: 0,
156 windows: VecDeque::with_capacity(max_windows),
157 current_window: PayloadWindow::new(window_duration_ms),
158 window_duration_ms,
159 max_windows,
160 first_seen: now,
161 last_seen: now,
162 access_count: 0,
163 }
164 }
165
166 pub fn record(&mut self, request_bytes: u64, response_bytes: u64) {
168 self.last_seen = Instant::now();
169 self.access_count += 1;
170
171 self.request_dist.update(request_bytes as f64);
173 self.response_dist.update(response_bytes as f64);
174
175 self.total_request_bytes += request_bytes;
177 self.total_response_bytes += response_bytes;
178
179 if self.current_window.is_expired() {
181 self.rotate_window();
182 }
183
184 self.current_window.record(request_bytes, response_bytes);
186 }
187
188 fn rotate_window(&mut self) {
190 let old_window = std::mem::replace(
191 &mut self.current_window,
192 PayloadWindow::new(self.window_duration_ms),
193 );
194 self.windows.push_back(old_window);
195
196 while self.windows.len() > self.max_windows {
198 self.windows.pop_front();
199 }
200 }
201
202 pub fn request_stats(&self) -> SizeStats {
204 SizeStats::from_distribution(&self.request_dist, self.total_request_bytes)
205 }
206
207 pub fn response_stats(&self) -> SizeStats {
209 SizeStats::from_distribution(&self.response_dist, self.total_response_bytes)
210 }
211
212 pub fn request_count(&self) -> u64 {
214 self.request_dist.count() as u64
215 }
216
217 pub fn bytes_per_minute(&self) -> (u64, u64) {
219 if self.windows.is_empty() {
220 return (0, 0);
221 }
222
223 let mut total_request = 0u64;
224 let mut total_response = 0u64;
225
226 for window in &self.windows {
227 total_request += window.request_bytes;
228 total_response += window.response_bytes;
229 }
230
231 let count = self.windows.len() as u64;
233 (total_request / count, total_response / count)
234 }
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize)]
239pub struct EndpointPayloadStatsSnapshot {
240 pub template: String,
241 pub request: SizeStats,
242 pub response: SizeStats,
243 pub request_count: u64,
244 pub first_seen_ms: i64,
245 pub last_seen_ms: i64,
246}
247
248impl From<&EndpointPayloadStats> for EndpointPayloadStatsSnapshot {
249 fn from(stats: &EndpointPayloadStats) -> Self {
250 let now = chrono::Utc::now().timestamp_millis();
251 let first_elapsed = stats.first_seen.elapsed().as_millis() as i64;
252 let last_elapsed = stats.last_seen.elapsed().as_millis() as i64;
253
254 Self {
255 template: stats.template.clone(),
256 request: stats.request_stats(),
257 response: stats.response_stats(),
258 request_count: stats.request_count(),
259 first_seen_ms: now - first_elapsed,
260 last_seen_ms: now - last_elapsed,
261 }
262 }
263}