use crate::fscore::structs::Dva;
use alloc::collections::{BTreeMap, VecDeque};
use alloc::vec::Vec;
use core::cmp;
use lazy_static::lazy_static;
use spin::Mutex;
const ARC_MAX_BYTES: usize = 100 * 1024 * 1024 * 1024;
const NUM_SHARDS: usize = 16;
const SHARD_MAX_BYTES: usize = ARC_MAX_BYTES / NUM_SHARDS;
pub struct ArcHeader {
pub data: Vec<u8>,
pub access_count: u64,
pub size: usize,
pub is_dedup: bool,
pub list_id: u8,
}
pub struct Arc {
pub index: BTreeMap<Dva, ArcHeader>,
pub t1: VecDeque<Dva>,
pub t2: VecDeque<Dva>,
pub b1: VecDeque<Dva>,
pub b2: VecDeque<Dva>,
pub current_size: usize,
pub hits: u64,
pub misses: u64,
pub p: usize,
pub max_bytes: usize,
}
lazy_static! {
pub static ref ARC: Mutex<Arc> = Mutex::new(Arc::new());
pub static ref SHARDED_ARC: [Mutex<Arc>; NUM_SHARDS] = [
Mutex::new(Arc::new_with_max(SHARD_MAX_BYTES)),
Mutex::new(Arc::new_with_max(SHARD_MAX_BYTES)),
Mutex::new(Arc::new_with_max(SHARD_MAX_BYTES)),
Mutex::new(Arc::new_with_max(SHARD_MAX_BYTES)),
Mutex::new(Arc::new_with_max(SHARD_MAX_BYTES)),
Mutex::new(Arc::new_with_max(SHARD_MAX_BYTES)),
Mutex::new(Arc::new_with_max(SHARD_MAX_BYTES)),
Mutex::new(Arc::new_with_max(SHARD_MAX_BYTES)),
Mutex::new(Arc::new_with_max(SHARD_MAX_BYTES)),
Mutex::new(Arc::new_with_max(SHARD_MAX_BYTES)),
Mutex::new(Arc::new_with_max(SHARD_MAX_BYTES)),
Mutex::new(Arc::new_with_max(SHARD_MAX_BYTES)),
Mutex::new(Arc::new_with_max(SHARD_MAX_BYTES)),
Mutex::new(Arc::new_with_max(SHARD_MAX_BYTES)),
Mutex::new(Arc::new_with_max(SHARD_MAX_BYTES)),
Mutex::new(Arc::new_with_max(SHARD_MAX_BYTES)),
];
}
#[inline]
fn get_shard_index(dva: &Dva) -> usize {
let hash = dva.vdev as usize ^ (dva.offset as usize).wrapping_mul(0x9e3779b9);
hash % NUM_SHARDS
}
pub fn arc_read(dva: &Dva) -> Option<Vec<u8>> {
let shard_idx = get_shard_index(dva);
SHARDED_ARC[shard_idx].lock().read(dva)
}
pub fn arc_cache(dva: Dva, data: Vec<u8>) {
let shard_idx = get_shard_index(&dva);
SHARDED_ARC[shard_idx].lock().cache(dva, data);
}
pub fn arc_stats() -> (u64, u64, f64, usize) {
let mut total_hits = 0u64;
let mut total_misses = 0u64;
let mut total_size = 0usize;
for shard in SHARDED_ARC.iter() {
let (hits, misses, _, size) = shard.lock().stats();
total_hits += hits;
total_misses += misses;
total_size += size;
}
let total = total_hits + total_misses;
let hit_rate = if total > 0 {
total_hits as f64 / total as f64
} else {
0.0
};
(total_hits, total_misses, hit_rate, total_size)
}
impl Arc {
pub fn new() -> Self {
Self::new_with_max(ARC_MAX_BYTES)
}
pub fn new_with_max(max_bytes: usize) -> Self {
Self {
index: BTreeMap::new(),
t1: VecDeque::new(),
t2: VecDeque::new(),
b1: VecDeque::new(),
b2: VecDeque::new(),
current_size: 0,
hits: 0,
misses: 0,
p: 0,
max_bytes,
}
}
pub fn read(&mut self, dva: &Dva) -> Option<Vec<u8>> {
if let Some(header) = self.index.get_mut(dva) {
header.access_count += 1;
self.hits += 1;
let list_id = header.list_id;
let t1_pos = if list_id == 1 {
self.t1.iter().position(|x| x == dva)
} else {
None
};
let t2_pos = if list_id == 2 {
self.t2.iter().position(|x| x == dva)
} else {
None
};
if list_id == 1 {
if let Some(pos) = t1_pos {
self.t1.remove(pos);
self.t2.push_back(*dva); header.list_id = 2;
}
} else if list_id == 2 {
if let Some(pos) = t2_pos {
self.t2.remove(pos);
self.t2.push_back(*dva);
}
}
Some(header.data.clone())
} else {
if self.b1.contains(dva) || self.b2.contains(dva) {
self.adapt_on_miss(dva);
}
self.misses += 1;
None
}
}
pub fn cache(&mut self, dva: Dva, data: Vec<u8>) {
let size = data.len();
while self.current_size + size > self.max_bytes {
self.evict_one();
}
let header = ArcHeader {
data,
access_count: 1,
size,
is_dedup: false,
list_id: 1, };
if self.index.insert(dva, header).is_none() {
self.current_size += size;
self.t1.push_back(dva); }
}
fn evict_one(&mut self) {
let t1_size = self.t1.len();
let b1_size = self.b1.len();
let b2_size = self.b2.len();
let to_evict = if t1_size > 0 && (t1_size > self.p || b1_size >= b2_size) {
if let Some(dva) = self.t1.pop_front() {
self.b1.push_back(dva); Some(dva)
} else {
None
}
} else if !self.t2.is_empty() {
if let Some(dva) = self.t2.pop_front() {
self.b2.push_back(dva); Some(dva)
} else {
None
}
} else {
None
};
if let Some(dva) = to_evict {
if let Some(header) = self.index.remove(&dva) {
self.current_size -= header.size;
}
}
while self.b1.len() + self.b2.len() > self.max_bytes / 4096 {
if !self.b1.is_empty() {
self.b1.pop_front(); } else if !self.b2.is_empty() {
self.b2.pop_front(); }
}
}
fn adapt_on_miss(&mut self, dva: &Dva) {
let b1_size = self.b1.len();
let b2_size = self.b2.len();
if self.b1.contains(dva) {
self.p = self.p.saturating_add(b2_size / cmp::max(b1_size, 1));
self.p = self.p.clamp(0, self.max_bytes / 4096);
} else if self.b2.contains(dva) {
self.p = self.p.saturating_sub(b1_size / cmp::max(b2_size, 1));
self.p = self.p.clamp(0, self.max_bytes / 4096);
}
}
pub fn stats(&self) -> (u64, u64, f64, usize) {
let total = self.hits + self.misses;
let hit_rate = if total > 0 {
self.hits as f64 / total as f64
} else {
0.0
};
(self.hits, self.misses, hit_rate, self.current_size)
}
}
impl Default for Arc {
fn default() -> Self {
Self::new()
}
}