Skip to main content

mabi_core/simulation/
memory_sim.rs

1//! Memory simulation patterns.
2//!
3//! Defines various memory allocation patterns to test different scenarios.
4
5use std::sync::Arc;
6use std::time::Duration;
7
8use serde::{Deserialize, Serialize};
9
10use crate::profiling::Profiler;
11
12/// Memory allocation pattern for simulation.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub enum MemoryPattern {
15    /// Steady state - no significant changes.
16    Steady,
17
18    /// Continuous memory growth (simulates leak).
19    GrowthOnly,
20
21    /// Growth followed by periodic release.
22    GrowthAndRelease,
23
24    /// High allocation/deallocation churn.
25    HighChurn,
26
27    /// Memory fragmentation pattern.
28    Fragmentation,
29
30    /// Custom pattern.
31    #[serde(skip)]
32    Custom(Arc<dyn CustomMemoryPattern>),
33}
34
35impl Default for MemoryPattern {
36    fn default() -> Self {
37        Self::Steady
38    }
39}
40
41impl MemoryPattern {
42    /// Get a description of this pattern.
43    pub fn description(&self) -> &str {
44        match self {
45            Self::Steady => "Steady state with minimal memory changes",
46            Self::GrowthOnly => "Continuous growth simulating memory leak",
47            Self::GrowthAndRelease => "Periodic growth and release cycles",
48            Self::HighChurn => "High allocation/deallocation frequency",
49            Self::Fragmentation => "Varied sizes causing fragmentation",
50            Self::Custom(_) => "Custom memory pattern",
51        }
52    }
53
54    /// Apply the pattern to the profiler.
55    pub fn apply(&self, profiler: &Profiler, elapsed: Duration) {
56        match self {
57            Self::Custom(pattern) => pattern.apply(profiler, elapsed),
58            _ => {} // Handled in Simulator
59        }
60    }
61}
62
63/// Trait for custom memory patterns.
64pub trait CustomMemoryPattern: Send + Sync + std::fmt::Debug {
65    /// Apply the pattern for the given elapsed time.
66    fn apply(&self, profiler: &Profiler, elapsed: Duration);
67
68    /// Get a description.
69    fn description(&self) -> &str;
70}
71
72/// Sawtooth memory pattern - grows then drops.
73#[derive(Debug, Clone)]
74pub struct SawtoothPattern {
75    /// Period of each cycle.
76    pub period: Duration,
77    /// Peak allocation per cycle.
78    pub peak_bytes: usize,
79    /// Region name.
80    pub region: String,
81}
82
83impl Default for SawtoothPattern {
84    fn default() -> Self {
85        Self {
86            period: Duration::from_secs(30),
87            peak_bytes: 1024 * 1024, // 1MB
88            region: "sawtooth".into(),
89        }
90    }
91}
92
93impl CustomMemoryPattern for SawtoothPattern {
94    fn apply(&self, profiler: &Profiler, elapsed: Duration) {
95        let cycle_pos = (elapsed.as_millis() % self.period.as_millis()) as f64
96            / self.period.as_millis() as f64;
97
98        if cycle_pos < 0.9 {
99            // Growing phase (90% of cycle)
100            let growth = (self.peak_bytes as f64 * cycle_pos / 0.9) as usize;
101            let increment = growth / 100;
102            if increment > 0 {
103                profiler.record_allocation(&self.region, increment);
104            }
105        } else {
106            // Release phase (10% of cycle)
107            let release_progress = (cycle_pos - 0.9) / 0.1;
108            let release = (self.peak_bytes as f64 * release_progress) as usize;
109            let decrement = release / 10;
110            if decrement > 0 {
111                profiler.record_deallocation(&self.region, decrement);
112            }
113        }
114    }
115
116    fn description(&self) -> &str {
117        "Sawtooth pattern: gradual growth then rapid release"
118    }
119}
120
121/// Stepped memory pattern - grows in discrete steps.
122#[derive(Debug, Clone)]
123pub struct SteppedPattern {
124    /// Duration of each step.
125    pub step_duration: Duration,
126    /// Memory increment per step.
127    pub step_bytes: usize,
128    /// Maximum steps before reset.
129    pub max_steps: usize,
130    /// Region name.
131    pub region: String,
132}
133
134impl Default for SteppedPattern {
135    fn default() -> Self {
136        Self {
137            step_duration: Duration::from_secs(10),
138            step_bytes: 256 * 1024, // 256KB
139            max_steps: 10,
140            region: "stepped".into(),
141        }
142    }
143}
144
145impl CustomMemoryPattern for SteppedPattern {
146    fn apply(&self, profiler: &Profiler, elapsed: Duration) {
147        let total_period = self.step_duration.as_millis() * self.max_steps as u128;
148        let cycle_ms = elapsed.as_millis() % total_period;
149        let current_step = (cycle_ms / self.step_duration.as_millis()) as usize;
150
151        // Only allocate at step boundaries
152        let step_boundary = cycle_ms % self.step_duration.as_millis();
153        if step_boundary < 100 && current_step > 0 {
154            if current_step == self.max_steps - 1 {
155                // Reset at last step
156                profiler.record_deallocation(&self.region, self.step_bytes * self.max_steps);
157            } else {
158                profiler.record_allocation(&self.region, self.step_bytes);
159            }
160        }
161    }
162
163    fn description(&self) -> &str {
164        "Stepped pattern: discrete memory increments"
165    }
166}
167
168/// Burst memory pattern - sudden spikes.
169#[derive(Debug, Clone)]
170pub struct BurstPattern {
171    /// Time between bursts.
172    pub burst_interval: Duration,
173    /// Size of each burst.
174    pub burst_size: usize,
175    /// How long before burst is released.
176    pub hold_duration: Duration,
177    /// Region name.
178    pub region: String,
179}
180
181impl Default for BurstPattern {
182    fn default() -> Self {
183        Self {
184            burst_interval: Duration::from_secs(20),
185            burst_size: 5 * 1024 * 1024, // 5MB
186            hold_duration: Duration::from_secs(5),
187            region: "burst".into(),
188        }
189    }
190}
191
192impl CustomMemoryPattern for BurstPattern {
193    fn apply(&self, profiler: &Profiler, elapsed: Duration) {
194        let cycle = elapsed.as_millis() % self.burst_interval.as_millis();
195
196        if cycle < 100 {
197            // Burst allocation at start of cycle
198            profiler.record_allocation(&self.region, self.burst_size);
199        } else if cycle >= self.hold_duration.as_millis()
200            && cycle < self.hold_duration.as_millis() + 100
201        {
202            // Release after hold duration
203            profiler.record_deallocation(&self.region, self.burst_size);
204        }
205    }
206
207    fn description(&self) -> &str {
208        "Burst pattern: sudden spikes held briefly"
209    }
210}
211
212/// Memory leak simulation pattern.
213#[derive(Debug, Clone)]
214pub struct LeakPattern {
215    /// Bytes leaked per second.
216    pub leak_rate_per_sec: usize,
217    /// Probability of leak each tick.
218    pub leak_probability: f64,
219    /// Region name.
220    pub region: String,
221    /// Whether to occasionally "fix" the leak (for testing detection).
222    pub sporadic_fix: bool,
223}
224
225impl Default for LeakPattern {
226    fn default() -> Self {
227        Self {
228            leak_rate_per_sec: 10 * 1024, // 10KB/s
229            leak_probability: 0.1,
230            region: "leak".into(),
231            sporadic_fix: false,
232        }
233    }
234}
235
236impl CustomMemoryPattern for LeakPattern {
237    fn apply(&self, profiler: &Profiler, elapsed: Duration) {
238        // Calculate expected leaked bytes based on elapsed time
239        let expected_leaked = (elapsed.as_secs_f64() * self.leak_rate_per_sec as f64) as usize;
240
241        // Leak incrementally (small amounts frequently)
242        let leak_amount = self.leak_rate_per_sec / 100; // Per tick (~10ms)
243        if leak_amount > 0 {
244            profiler.record_allocation(&self.region, leak_amount);
245        }
246
247        // Sporadic "fix" to test leak detector's ability to detect trends
248        if self.sporadic_fix && elapsed.as_secs() % 60 == 30 {
249            profiler.record_deallocation(&self.region, expected_leaked / 4);
250        }
251    }
252
253    fn description(&self) -> &str {
254        "Leak pattern: gradual memory leak simulation"
255    }
256}
257
258/// Factory for creating memory patterns.
259pub struct MemoryPatternFactory;
260
261impl MemoryPatternFactory {
262    /// Create a steady pattern.
263    pub fn steady() -> MemoryPattern {
264        MemoryPattern::Steady
265    }
266
267    /// Create a growth-only pattern (leak simulation).
268    pub fn growth_only() -> MemoryPattern {
269        MemoryPattern::GrowthOnly
270    }
271
272    /// Create a sawtooth pattern.
273    pub fn sawtooth(period: Duration, peak_bytes: usize) -> MemoryPattern {
274        MemoryPattern::Custom(Arc::new(SawtoothPattern {
275            period,
276            peak_bytes,
277            region: "sawtooth".into(),
278        }))
279    }
280
281    /// Create a stepped pattern.
282    pub fn stepped(step_duration: Duration, step_bytes: usize, max_steps: usize) -> MemoryPattern {
283        MemoryPattern::Custom(Arc::new(SteppedPattern {
284            step_duration,
285            step_bytes,
286            max_steps,
287            region: "stepped".into(),
288        }))
289    }
290
291    /// Create a burst pattern.
292    pub fn burst(interval: Duration, size: usize, hold: Duration) -> MemoryPattern {
293        MemoryPattern::Custom(Arc::new(BurstPattern {
294            burst_interval: interval,
295            burst_size: size,
296            hold_duration: hold,
297            region: "burst".into(),
298        }))
299    }
300
301    /// Create a leak pattern.
302    pub fn leak(rate_per_sec: usize) -> MemoryPattern {
303        MemoryPattern::Custom(Arc::new(LeakPattern {
304            leak_rate_per_sec: rate_per_sec,
305            ..Default::default()
306        }))
307    }
308
309    /// Create a combined pattern.
310    pub fn combined(patterns: Vec<MemoryPattern>) -> MemoryPattern {
311        MemoryPattern::Custom(Arc::new(CombinedPattern { patterns }))
312    }
313}
314
315/// Combined pattern that applies multiple patterns.
316#[derive(Debug)]
317struct CombinedPattern {
318    patterns: Vec<MemoryPattern>,
319}
320
321impl CustomMemoryPattern for CombinedPattern {
322    fn apply(&self, profiler: &Profiler, elapsed: Duration) {
323        for pattern in &self.patterns {
324            pattern.apply(profiler, elapsed);
325        }
326    }
327
328    fn description(&self) -> &str {
329        "Combined pattern: multiple patterns applied together"
330    }
331}
332
333#[cfg(test)]
334mod tests {
335    use super::*;
336    use crate::profiling::ProfilerConfig;
337
338    #[test]
339    fn test_memory_pattern_descriptions() {
340        assert!(!MemoryPattern::Steady.description().is_empty());
341        assert!(!MemoryPattern::GrowthOnly.description().is_empty());
342        assert!(!MemoryPattern::HighChurn.description().is_empty());
343    }
344
345    #[test]
346    fn test_sawtooth_pattern() {
347        let profiler = Profiler::new(ProfilerConfig::default());
348        profiler.start();
349
350        let pattern = SawtoothPattern::default();
351        pattern.apply(&profiler, Duration::from_secs(5));
352
353        // Should have some allocations
354        let snapshot = profiler.snapshot();
355        assert!(snapshot.allocation_count > 0 || snapshot.current_bytes >= 0);
356    }
357
358    #[test]
359    fn test_stepped_pattern() {
360        let profiler = Profiler::new(ProfilerConfig::default());
361        profiler.start();
362
363        let pattern = SteppedPattern::default();
364
365        // Apply at different times
366        pattern.apply(&profiler, Duration::from_secs(0));
367        pattern.apply(&profiler, Duration::from_secs(10));
368        pattern.apply(&profiler, Duration::from_secs(20));
369    }
370
371    #[test]
372    fn test_burst_pattern() {
373        let profiler = Profiler::new(ProfilerConfig::default());
374        profiler.start();
375
376        let pattern = BurstPattern {
377            burst_interval: Duration::from_secs(10),
378            burst_size: 1024,
379            hold_duration: Duration::from_secs(2),
380            region: "test".into(),
381        };
382
383        // At start of cycle - should allocate
384        pattern.apply(&profiler, Duration::from_millis(50));
385        let snapshot1 = profiler.snapshot();
386
387        // After hold - should deallocate
388        pattern.apply(&profiler, Duration::from_millis(2050));
389        let _snapshot2 = profiler.snapshot();
390
391        assert!(snapshot1.allocation_count > 0);
392    }
393
394    #[test]
395    fn test_leak_pattern() {
396        let profiler = Profiler::new(ProfilerConfig::default());
397        profiler.start();
398
399        let pattern = LeakPattern {
400            leak_rate_per_sec: 1024,
401            leak_probability: 1.0,
402            region: "test_leak".into(),
403            sporadic_fix: false,
404        };
405
406        // Apply multiple times
407        for i in 0..10 {
408            pattern.apply(&profiler, Duration::from_millis(i * 100));
409        }
410
411        let snapshot = profiler.snapshot();
412        assert!(snapshot.allocation_count > 0);
413    }
414
415    #[test]
416    fn test_pattern_factory() {
417        let _steady = MemoryPatternFactory::steady();
418        let _growth = MemoryPatternFactory::growth_only();
419        let _sawtooth = MemoryPatternFactory::sawtooth(Duration::from_secs(10), 1024);
420        let _stepped = MemoryPatternFactory::stepped(Duration::from_secs(5), 512, 5);
421        let _burst = MemoryPatternFactory::burst(
422            Duration::from_secs(20),
423            4096,
424            Duration::from_secs(3),
425        );
426        let _leak = MemoryPatternFactory::leak(1024);
427    }
428
429    #[test]
430    fn test_combined_pattern() {
431        let profiler = Profiler::new(ProfilerConfig::default());
432        profiler.start();
433
434        let pattern = MemoryPatternFactory::combined(vec![
435            MemoryPattern::HighChurn,
436            MemoryPatternFactory::leak(512),
437        ]);
438
439        pattern.apply(&profiler, Duration::from_secs(1));
440    }
441}