Coverage Report

Created: 2026-01-25 15:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/noah/src/realizar/src/bench/load_testing.rs
Line
Count
Source
1
//! Load testing framework for stress testing inference servers
2
//!
3
//! Extracted from bench/mod.rs (PMAT-802) to reduce module size.
4
//! Contains:
5
//! - LoadTestConfig for wrk2-style load testing configuration
6
//! - LoadTestResult for test outcomes
7
//! - LoadTestRunner for executing load tests
8
9
#![allow(clippy::cast_precision_loss)]
10
11
use serde::{Deserialize, Serialize};
12
13
14
// ============================================================================
15
// Load Testing (Section 14.1)
16
// ============================================================================
17
18
/// Configuration for load testing
19
///
20
/// Per spec ยง14: Implements wrk2-style load testing with configurable
21
/// concurrency, duration, and target rates.
22
#[derive(Debug, Clone, Serialize, Deserialize)]
23
pub struct LoadTestConfig {
24
    /// Number of concurrent connections/threads
25
    pub concurrency: usize,
26
    /// Test duration in seconds
27
    pub duration_secs: u64,
28
    /// Target requests per second (0 = unlimited)
29
    pub target_rps: f64,
30
    /// Request timeout in milliseconds
31
    pub timeout_ms: u64,
32
    /// Warm-up period in seconds
33
    pub warmup_secs: u64,
34
    /// Target latency threshold (p99) in milliseconds
35
    pub latency_threshold_ms: f64,
36
}
37
38
impl Default for LoadTestConfig {
39
4
    fn default() -> Self {
40
4
        Self {
41
4
            concurrency: 10,
42
4
            duration_secs: 60,
43
4
            target_rps: 0.0, // Unlimited
44
4
            timeout_ms: 5000,
45
4
            warmup_secs: 5,
46
4
            latency_threshold_ms: 500.0, // Per spec: <500ms p99 target
47
4
        }
48
4
    }
49
}
50
51
impl LoadTestConfig {
52
    /// Create config for stress testing
53
    #[must_use]
54
1
    pub fn for_stress_test() -> Self {
55
1
        Self {
56
1
            concurrency: 100,
57
1
            duration_secs: 300,
58
1
            target_rps: 0.0,
59
1
            timeout_ms: 10_000,
60
1
            warmup_secs: 10,
61
1
            latency_threshold_ms: 1000.0,
62
1
        }
63
1
    }
64
65
    /// Create config for latency-focused testing
66
    #[must_use]
67
1
    pub fn for_latency_test() -> Self {
68
1
        Self {
69
1
            concurrency: 1,
70
1
            duration_secs: 60,
71
1
            target_rps: 10.0, // Fixed rate
72
1
            timeout_ms: 2000,
73
1
            warmup_secs: 5,
74
1
            latency_threshold_ms: 200.0,
75
1
        }
76
1
    }
77
78
    /// Validate the configuration
79
    #[must_use]
80
2
    pub fn is_valid(&self) -> bool {
81
2
        self.concurrency > 0
82
1
            && self.duration_secs > 0
83
1
            && self.timeout_ms > 0
84
1
            && self.latency_threshold_ms > 0.0
85
2
    }
86
}
87
88
/// Results from a load test run
89
#[derive(Debug, Clone, Serialize, Deserialize)]
90
pub struct LoadTestResult {
91
    /// Total requests made
92
    pub total_requests: usize,
93
    /// Successful requests
94
    pub successful_requests: usize,
95
    /// Failed requests
96
    pub failed_requests: usize,
97
    /// Requests per second (achieved)
98
    pub rps_achieved: f64,
99
    /// Latency percentiles in milliseconds
100
    pub latency_p50_ms: f64,
101
    /// Latency p95 in milliseconds
102
    pub latency_p95_ms: f64,
103
    /// Latency p99 in milliseconds
104
    pub latency_p99_ms: f64,
105
    /// Maximum latency in milliseconds
106
    pub latency_max_ms: f64,
107
    /// Total data transferred in bytes
108
    pub data_transferred_bytes: u64,
109
    /// Test duration in seconds
110
    pub duration_secs: f64,
111
    /// Error rate (0.0-1.0)
112
    pub error_rate: f64,
113
    /// Whether the test passed the latency threshold
114
    pub passed_latency_threshold: bool,
115
}
116
117
impl LoadTestResult {
118
    /// Check if the load test passed all thresholds
119
    #[must_use]
120
3
    pub fn is_passing(&self) -> bool {
121
3
        self.passed_latency_threshold && 
self.error_rate < 0.012
// <1% error rate
122
3
    }
123
124
    /// Calculate throughput in MB/s
125
    #[must_use]
126
1
    pub fn throughput_mbps(&self) -> f64 {
127
1
        if self.duration_secs > 0.0 {
128
1
            (self.data_transferred_bytes as f64 / 1_000_000.0) / self.duration_secs
129
        } else {
130
0
            0.0
131
        }
132
1
    }
133
}
134
135
/// Load test runner
136
#[derive(Debug)]
137
pub struct LoadTestRunner {
138
    config: LoadTestConfig,
139
}
140
141
impl LoadTestRunner {
142
    /// Create a new load test runner
143
    #[must_use]
144
1
    pub fn new(config: LoadTestConfig) -> Self {
145
1
        Self { config }
146
1
    }
147
148
    /// Get the configuration
149
    #[must_use]
150
0
    pub fn config(&self) -> &LoadTestConfig {
151
0
        &self.config
152
0
    }
153
154
    /// Simulate a load test run (for testing purposes)
155
    ///
156
    /// In production, this would make actual HTTP requests.
157
    #[must_use]
158
1
    pub fn simulate_run(&self) -> LoadTestResult {
159
        // Simulate based on configuration
160
1
        let total_requests =
161
1
            (self.config.concurrency as f64 * self.config.duration_secs as f64 * 10.0) as usize;
162
1
        let error_count = total_requests / 100; // 1% error rate
163
1
        let successful = total_requests - error_count;
164
165
        // Simulate latencies based on concurrency
166
        // Higher concurrency = higher latencies
167
1
        let base_latency = 20.0; // 20ms base
168
1
        let concurrency_factor = (self.config.concurrency as f64).ln();
169
170
1
        let p50 = base_latency + concurrency_factor * 5.0;
171
1
        let p95 = p50 * 2.5;
172
1
        let p99 = p50 * 4.0;
173
1
        let max = p99 * 2.0;
174
175
1
        let duration = self.config.duration_secs as f64;
176
1
        let rps = if duration > 0.0 {
177
1
            total_requests as f64 / duration
178
        } else {
179
0
            0.0
180
        };
181
182
1
        LoadTestResult {
183
1
            total_requests,
184
1
            successful_requests: successful,
185
1
            failed_requests: error_count,
186
1
            rps_achieved: rps,
187
1
            latency_p50_ms: p50,
188
1
            latency_p95_ms: p95,
189
1
            latency_p99_ms: p99,
190
1
            latency_max_ms: max,
191
1
            data_transferred_bytes: (total_requests * 1024) as u64, // ~1KB per request
192
1
            duration_secs: duration,
193
1
            error_rate: error_count as f64 / total_requests as f64,
194
1
            passed_latency_threshold: p99 < self.config.latency_threshold_ms,
195
1
        }
196
1
    }
197
}