Skip to main content

mabi_core/simulation/
load.rs

1//! Load patterns for simulation.
2//!
3//! Defines various load profiles to test system performance under different conditions.
4
5use std::time::Duration;
6
7use serde::{Deserialize, Serialize};
8
9/// Load pattern for simulations.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub enum LoadPattern {
12    /// Constant load.
13    Steady {
14        /// Operations per second.
15        ops_per_sec: u64,
16    },
17
18    /// Linear ramp from start to end.
19    Ramp {
20        /// Starting operations per second.
21        start_ops: u64,
22        /// Ending operations per second.
23        end_ops: u64,
24        /// Duration of the ramp.
25        duration: Duration,
26    },
27
28    /// Step function (sudden changes).
29    Step {
30        /// Initial level.
31        initial: u64,
32        /// Steps as (time_offset, new_level) pairs.
33        steps: Vec<(Duration, u64)>,
34    },
35
36    /// Sinusoidal wave pattern.
37    Wave {
38        /// Base operations per second.
39        baseline: u64,
40        /// Amplitude of the wave.
41        amplitude: u64,
42        /// Period of one complete cycle.
43        period: Duration,
44    },
45
46    /// Sudden spike pattern.
47    Spike {
48        /// Normal operations per second.
49        baseline: u64,
50        /// Peak operations per second.
51        peak: u64,
52        /// Duration of the spike.
53        spike_duration: Duration,
54    },
55
56    /// Burst pattern (periodic high load).
57    Burst {
58        /// Normal operations per second.
59        baseline: u64,
60        /// Burst operations per second.
61        burst_ops: u64,
62        /// Duration of each burst.
63        burst_duration: Duration,
64        /// Time between bursts.
65        interval: Duration,
66    },
67
68    /// Random load within bounds.
69    Random {
70        /// Minimum operations per second.
71        min_ops: u64,
72        /// Maximum operations per second.
73        max_ops: u64,
74    },
75
76    /// Realistic daily traffic pattern.
77    DailyTraffic {
78        /// Peak operations per second.
79        peak: u64,
80        /// Off-peak operations per second.
81        off_peak: u64,
82        /// Simulated day duration.
83        day_duration: Duration,
84    },
85}
86
87impl Default for LoadPattern {
88    fn default() -> Self {
89        Self::Steady { ops_per_sec: 1000 }
90    }
91}
92
93impl LoadPattern {
94    /// Calculate operations per second for the given elapsed time.
95    pub fn ops_for_elapsed(&self, elapsed: Duration) -> u64 {
96        match self {
97            Self::Steady { ops_per_sec } => *ops_per_sec,
98
99            Self::Ramp {
100                start_ops,
101                end_ops,
102                duration,
103            } => {
104                let progress = (elapsed.as_secs_f64() / duration.as_secs_f64()).min(1.0);
105                let diff = *end_ops as f64 - *start_ops as f64;
106                (*start_ops as f64 + diff * progress) as u64
107            }
108
109            Self::Step { initial, steps } => {
110                let mut current = *initial;
111                for (time, level) in steps {
112                    if elapsed >= *time {
113                        current = *level;
114                    }
115                }
116                current
117            }
118
119            Self::Wave {
120                baseline,
121                amplitude,
122                period,
123            } => {
124                let phase = (elapsed.as_secs_f64() / period.as_secs_f64()) * 2.0 * std::f64::consts::PI;
125                let wave = phase.sin();
126                (*baseline as f64 + *amplitude as f64 * wave) as u64
127            }
128
129            Self::Spike {
130                baseline,
131                peak,
132                spike_duration,
133            } => {
134                // Spike at the midpoint of the simulation
135                // For now, spike if within first spike_duration
136                if elapsed < *spike_duration {
137                    *peak
138                } else {
139                    *baseline
140                }
141            }
142
143            Self::Burst {
144                baseline,
145                burst_ops,
146                burst_duration,
147                interval,
148            } => {
149                let cycle = elapsed.as_millis() % interval.as_millis();
150                if cycle < burst_duration.as_millis() {
151                    *burst_ops
152                } else {
153                    *baseline
154                }
155            }
156
157            Self::Random { min_ops, max_ops } => {
158                // Use elapsed time as pseudo-random seed
159                let seed = elapsed.as_micros() as u64;
160                let range = *max_ops - *min_ops;
161                *min_ops + (seed % (range + 1))
162            }
163
164            Self::DailyTraffic {
165                peak,
166                off_peak,
167                day_duration,
168            } => {
169                // Simulate 24-hour traffic pattern
170                let day_progress = (elapsed.as_secs_f64() % day_duration.as_secs_f64())
171                    / day_duration.as_secs_f64();
172
173                // Peak around 0.5 (noon), low at 0.0 and 1.0 (midnight)
174                let hour = day_progress * 24.0;
175                let traffic_factor = if hour >= 8.0 && hour <= 20.0 {
176                    // Business hours
177                    let peak_at_noon = 1.0 - ((hour - 14.0).abs() / 6.0);
178                    0.5 + 0.5 * peak_at_noon
179                } else {
180                    // Off hours
181                    0.2
182                };
183
184                let range = *peak - *off_peak;
185                (*off_peak as f64 + range as f64 * traffic_factor) as u64
186            }
187        }
188    }
189
190    /// Get a description of this pattern.
191    pub fn description(&self) -> String {
192        match self {
193            Self::Steady { ops_per_sec } => format!("Steady {} ops/s", ops_per_sec),
194            Self::Ramp {
195                start_ops,
196                end_ops,
197                duration,
198            } => format!(
199                "Ramp {} -> {} ops/s over {:?}",
200                start_ops, end_ops, duration
201            ),
202            Self::Step { initial, steps } => {
203                format!("Step pattern starting at {} with {} steps", initial, steps.len())
204            }
205            Self::Wave {
206                baseline,
207                amplitude,
208                period,
209            } => format!(
210                "Wave {}±{} ops/s, period {:?}",
211                baseline, amplitude, period
212            ),
213            Self::Spike {
214                baseline,
215                peak,
216                spike_duration,
217            } => format!(
218                "Spike {} -> {} ops/s for {:?}",
219                baseline, peak, spike_duration
220            ),
221            Self::Burst {
222                baseline,
223                burst_ops,
224                burst_duration,
225                interval,
226            } => format!(
227                "Burst {}->{} ops/s for {:?} every {:?}",
228                baseline, burst_ops, burst_duration, interval
229            ),
230            Self::Random { min_ops, max_ops } => format!("Random {}-{} ops/s", min_ops, max_ops),
231            Self::DailyTraffic { peak, off_peak, .. } => {
232                format!("Daily traffic {}-{} ops/s", off_peak, peak)
233            }
234        }
235    }
236
237    /// Create common load patterns.
238    pub fn steady(ops_per_sec: u64) -> Self {
239        Self::Steady { ops_per_sec }
240    }
241
242    /// Create a ramp pattern.
243    pub fn ramp(start: u64, end: u64, duration: Duration) -> Self {
244        Self::Ramp {
245            start_ops: start,
246            end_ops: end,
247            duration,
248        }
249    }
250
251    /// Create a wave pattern.
252    pub fn wave(baseline: u64, amplitude: u64, period: Duration) -> Self {
253        Self::Wave {
254            baseline,
255            amplitude,
256            period,
257        }
258    }
259
260    /// Create a spike pattern.
261    pub fn spike(baseline: u64, peak: u64, spike_duration: Duration) -> Self {
262        Self::Spike {
263            baseline,
264            peak,
265            spike_duration,
266        }
267    }
268
269    /// Create a burst pattern.
270    pub fn burst(
271        baseline: u64,
272        burst_ops: u64,
273        burst_duration: Duration,
274        interval: Duration,
275    ) -> Self {
276        Self::Burst {
277            baseline,
278            burst_ops,
279            burst_duration,
280            interval,
281        }
282    }
283}
284
285/// Load pattern builder for complex patterns.
286#[derive(Default)]
287pub struct LoadPatternBuilder {
288    steps: Vec<(Duration, u64)>,
289    initial: u64,
290}
291
292impl LoadPatternBuilder {
293    /// Create a new builder.
294    pub fn new() -> Self {
295        Self::default()
296    }
297
298    /// Set the initial load.
299    pub fn initial(mut self, ops: u64) -> Self {
300        self.initial = ops;
301        self
302    }
303
304    /// Add a step at a specific time.
305    pub fn step_at(mut self, time: Duration, ops: u64) -> Self {
306        self.steps.push((time, ops));
307        self
308    }
309
310    /// Build the step pattern.
311    pub fn build_step(self) -> LoadPattern {
312        LoadPattern::Step {
313            initial: self.initial,
314            steps: self.steps,
315        }
316    }
317}
318
319#[cfg(test)]
320mod tests {
321    use super::*;
322
323    #[test]
324    fn test_steady_pattern() {
325        let pattern = LoadPattern::steady(1000);
326
327        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(0)), 1000);
328        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(100)), 1000);
329    }
330
331    #[test]
332    fn test_ramp_pattern() {
333        let pattern = LoadPattern::ramp(100, 1000, Duration::from_secs(10));
334
335        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(0)), 100);
336        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(5)), 550);
337        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(10)), 1000);
338        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(20)), 1000); // Capped at end
339    }
340
341    #[test]
342    fn test_step_pattern() {
343        let pattern = LoadPattern::Step {
344            initial: 100,
345            steps: vec![
346                (Duration::from_secs(5), 500),
347                (Duration::from_secs(10), 1000),
348            ],
349        };
350
351        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(0)), 100);
352        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(3)), 100);
353        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(5)), 500);
354        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(7)), 500);
355        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(10)), 1000);
356    }
357
358    #[test]
359    fn test_wave_pattern() {
360        let pattern = LoadPattern::wave(1000, 500, Duration::from_secs(10));
361
362        let at_0 = pattern.ops_for_elapsed(Duration::from_secs(0));
363        let at_quarter = pattern.ops_for_elapsed(Duration::from_millis(2500));
364        let at_half = pattern.ops_for_elapsed(Duration::from_secs(5));
365
366        // At 0: sin(0) = 0, so baseline
367        assert_eq!(at_0, 1000);
368
369        // At quarter period: sin(π/2) = 1, so baseline + amplitude
370        assert!((at_quarter as i64 - 1500).abs() < 10);
371
372        // At half period: sin(π) = 0, so baseline
373        assert!((at_half as i64 - 1000).abs() < 10);
374    }
375
376    #[test]
377    fn test_spike_pattern() {
378        let pattern = LoadPattern::spike(100, 10000, Duration::from_secs(5));
379
380        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(0)), 10000); // During spike
381        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(3)), 10000); // During spike
382        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(6)), 100); // After spike
383    }
384
385    #[test]
386    fn test_burst_pattern() {
387        let pattern = LoadPattern::burst(
388            100,
389            5000,
390            Duration::from_secs(2),
391            Duration::from_secs(10),
392        );
393
394        // During burst (0-2s in each 10s cycle)
395        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(0)), 5000);
396        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(1)), 5000);
397
398        // After burst (2-10s)
399        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(3)), 100);
400        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(9)), 100);
401
402        // Next cycle
403        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(10)), 5000);
404    }
405
406    #[test]
407    fn test_random_pattern() {
408        let pattern = LoadPattern::Random {
409            min_ops: 100,
410            max_ops: 1000,
411        };
412
413        for i in 0..10 {
414            let ops = pattern.ops_for_elapsed(Duration::from_secs(i));
415            assert!(ops >= 100 && ops <= 1000);
416        }
417    }
418
419    #[test]
420    fn test_daily_traffic_pattern() {
421        let pattern = LoadPattern::DailyTraffic {
422            peak: 10000,
423            off_peak: 1000,
424            day_duration: Duration::from_secs(86400), // 24 hours
425        };
426
427        // Simulate different times of day
428        let midnight = pattern.ops_for_elapsed(Duration::from_secs(0));
429        let noon = pattern.ops_for_elapsed(Duration::from_secs(43200)); // 12 hours
430        let evening = pattern.ops_for_elapsed(Duration::from_secs(72000)); // 20 hours
431
432        // Midnight should be low
433        assert!(midnight < 5000);
434
435        // Noon should be high
436        assert!(noon > 5000);
437
438        // Evening declining
439        assert!(evening < noon);
440    }
441
442    #[test]
443    fn test_pattern_builder() {
444        let pattern = LoadPatternBuilder::new()
445            .initial(100)
446            .step_at(Duration::from_secs(5), 500)
447            .step_at(Duration::from_secs(10), 1000)
448            .build_step();
449
450        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(0)), 100);
451        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(5)), 500);
452        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(10)), 1000);
453    }
454
455    #[test]
456    fn test_pattern_descriptions() {
457        let patterns = vec![
458            LoadPattern::steady(1000),
459            LoadPattern::ramp(100, 1000, Duration::from_secs(10)),
460            LoadPattern::wave(500, 200, Duration::from_secs(30)),
461            LoadPattern::spike(100, 5000, Duration::from_secs(5)),
462        ];
463
464        for pattern in patterns {
465            let desc = pattern.description();
466            assert!(!desc.is_empty());
467        }
468    }
469}