1use std::sync::Arc;
6use std::time::Duration;
7
8use serde::{Deserialize, Serialize};
9
10use crate::profiling::Profiler;
11
12#[derive(Debug, Clone, Default, Serialize, Deserialize)]
14pub enum MemoryPattern {
15 #[default]
17 Steady,
18
19 GrowthOnly,
21
22 GrowthAndRelease,
24
25 HighChurn,
27
28 Fragmentation,
30
31 #[serde(skip)]
33 Custom(Arc<dyn CustomMemoryPattern>),
34}
35
36impl MemoryPattern {
37 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 pub fn apply(&self, profiler: &Profiler, elapsed: Duration) {
51 if let Self::Custom(pattern) = self {
52 pattern.apply(profiler, elapsed);
53 }
54 }
55}
56
57pub trait CustomMemoryPattern: Send + Sync + std::fmt::Debug {
59 fn apply(&self, profiler: &Profiler, elapsed: Duration);
61
62 fn description(&self) -> &str;
64}
65
66#[derive(Debug, Clone)]
68pub struct SawtoothPattern {
69 pub period: Duration,
71 pub peak_bytes: usize,
73 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, 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 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 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#[derive(Debug, Clone)]
117pub struct SteppedPattern {
118 pub step_duration: Duration,
120 pub step_bytes: usize,
122 pub max_steps: usize,
124 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, 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 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 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#[derive(Debug, Clone)]
164pub struct BurstPattern {
165 pub burst_interval: Duration,
167 pub burst_size: usize,
169 pub hold_duration: Duration,
171 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, 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 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 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#[derive(Debug, Clone)]
208pub struct LeakPattern {
209 pub leak_rate_per_sec: usize,
211 pub leak_probability: f64,
213 pub region: String,
215 pub sporadic_fix: bool,
217}
218
219impl Default for LeakPattern {
220 fn default() -> Self {
221 Self {
222 leak_rate_per_sec: 10 * 1024, 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 let expected_leaked = (elapsed.as_secs_f64() * self.leak_rate_per_sec as f64) as usize;
234
235 let leak_amount = self.leak_rate_per_sec / 100; if leak_amount > 0 {
238 profiler.record_allocation(&self.region, leak_amount);
239 }
240
241 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
252pub struct MemoryPatternFactory;
254
255impl MemoryPatternFactory {
256 pub fn steady() -> MemoryPattern {
258 MemoryPattern::Steady
259 }
260
261 pub fn growth_only() -> MemoryPattern {
263 MemoryPattern::GrowthOnly
264 }
265
266 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 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 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 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 pub fn combined(patterns: Vec<MemoryPattern>) -> MemoryPattern {
305 MemoryPattern::Custom(Arc::new(CombinedPattern { patterns }))
306 }
307}
308
309#[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 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 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 pattern.apply(&profiler, Duration::from_millis(50));
379 let snapshot1 = profiler.snapshot();
380
381 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 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}