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