Skip to main content

rusmes_loadtest/
metrics.rs

1//! Load test metrics collection and reporting
2
3use hdrhistogram::Histogram;
4use serde::{Deserialize, Serialize};
5use std::time::{Duration, Instant};
6
7/// Latency statistics
8#[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/// Load test metrics
34#[derive(Debug, Clone)]
35pub struct LoadTestMetrics {
36    /// Total requests sent
37    pub total_requests: u64,
38
39    /// Successful requests
40    pub successful_requests: u64,
41
42    /// Failed requests
43    pub failed_requests: u64,
44
45    /// Total bytes sent
46    pub bytes_sent: u64,
47
48    /// Total bytes received
49    pub bytes_received: u64,
50
51    /// Start time
52    pub start_time: Option<Instant>,
53
54    /// End time
55    pub end_time: Option<Instant>,
56
57    /// Latency histogram (in microseconds)
58    latency_histogram: Histogram<u64>,
59
60    /// Error messages
61    pub errors: Vec<String>,
62}
63
64impl LoadTestMetrics {
65    /// Create new metrics
66    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    /// Record a successful request
81    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    /// Record a failed request
92    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    /// Mark test as started
101    pub fn mark_started(&mut self) {
102        self.start_time = Some(Instant::now());
103    }
104
105    /// Mark test as completed
106    pub fn mark_completed(&mut self) {
107        self.end_time = Some(Instant::now());
108    }
109
110    /// Get test duration
111    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    /// Get requests per second
120    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    /// Get success rate
131    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    /// Get latency statistics
140    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    /// Print summary report
157    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}