use crate::Inode;
use btrfs_disk::{
items::{FileExtentItem, InodeItem},
reader::TreeBlockCache,
tree::TreeBlock,
};
use lru::LruCache;
use std::{
num::NonZeroUsize,
sync::{
Arc, Mutex,
atomic::{AtomicU64, Ordering},
},
};
pub(crate) const TREE_BLOCK_CACHE_DEFAULT_ENTRIES: usize = 4096;
pub(crate) const INODE_CACHE_DEFAULT_ENTRIES: usize = 4096;
pub(crate) const EXTENT_MAP_CACHE_DEFAULT_ENTRIES: usize = 1024;
#[derive(Debug, Clone, Copy)]
pub struct CacheConfig {
pub tree_blocks: usize,
pub inodes: usize,
pub extent_maps: usize,
}
impl Default for CacheConfig {
fn default() -> Self {
Self {
tree_blocks: TREE_BLOCK_CACHE_DEFAULT_ENTRIES,
inodes: INODE_CACHE_DEFAULT_ENTRIES,
extent_maps: EXTENT_MAP_CACHE_DEFAULT_ENTRIES,
}
}
}
impl CacheConfig {
#[must_use]
pub fn no_cache() -> Self {
Self {
tree_blocks: 1,
inodes: 1,
extent_maps: 1,
}
}
}
#[derive(Debug, Default, Clone, Copy)]
pub struct CacheStats {
pub hits: u64,
pub misses: u64,
pub insertions: u64,
pub invalidations: u64,
}
pub struct LruTreeBlockCache {
inner: Mutex<LruCache<u64, Arc<TreeBlock>>>,
hits: AtomicU64,
misses: AtomicU64,
insertions: AtomicU64,
invalidations: AtomicU64,
}
impl LruTreeBlockCache {
#[must_use]
pub fn new(capacity: usize) -> Self {
let cap = NonZeroUsize::new(capacity)
.expect("LruTreeBlockCache capacity must be > 0");
Self {
inner: Mutex::new(LruCache::new(cap)),
hits: AtomicU64::new(0),
misses: AtomicU64::new(0),
insertions: AtomicU64::new(0),
invalidations: AtomicU64::new(0),
}
}
#[must_use]
pub fn stats(&self) -> CacheStats {
CacheStats {
hits: self.hits.load(Ordering::Relaxed),
misses: self.misses.load(Ordering::Relaxed),
insertions: self.insertions.load(Ordering::Relaxed),
invalidations: self.invalidations.load(Ordering::Relaxed),
}
}
}
impl TreeBlockCache for LruTreeBlockCache {
fn get(&self, addr: u64) -> Option<Arc<TreeBlock>> {
let hit = self.inner.lock().unwrap().get(&addr).map(Arc::clone);
if hit.is_some() {
self.hits.fetch_add(1, Ordering::Relaxed);
} else {
self.misses.fetch_add(1, Ordering::Relaxed);
}
hit
}
fn put(&self, addr: u64, block: Arc<TreeBlock>) {
self.inner.lock().unwrap().put(addr, block);
self.insertions.fetch_add(1, Ordering::Relaxed);
}
fn invalidate(&self, addr: u64) {
self.inner.lock().unwrap().pop(&addr);
self.invalidations.fetch_add(1, Ordering::Relaxed);
}
}
pub(crate) struct InodeCache {
inner: Mutex<LruCache<Inode, Arc<InodeItem>>>,
}
impl InodeCache {
pub(crate) fn new(capacity: usize) -> Self {
let cap = NonZeroUsize::new(capacity)
.expect("InodeCache capacity must be > 0");
Self {
inner: Mutex::new(LruCache::new(cap)),
}
}
pub(crate) fn get(&self, ino: Inode) -> Option<Arc<InodeItem>> {
self.inner.lock().unwrap().get(&ino).map(Arc::clone)
}
pub(crate) fn put(&self, ino: Inode, item: Arc<InodeItem>) {
self.inner.lock().unwrap().put(ino, item);
}
pub(crate) fn invalidate(&self, ino: Inode) {
self.inner.lock().unwrap().pop(&ino);
}
}
#[derive(Clone)]
pub(crate) struct ExtentRecord {
pub file_pos: u64,
pub item: FileExtentItem,
pub raw: Vec<u8>,
}
#[derive(Default)]
pub(crate) struct ExtentMap {
pub records: Vec<ExtentRecord>,
}
pub(crate) struct ExtentMapCache {
inner: Mutex<LruCache<Inode, Arc<ExtentMap>>>,
}
impl ExtentMapCache {
pub(crate) fn new(capacity: usize) -> Self {
let cap = NonZeroUsize::new(capacity)
.expect("ExtentMapCache capacity must be > 0");
Self {
inner: Mutex::new(LruCache::new(cap)),
}
}
pub(crate) fn get(&self, ino: Inode) -> Option<Arc<ExtentMap>> {
self.inner.lock().unwrap().get(&ino).map(Arc::clone)
}
pub(crate) fn put(&self, ino: Inode, map: Arc<ExtentMap>) {
self.inner.lock().unwrap().put(ino, map);
}
pub(crate) fn invalidate(&self, ino: Inode) {
self.inner.lock().unwrap().pop(&ino);
}
}
#[cfg(test)]
mod tests {
}