use std::sync::Arc;
use std::time::Duration;
use serde::{Deserialize, Serialize};
use crate::profiling::Profiler;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MemoryPattern {
Steady,
GrowthOnly,
GrowthAndRelease,
HighChurn,
Fragmentation,
#[serde(skip)]
Custom(Arc<dyn CustomMemoryPattern>),
}
impl Default for MemoryPattern {
fn default() -> Self {
Self::Steady
}
}
impl MemoryPattern {
pub fn description(&self) -> &str {
match self {
Self::Steady => "Steady state with minimal memory changes",
Self::GrowthOnly => "Continuous growth simulating memory leak",
Self::GrowthAndRelease => "Periodic growth and release cycles",
Self::HighChurn => "High allocation/deallocation frequency",
Self::Fragmentation => "Varied sizes causing fragmentation",
Self::Custom(_) => "Custom memory pattern",
}
}
pub fn apply(&self, profiler: &Profiler, elapsed: Duration) {
match self {
Self::Custom(pattern) => pattern.apply(profiler, elapsed),
_ => {} }
}
}
pub trait CustomMemoryPattern: Send + Sync + std::fmt::Debug {
fn apply(&self, profiler: &Profiler, elapsed: Duration);
fn description(&self) -> &str;
}
#[derive(Debug, Clone)]
pub struct SawtoothPattern {
pub period: Duration,
pub peak_bytes: usize,
pub region: String,
}
impl Default for SawtoothPattern {
fn default() -> Self {
Self {
period: Duration::from_secs(30),
peak_bytes: 1024 * 1024, region: "sawtooth".into(),
}
}
}
impl CustomMemoryPattern for SawtoothPattern {
fn apply(&self, profiler: &Profiler, elapsed: Duration) {
let cycle_pos =
(elapsed.as_millis() % self.period.as_millis()) as f64 / self.period.as_millis() as f64;
if cycle_pos < 0.9 {
let growth = (self.peak_bytes as f64 * cycle_pos / 0.9) as usize;
let increment = growth / 100;
if increment > 0 {
profiler.record_allocation(&self.region, increment);
}
} else {
let release_progress = (cycle_pos - 0.9) / 0.1;
let release = (self.peak_bytes as f64 * release_progress) as usize;
let decrement = release / 10;
if decrement > 0 {
profiler.record_deallocation(&self.region, decrement);
}
}
}
fn description(&self) -> &str {
"Sawtooth pattern: gradual growth then rapid release"
}
}
#[derive(Debug, Clone)]
pub struct SteppedPattern {
pub step_duration: Duration,
pub step_bytes: usize,
pub max_steps: usize,
pub region: String,
}
impl Default for SteppedPattern {
fn default() -> Self {
Self {
step_duration: Duration::from_secs(10),
step_bytes: 256 * 1024, max_steps: 10,
region: "stepped".into(),
}
}
}
impl CustomMemoryPattern for SteppedPattern {
fn apply(&self, profiler: &Profiler, elapsed: Duration) {
let total_period = self.step_duration.as_millis() * self.max_steps as u128;
let cycle_ms = elapsed.as_millis() % total_period;
let current_step = (cycle_ms / self.step_duration.as_millis()) as usize;
let step_boundary = cycle_ms % self.step_duration.as_millis();
if step_boundary < 100 && current_step > 0 {
if current_step == self.max_steps - 1 {
profiler.record_deallocation(&self.region, self.step_bytes * self.max_steps);
} else {
profiler.record_allocation(&self.region, self.step_bytes);
}
}
}
fn description(&self) -> &str {
"Stepped pattern: discrete memory increments"
}
}
#[derive(Debug, Clone)]
pub struct BurstPattern {
pub burst_interval: Duration,
pub burst_size: usize,
pub hold_duration: Duration,
pub region: String,
}
impl Default for BurstPattern {
fn default() -> Self {
Self {
burst_interval: Duration::from_secs(20),
burst_size: 5 * 1024 * 1024, hold_duration: Duration::from_secs(5),
region: "burst".into(),
}
}
}
impl CustomMemoryPattern for BurstPattern {
fn apply(&self, profiler: &Profiler, elapsed: Duration) {
let cycle = elapsed.as_millis() % self.burst_interval.as_millis();
if cycle < 100 {
profiler.record_allocation(&self.region, self.burst_size);
} else if cycle >= self.hold_duration.as_millis()
&& cycle < self.hold_duration.as_millis() + 100
{
profiler.record_deallocation(&self.region, self.burst_size);
}
}
fn description(&self) -> &str {
"Burst pattern: sudden spikes held briefly"
}
}
#[derive(Debug, Clone)]
pub struct LeakPattern {
pub leak_rate_per_sec: usize,
pub leak_probability: f64,
pub region: String,
pub sporadic_fix: bool,
}
impl Default for LeakPattern {
fn default() -> Self {
Self {
leak_rate_per_sec: 10 * 1024, leak_probability: 0.1,
region: "leak".into(),
sporadic_fix: false,
}
}
}
impl CustomMemoryPattern for LeakPattern {
fn apply(&self, profiler: &Profiler, elapsed: Duration) {
let expected_leaked = (elapsed.as_secs_f64() * self.leak_rate_per_sec as f64) as usize;
let leak_amount = self.leak_rate_per_sec / 100; if leak_amount > 0 {
profiler.record_allocation(&self.region, leak_amount);
}
if self.sporadic_fix && elapsed.as_secs() % 60 == 30 {
profiler.record_deallocation(&self.region, expected_leaked / 4);
}
}
fn description(&self) -> &str {
"Leak pattern: gradual memory leak simulation"
}
}
pub struct MemoryPatternFactory;
impl MemoryPatternFactory {
pub fn steady() -> MemoryPattern {
MemoryPattern::Steady
}
pub fn growth_only() -> MemoryPattern {
MemoryPattern::GrowthOnly
}
pub fn sawtooth(period: Duration, peak_bytes: usize) -> MemoryPattern {
MemoryPattern::Custom(Arc::new(SawtoothPattern {
period,
peak_bytes,
region: "sawtooth".into(),
}))
}
pub fn stepped(step_duration: Duration, step_bytes: usize, max_steps: usize) -> MemoryPattern {
MemoryPattern::Custom(Arc::new(SteppedPattern {
step_duration,
step_bytes,
max_steps,
region: "stepped".into(),
}))
}
pub fn burst(interval: Duration, size: usize, hold: Duration) -> MemoryPattern {
MemoryPattern::Custom(Arc::new(BurstPattern {
burst_interval: interval,
burst_size: size,
hold_duration: hold,
region: "burst".into(),
}))
}
pub fn leak(rate_per_sec: usize) -> MemoryPattern {
MemoryPattern::Custom(Arc::new(LeakPattern {
leak_rate_per_sec: rate_per_sec,
..Default::default()
}))
}
pub fn combined(patterns: Vec<MemoryPattern>) -> MemoryPattern {
MemoryPattern::Custom(Arc::new(CombinedPattern { patterns }))
}
}
#[derive(Debug)]
struct CombinedPattern {
patterns: Vec<MemoryPattern>,
}
impl CustomMemoryPattern for CombinedPattern {
fn apply(&self, profiler: &Profiler, elapsed: Duration) {
for pattern in &self.patterns {
pattern.apply(profiler, elapsed);
}
}
fn description(&self) -> &str {
"Combined pattern: multiple patterns applied together"
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::profiling::ProfilerConfig;
#[test]
fn test_memory_pattern_descriptions() {
assert!(!MemoryPattern::Steady.description().is_empty());
assert!(!MemoryPattern::GrowthOnly.description().is_empty());
assert!(!MemoryPattern::HighChurn.description().is_empty());
}
#[test]
fn test_sawtooth_pattern() {
let profiler = Profiler::new(ProfilerConfig::default());
profiler.start();
let pattern = SawtoothPattern::default();
pattern.apply(&profiler, Duration::from_secs(5));
let snapshot = profiler.snapshot();
assert!(snapshot.allocation_count > 0 || snapshot.current_bytes > 0);
}
#[test]
fn test_stepped_pattern() {
let profiler = Profiler::new(ProfilerConfig::default());
profiler.start();
let pattern = SteppedPattern::default();
pattern.apply(&profiler, Duration::from_secs(0));
pattern.apply(&profiler, Duration::from_secs(10));
pattern.apply(&profiler, Duration::from_secs(20));
}
#[test]
fn test_burst_pattern() {
let profiler = Profiler::new(ProfilerConfig::default());
profiler.start();
let pattern = BurstPattern {
burst_interval: Duration::from_secs(10),
burst_size: 1024,
hold_duration: Duration::from_secs(2),
region: "test".into(),
};
pattern.apply(&profiler, Duration::from_millis(50));
let snapshot1 = profiler.snapshot();
pattern.apply(&profiler, Duration::from_millis(2050));
let _snapshot2 = profiler.snapshot();
assert!(snapshot1.allocation_count > 0);
}
#[test]
fn test_leak_pattern() {
let profiler = Profiler::new(ProfilerConfig::default());
profiler.start();
let pattern = LeakPattern {
leak_rate_per_sec: 1024,
leak_probability: 1.0,
region: "test_leak".into(),
sporadic_fix: false,
};
for i in 0..10 {
pattern.apply(&profiler, Duration::from_millis(i * 100));
}
let snapshot = profiler.snapshot();
assert!(snapshot.allocation_count > 0);
}
#[test]
fn test_pattern_factory() {
let _steady = MemoryPatternFactory::steady();
let _growth = MemoryPatternFactory::growth_only();
let _sawtooth = MemoryPatternFactory::sawtooth(Duration::from_secs(10), 1024);
let _stepped = MemoryPatternFactory::stepped(Duration::from_secs(5), 512, 5);
let _burst =
MemoryPatternFactory::burst(Duration::from_secs(20), 4096, Duration::from_secs(3));
let _leak = MemoryPatternFactory::leak(1024);
}
#[test]
fn test_combined_pattern() {
let profiler = Profiler::new(ProfilerConfig::default());
profiler.start();
let pattern = MemoryPatternFactory::combined(vec![
MemoryPattern::HighChurn,
MemoryPatternFactory::leak(512),
]);
pattern.apply(&profiler, Duration::from_secs(1));
}
}