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 =
125                    (elapsed.as_secs_f64() / period.as_secs_f64()) * 2.0 * std::f64::consts::PI;
126                let wave = phase.sin();
127                (*baseline as f64 + *amplitude as f64 * wave) as u64
128            }
129
130            Self::Spike {
131                baseline,
132                peak,
133                spike_duration,
134            } => {
135                // Spike at the midpoint of the simulation
136                // For now, spike if within first spike_duration
137                if elapsed < *spike_duration {
138                    *peak
139                } else {
140                    *baseline
141                }
142            }
143
144            Self::Burst {
145                baseline,
146                burst_ops,
147                burst_duration,
148                interval,
149            } => {
150                let cycle = elapsed.as_millis() % interval.as_millis();
151                if cycle < burst_duration.as_millis() {
152                    *burst_ops
153                } else {
154                    *baseline
155                }
156            }
157
158            Self::Random { min_ops, max_ops } => {
159                // Use elapsed time as pseudo-random seed
160                let seed = elapsed.as_micros() as u64;
161                let range = *max_ops - *min_ops;
162                *min_ops + (seed % (range + 1))
163            }
164
165            Self::DailyTraffic {
166                peak,
167                off_peak,
168                day_duration,
169            } => {
170                // Simulate 24-hour traffic pattern
171                let day_progress = (elapsed.as_secs_f64() % day_duration.as_secs_f64())
172                    / day_duration.as_secs_f64();
173
174                // Peak around 0.5 (noon), low at 0.0 and 1.0 (midnight)
175                let hour = day_progress * 24.0;
176                let traffic_factor = if hour >= 8.0 && hour <= 20.0 {
177                    // Business hours
178                    let peak_at_noon = 1.0 - ((hour - 14.0).abs() / 6.0);
179                    0.5 + 0.5 * peak_at_noon
180                } else {
181                    // Off hours
182                    0.2
183                };
184
185                let range = *peak - *off_peak;
186                (*off_peak as f64 + range as f64 * traffic_factor) as u64
187            }
188        }
189    }
190
191    /// Get a description of this pattern.
192    pub fn description(&self) -> String {
193        match self {
194            Self::Steady { ops_per_sec } => format!("Steady {} ops/s", ops_per_sec),
195            Self::Ramp {
196                start_ops,
197                end_ops,
198                duration,
199            } => format!(
200                "Ramp {} -> {} ops/s over {:?}",
201                start_ops, end_ops, duration
202            ),
203            Self::Step { initial, steps } => {
204                format!(
205                    "Step pattern starting at {} with {} steps",
206                    initial,
207                    steps.len()
208                )
209            }
210            Self::Wave {
211                baseline,
212                amplitude,
213                period,
214            } => format!("Wave {}±{} ops/s, period {:?}", baseline, amplitude, period),
215            Self::Spike {
216                baseline,
217                peak,
218                spike_duration,
219            } => format!(
220                "Spike {} -> {} ops/s for {:?}",
221                baseline, peak, spike_duration
222            ),
223            Self::Burst {
224                baseline,
225                burst_ops,
226                burst_duration,
227                interval,
228            } => format!(
229                "Burst {}->{} ops/s for {:?} every {:?}",
230                baseline, burst_ops, burst_duration, interval
231            ),
232            Self::Random { min_ops, max_ops } => format!("Random {}-{} ops/s", min_ops, max_ops),
233            Self::DailyTraffic { peak, off_peak, .. } => {
234                format!("Daily traffic {}-{} ops/s", off_peak, peak)
235            }
236        }
237    }
238
239    /// Create common load patterns.
240    pub fn steady(ops_per_sec: u64) -> Self {
241        Self::Steady { ops_per_sec }
242    }
243
244    /// Create a ramp pattern.
245    pub fn ramp(start: u64, end: u64, duration: Duration) -> Self {
246        Self::Ramp {
247            start_ops: start,
248            end_ops: end,
249            duration,
250        }
251    }
252
253    /// Create a wave pattern.
254    pub fn wave(baseline: u64, amplitude: u64, period: Duration) -> Self {
255        Self::Wave {
256            baseline,
257            amplitude,
258            period,
259        }
260    }
261
262    /// Create a spike pattern.
263    pub fn spike(baseline: u64, peak: u64, spike_duration: Duration) -> Self {
264        Self::Spike {
265            baseline,
266            peak,
267            spike_duration,
268        }
269    }
270
271    /// Create a burst pattern.
272    pub fn burst(
273        baseline: u64,
274        burst_ops: u64,
275        burst_duration: Duration,
276        interval: Duration,
277    ) -> Self {
278        Self::Burst {
279            baseline,
280            burst_ops,
281            burst_duration,
282            interval,
283        }
284    }
285}
286
287/// Load pattern builder for complex patterns.
288#[derive(Default)]
289pub struct LoadPatternBuilder {
290    steps: Vec<(Duration, u64)>,
291    initial: u64,
292}
293
294impl LoadPatternBuilder {
295    /// Create a new builder.
296    pub fn new() -> Self {
297        Self::default()
298    }
299
300    /// Set the initial load.
301    pub fn initial(mut self, ops: u64) -> Self {
302        self.initial = ops;
303        self
304    }
305
306    /// Add a step at a specific time.
307    pub fn step_at(mut self, time: Duration, ops: u64) -> Self {
308        self.steps.push((time, ops));
309        self
310    }
311
312    /// Build the step pattern.
313    pub fn build_step(self) -> LoadPattern {
314        LoadPattern::Step {
315            initial: self.initial,
316            steps: self.steps,
317        }
318    }
319}
320
321#[cfg(test)]
322mod tests {
323    use super::*;
324
325    #[test]
326    fn test_steady_pattern() {
327        let pattern = LoadPattern::steady(1000);
328
329        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(0)), 1000);
330        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(100)), 1000);
331    }
332
333    #[test]
334    fn test_ramp_pattern() {
335        let pattern = LoadPattern::ramp(100, 1000, Duration::from_secs(10));
336
337        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(0)), 100);
338        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(5)), 550);
339        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(10)), 1000);
340        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(20)), 1000); // Capped at end
341    }
342
343    #[test]
344    fn test_step_pattern() {
345        let pattern = LoadPattern::Step {
346            initial: 100,
347            steps: vec![
348                (Duration::from_secs(5), 500),
349                (Duration::from_secs(10), 1000),
350            ],
351        };
352
353        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(0)), 100);
354        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(3)), 100);
355        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(5)), 500);
356        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(7)), 500);
357        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(10)), 1000);
358    }
359
360    #[test]
361    fn test_wave_pattern() {
362        let pattern = LoadPattern::wave(1000, 500, Duration::from_secs(10));
363
364        let at_0 = pattern.ops_for_elapsed(Duration::from_secs(0));
365        let at_quarter = pattern.ops_for_elapsed(Duration::from_millis(2500));
366        let at_half = pattern.ops_for_elapsed(Duration::from_secs(5));
367
368        // At 0: sin(0) = 0, so baseline
369        assert_eq!(at_0, 1000);
370
371        // At quarter period: sin(π/2) = 1, so baseline + amplitude
372        assert!((at_quarter as i64 - 1500).abs() < 10);
373
374        // At half period: sin(π) = 0, so baseline
375        assert!((at_half as i64 - 1000).abs() < 10);
376    }
377
378    #[test]
379    fn test_spike_pattern() {
380        let pattern = LoadPattern::spike(100, 10000, Duration::from_secs(5));
381
382        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(0)), 10000); // During spike
383        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(3)), 10000); // During spike
384        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(6)), 100); // After spike
385    }
386
387    #[test]
388    fn test_burst_pattern() {
389        let pattern =
390            LoadPattern::burst(100, 5000, Duration::from_secs(2), Duration::from_secs(10));
391
392        // During burst (0-2s in each 10s cycle)
393        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(0)), 5000);
394        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(1)), 5000);
395
396        // After burst (2-10s)
397        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(3)), 100);
398        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(9)), 100);
399
400        // Next cycle
401        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(10)), 5000);
402    }
403
404    #[test]
405    fn test_random_pattern() {
406        let pattern = LoadPattern::Random {
407            min_ops: 100,
408            max_ops: 1000,
409        };
410
411        for i in 0..10 {
412            let ops = pattern.ops_for_elapsed(Duration::from_secs(i));
413            assert!(ops >= 100 && ops <= 1000);
414        }
415    }
416
417    #[test]
418    fn test_daily_traffic_pattern() {
419        let pattern = LoadPattern::DailyTraffic {
420            peak: 10000,
421            off_peak: 1000,
422            day_duration: Duration::from_secs(86400), // 24 hours
423        };
424
425        // Simulate different times of day
426        let midnight = pattern.ops_for_elapsed(Duration::from_secs(0));
427        let noon = pattern.ops_for_elapsed(Duration::from_secs(43200)); // 12 hours
428        let evening = pattern.ops_for_elapsed(Duration::from_secs(72000)); // 20 hours
429
430        // Midnight should be low
431        assert!(midnight < 5000);
432
433        // Noon should be high
434        assert!(noon > 5000);
435
436        // Evening declining
437        assert!(evening < noon);
438    }
439
440    #[test]
441    fn test_pattern_builder() {
442        let pattern = LoadPatternBuilder::new()
443            .initial(100)
444            .step_at(Duration::from_secs(5), 500)
445            .step_at(Duration::from_secs(10), 1000)
446            .build_step();
447
448        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(0)), 100);
449        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(5)), 500);
450        assert_eq!(pattern.ops_for_elapsed(Duration::from_secs(10)), 1000);
451    }
452
453    #[test]
454    fn test_pattern_descriptions() {
455        let patterns = vec![
456            LoadPattern::steady(1000),
457            LoadPattern::ramp(100, 1000, Duration::from_secs(10)),
458            LoadPattern::wave(500, 200, Duration::from_secs(30)),
459            LoadPattern::spike(100, 5000, Duration::from_secs(5)),
460        ];
461
462        for pattern in patterns {
463            let desc = pattern.description();
464            assert!(!desc.is_empty());
465        }
466    }
467}