Skip to main content

trueno/simulation/
stress.rs

1//! Stress Testing (Heijunka: Leveled Workload Testing)
2//!
3//! Provides configurable stress testing infrastructure for validating
4//! compute operations under various load conditions.
5
6use crate::Backend;
7
8/// Stress test configuration for trueno operations
9#[derive(Debug, Clone)]
10pub struct StressTestConfig {
11    /// Number of cycles per backend
12    pub cycles_per_backend: u32,
13    /// Input sizes to test (leveled)
14    pub input_sizes: Vec<usize>,
15    /// Backends to stress test
16    pub backends: Vec<Backend>,
17    /// Performance thresholds
18    pub thresholds: StressThresholds,
19    /// Master seed for RNG
20    pub master_seed: u64,
21}
22
23impl Default for StressTestConfig {
24    fn default() -> Self {
25        Self {
26            cycles_per_backend: 100,
27            input_sizes: vec![100, 1_000, 10_000, 100_000, 1_000_000],
28            backends: vec![Backend::Scalar, Backend::AVX2],
29            thresholds: StressThresholds::default(),
30            master_seed: 42,
31        }
32    }
33}
34
35impl StressTestConfig {
36    /// Create new stress test config
37    #[must_use]
38    pub fn new(master_seed: u64) -> Self {
39        Self { master_seed, ..Default::default() }
40    }
41
42    /// Set cycles per backend
43    #[must_use]
44    pub const fn with_cycles(mut self, cycles: u32) -> Self {
45        self.cycles_per_backend = cycles;
46        self
47    }
48
49    /// Set input sizes
50    #[must_use]
51    pub fn with_input_sizes(mut self, sizes: Vec<usize>) -> Self {
52        self.input_sizes = sizes;
53        self
54    }
55
56    /// Set backends to test
57    #[must_use]
58    pub fn with_backends(mut self, backends: Vec<Backend>) -> Self {
59        self.backends = backends;
60        self
61    }
62
63    /// Set performance thresholds
64    #[must_use]
65    pub fn with_thresholds(mut self, thresholds: StressThresholds) -> Self {
66        self.thresholds = thresholds;
67        self
68    }
69
70    /// Calculate total test count
71    #[must_use]
72    pub fn total_tests(&self) -> usize {
73        self.backends.len() * self.input_sizes.len() * self.cycles_per_backend as usize
74    }
75}
76
77/// Performance thresholds for stress testing
78#[derive(Debug, Clone)]
79pub struct StressThresholds {
80    /// Max time per operation (ms)
81    pub max_op_time_ms: u64,
82    /// Max memory per operation (bytes)
83    pub max_memory_bytes: usize,
84    /// Max variance in operation times (coefficient of variation)
85    pub max_timing_variance: f64,
86    /// Max allowed failure rate (0.0 to 1.0)
87    pub max_failure_rate: f64,
88}
89
90impl Default for StressThresholds {
91    fn default() -> Self {
92        Self {
93            max_op_time_ms: 1000,                // 1s max per op
94            max_memory_bytes: 256 * 1024 * 1024, // 256MB max
95            max_timing_variance: 0.5,            // 50% max variance
96            max_failure_rate: 0.0,               // Zero failures allowed
97        }
98    }
99}
100
101impl StressThresholds {
102    /// Strict thresholds for CI
103    #[must_use]
104    pub const fn strict() -> Self {
105        Self {
106            max_op_time_ms: 100,
107            max_memory_bytes: 64 * 1024 * 1024,
108            max_timing_variance: 0.2,
109            max_failure_rate: 0.0,
110        }
111    }
112
113    /// Maximum operation time for development (5 seconds)
114    const RELAXED_MAX_OP_TIME_MS: u64 = 5000;
115
116    /// Relaxed thresholds for development
117    #[must_use]
118    pub const fn relaxed() -> Self {
119        Self {
120            max_op_time_ms: Self::RELAXED_MAX_OP_TIME_MS,
121            max_memory_bytes: 512 * 1024 * 1024,
122            max_timing_variance: 1.0,
123            max_failure_rate: 0.01,
124        }
125    }
126}
127
128/// Stress test result for a single operation
129#[derive(Debug, Clone)]
130pub struct StressResult {
131    /// Backend used
132    pub backend: Backend,
133    /// Input size
134    pub input_size: usize,
135    /// Cycles completed
136    pub cycles_completed: u32,
137    /// Total tests passed
138    pub tests_passed: u32,
139    /// Total tests failed
140    pub tests_failed: u32,
141    /// Mean operation time (ms)
142    pub mean_op_time_ms: f64,
143    /// Max operation time (ms)
144    pub max_op_time_ms: u64,
145    /// Timing variance (coefficient of variation)
146    pub timing_variance: f64,
147    /// Detected anomalies
148    pub anomalies: Vec<StressAnomaly>,
149}
150
151impl StressResult {
152    /// Check if all tests passed
153    #[must_use]
154    pub fn passed(&self) -> bool {
155        self.tests_failed == 0 && self.anomalies.is_empty()
156    }
157
158    /// Calculate pass rate
159    #[must_use]
160    pub fn pass_rate(&self) -> f64 {
161        let total = self.tests_passed + self.tests_failed;
162        if total == 0 {
163            1.0
164        } else {
165            self.tests_passed as f64 / total as f64
166        }
167    }
168}
169
170/// Anomaly detected during stress testing
171#[derive(Debug, Clone)]
172pub struct StressAnomaly {
173    /// Cycle where anomaly was detected
174    pub cycle: u32,
175    /// Type of anomaly
176    pub kind: StressAnomalyKind,
177    /// Description
178    pub description: String,
179}
180
181/// Types of stress test anomalies
182#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub enum StressAnomalyKind {
184    /// Operation too slow
185    SlowOperation,
186    /// High memory usage
187    HighMemory,
188    /// Test failure
189    TestFailure,
190    /// Timing spike
191    TimingSpike,
192    /// Non-deterministic output
193    NonDeterministic,
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn test_stress_test_config_default() {
202        let config = StressTestConfig::default();
203
204        assert_eq!(config.cycles_per_backend, 100);
205        assert_eq!(config.input_sizes.len(), 5);
206        assert_eq!(config.backends.len(), 2);
207        assert_eq!(config.master_seed, 42);
208    }
209
210    #[test]
211    fn test_stress_test_config_builder() {
212        let config = StressTestConfig::new(123)
213            .with_cycles(50)
214            .with_input_sizes(vec![100, 1000])
215            .with_backends(vec![Backend::Scalar])
216            .with_thresholds(StressThresholds::strict());
217
218        assert_eq!(config.master_seed, 123);
219        assert_eq!(config.cycles_per_backend, 50);
220        assert_eq!(config.input_sizes.len(), 2);
221        assert_eq!(config.backends.len(), 1);
222    }
223
224    #[test]
225    fn test_stress_test_config_total_tests() {
226        let config = StressTestConfig::default()
227            .with_cycles(10)
228            .with_input_sizes(vec![100, 1000, 10000])
229            .with_backends(vec![Backend::Scalar, Backend::AVX2]);
230
231        // 2 backends * 3 sizes * 10 cycles = 60 tests
232        assert_eq!(config.total_tests(), 60);
233    }
234
235    #[test]
236    fn test_stress_thresholds_default() {
237        let thresholds = StressThresholds::default();
238
239        assert_eq!(thresholds.max_op_time_ms, 1000);
240        assert_eq!(thresholds.max_memory_bytes, 256 * 1024 * 1024);
241        assert!((thresholds.max_timing_variance - 0.5).abs() < 0.001);
242        assert_eq!(thresholds.max_failure_rate, 0.0);
243    }
244
245    #[test]
246    fn test_stress_thresholds_strict() {
247        let thresholds = StressThresholds::strict();
248
249        assert_eq!(thresholds.max_op_time_ms, 100);
250        assert_eq!(thresholds.max_memory_bytes, 64 * 1024 * 1024);
251        assert!((thresholds.max_timing_variance - 0.2).abs() < 0.001);
252    }
253
254    #[test]
255    fn test_stress_thresholds_relaxed() {
256        let thresholds = StressThresholds::relaxed();
257
258        assert_eq!(thresholds.max_op_time_ms, 5000);
259        assert_eq!(thresholds.max_memory_bytes, 512 * 1024 * 1024);
260        assert!((thresholds.max_timing_variance - 1.0).abs() < 0.001);
261    }
262
263    #[test]
264    fn test_stress_result_passed() {
265        let result = StressResult {
266            backend: Backend::Scalar,
267            input_size: 1000,
268            cycles_completed: 10,
269            tests_passed: 100,
270            tests_failed: 0,
271            mean_op_time_ms: 50.0,
272            max_op_time_ms: 100,
273            timing_variance: 0.1,
274            anomalies: vec![],
275        };
276
277        assert!(result.passed());
278        assert_eq!(result.pass_rate(), 1.0);
279    }
280
281    #[test]
282    fn test_stress_result_failed() {
283        let result = StressResult {
284            backend: Backend::AVX2,
285            input_size: 10000,
286            cycles_completed: 10,
287            tests_passed: 95,
288            tests_failed: 5,
289            mean_op_time_ms: 100.0,
290            max_op_time_ms: 500,
291            timing_variance: 0.3,
292            anomalies: vec![],
293        };
294
295        assert!(!result.passed()); // Failed because tests_failed > 0
296        assert!((result.pass_rate() - 0.95).abs() < 0.001);
297    }
298
299    #[test]
300    fn test_stress_result_with_anomaly() {
301        let result = StressResult {
302            backend: Backend::Scalar,
303            input_size: 1000,
304            cycles_completed: 10,
305            tests_passed: 100,
306            tests_failed: 0,
307            mean_op_time_ms: 50.0,
308            max_op_time_ms: 100,
309            timing_variance: 0.1,
310            anomalies: vec![StressAnomaly {
311                cycle: 5,
312                kind: StressAnomalyKind::SlowOperation,
313                description: "Operation took 200ms".to_string(),
314            }],
315        };
316
317        assert!(!result.passed()); // Failed because anomalies not empty
318    }
319
320    #[test]
321    fn test_stress_anomaly_kinds() {
322        assert_eq!(StressAnomalyKind::SlowOperation, StressAnomalyKind::SlowOperation);
323        assert_ne!(StressAnomalyKind::SlowOperation, StressAnomalyKind::TestFailure);
324
325        // Test all variants exist
326        let _ = StressAnomalyKind::SlowOperation;
327        let _ = StressAnomalyKind::HighMemory;
328        let _ = StressAnomalyKind::TestFailure;
329        let _ = StressAnomalyKind::TimingSpike;
330        let _ = StressAnomalyKind::NonDeterministic;
331    }
332
333    #[test]
334    fn test_stress_result_zero_tests() {
335        let result = StressResult {
336            backend: Backend::Scalar,
337            input_size: 0,
338            cycles_completed: 0,
339            tests_passed: 0,
340            tests_failed: 0,
341            mean_op_time_ms: 0.0,
342            max_op_time_ms: 0,
343            timing_variance: 0.0,
344            anomalies: vec![],
345        };
346
347        // Zero tests should still pass with pass_rate of 1.0
348        assert!(result.passed());
349        assert_eq!(result.pass_rate(), 1.0);
350    }
351}