use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct EvictionConfig {
pub enabled: bool,
pub target_memory_fraction: f64,
pub min_eviction_depth: usize,
pub batch_size: usize,
pub quiescence_timeout: Duration,
pub quiescence_poll_interval: Duration,
pub cooldown_period: Duration,
pub use_lru_tracking: bool,
pub enable_memory_pressure_monitor: bool,
pub memory_pressure_config:
Option<crate::persistent_artrie::memory_monitor::MemoryPressureConfig>,
pub resident_budget_bytes: Option<usize>,
pub resident_budget_eviction_cap: Option<usize>,
}
impl Default for EvictionConfig {
fn default() -> Self {
Self {
enabled: true,
target_memory_fraction: 0.70,
min_eviction_depth: 1,
batch_size: 256,
quiescence_timeout: Duration::from_millis(100),
quiescence_poll_interval: Duration::from_micros(100),
cooldown_period: Duration::from_millis(100),
use_lru_tracking: true,
enable_memory_pressure_monitor: true,
memory_pressure_config: None,
resident_budget_bytes: None,
resident_budget_eviction_cap: None,
}
}
}
impl EvictionConfig {
pub fn disabled() -> Self {
Self {
enabled: false,
enable_memory_pressure_monitor: false,
..Default::default()
}
}
pub fn memory_constrained() -> Self {
Self {
enabled: true,
target_memory_fraction: 0.80,
min_eviction_depth: 0,
batch_size: 512,
quiescence_timeout: Duration::from_millis(50),
quiescence_poll_interval: Duration::from_micros(50),
cooldown_period: Duration::from_millis(50),
use_lru_tracking: true,
enable_memory_pressure_monitor: true,
memory_pressure_config: Some(
crate::persistent_artrie::memory_monitor::MemoryPressureConfig {
low_memory_threshold: 0.20,
critical_memory_threshold: 0.05,
..Default::default()
},
),
resident_budget_bytes: None,
resident_budget_eviction_cap: None,
}
}
pub fn read_optimized() -> Self {
Self {
enabled: true,
target_memory_fraction: 0.50,
min_eviction_depth: 3,
batch_size: 128,
quiescence_timeout: Duration::from_millis(200),
quiescence_poll_interval: Duration::from_micros(200),
cooldown_period: Duration::from_millis(200),
use_lru_tracking: true,
enable_memory_pressure_monitor: true,
memory_pressure_config: None,
resident_budget_bytes: None,
resident_budget_eviction_cap: None,
}
}
pub fn without_memory_monitor() -> Self {
Self {
enabled: true,
enable_memory_pressure_monitor: false,
..Default::default()
}
}
pub fn validate(&self) -> Result<(), String> {
if self.target_memory_fraction < 0.50 || self.target_memory_fraction > 0.90 {
return Err(format!(
"target_memory_fraction must be between 0.50 and 0.90, got {}",
self.target_memory_fraction
));
}
if self.batch_size < 16 || self.batch_size > 4096 {
return Err(format!(
"batch_size must be between 16 and 4096, got {}",
self.batch_size
));
}
if self.quiescence_timeout < Duration::from_millis(10) {
return Err(format!(
"quiescence_timeout must be at least 10ms, got {:?}",
self.quiescence_timeout
));
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum EvictionUrgency {
Moderate,
Urgent,
Emergency,
}
impl EvictionUrgency {
pub fn batch_multiplier(&self) -> usize {
match self {
EvictionUrgency::Moderate => 1,
EvictionUrgency::Urgent => 2,
EvictionUrgency::Emergency => 4,
}
}
pub fn cooldown_divisor(&self) -> u32 {
match self {
EvictionUrgency::Moderate => 1,
EvictionUrgency::Urgent => 2,
EvictionUrgency::Emergency => 4,
}
}
}
#[derive(Debug, Default)]
pub struct EvictionStatsAtomic {
pub nodes_evicted: AtomicU64,
pub bytes_freed: AtomicU64,
pub eviction_cycles: AtomicU64,
pub last_eviction_duration_ms: AtomicU64,
pub eviction_requests: AtomicU64,
pub skipped_evictions: AtomicU64,
pub quiescence_timeouts: AtomicU64,
}
impl EvictionStatsAtomic {
pub fn new() -> Self {
Self::default()
}
pub fn record_eviction(&self, nodes: u64, bytes: u64, duration_ms: u64) {
self.nodes_evicted.fetch_add(nodes, Ordering::Relaxed);
self.bytes_freed.fetch_add(bytes, Ordering::Relaxed);
self.eviction_cycles.fetch_add(1, Ordering::Relaxed);
self.last_eviction_duration_ms
.store(duration_ms, Ordering::Relaxed);
}
pub fn record_request(&self) {
self.eviction_requests.fetch_add(1, Ordering::Relaxed);
}
pub fn record_skip(&self) {
self.skipped_evictions.fetch_add(1, Ordering::Relaxed);
}
pub fn record_quiescence_timeout(&self) {
self.quiescence_timeouts.fetch_add(1, Ordering::Relaxed);
}
pub fn snapshot(&self) -> EvictionStats {
EvictionStats {
nodes_evicted: self.nodes_evicted.load(Ordering::Relaxed),
bytes_freed: self.bytes_freed.load(Ordering::Relaxed),
eviction_cycles: self.eviction_cycles.load(Ordering::Relaxed),
last_eviction_duration_ms: self.last_eviction_duration_ms.load(Ordering::Relaxed),
eviction_requests: self.eviction_requests.load(Ordering::Relaxed),
skipped_evictions: self.skipped_evictions.load(Ordering::Relaxed),
quiescence_timeouts: self.quiescence_timeouts.load(Ordering::Relaxed),
resident_bytes: 0,
}
}
pub fn reset(&self) {
self.nodes_evicted.store(0, Ordering::Relaxed);
self.bytes_freed.store(0, Ordering::Relaxed);
self.eviction_cycles.store(0, Ordering::Relaxed);
self.last_eviction_duration_ms.store(0, Ordering::Relaxed);
self.eviction_requests.store(0, Ordering::Relaxed);
self.skipped_evictions.store(0, Ordering::Relaxed);
self.quiescence_timeouts.store(0, Ordering::Relaxed);
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EvictionStats {
pub nodes_evicted: u64,
pub bytes_freed: u64,
pub eviction_cycles: u64,
pub last_eviction_duration_ms: u64,
pub eviction_requests: u64,
pub skipped_evictions: u64,
pub quiescence_timeouts: u64,
pub resident_bytes: u64,
}
impl EvictionStats {
pub fn nodes_per_cycle(&self) -> f64 {
if self.eviction_cycles == 0 {
0.0
} else {
self.nodes_evicted as f64 / self.eviction_cycles as f64
}
}
pub fn bytes_per_cycle(&self) -> f64 {
if self.eviction_cycles == 0 {
0.0
} else {
self.bytes_freed as f64 / self.eviction_cycles as f64
}
}
pub fn skip_rate(&self) -> f64 {
if self.eviction_requests == 0 {
0.0
} else {
self.skipped_evictions as f64 / self.eviction_requests as f64
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_eviction_config_default() {
let config = EvictionConfig::default();
assert!(config.enabled);
assert_eq!(config.target_memory_fraction, 0.70);
assert_eq!(config.min_eviction_depth, 1);
assert_eq!(config.batch_size, 256);
assert!(config.validate().is_ok());
}
#[test]
fn test_eviction_config_disabled() {
let config = EvictionConfig::disabled();
assert!(!config.enabled);
assert!(config.validate().is_ok());
}
#[test]
fn test_eviction_config_validation() {
let config = EvictionConfig {
target_memory_fraction: 0.40,
..Default::default()
};
assert!(config.validate().is_err());
let config = EvictionConfig {
target_memory_fraction: 0.95,
..Default::default()
};
assert!(config.validate().is_err());
let config = EvictionConfig {
batch_size: 8,
..Default::default()
};
assert!(config.validate().is_err());
let config = EvictionConfig {
batch_size: 8192,
..Default::default()
};
assert!(config.validate().is_err());
}
#[test]
fn test_eviction_urgency() {
assert_eq!(EvictionUrgency::Moderate.batch_multiplier(), 1);
assert_eq!(EvictionUrgency::Urgent.batch_multiplier(), 2);
assert_eq!(EvictionUrgency::Emergency.batch_multiplier(), 4);
assert_eq!(EvictionUrgency::Moderate.cooldown_divisor(), 1);
assert_eq!(EvictionUrgency::Urgent.cooldown_divisor(), 2);
assert_eq!(EvictionUrgency::Emergency.cooldown_divisor(), 4);
assert!(EvictionUrgency::Moderate < EvictionUrgency::Urgent);
assert!(EvictionUrgency::Urgent < EvictionUrgency::Emergency);
}
#[test]
fn test_eviction_stats_atomic() {
let stats = EvictionStatsAtomic::new();
stats.record_request();
stats.record_eviction(100, 1024 * 1024, 50);
stats.record_request();
stats.record_skip();
let snapshot = stats.snapshot();
assert_eq!(snapshot.nodes_evicted, 100);
assert_eq!(snapshot.bytes_freed, 1024 * 1024);
assert_eq!(snapshot.eviction_cycles, 1);
assert_eq!(snapshot.last_eviction_duration_ms, 50);
assert_eq!(snapshot.eviction_requests, 2);
assert_eq!(snapshot.skipped_evictions, 1);
}
#[test]
fn test_eviction_stats_calculations() {
let stats = EvictionStats {
nodes_evicted: 1000,
bytes_freed: 10 * 1024 * 1024,
eviction_cycles: 10,
eviction_requests: 20,
skipped_evictions: 5,
..Default::default()
};
assert_eq!(stats.nodes_per_cycle(), 100.0);
assert_eq!(stats.bytes_per_cycle(), 1024.0 * 1024.0);
assert_eq!(stats.skip_rate(), 0.25);
}
}