1use hdrhistogram::Histogram;
4use serde::{Deserialize, Serialize};
5use std::time::{Duration, Instant};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct LatencyStats {
10 pub min: Duration,
11 pub max: Duration,
12 pub mean: Duration,
13 pub p50: Duration,
14 pub p95: Duration,
15 pub p99: Duration,
16 pub p999: Duration,
17}
18
19impl Default for LatencyStats {
20 fn default() -> Self {
21 Self {
22 min: Duration::from_millis(0),
23 max: Duration::from_millis(0),
24 mean: Duration::from_millis(0),
25 p50: Duration::from_millis(0),
26 p95: Duration::from_millis(0),
27 p99: Duration::from_millis(0),
28 p999: Duration::from_millis(0),
29 }
30 }
31}
32
33#[derive(Debug, Clone)]
35pub struct LoadTestMetrics {
36 pub total_requests: u64,
38
39 pub successful_requests: u64,
41
42 pub failed_requests: u64,
44
45 pub bytes_sent: u64,
47
48 pub bytes_received: u64,
50
51 pub start_time: Option<Instant>,
53
54 pub end_time: Option<Instant>,
56
57 latency_histogram: Histogram<u64>,
59
60 pub errors: Vec<String>,
62}
63
64impl LoadTestMetrics {
65 pub fn new() -> Self {
67 Self {
68 total_requests: 0,
69 successful_requests: 0,
70 failed_requests: 0,
71 bytes_sent: 0,
72 bytes_received: 0,
73 start_time: None,
74 end_time: None,
75 latency_histogram: Histogram::new(3).expect("Failed to create histogram"),
76 errors: Vec::new(),
77 }
78 }
79
80 pub fn record_success(&mut self, latency: Duration, bytes_sent: usize, bytes_received: usize) {
82 self.total_requests += 1;
83 self.successful_requests += 1;
84 self.bytes_sent += bytes_sent as u64;
85 self.bytes_received += bytes_received as u64;
86
87 let latency_us = latency.as_micros() as u64;
88 let _ = self.latency_histogram.record(latency_us);
89 }
90
91 pub fn record_failure(&mut self, error: String) {
93 self.total_requests += 1;
94 self.failed_requests += 1;
95 if self.errors.len() < 100 {
96 self.errors.push(error);
97 }
98 }
99
100 pub fn mark_started(&mut self) {
102 self.start_time = Some(Instant::now());
103 }
104
105 pub fn mark_completed(&mut self) {
107 self.end_time = Some(Instant::now());
108 }
109
110 pub fn duration(&self) -> Option<Duration> {
112 match (self.start_time, self.end_time) {
113 (Some(start), Some(end)) => Some(end.duration_since(start)),
114 (Some(start), None) => Some(Instant::now().duration_since(start)),
115 _ => None,
116 }
117 }
118
119 pub fn requests_per_second(&self) -> f64 {
121 if let Some(duration) = self.duration() {
122 let secs = duration.as_secs_f64();
123 if secs > 0.0 {
124 return self.total_requests as f64 / secs;
125 }
126 }
127 0.0
128 }
129
130 pub fn success_rate(&self) -> f64 {
132 if self.total_requests > 0 {
133 self.successful_requests as f64 / self.total_requests as f64
134 } else {
135 0.0
136 }
137 }
138
139 pub fn latency_stats(&self) -> LatencyStats {
141 if self.latency_histogram.is_empty() {
142 return LatencyStats::default();
143 }
144
145 LatencyStats {
146 min: Duration::from_micros(self.latency_histogram.min()),
147 max: Duration::from_micros(self.latency_histogram.max()),
148 mean: Duration::from_micros(self.latency_histogram.mean() as u64),
149 p50: Duration::from_micros(self.latency_histogram.value_at_quantile(0.50)),
150 p95: Duration::from_micros(self.latency_histogram.value_at_quantile(0.95)),
151 p99: Duration::from_micros(self.latency_histogram.value_at_quantile(0.99)),
152 p999: Duration::from_micros(self.latency_histogram.value_at_quantile(0.999)),
153 }
154 }
155
156 pub fn print_summary(&self) {
158 println!("\n=== Load Test Results ===\n");
159
160 if let Some(duration) = self.duration() {
161 println!("Duration: {:.2}s", duration.as_secs_f64());
162 }
163
164 println!("Total Requests: {}", self.total_requests);
165 println!("Successful: {}", self.successful_requests);
166 println!("Failed: {}", self.failed_requests);
167 println!("Success Rate: {:.2}%", self.success_rate() * 100.0);
168 println!("Throughput: {:.2} req/s", self.requests_per_second());
169
170 println!("\nData Transfer:");
171 println!(
172 " Sent: {} bytes ({:.2} MB)",
173 self.bytes_sent,
174 self.bytes_sent as f64 / 1_000_000.0
175 );
176 println!(
177 " Received: {} bytes ({:.2} MB)",
178 self.bytes_received,
179 self.bytes_received as f64 / 1_000_000.0
180 );
181
182 let stats = self.latency_stats();
183 println!("\nLatency:");
184 println!(" Min: {:?}", stats.min);
185 println!(" Mean: {:?}", stats.mean);
186 println!(" Max: {:?}", stats.max);
187 println!(" p50: {:?}", stats.p50);
188 println!(" p95: {:?}", stats.p95);
189 println!(" p99: {:?}", stats.p99);
190 println!(" p99.9: {:?}", stats.p999);
191
192 if !self.errors.is_empty() {
193 println!("\nFirst {} Errors:", self.errors.len().min(10));
194 for (i, error) in self.errors.iter().take(10).enumerate() {
195 println!(" {}: {}", i + 1, error);
196 }
197 }
198 }
199}
200
201impl Default for LoadTestMetrics {
202 fn default() -> Self {
203 Self::new()
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn test_new_metrics() {
213 let metrics = LoadTestMetrics::new();
214 assert_eq!(metrics.total_requests, 0);
215 assert_eq!(metrics.successful_requests, 0);
216 assert_eq!(metrics.failed_requests, 0);
217 }
218
219 #[test]
220 fn test_record_success() {
221 let mut metrics = LoadTestMetrics::new();
222 metrics.record_success(Duration::from_millis(10), 100, 50);
223
224 assert_eq!(metrics.total_requests, 1);
225 assert_eq!(metrics.successful_requests, 1);
226 assert_eq!(metrics.bytes_sent, 100);
227 assert_eq!(metrics.bytes_received, 50);
228 }
229
230 #[test]
231 fn test_record_failure() {
232 let mut metrics = LoadTestMetrics::new();
233 metrics.record_failure("Test error".to_string());
234
235 assert_eq!(metrics.total_requests, 1);
236 assert_eq!(metrics.failed_requests, 1);
237 assert_eq!(metrics.errors.len(), 1);
238 }
239
240 #[test]
241 fn test_success_rate() {
242 let mut metrics = LoadTestMetrics::new();
243 metrics.record_success(Duration::from_millis(10), 100, 50);
244 metrics.record_success(Duration::from_millis(10), 100, 50);
245 metrics.record_failure("Error".to_string());
246
247 assert_eq!(metrics.success_rate(), 2.0 / 3.0);
248 }
249
250 #[test]
251 fn test_latency_stats() {
252 let mut metrics = LoadTestMetrics::new();
253 metrics.record_success(Duration::from_millis(10), 100, 50);
254 metrics.record_success(Duration::from_millis(20), 100, 50);
255 metrics.record_success(Duration::from_millis(30), 100, 50);
256
257 let stats = metrics.latency_stats();
258 assert!(stats.min.as_millis() >= 10);
259 assert!(stats.max.as_millis() >= 30);
260 }
261}