trueno_gpu/testing/stress/
runner.rs1use 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#[derive(Debug, Clone)]
13pub struct StressConfig {
14 pub cycles: u32,
16 pub interval_ms: u64,
18 pub seed: u64,
20 pub min_input_size: usize,
22 pub max_input_size: usize,
24 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
41pub struct StressTestRunner {
43 rng: StressRng,
44 config: StressConfig,
45 report: StressReport,
46}
47
48impl StressTestRunner {
49 #[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 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 pub fn run_cycle<F>(&mut self, cycle: u32, test_fn: F) -> FrameProfile
75 where
76 F: FnOnce(&[f32]) -> (u32, u32), {
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 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 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 #[must_use]
141 pub fn report(&self) -> &StressReport {
142 &self.report
143 }
144
145 #[must_use]
147 pub fn verify(&self) -> PerformanceResult {
148 verify_performance(&self.report, &self.config.thresholds)
149 }
150}