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        match 1_000_000u64.checked_div(rate) {
101            Some(micros) => Duration::from_micros(micros),
102            None => Duration::from_secs(1),
103        }
104    }
105}
106
107/// Workload controller
108pub struct WorkloadController {
109    pattern: WorkloadPattern,
110    start_time: Instant,
111}
112
113impl WorkloadController {
114    /// Create a new workload controller
115    pub fn new(pattern: WorkloadPattern) -> Self {
116        Self {
117            pattern,
118            start_time: Instant::now(),
119        }
120    }
121
122    /// Get current target rate
123    pub fn current_rate(&self) -> u64 {
124        let elapsed = self.start_time.elapsed();
125        self.pattern.rate_at(elapsed)
126    }
127
128    /// Get delay until next request
129    pub fn next_delay(&self) -> Duration {
130        let rate = self.current_rate();
131        WorkloadPattern::delay_for_rate(rate)
132    }
133
134    /// Reset the start time
135    pub fn reset(&mut self) {
136        self.start_time = Instant::now();
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn test_steady_workload() {
146        let pattern = WorkloadPattern::Steady { rate: 100 };
147        assert_eq!(pattern.rate_at(Duration::from_secs(0)), 100);
148        assert_eq!(pattern.rate_at(Duration::from_secs(10)), 100);
149        assert_eq!(pattern.rate_at(Duration::from_secs(100)), 100);
150    }
151
152    #[test]
153    fn test_spike_workload() {
154        let pattern = WorkloadPattern::Spike {
155            baseline: 100,
156            peak: 1000,
157            spike_duration: Duration::from_secs(10),
158            spike_start: Duration::from_secs(5),
159        };
160
161        assert_eq!(pattern.rate_at(Duration::from_secs(0)), 100);
162        assert_eq!(pattern.rate_at(Duration::from_secs(7)), 1000);
163        assert_eq!(pattern.rate_at(Duration::from_secs(20)), 100);
164    }
165
166    #[test]
167    fn test_rampup_workload() {
168        let pattern = WorkloadPattern::RampUp {
169            start_rate: 100,
170            end_rate: 1000,
171            duration: Duration::from_secs(10),
172        };
173
174        assert_eq!(pattern.rate_at(Duration::from_secs(0)), 100);
175        assert_eq!(pattern.rate_at(Duration::from_secs(5)), 550);
176        assert_eq!(pattern.rate_at(Duration::from_secs(10)), 1000);
177        assert_eq!(pattern.rate_at(Duration::from_secs(20)), 1000);
178    }
179
180    #[test]
181    fn test_stress_workload() {
182        let pattern = WorkloadPattern::Stress {
183            start_rate: 100,
184            increment: 50,
185            interval: Duration::from_secs(10),
186        };
187
188        assert_eq!(pattern.rate_at(Duration::from_secs(0)), 100);
189        assert_eq!(pattern.rate_at(Duration::from_secs(10)), 150);
190        assert_eq!(pattern.rate_at(Duration::from_secs(20)), 200);
191        assert_eq!(pattern.rate_at(Duration::from_secs(30)), 250);
192    }
193
194    #[test]
195    fn test_wave_workload() {
196        let pattern = WorkloadPattern::Wave {
197            min_rate: 100,
198            max_rate: 500,
199            period: Duration::from_secs(60),
200        };
201
202        let rate_0 = pattern.rate_at(Duration::from_secs(0));
203        let rate_15 = pattern.rate_at(Duration::from_secs(15));
204        let rate_30 = pattern.rate_at(Duration::from_secs(30));
205
206        // At 0 seconds, sine wave is at 0 (center)
207        assert!((rate_0 as i64 - 300).abs() < 10);
208        // At 15 seconds (quarter period), sine wave is at peak
209        assert!(rate_15 > 400);
210        // At 30 seconds (half period), sine wave is back at center
211        assert!((rate_30 as i64 - 300).abs() < 10);
212    }
213
214    #[test]
215    fn test_delay_calculation() {
216        let delay_100 = WorkloadPattern::delay_for_rate(100);
217        assert_eq!(delay_100, Duration::from_micros(10_000));
218
219        let delay_1000 = WorkloadPattern::delay_for_rate(1000);
220        assert_eq!(delay_1000, Duration::from_micros(1_000));
221    }
222
223    #[test]
224    fn test_workload_controller() {
225        let pattern = WorkloadPattern::Steady { rate: 100 };
226        let controller = WorkloadController::new(pattern);
227
228        assert_eq!(controller.current_rate(), 100);
229        assert_eq!(controller.next_delay(), Duration::from_micros(10_000));
230    }
231
232    #[test]
233    fn test_workload_controller_reset() {
234        let pattern = WorkloadPattern::RampUp {
235            start_rate: 100,
236            end_rate: 1000,
237            duration: Duration::from_secs(10),
238        };
239        let mut controller = WorkloadController::new(pattern);
240
241        std::thread::sleep(Duration::from_millis(100));
242        let rate_before = controller.current_rate();
243
244        controller.reset();
245        let rate_after = controller.current_rate();
246
247        // After reset, rate should be back to start_rate
248        assert!(rate_after < rate_before || rate_before == 100);
249    }
250}