#[derive(Clone)]
pub enum EvictionPolicy {
Lru,
Lfu,
TimeToLive(std::time::Duration),
Custom(std::sync::Arc<dyn Fn() -> bool + Send + Sync>),
}
impl std::fmt::Debug for EvictionPolicy {
#[tracing::instrument(skip(self, f), level = "trace")]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EvictionPolicy::Lru => f.write_str("LRU"),
EvictionPolicy::Lfu => f.write_str("LFU"),
EvictionPolicy::TimeToLive(duration) => {
f.debug_tuple("TimeToLive").field(duration).finish()
}
EvictionPolicy::Custom(_) => f.write_str("Custom(..)"),
}
}
}
#[derive(Debug, Clone)]
pub struct EvictionConfig {
pub policy: EvictionPolicy,
pub max_entries: Option<usize>,
pub max_items_per_shard: Option<usize>,
pub ttl: Option<std::time::Duration>,
pub check_interval: std::time::Duration,
pub background_enabled: bool,
}
impl Default for EvictionConfig {
#[tracing::instrument(level = "trace")]
fn default() -> Self {
Self {
policy: EvictionPolicy::Lru,
max_entries: None,
max_items_per_shard: None,
ttl: None,
check_interval: std::time::Duration::from_secs(60),
background_enabled: true,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ShardEvictionStats {
pub total_evictions: u64,
pub last_eviction_time: Option<std::time::Instant>,
pub pending_evictions: usize,
pub epoch: u64,
}
#[derive(Debug, Clone)]
pub struct MetricsStats {
pub total_inserts: u64,
pub total_removes: u64,
pub total_gets: u64,
pub hits: u64,
pub misses: u64,
pub evictions: u64,
pub last_evict_time: Option<std::time::Instant>,
pub hit_rate: f64,
}
impl Default for MetricsStats {
#[tracing::instrument(level = "trace")]
fn default() -> Self {
Self {
total_inserts: 0,
total_removes: 0,
total_gets: 0,
hits: 0,
misses: 0,
evictions: 0,
last_evict_time: None,
hit_rate: 0.0,
}
}
}
pub struct AtomicMetrics {
pub inserts: std::sync::atomic::AtomicU64,
pub removes: std::sync::atomic::AtomicU64,
pub gets: std::sync::atomic::AtomicU64,
pub hits: std::sync::atomic::AtomicU64,
pub misses: std::sync::atomic::AtomicU64,
pub evictions: std::sync::atomic::AtomicU64,
}
impl Default for AtomicMetrics {
#[tracing::instrument(level = "trace")]
fn default() -> Self {
Self {
inserts: std::sync::atomic::AtomicU64::new(0),
removes: std::sync::atomic::AtomicU64::new(0),
gets: std::sync::atomic::AtomicU64::new(0),
hits: std::sync::atomic::AtomicU64::new(0),
misses: std::sync::atomic::AtomicU64::new(0),
evictions: std::sync::atomic::AtomicU64::new(0),
}
}
}
impl AtomicMetrics {
#[tracing::instrument(skip(self), level = "trace")]
pub fn snapshot(&self) -> MetricsStats {
let total_gets = self.gets.load(std::sync::atomic::Ordering::Relaxed);
let hits = self.hits.load(std::sync::atomic::Ordering::Relaxed);
let hit_rate = if total_gets > 0 {
(hits as f64) / (total_gets as f64)
} else {
0.0
};
MetricsStats {
total_inserts: self.inserts.load(std::sync::atomic::Ordering::Relaxed),
total_removes: self.removes.load(std::sync::atomic::Ordering::Relaxed),
total_gets,
hits,
misses: self.misses.load(std::sync::atomic::Ordering::Relaxed),
evictions: self.evictions.load(std::sync::atomic::Ordering::Relaxed),
last_evict_time: None,
hit_rate,
}
}
#[tracing::instrument(skip(self), level = "trace")]
pub fn reset(&self) {
self.inserts.store(0, std::sync::atomic::Ordering::Relaxed);
self.removes.store(0, std::sync::atomic::Ordering::Relaxed);
self.gets.store(0, std::sync::atomic::Ordering::Relaxed);
self.hits.store(0, std::sync::atomic::Ordering::Relaxed);
self.misses.store(0, std::sync::atomic::Ordering::Relaxed);
self.evictions
.store(0, std::sync::atomic::Ordering::Relaxed);
}
}
#[derive(Debug, Clone)]
pub struct MemoryStats {
pub shards_allocated: usize,
pub total_capacity: usize,
pub load_factor: f64,
}
type IterFilter<K, V> = std::sync::Arc<dyn Fn(&K, &V) -> bool + Send + Sync>;
pub struct IterBuilder<K, V> {
pub(crate) filter: Option<IterFilter<K, V>>,
pub(crate) limit: Option<usize>,
pub(crate) parallel: bool,
}
impl<K: Clone + Send + Sync, V: Clone + Send + Sync> IterBuilder<K, V> {
#[tracing::instrument(level = "trace")]
pub fn new() -> Self {
Self {
filter: None,
limit: None,
parallel: false,
}
}
#[tracing::instrument(skip(self, f), level = "trace")]
pub fn filter<F: Fn(&K, &V) -> bool + Send + Sync + 'static>(mut self, f: F) -> Self {
self.filter = Some(std::sync::Arc::new(f));
self
}
#[tracing::instrument(skip(self), level = "trace")]
pub fn limit(mut self, n: usize) -> Self {
self.limit = Some(n);
self
}
#[tracing::instrument(skip(self), level = "trace")]
pub fn parallel(mut self, enabled: bool) -> Self {
self.parallel = enabled;
self
}
#[tracing::instrument(skip(self, items), level = "trace")]
pub fn collect(&self, items: Vec<(K, V)>) -> Vec<(K, V)> {
let mut result = items;
if let Some(ref filter) = self.filter {
result.retain(|(k, v)| filter(k, v));
}
if let Some(limit) = self.limit {
result.truncate(limit);
}
result
}
#[tracing::instrument(skip(self, items, f), level = "trace")]
pub fn for_each<F: Fn((K, V))>(&self, items: Vec<(K, V)>, f: F) {
let items = self.collect(items);
for item in items {
f(item);
}
}
}
impl<K: Clone + Send + Sync, V: Clone + Send + Sync> Default for IterBuilder<K, V> {
#[tracing::instrument(level = "trace")]
fn default() -> Self {
Self::new()
}
}
pub struct DrainIterator<K, V> {
pub(crate) items: Vec<(K, V)>,
pub(crate) index: usize,
}
impl<K, V> Iterator for DrainIterator<K, V> {
type Item = (K, V);
#[tracing::instrument(skip(self), level = "trace")]
fn next(&mut self) -> Option<(K, V)> {
if self.index < self.items.len() {
let item = self.items.swap_remove(self.index);
Some(item)
} else {
None
}
}
}
impl<K, V> ExactSizeIterator for DrainIterator<K, V> {
#[tracing::instrument(skip(self), level = "trace")]
fn len(&self) -> usize {
self.items.len() - self.index
}
}
#[derive(Debug, Clone)]
pub struct PerShardLoad {
pub shard_idx: usize,
pub entry_count: usize,
pub capacity: usize,
}
#[cfg(test)]
mod tests {
use crate::{AtomicMetrics, DrainIterator, EvictionConfig, IterBuilder};
#[test]
fn eviction_config_default() {
let config = EvictionConfig::default();
assert_eq!(config.check_interval, std::time::Duration::from_secs(60));
assert!(config.background_enabled);
assert_eq!(config.max_entries, None);
}
#[test]
fn atomic_metrics_default() {
let metrics = AtomicMetrics::default();
let snap = metrics.snapshot();
assert_eq!(snap.total_inserts, 0);
assert_eq!(snap.hit_rate, 0.0);
}
#[test]
fn atomic_metrics_hit_rate() {
let metrics = AtomicMetrics::default();
metrics.gets.store(10, std::sync::atomic::Ordering::Relaxed);
metrics.hits.store(7, std::sync::atomic::Ordering::Relaxed);
let snap = metrics.snapshot();
assert!((snap.hit_rate - 0.7).abs() < 0.01);
}
#[test]
fn iter_builder_filter() {
let builder = IterBuilder::<String, i32>::new()
.filter(|_, v| v > &10)
.limit(5);
let items = vec![("a".into(), 5), ("b".into(), 15), ("c".into(), 25)];
let result = builder.collect(items);
assert_eq!(result.len(), 2);
}
#[test]
fn drain_iterator() {
let drain = DrainIterator {
items: vec![("a", 1), ("b", 2), ("c", 3)],
index: 0,
};
let collected: Vec<_> = drain.collect();
assert_eq!(collected.len(), 3);
}
}