#[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec};
#[cfg(feature = "std")]
use std::collections::BTreeMap;
use crate::address::BaseAddress;
use crate::convert::TryToUsize;
use crate::error::FormatError;
pub const DEFAULT_METADATA_CACHE_MAX_ENTRY_BYTES: usize = 64 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MetadataCacheConfig {
max_bytes: usize,
max_entry_bytes: usize,
}
impl MetadataCacheConfig {
pub const fn new(max_bytes: usize) -> Self {
let max_entry_bytes = if max_bytes < DEFAULT_METADATA_CACHE_MAX_ENTRY_BYTES {
max_bytes
} else {
DEFAULT_METADATA_CACHE_MAX_ENTRY_BYTES
};
Self {
max_bytes,
max_entry_bytes,
}
}
pub const fn disabled() -> Self {
Self {
max_bytes: 0,
max_entry_bytes: 0,
}
}
pub const fn with_max_entry_bytes(mut self, max_entry_bytes: usize) -> Self {
self.max_entry_bytes = max_entry_bytes;
self
}
pub const fn max_bytes(&self) -> usize {
self.max_bytes
}
pub const fn max_entry_bytes(&self) -> usize {
self.max_entry_bytes
}
pub const fn is_enabled(&self) -> bool {
self.max_bytes > 0 && self.max_entry_bytes > 0
}
}
impl Default for MetadataCacheConfig {
fn default() -> Self {
Self::disabled()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct MetadataCacheStats {
hits: u64,
misses: u64,
oversize_reads: u64,
evictions: u64,
invalidations: u64,
entries: usize,
bytes: usize,
}
impl MetadataCacheStats {
pub const fn hits(&self) -> u64 {
self.hits
}
pub const fn misses(&self) -> u64 {
self.misses
}
pub const fn oversize_reads(&self) -> u64 {
self.oversize_reads
}
pub const fn evictions(&self) -> u64 {
self.evictions
}
pub const fn invalidations(&self) -> u64 {
self.invalidations
}
pub const fn entries(&self) -> usize {
self.entries
}
pub const fn bytes(&self) -> usize {
self.bytes
}
pub const fn reads(&self) -> u64 {
self.hits
.saturating_add(self.misses)
.saturating_add(self.oversize_reads)
}
pub fn hit_rate(&self) -> Option<f64> {
let eligible = self.hits.saturating_add(self.misses);
if eligible == 0 {
return None;
}
#[expect(
clippy::cast_precision_loss,
reason = "a hit rate is a ratio; f64 holds these counts exactly far past any \
read count a process will reach"
)]
Some(self.hits as f64 / eligible as f64)
}
}
pub trait Source {
fn len(&self) -> u64;
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<(), FormatError>;
fn read_exact_at(&self, offset: u64, len: usize) -> Result<Vec<u8>, FormatError> {
let end = offset
.checked_add(len as u64)
.ok_or(FormatError::OffsetOverflow {
offset,
length: len as u64,
})?;
if end > self.len() {
return Err(FormatError::UnexpectedEof {
expected: end.to_usize().unwrap_or(usize::MAX),
available: self.len().to_usize().unwrap_or(usize::MAX),
});
}
let mut buf = vec![0u8; len];
self.read_at(offset, &mut buf)?;
Ok(buf)
}
fn read_metadata_at(&self, offset: u64, len: usize) -> Result<Vec<u8>, FormatError> {
self.read_exact_at(offset, len)
}
fn metadata_cache_stats(&self) -> Option<MetadataCacheStats> {
None
}
fn reset_metadata_cache_stats(&self) {}
}
impl<S: Source + ?Sized> Source for &S {
fn len(&self) -> u64 {
(**self).len()
}
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<(), FormatError> {
(**self).read_at(offset, buf)
}
fn read_exact_at(&self, offset: u64, len: usize) -> Result<Vec<u8>, FormatError> {
(**self).read_exact_at(offset, len)
}
fn read_metadata_at(&self, offset: u64, len: usize) -> Result<Vec<u8>, FormatError> {
(**self).read_metadata_at(offset, len)
}
fn metadata_cache_stats(&self) -> Option<MetadataCacheStats> {
(**self).metadata_cache_stats()
}
fn reset_metadata_cache_stats(&self) {
(**self).reset_metadata_cache_stats();
}
}
#[cfg(feature = "std")]
impl<S: Source + ?Sized> Source for std::boxed::Box<S> {
fn len(&self) -> u64 {
(**self).len()
}
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<(), FormatError> {
(**self).read_at(offset, buf)
}
fn read_exact_at(&self, offset: u64, len: usize) -> Result<Vec<u8>, FormatError> {
(**self).read_exact_at(offset, len)
}
fn read_metadata_at(&self, offset: u64, len: usize) -> Result<Vec<u8>, FormatError> {
(**self).read_metadata_at(offset, len)
}
fn metadata_cache_stats(&self) -> Option<MetadataCacheStats> {
(**self).metadata_cache_stats()
}
fn reset_metadata_cache_stats(&self) {
(**self).reset_metadata_cache_stats();
}
}
pub(crate) struct BaseOffsetSource<'a, S: Source + ?Sized> {
pub(crate) inner: &'a S,
pub(crate) base: BaseAddress,
}
pub(crate) fn frame(bytes: &[u8], base: BaseAddress) -> Result<&[u8], FormatError> {
if base.is_zero() {
return Ok(bytes);
}
let start = base.get().to_usize()?;
bytes.get(start..).ok_or(FormatError::UnexpectedEof {
expected: start,
available: bytes.len(),
})
}
impl<S: Source + ?Sized> Source for BaseOffsetSource<'_, S> {
fn len(&self) -> u64 {
self.inner.len().saturating_sub(self.base.get())
}
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<(), FormatError> {
self.inner.read_at(self.base.absolute(offset)?, buf)
}
fn read_metadata_at(&self, offset: u64, len: usize) -> Result<Vec<u8>, FormatError> {
self.inner
.read_metadata_at(self.base.absolute(offset)?, len)
}
fn metadata_cache_stats(&self) -> Option<MetadataCacheStats> {
self.inner.metadata_cache_stats()
}
fn reset_metadata_cache_stats(&self) {
self.inner.reset_metadata_cache_stats();
}
}
#[derive(Debug, Clone, Copy)]
pub struct BytesSource<T>(pub T);
impl<T: AsRef<[u8]>> BytesSource<T> {
pub fn new(bytes: T) -> Self {
BytesSource(bytes)
}
}
impl<T: AsRef<[u8]>> Source for BytesSource<T> {
fn len(&self) -> u64 {
self.0.as_ref().len() as u64
}
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<(), FormatError> {
let bytes = self.0.as_ref();
let start = offset.to_usize()?;
let end = start
.checked_add(buf.len())
.ok_or(FormatError::OffsetOverflow {
offset,
length: buf.len() as u64,
})?;
if end > bytes.len() {
return Err(FormatError::UnexpectedEof {
expected: end,
available: bytes.len(),
});
}
buf.copy_from_slice(&bytes[start..end]);
Ok(())
}
}
#[cfg(feature = "std")]
struct CachedMetadataRead {
bytes: Vec<u8>,
last_access: u64,
}
#[cfg(feature = "std")]
pub(crate) struct MetadataReadCache {
entries: BTreeMap<(u64, usize), CachedMetadataRead>,
by_access: BTreeMap<u64, (u64, usize)>,
current_bytes: usize,
tick: u64,
longest_entry: usize,
hits: u64,
misses: u64,
oversize_reads: u64,
evictions: u64,
invalidations: u64,
}
#[cfg(feature = "std")]
impl MetadataReadCache {
pub(crate) fn new() -> Self {
Self {
entries: BTreeMap::new(),
by_access: BTreeMap::new(),
current_bytes: 0,
tick: 0,
longest_entry: 0,
hits: 0,
misses: 0,
oversize_reads: 0,
evictions: 0,
invalidations: 0,
}
}
pub(crate) fn locked(lock: &std::sync::Mutex<Self>) -> std::sync::MutexGuard<'_, Self> {
lock.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
pub(crate) fn read_through(
lock: &std::sync::Mutex<Self>,
config: MetadataCacheConfig,
offset: u64,
len: usize,
read: impl FnOnce() -> Result<Vec<u8>, FormatError>,
) -> Result<Vec<u8>, FormatError> {
if len == 0 || !config.is_enabled() {
return read();
}
if len > config.max_entry_bytes() || len > config.max_bytes() {
Self::locked(lock).oversize_reads += 1;
return read();
}
if let Some(bytes) = Self::locked(lock).get(offset, len) {
return Ok(bytes);
}
let bytes = read()?;
Self::locked(lock).insert(offset, len, bytes.clone(), config.max_bytes());
Ok(bytes)
}
pub(crate) fn stats(&self) -> MetadataCacheStats {
MetadataCacheStats {
hits: self.hits,
misses: self.misses,
oversize_reads: self.oversize_reads,
evictions: self.evictions,
invalidations: self.invalidations,
entries: self.entries.len(),
bytes: self.current_bytes,
}
}
pub(crate) fn reset_stats(&mut self) {
self.hits = 0;
self.misses = 0;
self.oversize_reads = 0;
self.evictions = 0;
self.invalidations = 0;
}
fn remove(&mut self, key: (u64, usize)) {
if let Some(entry) = self.entries.remove(&key) {
self.by_access.remove(&entry.last_access);
self.current_bytes -= entry.bytes.len();
}
debug_assert_eq!(
self.entries.len(),
self.by_access.len(),
"every entry holds exactly one access row"
);
}
pub(crate) fn invalidate_overlapping(&mut self, offset: u64, len: usize) {
if len == 0 {
return;
}
let end = offset.saturating_add(len as u64);
let first = offset.saturating_sub(self.longest_entry as u64);
let doomed: Vec<(u64, usize)> = self
.entries
.range((first, 0)..(end, 0))
.filter(|((entry_offset, entry_len), _)| {
entry_offset.saturating_add(*entry_len as u64) > offset
})
.map(|(key, _)| *key)
.collect();
self.invalidations += doomed.len() as u64;
for key in doomed {
self.remove(key);
}
}
pub(crate) fn get(&mut self, offset: u64, len: usize) -> Option<Vec<u8>> {
let key = (offset, len);
let tick = self.tick + 1;
let Some(entry) = self.entries.get_mut(&key) else {
self.misses += 1;
return None;
};
let previous = core::mem::replace(&mut entry.last_access, tick);
let bytes = entry.bytes.clone();
self.tick = tick;
self.hits += 1;
self.by_access.remove(&previous);
self.by_access.insert(tick, key);
Some(bytes)
}
pub(crate) fn insert(&mut self, offset: u64, len: usize, bytes: Vec<u8>, max_bytes: usize) {
if len == 0 || bytes.len() > max_bytes {
return;
}
let key = (offset, len);
self.remove(key);
self.tick += 1;
let tick = self.tick;
self.longest_entry = self.longest_entry.max(len);
self.current_bytes += bytes.len();
self.entries.insert(
key,
CachedMetadataRead {
bytes,
last_access: tick,
},
);
self.by_access.insert(tick, key);
debug_assert_eq!(
self.entries.len(),
self.by_access.len(),
"every entry holds exactly one access row"
);
self.evict_to_budget(max_bytes);
}
fn evict_to_budget(&mut self, max_bytes: usize) {
while self.current_bytes > max_bytes {
let Some((_, &key)) = self.by_access.first_key_value() else {
break;
};
self.evictions += 1;
self.remove(key);
}
}
}
#[cfg(feature = "std")]
pub struct MetadataCachingSource<S> {
inner: S,
config: MetadataCacheConfig,
cache: std::sync::Mutex<MetadataReadCache>,
}
#[cfg(feature = "std")]
impl<S> MetadataCachingSource<S> {
pub fn new(inner: S, config: MetadataCacheConfig) -> Self {
Self {
inner,
config,
cache: std::sync::Mutex::new(MetadataReadCache::new()),
}
}
}
#[cfg(feature = "std")]
impl<S: Source> Source for MetadataCachingSource<S> {
fn len(&self) -> u64 {
self.inner.len()
}
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<(), FormatError> {
self.inner.read_at(offset, buf)
}
fn read_exact_at(&self, offset: u64, len: usize) -> Result<Vec<u8>, FormatError> {
self.inner.read_exact_at(offset, len)
}
fn read_metadata_at(&self, offset: u64, len: usize) -> Result<Vec<u8>, FormatError> {
MetadataReadCache::read_through(&self.cache, self.config, offset, len, || {
self.inner.read_metadata_at(offset, len)
})
}
fn metadata_cache_stats(&self) -> Option<MetadataCacheStats> {
self.config
.is_enabled()
.then(|| MetadataReadCache::locked(&self.cache).stats())
}
fn reset_metadata_cache_stats(&self) {
MetadataReadCache::locked(&self.cache).reset_stats();
}
}
#[cfg(feature = "std")]
pub struct ReadSeekSource<R> {
inner: std::sync::Mutex<R>,
len: u64,
}
#[cfg(feature = "std")]
impl<R: std::io::Read + std::io::Seek> ReadSeekSource<R> {
pub fn new(mut reader: R) -> Result<Self, FormatError> {
let len = reader
.seek(std::io::SeekFrom::End(0))
.map_err(|e| FormatError::Source(format_io(&e)))?;
Ok(ReadSeekSource {
inner: std::sync::Mutex::new(reader),
len,
})
}
}
#[cfg(feature = "std")]
impl<R: std::io::Read + std::io::Seek> Source for ReadSeekSource<R> {
fn len(&self) -> u64 {
self.len
}
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<(), FormatError> {
let end = offset
.checked_add(buf.len() as u64)
.ok_or(FormatError::OffsetOverflow {
offset,
length: buf.len() as u64,
})?;
if end > self.len {
return Err(FormatError::UnexpectedEof {
expected: end.to_usize().unwrap_or(usize::MAX),
available: self.len.to_usize().unwrap_or(usize::MAX),
});
}
let mut guard = self
.inner
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
guard
.seek(std::io::SeekFrom::Start(offset))
.map_err(|e| FormatError::Source(format_io(&e)))?;
guard
.read_exact(buf)
.map_err(|e| FormatError::Source(format_io(&e)))?;
Ok(())
}
}
#[cfg(feature = "std")]
fn format_io(e: &std::io::Error) -> std::string::String {
std::format!("{e}")
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(feature = "std"))]
use alloc::vec;
#[test]
fn bytes_source_reads_and_reports_len() {
let data = (0u8..=255).collect::<Vec<u8>>();
let src = BytesSource::new(data.clone());
assert_eq!(src.len(), 256);
let mut buf = [0u8; 4];
src.read_at(10, &mut buf).unwrap();
assert_eq!(buf, [10, 11, 12, 13]);
let owned = src.read_exact_at(250, 6).unwrap();
assert_eq!(owned, vec![250, 251, 252, 253, 254, 255]);
}
#[test]
fn bytes_source_short_read_is_eof() {
let src = BytesSource::new(vec![1u8, 2, 3]);
let mut buf = [0u8; 4];
let err = src.read_at(0, &mut buf).unwrap_err();
assert!(matches!(err, FormatError::UnexpectedEof { .. }));
let mut ok = [0u8; 3];
src.read_at(0, &mut ok).unwrap();
assert_eq!(ok, [1, 2, 3]);
}
#[test]
fn bytes_source_offset_past_end_is_eof() {
let src = BytesSource::new(vec![0u8; 8]);
let mut buf = [0u8; 1];
assert!(matches!(
src.read_at(8, &mut buf).unwrap_err(),
FormatError::UnexpectedEof { .. }
));
src.read_at(8, &mut []).unwrap();
}
#[test]
fn read_exact_at_rejects_oversized_len_without_allocating() {
let src = BytesSource::new(vec![1u8, 2, 3, 4]);
assert!(matches!(
src.read_exact_at(0, usize::MAX).unwrap_err(),
FormatError::UnexpectedEof { .. }
));
assert_eq!(src.read_exact_at(1, 3).unwrap(), vec![2, 3, 4]);
}
#[test]
fn empty_source() {
let src = BytesSource::new(Vec::<u8>::new());
assert_eq!(src.len(), 0);
}
#[test]
fn forwarding_through_reference() {
let src = BytesSource::new(vec![9u8, 8, 7]);
let r: &dyn Source = &src;
let mut buf = [0u8; 2];
r.read_at(1, &mut buf).unwrap();
assert_eq!(buf, [8, 7]);
}
#[test]
fn forwarding_through_reference_preserves_metadata_reads() {
use core::cell::Cell;
struct MetadataSource {
metadata_reads: Cell<usize>,
}
impl Source for MetadataSource {
fn len(&self) -> u64 {
16
}
fn read_at(&self, _offset: u64, buf: &mut [u8]) -> Result<(), FormatError> {
buf.fill(0);
Ok(())
}
fn read_metadata_at(&self, _offset: u64, len: usize) -> Result<Vec<u8>, FormatError> {
self.metadata_reads.set(self.metadata_reads.get() + 1);
Ok(vec![0xAB; len])
}
}
fn read_metadata_via_trait<T: Source>(source: T) -> Vec<u8> {
source.read_metadata_at(4, 3).unwrap()
}
let source = MetadataSource {
metadata_reads: Cell::new(0),
};
assert_eq!(read_metadata_via_trait(&source), vec![0xAB; 3]);
assert_eq!(source.metadata_reads.get(), 1);
}
#[cfg(feature = "std")]
#[test]
fn metadata_cache_caches_only_metadata_reads() {
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
struct CountingSource {
data: Vec<u8>,
reads: Arc<AtomicUsize>,
}
impl Source for CountingSource {
fn len(&self) -> u64 {
self.data.len() as u64
}
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<(), FormatError> {
self.reads.fetch_add(1, Ordering::SeqCst);
BytesSource::new(&self.data).read_at(offset, buf)
}
}
let reads = Arc::new(AtomicUsize::new(0));
let source = MetadataCachingSource::new(
CountingSource {
data: (0u8..16).collect(),
reads: Arc::clone(&reads),
},
MetadataCacheConfig::new(16),
);
assert_eq!(source.read_metadata_at(4, 4).unwrap(), vec![4, 5, 6, 7]);
assert_eq!(source.read_metadata_at(4, 4).unwrap(), vec![4, 5, 6, 7]);
assert_eq!(reads.load(Ordering::SeqCst), 1);
assert_eq!(source.read_exact_at(4, 4).unwrap(), vec![4, 5, 6, 7]);
assert_eq!(source.read_exact_at(4, 4).unwrap(), vec![4, 5, 6, 7]);
assert_eq!(reads.load(Ordering::SeqCst), 3);
}
#[cfg(feature = "std")]
#[test]
fn read_seek_source_matches_in_memory() {
use std::io::Cursor;
let data = (0u8..200).collect::<Vec<u8>>();
let mem = BytesSource::new(data.clone());
let seek = ReadSeekSource::new(Cursor::new(data.clone())).unwrap();
assert_eq!(seek.len(), mem.len());
for &(off, len) in &[(0u64, 1usize), (5, 10), (199, 1), (100, 50)] {
let a = mem.read_exact_at(off, len).unwrap();
let b = seek.read_exact_at(off, len).unwrap();
assert_eq!(a, b, "mismatch at offset {off} len {len}");
}
}
#[cfg(feature = "std")]
#[test]
fn read_seek_source_past_end_is_error() {
use std::io::Cursor;
let seek = ReadSeekSource::new(Cursor::new(vec![1u8, 2, 3, 4])).unwrap();
let mut buf = [0u8; 3];
assert!(matches!(
seek.read_at(2, &mut buf).unwrap_err(),
FormatError::UnexpectedEof { .. }
));
}
#[cfg(feature = "std")]
#[test]
fn read_seek_source_is_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<ReadSeekSource<std::io::Cursor<Vec<u8>>>>();
}
#[test]
fn eviction_drops_the_least_recently_used_entry_not_the_oldest() {
let budget = 30;
let mut cache = MetadataReadCache::new();
cache.insert(0, 10, vec![0u8; 10], budget);
cache.insert(100, 10, vec![1u8; 10], budget);
cache.insert(200, 10, vec![2u8; 10], budget);
assert!(cache.get(0, 10).is_some());
cache.insert(300, 10, vec![3u8; 10], budget);
assert!(
cache.get(0, 10).is_some(),
"read most recently, must survive"
);
assert!(cache.get(100, 10).is_none(), "least recently used, must go");
assert!(cache.get(200, 10).is_some());
assert!(cache.get(300, 10).is_some());
}
#[test]
fn invalidation_takes_every_overlap_and_spares_the_neighbours() {
let budget = 1024;
let mut cache = MetadataReadCache::new();
for offset in [0u64, 10, 20, 30] {
cache.insert(offset, 10, vec![offset as u8; 10], budget);
}
cache.insert(5, 40, vec![9u8; 40], budget);
cache.invalidate_overlapping(20, 5);
assert!(cache.get(0, 10).is_some(), "ends at 10, short of the write");
assert!(
cache.get(10, 10).is_some(),
"ends exactly where the write starts, so it shares no byte with it"
);
assert!(cache.get(20, 10).is_none(), "the write lands inside it");
assert!(cache.get(30, 10).is_some(), "starts after the write ends");
assert!(
cache.get(5, 40).is_none(),
"starts before the write and spans it, so a search beginning at the \
write's own offset would walk straight past it"
);
}
#[test]
fn re_inserting_a_key_replaces_it_rather_than_counting_it_twice() {
let budget = 20;
let mut cache = MetadataReadCache::new();
cache.insert(0, 10, vec![0u8; 10], budget);
cache.insert(0, 10, vec![1u8; 10], budget);
cache.insert(100, 10, vec![2u8; 10], budget);
assert_eq!(
cache.get(0, 10).as_deref(),
Some(&[1u8; 10][..]),
"the later value replaces the earlier one"
);
assert!(
cache.get(100, 10).is_some(),
"a replacement that was counted twice would have evicted to make room"
);
}
#[test]
fn one_offset_at_two_lengths_holds_two_entries() {
let budget = 1024;
let mut cache = MetadataReadCache::new();
cache.insert(64, 4, vec![1u8; 4], budget);
cache.insert(64, 8, vec![2u8; 8], budget);
assert_eq!(cache.get(64, 4).as_deref(), Some(&[1u8; 4][..]));
assert_eq!(cache.get(64, 8).as_deref(), Some(&[2u8; 8][..]));
}
#[test]
fn a_hit_does_not_get_slower_as_the_cache_grows() {
const SMALL: usize = 1_024;
const LARGE: usize = 16_384;
const ALLOWED_GROWTH: f64 = 8.0;
fn nanos_per_hit(entries: usize) -> f64 {
const ENTRY_LEN: usize = 64;
let budget = entries * ENTRY_LEN * 2;
let mut cache = MetadataReadCache::new();
for i in 0..entries {
cache.insert(
(i * ENTRY_LEN) as u64,
ENTRY_LEN,
vec![7u8; ENTRY_LEN],
budget,
);
}
for i in 0..entries {
assert!(cache.get((i * ENTRY_LEN) as u64, ENTRY_LEN).is_some());
}
let started = std::time::Instant::now();
for i in 0..entries {
assert!(cache.get((i * ENTRY_LEN) as u64, ENTRY_LEN).is_some());
}
started.elapsed().as_secs_f64() * 1e9 / entries as f64
}
let small = nanos_per_hit(SMALL);
let large = nanos_per_hit(LARGE);
assert!(
large < small * ALLOWED_GROWTH,
"a hit cost {large:.0} ns with {LARGE} entries against {small:.0} ns with \
{SMALL} -- growing the cache should not move it, and a cost that tracks \
its size is the shape of a store being searched rather than indexed"
);
}
#[cfg(feature = "std")]
fn ramp(len: usize) -> BytesSource<Vec<u8>> {
BytesSource::new((0..len).map(|i| i as u8).collect::<Vec<u8>>())
}
#[cfg(feature = "std")]
#[test]
fn a_read_too_large_to_admit_is_not_charged_as_a_miss() {
let config = MetadataCacheConfig::new(4096).with_max_entry_bytes(64);
let source = MetadataCachingSource::new(ramp(4096), config);
source.read_metadata_at(0, 64).unwrap(); source.read_metadata_at(0, 64).unwrap(); source.read_metadata_at(128, 256).unwrap(); source.read_metadata_at(128, 256).unwrap();
let stats = source.metadata_cache_stats().unwrap();
assert_eq!(stats.hits(), 1);
assert_eq!(stats.misses(), 1);
assert_eq!(stats.oversize_reads(), 2);
assert_eq!(stats.reads(), 4);
assert_eq!(stats.hit_rate(), Some(0.5));
let lopsided = MetadataCacheConfig::new(128).with_max_entry_bytes(512);
let source = MetadataCachingSource::new(ramp(4096), lopsided);
source.read_metadata_at(0, 256).unwrap();
source.read_metadata_at(0, 256).unwrap();
let stats = source.metadata_cache_stats().unwrap();
assert_eq!(stats.oversize_reads(), 2);
assert_eq!(stats.misses(), 0);
assert_eq!(stats.hit_rate(), None);
}
#[cfg(feature = "std")]
#[test]
fn no_eligible_read_yet_is_not_a_hit_rate_of_zero() {
let config = MetadataCacheConfig::new(4096).with_max_entry_bytes(64);
let source = MetadataCachingSource::new(ramp(4096), config);
let fresh = source.metadata_cache_stats().unwrap();
assert_eq!(fresh.hit_rate(), None, "nothing has been read");
source.read_metadata_at(0, 256).unwrap();
assert_eq!(
source.metadata_cache_stats().unwrap().hit_rate(),
None,
"a read the cache never saw does not make a rate out of it"
);
source.read_metadata_at(0, 64).unwrap();
assert_eq!(
source.metadata_cache_stats().unwrap().hit_rate(),
Some(0.0),
"one eligible read that missed *is* a rate, and the opposite reading"
);
}
#[cfg(feature = "std")]
#[test]
fn the_budget_and_a_write_drop_entries_for_different_reasons() {
const BUDGET: usize = 128;
let mut cache = MetadataReadCache::new();
cache.insert(0, 64, vec![1u8; 64], BUDGET);
cache.insert(64, 64, vec![2u8; 64], BUDGET);
cache.insert(0, 64, vec![1u8; 64], BUDGET);
let replaced = cache.stats();
assert_eq!(replaced.entries(), 2);
assert_eq!(replaced.evictions(), 0);
assert_eq!(replaced.invalidations(), 0);
cache.insert(128, 64, vec![3u8; 64], BUDGET);
let evicted = cache.stats();
assert_eq!(evicted.evictions(), 1, "the budget forced this one");
assert_eq!(evicted.invalidations(), 0);
assert_eq!(evicted.entries(), 2);
cache.invalidate_overlapping(32, 128);
let invalidated = cache.stats();
assert_eq!(invalidated.invalidations(), 2, "the write overlapped both");
assert_eq!(
invalidated.evictions(),
1,
"a write is not the budget, and a caller told to raise the budget \
because of one would be raising it for nothing"
);
assert_eq!(invalidated.entries(), 0);
assert_eq!(invalidated.bytes(), 0);
}
#[cfg(feature = "std")]
#[test]
fn resetting_the_counters_keeps_the_entries() {
const BUDGET: usize = 4096;
let mut cache = MetadataReadCache::new();
cache.insert(0, 64, vec![1u8; 64], BUDGET);
assert!(cache.get(0, 64).is_some());
assert!(cache.get(512, 64).is_none());
cache.reset_stats();
let stats = cache.stats();
assert_eq!(stats.hits(), 0);
assert_eq!(stats.misses(), 0);
assert_eq!(stats.hit_rate(), None);
assert_eq!(stats.entries(), 1);
assert_eq!(stats.bytes(), 64);
assert!(cache.get(0, 64).is_some(), "the entry is still servable");
}
#[cfg(feature = "std")]
#[test]
fn a_disabled_cache_reports_nothing_rather_than_zeroes() {
let source = MetadataCachingSource::new(ramp(4096), MetadataCacheConfig::disabled());
assert_eq!(
source.read_metadata_at(0, 64).unwrap(),
(0..64u8).collect::<Vec<u8>>()
);
assert_eq!(
source.metadata_cache_stats(),
None,
"an all-zero snapshot would read as a cache that is on and idle"
);
source.reset_metadata_cache_stats();
assert_eq!(
source.metadata_cache_stats(),
None,
"and resetting one there is nothing to reset does not conjure one"
);
}
#[cfg(feature = "std")]
#[test]
fn a_wrapper_reports_the_cache_it_reads_through() {
let config = MetadataCacheConfig::new(4096);
let source = MetadataCachingSource::new(ramp(4096), config);
let framed = BaseOffsetSource {
inner: &source,
base: BaseAddress::new(512),
};
framed.read_metadata_at(0, 64).unwrap();
framed.read_metadata_at(0, 64).unwrap();
let stats = framed.metadata_cache_stats().expect("forwarded");
assert_eq!((stats.hits(), stats.misses()), (1, 1));
assert_eq!(stats, source.metadata_cache_stats().unwrap());
framed.reset_metadata_cache_stats();
assert_eq!(source.metadata_cache_stats().unwrap().hits(), 0);
assert_eq!(
source.metadata_cache_stats().unwrap().entries(),
1,
"reset through the view is a reset of counters, not a flush"
);
}
}