use std::time::{Duration, Instant};
use super::fixed::{align_to_block_size, get_optimal_block_size};
use super::*;
use crate::defaults::io::INITIAL_CHUNK_SIZE;
use crate::metrics::*;
#[derive(Debug, Clone)]
struct MemoryCache {
last_check: Instant,
last_pressure: MemoryPressure,
}
impl MemoryCache {
fn get_pressure(&mut self) -> MemoryPressure {
if self.last_check.elapsed() > Duration::from_secs(1) {
self.last_pressure = detect_memory_pressure(None);
self.last_check = Instant::now();
}
self.last_pressure
}
}
#[derive(Debug, Clone)]
pub struct AdaptiveStrategy {
pub config: AdaptationConfig,
metrics_history: MovingAverage<10>,
memory: MemoryCache,
}
impl Default for AdaptiveStrategy {
fn default() -> Self {
let config = AdaptationConfig::default();
Self {
config,
metrics_history: MovingAverage::new(),
memory: MemoryCache {
last_check: Instant::now(),
last_pressure: MemoryPressure::Unknown,
},
}
}
}
impl AdaptiveStrategy {
pub fn optimal_for_file(file_size: u64) -> Self {
let config = AdaptationConfig {
initial_chunk_size: optimal_chunk_size(file_size, None),
..Default::default()
};
Self {
config,
..Default::default()
}
}
}
impl IsStrategy for AdaptiveStrategy {
fn initial_chunk_size(&self) -> usize {
self.config.initial_chunk_size
}
fn adapt_size(&mut self, metrics: &ChunkMetrics, current_size_bytes: usize) -> usize {
let pressure = self.memory.get_pressure();
if matches!(pressure, MemoryPressure::Critical) {
eprintln!("Memory critical! Dropping to minimum chunk size");
return self.config.min_chunk_size;
}
if matches!(pressure, MemoryPressure::High) {
let shrunk_size = (current_size_bytes as f64 * 0.5) as usize;
return align_to_block_size(shrunk_size, get_optimal_block_size());
}
let newsize = match pressure {
MemoryPressure::Critical => self.config.min_chunk_size, MemoryPressure::High => {
let shrunk = (current_size_bytes as f64 * self.config.shrink_factor) as usize;
align_to_block_size(shrunk, get_optimal_block_size())
}
_ => {
let moving_ave = metrics.speed_moving();
let perf = moving_ave.trend(self.config.speed_increase_threshold);
let new_size = match perf {
Trend::Improving => (current_size_bytes as f64 * self.config.growth_factor) as usize,
Trend::Degrading => (current_size_bytes as f64 * self.config.shrink_factor) as usize,
Trend::Stable => current_size_bytes,
Trend::Unknown => current_size_bytes,
};
align_to_block_size(new_size, get_optimal_block_size())
}
};
let clamped = newsize.clamp(self.config.min_chunk_size, self.config.max_chunk_size);
self.metrics_history.push(clamped as f64);
clamped
}
fn should_adapt(&self, metrics: &ChunkMetrics) -> bool {
metrics.should_adapt(self.config.interval(metrics))
}
}
#[derive(Debug, Clone)]
pub struct AdaptationConfig {
pub min_chunk_size: usize, pub max_chunk_size: usize, pub initial_chunk_size: usize,
pub speed_increase_threshold: f64, pub _speed_decrease_threshold: f64, pub memory_threshold_mb: usize,
pub growth_factor: f64, pub shrink_factor: f64,
pub interval: usize, }
impl AdaptationConfig {
pub fn interval(&self, metrics: &ChunkMetrics) -> usize {
if metrics.chunks_seen < 50 {
5 } else if metrics.chunks_seen < 200 {
10 } else {
20 }
}
}
impl Default for AdaptationConfig {
fn default() -> Self {
let block_size = get_optimal_block_size();
Self {
min_chunk_size: align_to_block_size(8 * 1024, block_size), max_chunk_size: align_to_block_size(5 * 1024 * 1024, block_size), initial_chunk_size: align_to_block_size(INITIAL_CHUNK_SIZE, block_size),
speed_increase_threshold: 3.0, _speed_decrease_threshold: 0.0, memory_threshold_mb: 200,
growth_factor: 2.0, shrink_factor: 0.5, interval: 20,
}
}
}