use std::collections::HashMap;
pub enum Kind {
Overall,
SincePrevious,
}
#[derive(Debug, Clone)]
pub struct IoStats {
pub transactions: u64,
pub reads: u64,
pub cache_reads: u64,
pub writes: u64,
pub bytes_read: u64,
pub cache_read_bytes: u64,
pub bytes_written: u64,
pub deletes: u64,
pub prefix_deletes: u64,
pub write_size_buckets: HashMap<usize, u64>,
pub tx_write_size_buckets: HashMap<usize, (u64, f64)>,
pub started: u64,
pub span: std::time::Duration,
}
impl IoStats {
pub fn empty() -> Self {
Self {
transactions: 0,
reads: 0,
cache_reads: 0,
writes: 0,
bytes_read: 0,
cache_read_bytes: 0,
bytes_written: 0,
deletes: 0,
prefix_deletes: 0,
write_size_buckets: HashMap::new(),
tx_write_size_buckets: HashMap::new(),
started: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(Default::default(), |d| d.as_micros() as u64),
span: std::time::Duration::default(),
}
}
pub fn record_write(&mut self, size: usize) {
self.writes += 1;
self.bytes_written += size as u64;
let size_bucket = estimate_bucket_size(size);
self.write_size_buckets
.entry(size_bucket)
.and_modify(|x| *x += 1)
.or_insert(1);
}
pub fn record_tx_write(&mut self, size: usize, duration_micros: f64) {
let size_bucket = estimate_bucket_size(size);
self.tx_write_size_buckets
.entry(size_bucket)
.and_modify(|(count, avg_duration)| {
*avg_duration =
(*avg_duration * (*count as f64) + duration_micros) / ((*count as f64) + 1.);
*count += 1;
})
.or_insert((1, duration_micros));
}
pub fn avg_batch_size(&self) -> f64 {
if self.writes == 0 {
return 0.0;
}
self.transactions as f64 / self.writes as f64
}
pub fn reads_per_sec(&self) -> f64 {
if self.span.as_secs_f64() == 0.0 {
return 0.0;
}
self.reads as f64 / self.span.as_secs_f64()
}
pub fn byte_reads_per_sec(&self) -> f64 {
if self.span.as_secs_f64() == 0.0 {
return 0.0;
}
self.bytes_read as f64 / self.span.as_secs_f64()
}
pub fn writes_per_sec(&self) -> f64 {
if self.span.as_secs_f64() == 0.0 {
return 0.0;
}
self.writes as f64 / self.span.as_secs_f64()
}
pub fn byte_writes_per_sec(&self) -> f64 {
if self.span.as_secs_f64() == 0.0 {
return 0.0;
}
self.bytes_written as f64 / self.span.as_secs_f64()
}
pub fn ops_per_sec(&self) -> f64 {
if self.span.as_secs_f64() == 0.0 {
return 0.0;
}
(self.writes as f64 + self.reads as f64) / self.span.as_secs_f64()
}
pub fn transactions_per_sec(&self) -> f64 {
if self.span.as_secs_f64() == 0.0 {
return 0.0;
}
(self.transactions as f64) / self.span.as_secs_f64()
}
pub fn avg_transaction_size(&self) -> f64 {
if self.transactions == 0 {
return 0.0;
}
self.bytes_written as f64 / self.transactions as f64
}
pub fn cache_hit_ratio(&self) -> f64 {
if self.reads == 0 {
return 0.0;
}
self.cache_reads as f64 / self.reads as f64
}
}
fn estimate_bucket_size(size: usize) -> usize {
if size == 0 {
return 0;
}
let step = (size.next_power_of_two() / 16).max(128);
size.div_ceil(step) * step
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_record_write() {
assert_eq!(estimate_bucket_size(100), 128);
assert_eq!(estimate_bucket_size(200), 256);
assert_eq!(estimate_bucket_size(1000), 1024);
assert_eq!(estimate_bucket_size(2500), 2560);
assert_eq!(estimate_bucket_size(5100), 5120);
assert_eq!(estimate_bucket_size(9000), 9216);
assert_eq!(estimate_bucket_size(9900), 10240);
}
}