use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::{Duration, SystemTime};
#[derive(Debug, Clone)]
pub struct SmartStrategyConfig {
pub prefetch_enabled: bool,
pub prefetch_threshold: f64,
pub prefetch_window_size: usize,
pub prefetch_batch_size: usize,
pub compression_enabled: bool,
pub compression_threshold: usize,
pub min_compression_ratio: f64,
pub compression_sample_rate: f64,
}
impl Default for SmartStrategyConfig {
fn default() -> Self {
Self {
prefetch_enabled: true,
prefetch_threshold: 0.8,
prefetch_window_size: 1000,
prefetch_batch_size: 10,
compression_enabled: true,
compression_threshold: 1024,
min_compression_ratio: 0.8,
compression_sample_rate: 0.1,
}
}
}
#[derive(Debug, Clone)]
pub struct HitRateCollector {
hits: Arc<AtomicU64>,
misses: Arc<AtomicU64>,
window_size: usize,
recent_hits: Arc<AtomicU64>,
recent_misses: Arc<AtomicU64>,
}
impl HitRateCollector {
pub fn new(window_size: usize) -> Self {
Self {
hits: Arc::new(AtomicU64::new(0)),
misses: Arc::new(AtomicU64::new(0)),
window_size,
recent_hits: Arc::new(AtomicU64::new(0)),
recent_misses: Arc::new(AtomicU64::new(0)),
}
}
pub fn record_hit(&self) {
self.hits.fetch_add(1, Ordering::Relaxed);
self.recent_hits.fetch_add(1, Ordering::Relaxed);
self.rotate_window_if_needed();
}
pub fn record_miss(&self) {
self.misses.fetch_add(1, Ordering::Relaxed);
self.recent_misses.fetch_add(1, Ordering::Relaxed);
self.rotate_window_if_needed();
}
pub fn hit_rate(&self) -> f64 {
let hits = self.hits.load(Ordering::Relaxed);
let misses = self.misses.load(Ordering::Relaxed);
let total = hits + misses;
if total == 0 {
1.0
} else {
hits as f64 / total as f64
}
}
pub fn recent_hit_rate(&self) -> f64 {
let hits = self.recent_hits.load(Ordering::Relaxed);
let misses = self.recent_misses.load(Ordering::Relaxed);
let total = hits + misses;
if total == 0 {
1.0
} else {
hits as f64 / total as f64
}
}
pub fn should_prefetch(&self, threshold: f64) -> bool {
self.recent_hit_rate() < threshold
}
fn rotate_window_if_needed(&self) {
let total =
self.recent_hits.load(Ordering::Relaxed) + self.recent_misses.load(Ordering::Relaxed);
if total >= self.window_size as u64 {
self.recent_hits.store(0, Ordering::Relaxed);
self.recent_misses.store(0, Ordering::Relaxed);
}
}
pub fn stats(&self) -> HitRateStats {
HitRateStats {
total_hits: self.hits.load(Ordering::Relaxed),
total_misses: self.misses.load(Ordering::Relaxed),
recent_hits: self.recent_hits.load(Ordering::Relaxed),
recent_misses: self.recent_misses.load(Ordering::Relaxed),
hit_rate: self.hit_rate(),
recent_hit_rate: self.recent_hit_rate(),
}
}
pub fn reset(&self) {
self.hits.store(0, Ordering::Relaxed);
self.misses.store(0, Ordering::Relaxed);
self.recent_hits.store(0, Ordering::Relaxed);
self.recent_misses.store(0, Ordering::Relaxed);
}
}
#[derive(Debug, Clone)]
pub struct HitRateStats {
pub total_hits: u64,
pub total_misses: u64,
pub recent_hits: u64,
pub recent_misses: u64,
pub hit_rate: f64,
pub recent_hit_rate: f64,
}
#[derive(Debug, Clone)]
pub struct CompressibilityChecker {
sample_size: usize,
entropy_threshold: f64,
}
impl CompressibilityChecker {
pub fn new(sample_size: usize, entropy_threshold: f64) -> Self {
Self {
sample_size: sample_size.max(1), entropy_threshold,
}
}
pub fn check_compressibility(&self, data: &[u8]) -> (bool, f64) {
if data.len() < self.sample_size || self.sample_size == 0 {
return (false, 1.0);
}
let step = (data.len() / self.sample_size).max(1);
let sample: Vec<u8> = data.iter().step_by(step).copied().collect();
if sample.is_empty() {
return (false, 1.0);
}
let entropy = self.calculate_entropy(&sample);
let normalized_entropy = entropy / 8.0;
let estimated_ratio = 0.3 + 0.7 * normalized_entropy;
let worth_compressing = normalized_entropy < self.entropy_threshold
&& estimated_ratio < 0.8
&& data.len() > self.sample_size;
(worth_compressing, estimated_ratio)
}
fn calculate_entropy(&self, data: &[u8]) -> f64 {
if data.is_empty() {
return 0.0;
}
let mut counts = [0u64; 256];
for &byte in data {
counts[byte as usize] += 1;
}
let total = data.len() as f64;
let mut entropy = 0.0f64;
for &count in &counts {
if count > 0 {
let p = count as f64 / total;
entropy -= p * p.log2();
}
}
entropy
}
}
impl Default for CompressibilityChecker {
fn default() -> Self {
Self {
sample_size: 256,
entropy_threshold: 0.9,
}
}
}
#[derive(Debug, Clone)]
pub struct PrefetchDecider {
config: SmartStrategyConfig,
hit_rate_collector: Arc<HitRateCollector>,
last_prefetch_time: Arc<std::sync::atomic::AtomicU64>,
min_prefetch_interval: Duration,
}
impl PrefetchDecider {
pub fn new(config: SmartStrategyConfig) -> Self {
Self {
config: config.clone(),
hit_rate_collector: Arc::new(HitRateCollector::new(config.prefetch_window_size)),
last_prefetch_time: Arc::new(std::sync::atomic::AtomicU64::new(0)),
min_prefetch_interval: Duration::from_secs(5),
}
}
pub fn record_access(&self, hit: bool) {
if hit {
self.hit_rate_collector.record_hit();
} else {
self.hit_rate_collector.record_miss();
}
}
pub fn should_prefetch(&self) -> bool {
if !self.config.prefetch_enabled {
return false;
}
let last = std::sync::atomic::AtomicU64::load(&self.last_prefetch_time, Ordering::Relaxed);
let last_time = SystemTime::UNIX_EPOCH + Duration::from_secs(last);
let now = SystemTime::now();
if let Ok(elapsed) = now.duration_since(last_time) {
if elapsed < self.min_prefetch_interval {
return false;
}
}
self.hit_rate_collector
.should_prefetch(self.config.prefetch_threshold)
}
pub fn mark_prefetched(&self) {
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
std::sync::atomic::AtomicU64::store(&self.last_prefetch_time, now, Ordering::Relaxed);
}
pub fn config(&self) -> &SmartStrategyConfig {
&self.config
}
pub fn hit_rate_stats(&self) -> HitRateStats {
self.hit_rate_collector.stats()
}
pub fn hit_rate_collector(&self) -> &Arc<HitRateCollector> {
&self.hit_rate_collector
}
}
#[derive(Debug, Clone)]
pub struct CompressionDecider {
config: SmartStrategyConfig,
checker: CompressibilityChecker,
}
impl CompressionDecider {
pub fn new(config: SmartStrategyConfig) -> Self {
Self {
config: config.clone(),
checker: CompressibilityChecker::new(1024, 0.9), }
}
pub fn should_compress(&self, data: &[u8]) -> bool {
if !self.config.compression_enabled {
return false;
}
if data.len() < self.config.compression_threshold {
return false;
}
let (worth_compressing, _) = self.checker.check_compressibility(data);
worth_compressing
}
pub fn config(&self) -> &SmartStrategyConfig {
&self.config
}
}
#[derive(Debug, Clone)]
pub struct SmartStrategyManager {
prefetch_decider: Arc<PrefetchDecider>,
compression_decider: Arc<CompressionDecider>,
config: SmartStrategyConfig,
}
impl SmartStrategyManager {
pub fn new(config: Option<SmartStrategyConfig>) -> Self {
let config = config.unwrap_or_default();
Self {
prefetch_decider: Arc::new(PrefetchDecider::new(config.clone())),
compression_decider: Arc::new(CompressionDecider::new(config.clone())),
config,
}
}
pub fn record_access(&self, hit: bool) {
self.prefetch_decider.record_access(hit);
}
pub fn should_prefetch(&self) -> bool {
self.prefetch_decider.should_prefetch()
}
pub fn mark_prefetched(&self) {
self.prefetch_decider.mark_prefetched();
}
pub fn should_compress(&self, data: &[u8]) -> bool {
self.compression_decider.should_compress(data)
}
pub fn config(&self) -> &SmartStrategyConfig {
&self.config
}
pub fn prefetch_decider(&self) -> &Arc<PrefetchDecider> {
&self.prefetch_decider
}
pub fn compression_decider(&self) -> &Arc<CompressionDecider> {
&self.compression_decider
}
pub fn update_config(&mut self, config: SmartStrategyConfig) {
self.config = config.clone();
self.prefetch_decider = Arc::new(PrefetchDecider::new(config.clone()));
self.compression_decider = Arc::new(CompressionDecider::new(config));
}
pub fn hit_rate_stats(&self) -> HitRateStats {
self.prefetch_decider.hit_rate_stats()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hit_rate_collector() {
let collector = HitRateCollector::new(200);
for _ in 0..80 {
collector.record_hit();
}
for _ in 0..20 {
collector.record_miss();
}
assert_eq!(collector.hit_rate(), 0.8);
assert_eq!(collector.recent_hit_rate(), 0.8);
}
#[test]
fn test_should_prefetch() {
let decider = PrefetchDecider::new(SmartStrategyConfig {
prefetch_enabled: true,
prefetch_threshold: 0.8,
prefetch_window_size: 200, ..Default::default()
});
let collector_ref = decider.hit_rate_collector();
for _ in 0..90 {
collector_ref.record_hit();
}
for _ in 0..10 {
collector_ref.record_miss();
}
assert!(!decider.should_prefetch());
collector_ref.reset();
for _ in 0..30 {
collector_ref.record_hit();
}
for _ in 0..70 {
collector_ref.record_miss();
}
assert!(decider.should_prefetch());
}
#[test]
fn test_compressibility_checker() {
let checker = CompressibilityChecker::default();
let compressible = vec![0x00u8; 1000];
let (worth_compressing, ratio) = checker.check_compressibility(&compressible);
assert!(worth_compressing);
assert!(ratio < 0.5);
let incompressible: Vec<u8> = (0..1000).map(|_| rand::random()).collect();
let (worth_compressing, _) = checker.check_compressibility(&incompressible);
assert!(!worth_compressing);
}
#[test]
fn test_compression_decider_disabled() {
let config = SmartStrategyConfig {
compression_enabled: false,
..Default::default()
};
let decider = CompressionDecider::new(config);
let data = vec![0x00u8; 2000];
assert!(!decider.should_compress(&data));
}
#[test]
fn test_compression_decider_size_threshold() {
let config = SmartStrategyConfig {
compression_enabled: true,
compression_threshold: 1024,
..Default::default()
};
let decider = CompressionDecider::new(config);
let small_data = vec![0x00u8; 500];
assert!(!decider.should_compress(&small_data));
let large_data = vec![0x00u8; 2000];
assert!(decider.should_compress(&large_data));
}
#[test]
fn test_smart_strategy_manager() {
let manager = SmartStrategyManager::new(None);
for _ in 0..80 {
manager.record_access(true);
}
for _ in 0..20 {
manager.record_access(false);
}
let stats = manager.hit_rate_stats();
assert_eq!(stats.hit_rate, 0.8);
assert_eq!(stats.total_hits, 80);
assert_eq!(stats.total_misses, 20);
}
#[test]
fn test_smart_strategy_manager_update_config() {
let mut manager = SmartStrategyManager::new(None);
let new_config = SmartStrategyConfig {
prefetch_threshold: 0.5,
compression_threshold: 2048,
..Default::default()
};
manager.update_config(new_config.clone());
assert_eq!(manager.config().prefetch_threshold, 0.5);
assert_eq!(manager.config().compression_threshold, 2048);
}
}