use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use std::time::Duration;
use std::time::Instant;
#[derive(Debug)]
pub struct MappedFileMetrics {
total_writes: AtomicU64,
total_bytes_written: AtomicU64,
total_flushes: AtomicU64,
total_flush_time_us: AtomicU64,
total_reads: AtomicU64,
total_bytes_read: AtomicU64,
zero_copy_reads: AtomicU64,
cache_hits: AtomicU64,
cache_misses: AtomicU64,
start_time: Instant,
}
impl Default for MappedFileMetrics {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl MappedFileMetrics {
#[inline]
pub fn new() -> Self {
Self {
total_writes: AtomicU64::new(0),
total_bytes_written: AtomicU64::new(0),
total_flushes: AtomicU64::new(0),
total_flush_time_us: AtomicU64::new(0),
total_reads: AtomicU64::new(0),
total_bytes_read: AtomicU64::new(0),
zero_copy_reads: AtomicU64::new(0),
cache_hits: AtomicU64::new(0),
cache_misses: AtomicU64::new(0),
start_time: Instant::now(),
}
}
#[inline]
pub fn record_write(&self, bytes: usize) {
self.total_writes.fetch_add(1, Ordering::Relaxed);
self.total_bytes_written.fetch_add(bytes as u64, Ordering::Relaxed);
}
#[inline]
pub fn record_flush(&self, duration: Duration) {
self.total_flushes.fetch_add(1, Ordering::Relaxed);
self.total_flush_time_us
.fetch_add(duration.as_micros() as u64, Ordering::Relaxed);
}
#[inline]
pub fn record_read(&self, bytes: usize, zero_copy: bool) {
self.total_reads.fetch_add(1, Ordering::Relaxed);
self.total_bytes_read.fetch_add(bytes as u64, Ordering::Relaxed);
if zero_copy {
self.zero_copy_reads.fetch_add(1, Ordering::Relaxed);
}
}
#[inline]
pub fn record_cache_hit(&self) {
self.cache_hits.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn record_cache_miss(&self) {
self.cache_misses.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn total_writes(&self) -> u64 {
self.total_writes.load(Ordering::Relaxed)
}
#[inline]
pub fn total_bytes_written(&self) -> u64 {
self.total_bytes_written.load(Ordering::Relaxed)
}
#[inline]
pub fn total_flushes(&self) -> u64 {
self.total_flushes.load(Ordering::Relaxed)
}
#[inline]
pub fn total_reads(&self) -> u64 {
self.total_reads.load(Ordering::Relaxed)
}
#[inline]
pub fn total_bytes_read(&self) -> u64 {
self.total_bytes_read.load(Ordering::Relaxed)
}
pub fn writes_per_sec(&self) -> f64 {
let elapsed = self.start_time.elapsed().as_secs_f64();
if elapsed > 0.0 {
self.total_writes() as f64 / elapsed
} else {
0.0
}
}
pub fn write_throughput_bytes_per_sec(&self) -> f64 {
let elapsed = self.start_time.elapsed().as_secs_f64();
if elapsed > 0.0 {
self.total_bytes_written() as f64 / elapsed
} else {
0.0
}
}
pub fn write_throughput_mb_per_sec(&self) -> f64 {
self.write_throughput_bytes_per_sec() / (1024.0 * 1024.0)
}
pub fn avg_write_size(&self) -> f64 {
let writes = self.total_writes();
if writes > 0 {
self.total_bytes_written() as f64 / writes as f64
} else {
0.0
}
}
pub fn avg_flush_duration(&self) -> Duration {
let flushes = self.total_flushes();
let total_time_us = self.total_flush_time_us.load(Ordering::Relaxed);
total_time_us
.checked_div(flushes)
.map(Duration::from_micros)
.unwrap_or(Duration::ZERO)
}
pub fn zero_copy_read_percentage(&self) -> f64 {
let total = self.total_reads();
if total > 0 {
let zero_copy = self.zero_copy_reads.load(Ordering::Relaxed);
(zero_copy as f64 / total as f64) * 100.0
} else {
0.0
}
}
pub fn cache_hit_rate(&self) -> f64 {
let hits = self.cache_hits.load(Ordering::Relaxed);
let misses = self.cache_misses.load(Ordering::Relaxed);
let total = hits + misses;
if total > 0 {
(hits as f64 / total as f64) * 100.0
} else {
0.0
}
}
pub fn reset(&mut self) {
self.total_writes.store(0, Ordering::Relaxed);
self.total_bytes_written.store(0, Ordering::Relaxed);
self.total_flushes.store(0, Ordering::Relaxed);
self.total_flush_time_us.store(0, Ordering::Relaxed);
self.total_reads.store(0, Ordering::Relaxed);
self.total_bytes_read.store(0, Ordering::Relaxed);
self.zero_copy_reads.store(0, Ordering::Relaxed);
self.cache_hits.store(0, Ordering::Relaxed);
self.cache_misses.store(0, Ordering::Relaxed);
self.start_time = Instant::now();
}
pub fn summary(&self) -> String {
format!(
"MappedFile Metrics:\nWrites: {} ({:.2} writes/sec, {:.2} MB/s)\nReads: {} ({:.1}% zero-copy)\nFlushes: \
{} (avg: {:?})\nCache Hit Rate: {:.1}%\nAvg Write Size: {:.1} bytes",
self.total_writes(),
self.writes_per_sec(),
self.write_throughput_mb_per_sec(),
self.total_reads(),
self.zero_copy_read_percentage(),
self.total_flushes(),
self.avg_flush_duration(),
self.cache_hit_rate(),
self.avg_write_size()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_record_write() {
let metrics = MappedFileMetrics::new();
metrics.record_write(1024);
metrics.record_write(2048);
assert_eq!(metrics.total_writes(), 2);
assert_eq!(metrics.total_bytes_written(), 3072);
}
#[test]
fn test_record_flush() {
let metrics = MappedFileMetrics::new();
metrics.record_flush(Duration::from_micros(100));
metrics.record_flush(Duration::from_micros(200));
assert_eq!(metrics.total_flushes(), 2);
assert_eq!(metrics.avg_flush_duration(), Duration::from_micros(150));
}
#[test]
fn test_record_read() {
let metrics = MappedFileMetrics::new();
metrics.record_read(1024, false);
metrics.record_read(2048, true);
metrics.record_read(4096, true);
assert_eq!(metrics.total_reads(), 3);
assert_eq!(metrics.total_bytes_read(), 7168);
let percentage = metrics.zero_copy_read_percentage();
assert!((percentage - 66.666).abs() < 0.01);
}
#[test]
fn test_cache_hit_rate() {
let metrics = MappedFileMetrics::new();
metrics.record_cache_hit();
metrics.record_cache_hit();
metrics.record_cache_miss();
assert!((metrics.cache_hit_rate() - 66.666).abs() < 0.01);
}
#[test]
fn test_avg_write_size() {
let metrics = MappedFileMetrics::new();
metrics.record_write(1000);
metrics.record_write(2000);
metrics.record_write(3000);
assert_eq!(metrics.avg_write_size(), 2000.0);
}
#[test]
fn test_reset() {
let mut metrics = MappedFileMetrics::new();
metrics.record_write(1024);
metrics.record_flush(Duration::from_micros(100));
assert!(metrics.total_writes() > 0);
metrics.reset();
assert_eq!(metrics.total_writes(), 0);
assert_eq!(metrics.total_flushes(), 0);
}
}