Skip to main content

rusmes_loadtest/
workload.rs

1//! Workload patterns for load testing
2
3use std::time::{Duration, Instant};
4
5/// Workload pattern
6#[derive(Debug, Clone)]
7pub enum WorkloadPattern {
8    /// Steady load - constant rate
9    Steady { rate: u64 },
10
11    /// Spike test - sudden increase
12    Spike {
13        baseline: u64,
14        peak: u64,
15        spike_duration: Duration,
16        spike_start: Duration,
17    },
18
19    /// Ramp-up - gradual increase
20    RampUp {
21        start_rate: u64,
22        end_rate: u64,
23        duration: Duration,
24    },
25
26    /// Stress test - find breaking point
27    Stress {
28        start_rate: u64,
29        increment: u64,
30        interval: Duration,
31    },
32
33    /// Wave pattern - oscillating load
34    Wave {
35        min_rate: u64,
36        max_rate: u64,
37        period: Duration,
38    },
39}
40
41impl WorkloadPattern {
42    /// Get the target rate at a given time
43    pub fn rate_at(&self, elapsed: Duration) -> u64 {
44        match self {
45            WorkloadPattern::Steady { rate } => *rate,
46
47            WorkloadPattern::Spike {
48                baseline,
49                peak,
50                spike_duration,
51                spike_start,
52            } => {
53                if elapsed >= *spike_start && elapsed < *spike_start + *spike_duration {
54                    *peak
55                } else {
56                    *baseline
57                }
58            }
59
60            WorkloadPattern::RampUp {
61                start_rate,
62                end_rate,
63                duration,
64            } => {
65                if elapsed >= *duration {
66                    *end_rate
67                } else {
68                    let progress = elapsed.as_secs_f64() / duration.as_secs_f64();
69                    let rate_diff = *end_rate as f64 - *start_rate as f64;
70                    (*start_rate as f64 + rate_diff * progress) as u64
71                }
72            }
73
74            WorkloadPattern::Stress {
75                start_rate,
76                increment,
77                interval,
78            } => {
79                let intervals = elapsed.as_secs() / interval.as_secs();
80                *start_rate + (*increment * intervals)
81            }
82
83            WorkloadPattern::Wave {
84                min_rate,
85                max_rate,
86                period,
87            } => {
88                let progress =
89                    (elapsed.as_secs_f64() % period.as_secs_f64()) / period.as_secs_f64();
90                let amplitude = (*max_rate - *min_rate) as f64 / 2.0;
91                let center = (*min_rate + *max_rate) as f64 / 2.0;
92                let rate = center + amplitude * (progress * 2.0 * std::f64::consts::PI).sin();
93                rate as u64
94            }
95        }
96    }
97
98    /// Calculate delay between requests for the given rate
99    pub fn delay_for_rate(rate: u64) -> Duration {
100        if rate == 0 {
101            Duration::from_secs(1)
102        } else {
103            Duration::from_micros(1_000_000 / rate)
104        }
105    }
106}
107
108/// Workload controller
109pub struct WorkloadController {
110    pattern: WorkloadPattern,
111    start_time: Instant,
112}
113
114impl WorkloadController {
115    /// Create a new workload controller
116    pub fn new(pattern: WorkloadPattern) -> Self {
117        Self {
118            pattern,
119            start_time: Instant::now(),
120        }
121    }
122
123    /// Get current target rate
124    pub fn current_rate(&self) -> u64 {
125        let elapsed = self.start_time.elapsed();
126        self.pattern.rate_at(elapsed)
127    }
128
129    /// Get delay until next request
130    pub fn next_delay(&self) -> Duration {
131        let rate = self.current_rate();
132        WorkloadPattern::delay_for_rate(rate)
133    }
134
135    /// Reset the start time
136    pub fn reset(&mut self) {
137        self.start_time = Instant::now();
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn test_steady_workload() {
147        let pattern = WorkloadPattern::Steady { rate: 100 };
148        assert_eq!(pattern.rate_at(Duration::from_secs(0)), 100);
149        assert_eq!(pattern.rate_at(Duration::from_secs(10)), 100);
150        assert_eq!(pattern.rate_at(Duration::from_secs(100)), 100);
151    }
152
153    #[test]
154    fn test_spike_workload() {
155        let pattern = WorkloadPattern::Spike {
156            baseline: 100,
157            peak: 1000,
158            spike_duration: Duration::from_secs(10),
159            spike_start: Duration::from_secs(5),
160        };
161
162        assert_eq!(pattern.rate_at(Duration::from_secs(0)), 100);
163        assert_eq!(pattern.rate_at(Duration::from_secs(7)), 1000);
164        assert_eq!(pattern.rate_at(Duration::from_secs(20)), 100);
165    }
166
167    #[test]
168    fn test_rampup_workload() {
169        let pattern = WorkloadPattern::RampUp {
170            start_rate: 100,
171            end_rate: 1000,
172            duration: Duration::from_secs(10),
173        };
174
175        assert_eq!(pattern.rate_at(Duration::from_secs(0)), 100);
176        assert_eq!(pattern.rate_at(Duration::from_secs(5)), 550);
177        assert_eq!(pattern.rate_at(Duration::from_secs(10)), 1000);
178        assert_eq!(pattern.rate_at(Duration::from_secs(20)), 1000);
179    }
180
181    #[test]
182    fn test_stress_workload() {
183        let pattern = WorkloadPattern::Stress {
184            start_rate: 100,
185            increment: 50,
186            interval: Duration::from_secs(10),
187        };
188
189        assert_eq!(pattern.rate_at(Duration::from_secs(0)), 100);
190        assert_eq!(pattern.rate_at(Duration::from_secs(10)), 150);
191        assert_eq!(pattern.rate_at(Duration::from_secs(20)), 200);
192        assert_eq!(pattern.rate_at(Duration::from_secs(30)), 250);
193    }
194
195    #[test]
196    fn test_wave_workload() {
197        let pattern = WorkloadPattern::Wave {
198            min_rate: 100,
199            max_rate: 500,
200            period: Duration::from_secs(60),
201        };
202
203        let rate_0 = pattern.rate_at(Duration::from_secs(0));
204        let rate_15 = pattern.rate_at(Duration::from_secs(15));
205        let rate_30 = pattern.rate_at(Duration::from_secs(30));
206
207        // At 0 seconds, sine wave is at 0 (center)
208        assert!((rate_0 as i64 - 300).abs() < 10);
209        // At 15 seconds (quarter period), sine wave is at peak
210        assert!(rate_15 > 400);
211        // At 30 seconds (half period), sine wave is back at center
212        assert!((rate_30 as i64 - 300).abs() < 10);
213    }
214
215    #[test]
216    fn test_delay_calculation() {
217        let delay_100 = WorkloadPattern::delay_for_rate(100);
218        assert_eq!(delay_100, Duration::from_micros(10_000));
219
220        let delay_1000 = WorkloadPattern::delay_for_rate(1000);
221        assert_eq!(delay_1000, Duration::from_micros(1_000));
222    }
223
224    #[test]
225    fn test_workload_controller() {
226        let pattern = WorkloadPattern::Steady { rate: 100 };
227        let controller = WorkloadController::new(pattern);
228
229        assert_eq!(controller.current_rate(), 100);
230        assert_eq!(controller.next_delay(), Duration::from_micros(10_000));
231    }
232
233    #[test]
234    fn test_workload_controller_reset() {
235        let pattern = WorkloadPattern::RampUp {
236            start_rate: 100,
237            end_rate: 1000,
238            duration: Duration::from_secs(10),
239        };
240        let mut controller = WorkloadController::new(pattern);
241
242        std::thread::sleep(Duration::from_millis(100));
243        let rate_before = controller.current_rate();
244
245        controller.reset();
246        let rate_after = controller.current_rate();
247
248        // After reset, rate should be back to start_rate
249        assert!(rate_after < rate_before || rate_before == 100);
250    }
251}