use std::collections::VecDeque;
pub struct SpectralAnalyzer {
samples: VecDeque<f64>,
window_size: usize,
frequency_bins: Vec<f64>,
num_bins: usize,
}
impl SpectralAnalyzer {
pub fn new(window_size: usize) -> Self {
let num_bins = 8; Self {
samples: VecDeque::with_capacity(window_size),
window_size,
frequency_bins: vec![0.0; num_bins],
num_bins,
}
}
pub fn add_sample(&mut self, usage: f64) {
if self.samples.len() >= self.window_size {
self.samples.pop_front();
}
self.samples.push_back(usage.clamp(0.0, 1.0));
if self.samples.len() >= self.window_size / 2 {
self.update_spectrum();
}
}
fn update_spectrum(&mut self) {
let n = self.samples.len();
if n < 2 {
return;
}
let samples: Vec<f64> = self.samples.iter().copied().collect();
for k in 0..self.num_bins {
let mut real = 0.0;
let mut imag = 0.0;
for (i, &sample) in samples.iter().enumerate() {
let angle = 2.0 * std::f64::consts::PI * (k as f64) * (i as f64) / (n as f64);
real += sample * angle.cos();
imag += sample * angle.sin();
}
self.frequency_bins[k] = (real * real + imag * imag).sqrt() / (n as f64);
}
}
pub fn classify(&self) -> MemoryPatternClass {
if self.samples.len() < self.window_size / 4 {
return MemoryPatternClass::Unknown;
}
let mean = self.mean();
let variance = self.variance();
let trend = self.trend();
let dominant_freq = self.dominant_frequency();
if variance < 0.01 {
if mean > 0.8 {
MemoryPatternClass::ConstantHigh
} else if mean < 0.4 {
MemoryPatternClass::ConstantLow
} else {
MemoryPatternClass::Stable
}
} else if trend > 0.1 {
MemoryPatternClass::Increasing
} else if trend < -0.1 {
MemoryPatternClass::Decreasing
} else if dominant_freq > 0 && self.frequency_bins[dominant_freq] > 0.1 {
MemoryPatternClass::Oscillating
} else if variance > 0.1 {
MemoryPatternClass::Volatile
} else {
MemoryPatternClass::Normal
}
}
fn mean(&self) -> f64 {
if self.samples.is_empty() {
return 0.0;
}
self.samples.iter().sum::<f64>() / self.samples.len() as f64
}
fn variance(&self) -> f64 {
if self.samples.len() < 2 {
return 0.0;
}
let mean = self.mean();
self.samples.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / self.samples.len() as f64
}
fn trend(&self) -> f64 {
let n = self.samples.len();
if n < 2 {
return 0.0;
}
let mut sum_x = 0.0;
let mut sum_y = 0.0;
let mut sum_xy = 0.0;
let mut sum_xx = 0.0;
for (i, &y) in self.samples.iter().enumerate() {
let x = i as f64;
sum_x += x;
sum_y += y;
sum_xy += x * y;
sum_xx += x * x;
}
let n = n as f64;
let denom = n * sum_xx - sum_x * sum_x;
if denom.abs() < 1e-10 {
return 0.0;
}
(n * sum_xy - sum_x * sum_y) / denom
}
fn dominant_frequency(&self) -> usize {
self.frequency_bins
.iter()
.enumerate()
.skip(1) .max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
.map(|(i, _)| i)
.unwrap_or(0)
}
pub fn get_recommendation(&self) -> SpectralRecommendation {
let class = self.classify();
let mean = self.mean();
let trend = self.trend();
match class {
MemoryPatternClass::ConstantHigh => SpectralRecommendation {
action: RecommendedAction::OptimizeNow,
confidence: 0.95,
reason: "Consistently high memory usage".into(),
predicted_relief_mb: (mean * 1000.0) as u64,
},
MemoryPatternClass::Increasing => SpectralRecommendation {
action: RecommendedAction::OptimizeSoon,
confidence: 0.85,
reason: format!("Memory usage trending up (slope: {:.3})", trend),
predicted_relief_mb: ((mean + trend * 10.0) * 500.0) as u64,
},
MemoryPatternClass::Volatile => SpectralRecommendation {
action: RecommendedAction::Monitor,
confidence: 0.7,
reason: "Volatile memory usage pattern".into(),
predicted_relief_mb: (mean * 300.0) as u64,
},
MemoryPatternClass::Oscillating => SpectralRecommendation {
action: RecommendedAction::ScheduleOptimization,
confidence: 0.75,
reason: "Cyclic memory pattern detected".into(),
predicted_relief_mb: (mean * 400.0) as u64,
},
MemoryPatternClass::Decreasing => SpectralRecommendation {
action: RecommendedAction::Wait,
confidence: 0.8,
reason: "Memory usage decreasing naturally".into(),
predicted_relief_mb: 0,
},
MemoryPatternClass::ConstantLow | MemoryPatternClass::Stable => SpectralRecommendation {
action: RecommendedAction::NoAction,
confidence: 0.9,
reason: "Memory usage is healthy".into(),
predicted_relief_mb: 0,
},
_ => SpectralRecommendation {
action: RecommendedAction::Monitor,
confidence: 0.5,
reason: "Insufficient data".into(),
predicted_relief_mb: 0,
},
}
}
pub fn stats(&self) -> SpectralStats {
SpectralStats {
sample_count: self.samples.len(),
mean: self.mean(),
variance: self.variance(),
trend: self.trend(),
dominant_frequency: self.dominant_frequency(),
classification: self.classify(),
}
}
}
impl Default for SpectralAnalyzer {
fn default() -> Self {
Self::new(60) }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MemoryPatternClass {
Unknown,
Stable,
ConstantLow,
ConstantHigh,
Increasing,
Decreasing,
Oscillating,
Volatile,
Normal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RecommendedAction {
NoAction,
Monitor,
Wait,
ScheduleOptimization,
OptimizeSoon,
OptimizeNow,
}
#[derive(Debug, Clone)]
pub struct SpectralRecommendation {
pub action: RecommendedAction,
pub confidence: f64,
pub reason: String,
pub predicted_relief_mb: u64,
}
#[derive(Debug, Clone)]
pub struct SpectralStats {
pub sample_count: usize,
pub mean: f64,
pub variance: f64,
pub trend: f64,
pub dominant_frequency: usize,
pub classification: MemoryPatternClass,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_spectral_stable() {
let mut analyzer = SpectralAnalyzer::new(20);
for _ in 0..20 {
analyzer.add_sample(0.5);
}
let class = analyzer.classify();
assert!(matches!(class, MemoryPatternClass::Stable | MemoryPatternClass::Normal));
}
#[test]
fn test_spectral_increasing() {
let mut analyzer = SpectralAnalyzer::new(20);
for i in 0..20 {
analyzer.add_sample(0.2 + (i as f64) * 0.04);
}
let class = analyzer.classify();
let stats = analyzer.stats();
assert!(stats.trend > 0.0, "Trend should be positive: {}", stats.trend);
}
#[test]
fn test_recommendation() {
let mut analyzer = SpectralAnalyzer::new(20);
for _ in 0..20 {
analyzer.add_sample(0.9);
}
let rec = analyzer.get_recommendation();
assert_eq!(rec.action, RecommendedAction::OptimizeNow);
assert!(rec.confidence > 0.9);
}
}