use std::{
path::{Path, PathBuf},
sync::{
Arc, Mutex, Weak,
atomic::{AtomicU64, AtomicUsize, Ordering},
},
vec::Vec,
};
static REGISTRY: CounterRegistry = CounterRegistry {
counters: Mutex::new(Vec::new()),
alive: AtomicUsize::new(0),
};
pub struct FileReadCounter {
counted: Arc<Counted>,
}
impl FileReadCounter {
pub fn new(path: impl Into<PathBuf>) -> Self {
let counted = Arc::new(Counted {
path: path.into(),
read: AtomicU64::new(0),
});
REGISTRY.register(&counted);
Self { counted }
}
pub fn read(&self) -> u64 {
self.counted.read.load(Ordering::Relaxed)
}
}
impl Drop for FileReadCounter {
fn drop(&mut self) {
REGISTRY.unregister();
}
}
pub(crate) fn count(path: &Path, size: u64) {
REGISTRY.count(path, size);
}
struct Counted {
path: PathBuf,
read: AtomicU64,
}
struct CounterRegistry {
counters: Mutex<Vec<Weak<Counted>>>,
alive: AtomicUsize,
}
impl CounterRegistry {
fn register(&self, counted: &Arc<Counted>) {
let mut counters = self.lock();
counters.retain(|counter| counter.strong_count() > 0);
counters.push(Arc::downgrade(counted));
self.alive.fetch_add(1, Ordering::Relaxed);
}
fn unregister(&self) {
self.alive.fetch_sub(1, Ordering::Relaxed);
}
fn count(&self, path: &Path, size: u64) {
if self.alive.load(Ordering::Relaxed) == 0 {
return;
}
self.lock()
.iter()
.filter_map(Weak::upgrade)
.filter(|counted| counted.path == path)
.for_each(|counted| {
counted.read.fetch_add(size, Ordering::Relaxed);
});
}
fn lock(&self) -> std::sync::MutexGuard<'_, Vec<Weak<Counted>>> {
self.counters
.lock()
.unwrap_or_else(|poison| poison.into_inner())
}
}
#[cfg(test)]
#[cfg(not(miri))]
mod tests {
use super::super::{Bytes, Reader};
use super::*;
use std::io::Write;
#[test]
fn reads_are_counted_when_the_file_is_accessed() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("weights");
std::fs::File::create(&path)
.unwrap()
.write_all(&[7u8; 64])
.unwrap();
let counter = FileReadCounter::new(&path);
let bytes = Bytes::from_file(&path, 64, 0);
let view = bytes.view(0, 16).unwrap();
assert_eq!(counter.read(), 0);
let mut copied = Bytes::from_bytes_vec(std::vec![0u8; 16]);
view.copy_into(&mut copied);
assert_eq!(counter.read(), 16);
bytes.read(Reader::new()).unwrap();
bytes.read(Reader::new()).unwrap();
assert_eq!(counter.read(), 16 + 64);
let other = dir.path().join("other");
std::fs::File::create(&other)
.unwrap()
.write_all(&[1])
.unwrap();
Bytes::from_file(&other, 1, 0).read(Reader::new()).unwrap();
assert_eq!(counter.read(), 16 + 64);
}
}