1use std::sync::Arc;
6use std::time::Duration;
7
8use serde::{Deserialize, Serialize};
9
10use crate::profiling::Profiler;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub enum MemoryPattern {
15 Steady,
17
18 GrowthOnly,
20
21 GrowthAndRelease,
23
24 HighChurn,
26
27 Fragmentation,
29
30 #[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 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 pub fn apply(&self, profiler: &Profiler, elapsed: Duration) {
56 match self {
57 Self::Custom(pattern) => pattern.apply(profiler, elapsed),
58 _ => {} }
60 }
61}
62
63pub trait CustomMemoryPattern: Send + Sync + std::fmt::Debug {
65 fn apply(&self, profiler: &Profiler, elapsed: Duration);
67
68 fn description(&self) -> &str;
70}
71
72#[derive(Debug, Clone)]
74pub struct SawtoothPattern {
75 pub period: Duration,
77 pub peak_bytes: usize,
79 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, 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 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 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#[derive(Debug, Clone)]
123pub struct SteppedPattern {
124 pub step_duration: Duration,
126 pub step_bytes: usize,
128 pub max_steps: usize,
130 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, 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 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 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#[derive(Debug, Clone)]
170pub struct BurstPattern {
171 pub burst_interval: Duration,
173 pub burst_size: usize,
175 pub hold_duration: Duration,
177 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, 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 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 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#[derive(Debug, Clone)]
214pub struct LeakPattern {
215 pub leak_rate_per_sec: usize,
217 pub leak_probability: f64,
219 pub region: String,
221 pub sporadic_fix: bool,
223}
224
225impl Default for LeakPattern {
226 fn default() -> Self {
227 Self {
228 leak_rate_per_sec: 10 * 1024, 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 let expected_leaked = (elapsed.as_secs_f64() * self.leak_rate_per_sec as f64) as usize;
240
241 let leak_amount = self.leak_rate_per_sec / 100; if leak_amount > 0 {
244 profiler.record_allocation(&self.region, leak_amount);
245 }
246
247 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
258pub struct MemoryPatternFactory;
260
261impl MemoryPatternFactory {
262 pub fn steady() -> MemoryPattern {
264 MemoryPattern::Steady
265 }
266
267 pub fn growth_only() -> MemoryPattern {
269 MemoryPattern::GrowthOnly
270 }
271
272 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 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 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 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 pub fn combined(patterns: Vec<MemoryPattern>) -> MemoryPattern {
311 MemoryPattern::Custom(Arc::new(CombinedPattern { patterns }))
312 }
313}
314
315#[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 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 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 pattern.apply(&profiler, Duration::from_millis(50));
385 let snapshot1 = profiler.snapshot();
386
387 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 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}