Skip to main content

trueno_gpu/testing/stress/
runner.rs

1//! Stress test runner and configuration.
2
3use std::time::{Duration, Instant};
4
5use super::rng::StressRng;
6use super::types::{
7    verify_performance, Anomaly, AnomalyKind, FrameProfile, PerformanceResult,
8    PerformanceThresholds, StressReport,
9};
10
11/// Stress test configuration
12#[derive(Debug, Clone)]
13pub struct StressConfig {
14    /// Number of cycles to run
15    pub cycles: u32,
16    /// Interval between cycles (ms)
17    pub interval_ms: u64,
18    /// Base seed for RNG
19    pub seed: u64,
20    /// Min input size
21    pub min_input_size: usize,
22    /// Max input size
23    pub max_input_size: usize,
24    /// Performance thresholds
25    pub thresholds: PerformanceThresholds,
26}
27
28impl Default for StressConfig {
29    fn default() -> Self {
30        Self {
31            cycles: 100,
32            interval_ms: 100,
33            seed: 42,
34            min_input_size: 64,
35            max_input_size: 512,
36            thresholds: PerformanceThresholds::default(),
37        }
38    }
39}
40
41/// Stress test runner
42pub struct StressTestRunner {
43    rng: StressRng,
44    config: StressConfig,
45    report: StressReport,
46}
47
48impl StressTestRunner {
49    /// Create new stress test runner
50    #[must_use]
51    pub fn new(config: StressConfig) -> Self {
52        Self {
53            rng: StressRng::new(config.seed),
54            config,
55            report: StressReport::default(),
56        }
57    }
58
59    /// Generate randomized input for a cycle
60    pub fn generate_input(&mut self) -> (u64, Vec<f32>) {
61        let seed = self.rng.next_u64();
62        let size = self.rng.gen_range_u32(
63            self.config.min_input_size as u32,
64            self.config.max_input_size as u32,
65        ) as usize;
66
67        let mut input_rng = StressRng::new(seed);
68        let input: Vec<f32> = (0..size).map(|_| input_rng.gen_f32()).collect();
69
70        (seed, input)
71    }
72
73    /// Run a single cycle with provided test function
74    pub fn run_cycle<F>(&mut self, cycle: u32, test_fn: F) -> FrameProfile
75    where
76        F: FnOnce(&[f32]) -> (u32, u32), // Returns (passed, failed)
77    {
78        let (input_seed, input) = self.generate_input();
79        let input_size = input.len();
80
81        let start = Instant::now();
82        let (tests_passed, tests_failed) = test_fn(&input);
83        let duration = start.elapsed();
84
85        let profile = FrameProfile {
86            cycle,
87            duration_ms: duration.as_millis() as u64,
88            memory_bytes: input_size * std::mem::size_of::<f32>(),
89            tests_passed,
90            tests_failed,
91            input_seed,
92            input_size,
93        };
94
95        // Check for anomalies
96        if profile.duration_ms > self.config.thresholds.max_frame_time_ms {
97            self.report.anomalies.push(Anomaly {
98                cycle,
99                kind: AnomalyKind::SlowFrame,
100                description: format!(
101                    "Frame {}ms exceeds threshold {}ms",
102                    profile.duration_ms, self.config.thresholds.max_frame_time_ms
103                ),
104            });
105        }
106
107        if tests_failed > 0 {
108            self.report.anomalies.push(Anomaly {
109                cycle,
110                kind: AnomalyKind::TestFailure,
111                description: format!("{} tests failed in cycle {}", tests_failed, cycle),
112            });
113        }
114
115        self.report.add_frame(profile.clone());
116        profile
117    }
118
119    /// Run all cycles
120    pub fn run_all<F>(&mut self, mut test_fn: F) -> &StressReport
121    where
122        F: FnMut(&[f32]) -> (u32, u32),
123    {
124        let interval = Duration::from_millis(self.config.interval_ms);
125
126        for cycle in 0..self.config.cycles {
127            let start = Instant::now();
128            self.run_cycle(cycle, &mut test_fn);
129
130            let elapsed = start.elapsed();
131            if let Some(remaining) = interval.checked_sub(elapsed) {
132                std::thread::sleep(remaining);
133            }
134        }
135
136        &self.report
137    }
138
139    /// Get the current report
140    #[must_use]
141    pub fn report(&self) -> &StressReport {
142        &self.report
143    }
144
145    /// Verify performance and return result
146    #[must_use]
147    pub fn verify(&self) -> PerformanceResult {
148        verify_performance(&self.report, &self.config.thresholds)
149    }
150}