use std::collections::{HashMap, VecDeque};
use std::time::{Duration, Instant};
use crate::memory::metrics::{MemoryEvent, MemoryEventType};
#[cfg(feature = "memory_metrics")]
#[cfg(feature = "serialization")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
pub struct LeakDetectionConfig {
pub analysis_windowsize: usize,
pub leak_threshold_bytes: usize,
pub leak_threshold_duration: Duration,
pub growth_rate_threshold: f64,
pub min_data_points: usize,
}
impl Default for LeakDetectionConfig {
fn default() -> Self {
Self {
analysis_windowsize: 100,
leak_threshold_bytes: 1024 * 1024, leak_threshold_duration: Duration::from_secs(30),
growth_rate_threshold: 1024.0, min_data_points: 10,
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "memory_metrics", derive(Serialize, Deserialize))]
pub struct MemoryPatternAnalysis {
pub component: String,
pub patterns: Vec<AllocationPattern>,
pub efficiency: MemoryEfficiencyMetrics,
pub potential_issues: Vec<MemoryIssue>,
pub recommendations: Vec<OptimizationRecommendation>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "memory_metrics", derive(Serialize, Deserialize))]
pub enum AllocationPattern {
SteadyGrowth {
rate: f64,
confidence: f64,
},
PeriodicCycle {
cycle_duration: Duration,
peak_size: usize,
confidence: f64,
},
BurstAllocation {
burst_size: usize,
burst_duration: Duration,
confidence: f64,
},
Plateau {
size: usize,
duration: Duration,
confidence: f64,
},
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "memory_metrics", derive(Serialize, Deserialize))]
pub struct MemoryEfficiencyMetrics {
pub reuse_ratio: f64,
pub allocation_frequency: f64,
pub avg_allocation_lifetime: Duration,
pub fragmentation_estimate: f64,
pub buffer_pool_efficiency: Option<f64>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "memory_metrics", derive(Serialize, Deserialize))]
pub enum MemoryIssue {
MemoryLeak {
growth_rate: f64,
duration: Duration,
severity: f64,
},
HighAllocationFrequency {
frequency: f64,
impact: String,
},
HighPeakUsage {
peak_size: usize,
system_percentage: Option<f64>,
},
MemoryFragmentation {
fragmentation_ratio: f64,
potential_waste: usize,
},
IneffientBufferPool {
efficiency: f64,
pool_misses: usize,
},
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "memory_metrics", derive(Serialize, Deserialize))]
pub enum OptimizationRecommendation {
UseBufferPooling {
expected_savings: usize,
suggested_poolsizes: Vec<usize>,
},
BatchAllocations {
current_frequency: f64,
suggested_batch_size: usize,
},
PreAllocateMemory {
suggested_size: usize,
performance_gain: String,
},
UseMemoryEfficientStructures {
current_type: String,
suggested_alternative: String,
memory_reduction: usize,
},
ImplementCompaction {
fragmentation_reduction: f64,
suggested_frequency: Duration,
},
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "memory_metrics", derive(Serialize, Deserialize))]
pub struct LeakDetectionResult {
pub component: String,
pub leak_detected: bool,
pub growth_rate: f64,
pub confidence: f64,
pub analysis_duration: Duration,
pub current_usage: usize,
pub projected_usage_1h: usize,
pub projected_usage_24h: usize,
}
pub struct MemoryAnalytics {
leak_config: LeakDetectionConfig,
usage_history: HashMap<String, VecDeque<(Instant, usize)>>,
allocation_history: HashMap<String, VecDeque<(Instant, usize, MemoryEventType)>>,
}
impl MemoryAnalytics {
pub fn new(leakconfig: LeakDetectionConfig) -> Self {
Self {
leak_config: leakconfig,
usage_history: HashMap::new(),
allocation_history: HashMap::new(),
}
}
pub fn record_event(&mut self, event: MemoryEvent) {
let component = event.component.clone();
let timestamp = event.timestamp;
let alloc_history = self
.allocation_history
.entry(component.clone())
.or_default();
alloc_history.push_back((timestamp, event.size, event.event_type));
while alloc_history.len() > self.leak_config.analysis_windowsize {
alloc_history.pop_front();
}
let current_usage = self.calculate_current_usage(&component);
let usage_history = self.usage_history.entry(component).or_default();
usage_history.push_back((timestamp, current_usage));
while usage_history.len() > self.leak_config.analysis_windowsize {
usage_history.pop_front();
}
}
fn calculate_current_usage(&self, component: &str) -> usize {
if let Some(history) = self.allocation_history.get(component) {
let mut usage = 0usize;
for (_timestamp, size, event_type) in history {
match event_type {
MemoryEventType::Allocation => usage += size,
MemoryEventType::Deallocation => usage = usage.saturating_sub(*size),
MemoryEventType::Resize => {
usage += size;
}
MemoryEventType::Access | MemoryEventType::Transfer => {
}
}
}
usage
} else {
0
}
}
pub fn detect_memory_leak(&self, component: &str) -> Option<LeakDetectionResult> {
let usage_history = self.usage_history.get(component)?;
if usage_history.len() < self.leak_config.min_data_points {
return None;
}
let (growth_rate, confidence) = self.calculate_growth_rate(usage_history);
let analysis_duration = usage_history
.back()?
.0
.duration_since(usage_history.front()?.0);
if analysis_duration < self.leak_config.leak_threshold_duration {
return None;
}
let current_usage = usage_history.back()?.1;
let leak_detected = growth_rate > self.leak_config.growth_rate_threshold
&& confidence > 0.7
&& current_usage > self.leak_config.leak_threshold_bytes;
Some(LeakDetectionResult {
component: component.to_string(),
leak_detected,
growth_rate,
confidence,
analysis_duration,
current_usage,
projected_usage_1h: current_usage + (growth_rate * 3600.0) as usize,
projected_usage_24h: current_usage + (growth_rate * 86400.0) as usize,
})
}
fn calculate_growth_rate(&self, history: &VecDeque<(Instant, usize)>) -> (f64, f64) {
if history.len() < 2 {
return (0.0, 0.0);
}
let start_time = history.front().expect("Operation failed").0;
let points: Vec<(f64, f64)> = history
.iter()
.map(|(timestamp, usage)| {
let x = timestamp.duration_since(start_time).as_secs_f64();
let y = *usage as f64;
(x, y)
})
.collect();
self.linear_regression(&points)
}
fn linear_regression(&self, points: &[(f64, f64)]) -> (f64, f64) {
let n = points.len() as f64;
if n < 2.0 {
return (0.0, 0.0);
}
let sum_x: f64 = points.iter().map(|(x, _)| x).sum();
let sum_y: f64 = points.iter().map(|(_, y)| y).sum();
let sum_xy: f64 = points.iter().map(|(x, y)| x * y).sum();
let sum_x2: f64 = points.iter().map(|(x, _)| x * x).sum();
let sum_y2: f64 = points.iter().map(|(_, y)| y * y).sum();
let mean_x = sum_x / n;
let mean_y = sum_y / n;
let numerator = sum_xy - n * mean_x * mean_y;
let denominator = sum_x2 - n * mean_x * mean_x;
if denominator.abs() < f64::EPSILON {
return (0.0, 0.0);
}
let slope = numerator / denominator;
let ss_tot = sum_y2 - n * mean_y * mean_y;
let ss_res: f64 = points
.iter()
.map(|(x, y)| {
let predicted = slope * (x - mean_x) + mean_y;
(y - predicted).powi(2)
})
.sum();
let r_squared = if ss_tot.abs() < f64::EPSILON {
0.0
} else {
1.0 - (ss_res / ss_tot).max(0.0)
};
(slope, r_squared)
}
pub fn analyze_patterns(&self, component: &str) -> Option<MemoryPatternAnalysis> {
let usage_history = self.usage_history.get(component)?;
let allocation_history = self.allocation_history.get(component)?;
if usage_history.len() < self.leak_config.min_data_points {
return None;
}
let patterns = self.detect_allocation_patterns(usage_history, allocation_history);
let efficiency = self.calculate_efficiency_metrics(component, allocation_history);
let potential_issues = self.identify_potential_issues(component);
let recommendations = self.generate_recommendations(&efficiency, &potential_issues);
Some(MemoryPatternAnalysis {
component: component.to_string(),
patterns,
efficiency,
potential_issues,
recommendations,
})
}
fn detect_allocation_patterns(
&self,
usage_history: &VecDeque<(Instant, usize)>,
allocation_history: &VecDeque<(Instant, usize, MemoryEventType)>,
) -> Vec<AllocationPattern> {
let mut patterns = Vec::new();
let (growth_rate, confidence) = self.calculate_growth_rate(usage_history);
if growth_rate > 100.0 && confidence > 0.8 {
patterns.push(AllocationPattern::SteadyGrowth {
rate: growth_rate,
confidence,
});
}
if let Some(cycle) = self.detect_periodic_cycles(usage_history) {
patterns.push(cycle);
}
if let Some(burst) = self.detect_burst_allocations(allocation_history) {
patterns.push(burst);
}
if let Some(plateau) = self.detect_plateaus(usage_history) {
patterns.push(plateau);
}
patterns
}
fn detect_periodic_cycles(
&self,
usage_history: &VecDeque<(Instant, usize)>,
) -> Option<AllocationPattern> {
if usage_history.len() < 10 {
return None;
}
let values: Vec<usize> = usage_history.iter().map(|(_, usage)| *usage).collect();
for cycle_len in 3..values.len() / 3 {
let mut correlation = 0.0;
let mut count = 0;
for i in cycle_len..values.len() {
let diff = (values[i] as f64 - values[i - cycle_len] as f64).abs();
let avg = (values[i] + values[i - cycle_len]) as f64 / 2.0;
if avg > 0.0 {
correlation += 1.0 - (diff / avg).min(1.0);
count += 1;
}
}
if count > 0 {
correlation /= count as f64;
if correlation > 0.8 {
let cycle_duration = Duration::from_secs((cycle_len * 5) as u64); let peak_size = values.iter().max().copied().unwrap_or(0);
return Some(AllocationPattern::PeriodicCycle {
cycle_duration,
peak_size,
confidence: correlation,
});
}
}
}
None
}
fn detect_burst_allocations(
&self,
allocation_history: &VecDeque<(Instant, usize, MemoryEventType)>,
) -> Option<AllocationPattern> {
if allocation_history.len() < 5 {
return None;
}
let mut burst_size = 0usize;
let mut burst_start: Option<Instant> = None;
let mut current_burst_size = 0usize;
for (timestamp, size, event_type) in allocation_history {
match event_type {
MemoryEventType::Allocation => {
if burst_start.is_none() {
burst_start = Some(*timestamp);
current_burst_size = *size;
} else {
current_burst_size += size;
}
}
MemoryEventType::Deallocation => {
if let Some(start) = burst_start {
let duration = timestamp.duration_since(start);
if duration > Duration::from_millis(100) && current_burst_size > burst_size
{
burst_size = current_burst_size;
}
burst_start = None;
current_burst_size = 0;
}
}
_ => {}
}
}
if burst_size > 1024 * 1024 {
Some(AllocationPattern::BurstAllocation {
burst_size,
burst_duration: Duration::from_millis(500), confidence: 0.9,
})
} else {
None
}
}
fn detect_plateaus(
&self,
usage_history: &VecDeque<(Instant, usize)>,
) -> Option<AllocationPattern> {
if usage_history.len() < 10 {
return None;
}
let values: Vec<usize> = usage_history.iter().map(|(_, usage)| *usage).collect();
let mut plateau_start = 0;
let mut max_plateau_len = 0;
let mut plateau_value = 0;
for i in 1..values.len() {
let diff_ratio = if values[i.saturating_sub(1)] > 0 {
(values[i] as f64 - values[i.saturating_sub(1)] as f64).abs()
/ values[i.saturating_sub(1)] as f64
} else {
0.0
};
if diff_ratio < 0.05 {
if plateau_start == 0 {
plateau_start = i.saturating_sub(1);
}
} else {
if plateau_start > 0 {
let plateau_len = i - plateau_start;
if plateau_len > max_plateau_len {
max_plateau_len = plateau_len;
plateau_value = values[plateau_start];
}
}
plateau_start = 0;
}
}
if plateau_start > 0 {
let plateau_len = values.len() - plateau_start;
if plateau_len > max_plateau_len {
max_plateau_len = plateau_len;
plateau_value = values[plateau_start];
}
}
if max_plateau_len >= 5 && plateau_value > 0 {
Some(AllocationPattern::Plateau {
size: plateau_value,
duration: Duration::from_secs((max_plateau_len * 5) as u64), confidence: 0.8,
})
} else {
None
}
}
fn calculate_efficiency_metrics(
&self,
component: &str,
allocation_history: &VecDeque<(Instant, usize, MemoryEventType)>,
) -> MemoryEfficiencyMetrics {
if allocation_history.is_empty() {
return MemoryEfficiencyMetrics {
reuse_ratio: 0.0,
allocation_frequency: 0.0,
avg_allocation_lifetime: Duration::from_secs(0),
fragmentation_estimate: 0.0,
buffer_pool_efficiency: None,
};
}
let total_allocated = allocation_history
.iter()
.filter_map(|(_, size, event_type)| {
if matches!(event_type, MemoryEventType::Allocation) {
Some(*size)
} else {
None
}
})
.sum::<usize>();
let current_usage = self.calculate_current_usage(component);
let reuse_ratio = if current_usage > 0 {
total_allocated as f64 / current_usage as f64
} else {
0.0
};
let allocation_count = allocation_history
.iter()
.filter(|(_, _, event_type)| matches!(event_type, MemoryEventType::Allocation))
.count();
let duration = if let (Some(first), Some(last)) =
(allocation_history.front(), allocation_history.back())
{
last.0.duration_since(first.0)
} else {
Duration::from_secs(1)
};
let allocation_frequency = allocation_count as f64 / duration.as_secs_f64();
let avg_allocation_lifetime = if allocation_count > 0 {
duration / allocation_count as u32
} else {
Duration::from_secs(0)
};
let fragmentation_estimate = self.estimate_fragmentation(allocation_history);
MemoryEfficiencyMetrics {
reuse_ratio,
allocation_frequency,
avg_allocation_lifetime,
fragmentation_estimate,
buffer_pool_efficiency: None, }
}
fn estimate_fragmentation(
&self,
allocation_history: &VecDeque<(Instant, usize, MemoryEventType)>,
) -> f64 {
let allocation_sizes: Vec<usize> = allocation_history
.iter()
.filter_map(|(_, size, event_type)| {
if matches!(event_type, MemoryEventType::Allocation) {
Some(*size)
} else {
None
}
})
.collect();
if allocation_sizes.is_empty() {
return 0.0;
}
let mean = allocation_sizes.iter().sum::<usize>() as f64 / allocation_sizes.len() as f64;
let variance = allocation_sizes
.iter()
.map(|&size| {
let diff = size as f64 - mean;
diff * diff
})
.sum::<f64>()
/ allocation_sizes.len() as f64;
let std_dev = variance.sqrt();
if mean > 0.0 {
(std_dev / mean).min(1.0)
} else {
0.0
}
}
fn identify_potential_issues(&self, component: &str) -> Vec<MemoryIssue> {
let mut issues = Vec::new();
if let Some(leak_result) = self.detect_memory_leak(component) {
if leak_result.leak_detected {
issues.push(MemoryIssue::MemoryLeak {
growth_rate: leak_result.growth_rate,
duration: leak_result.analysis_duration,
severity: leak_result.confidence,
});
}
}
if let Some(allocation_history) = self.allocation_history.get(component) {
let efficiency = self.calculate_efficiency_metrics(component, allocation_history);
if efficiency.allocation_frequency > 100.0 {
issues.push(MemoryIssue::HighAllocationFrequency {
frequency: efficiency.allocation_frequency,
impact: "High allocation frequency can cause performance degradation"
.to_string(),
});
}
if efficiency.fragmentation_estimate > 0.7 {
let current_usage = self.calculate_current_usage(component);
let potential_waste =
(current_usage as f64 * efficiency.fragmentation_estimate) as usize;
issues.push(MemoryIssue::MemoryFragmentation {
fragmentation_ratio: efficiency.fragmentation_estimate,
potential_waste,
});
}
}
if let Some(usage_history) = self.usage_history.get(component) {
if let Some(peak_usage) = usage_history.iter().map(|(_, usage)| *usage).max() {
if peak_usage > 100 * 1024 * 1024 {
issues.push(MemoryIssue::HighPeakUsage {
peak_size: peak_usage,
system_percentage: None, });
}
}
}
issues
}
fn generate_recommendations(
&self,
efficiency: &MemoryEfficiencyMetrics,
issues: &[MemoryIssue],
) -> Vec<OptimizationRecommendation> {
let mut recommendations = Vec::new();
if efficiency.allocation_frequency > 50.0 {
recommendations.push(OptimizationRecommendation::UseBufferPooling {
expected_savings: (efficiency.allocation_frequency * 1024.0) as usize, suggested_poolsizes: vec![1024, 4096, 16384, 65536],
});
}
if efficiency.allocation_frequency > 20.0
&& efficiency.avg_allocation_lifetime.as_secs() < 10
{
recommendations.push(OptimizationRecommendation::BatchAllocations {
current_frequency: efficiency.allocation_frequency,
suggested_batch_size: (efficiency.allocation_frequency * 2.0) as usize,
});
}
if efficiency.reuse_ratio > 2.0 {
recommendations.push(OptimizationRecommendation::PreAllocateMemory {
suggested_size: (efficiency.allocation_frequency * 1024.0) as usize,
performance_gain: "Reduced allocation overhead".to_string(),
});
}
if efficiency.fragmentation_estimate > 0.5 {
recommendations.push(OptimizationRecommendation::ImplementCompaction {
fragmentation_reduction: efficiency.fragmentation_estimate * 0.7, suggested_frequency: Duration::from_secs(300), });
}
for issue in issues {
if let MemoryIssue::HighPeakUsage { peak_size, .. } = issue {
recommendations.push(OptimizationRecommendation::UseMemoryEfficientStructures {
current_type: "Unknown".to_string(),
suggested_alternative: "Streaming or memory-mapped structures".to_string(),
memory_reduction: peak_size / 2, });
}
}
recommendations
}
pub fn get_leak_detection_results(&self) -> Vec<LeakDetectionResult> {
self.usage_history
.keys()
.filter_map(|component| self.detect_memory_leak(component))
.collect()
}
pub fn get_pattern_analysis_results(&self) -> Vec<MemoryPatternAnalysis> {
self.usage_history
.keys()
.filter_map(|component| self.analyze_patterns(component))
.collect()
}
pub fn clear(&mut self) {
self.usage_history.clear();
self.allocation_history.clear();
}
}
impl Default for MemoryAnalytics {
fn default() -> Self {
Self::new(LeakDetectionConfig::default())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_memory_analytics_creation() {
let analytics = MemoryAnalytics::new(LeakDetectionConfig::default());
assert!(analytics.usage_history.is_empty());
assert!(analytics.allocation_history.is_empty());
}
#[test]
fn test_leak_detection_insufficient_data() {
let analytics = MemoryAnalytics::new(LeakDetectionConfig::default());
let result = analytics.detect_memory_leak("test_component");
assert!(result.is_none());
}
#[test]
fn test_linear_regression() {
let analytics = MemoryAnalytics::new(LeakDetectionConfig::default());
let points = vec![(0.0, 0.0), (1.0, 100.0), (2.0, 200.0), (3.0, 300.0)];
let (slope, r_squared) = analytics.linear_regression(&points);
assert!((slope - 100.0).abs() < 1.0);
assert!(r_squared > 0.99);
}
#[test]
fn test_pattern_analysis_with_insufficient_data() {
let analytics = MemoryAnalytics::new(LeakDetectionConfig::default());
let result = analytics.analyze_patterns("test_component");
assert!(result.is_none());
}
}