use super::histogram::LockFreeHistogram;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};
#[derive(Debug)]
pub struct StorageMetrics {
resize_count: AtomicU64,
total_bytes_resized: AtomicU64,
latency_histogram: LockFreeHistogram,
}
impl Default for StorageMetrics {
fn default() -> Self {
Self::new()
}
}
impl StorageMetrics {
#[must_use]
pub fn new() -> Self {
Self {
resize_count: AtomicU64::new(0),
total_bytes_resized: AtomicU64::new(0),
latency_histogram: LockFreeHistogram::new(),
}
}
#[inline]
pub fn record_ensure_capacity(&self, latency: Duration, did_resize: bool, bytes_resized: u64) {
#[allow(clippy::cast_possible_truncation)]
let micros = latency.as_micros().min(u128::from(u64::MAX)) as u64;
self.latency_histogram.record(micros);
if did_resize {
self.resize_count.fetch_add(1, Ordering::Relaxed);
self.total_bytes_resized
.fetch_add(bytes_resized, Ordering::Relaxed);
}
}
#[must_use]
pub fn ensure_capacity_count(&self) -> u64 {
self.latency_histogram.count()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.latency_histogram.is_empty()
}
#[must_use]
pub fn resize_count(&self) -> u64 {
self.resize_count.load(Ordering::Relaxed)
}
#[must_use]
pub fn total_bytes_resized(&self) -> u64 {
self.total_bytes_resized.load(Ordering::Relaxed)
}
#[must_use]
pub fn ensure_capacity_latency_stats(&self) -> LatencyStats {
LatencyStats {
count: self.latency_histogram.count(),
min_us: self.latency_histogram.min(),
max_us: self.latency_histogram.max(),
mean_us: self.latency_histogram.mean(),
p50_us: self.latency_histogram.percentile(50),
p95_us: self.latency_histogram.percentile(95),
p99_us: self.latency_histogram.percentile(99),
}
}
pub fn reset(&self) {
self.resize_count.store(0, Ordering::Relaxed);
self.total_bytes_resized.store(0, Ordering::Relaxed);
self.latency_histogram.reset();
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct LatencyStats {
pub count: u64,
pub min_us: u64,
pub max_us: u64,
pub mean_us: u64,
pub p50_us: u64,
pub p95_us: u64,
pub p99_us: u64,
}
impl LatencyStats {
#[must_use]
pub fn p99(&self) -> Duration {
Duration::from_micros(self.p99_us)
}
#[must_use]
pub fn p95(&self) -> Duration {
Duration::from_micros(self.p95_us)
}
#[must_use]
pub fn p50(&self) -> Duration {
Duration::from_micros(self.p50_us)
}
#[must_use]
pub fn mean(&self) -> Duration {
Duration::from_micros(self.mean_us)
}
#[must_use]
pub fn p99_exceeds(&self, threshold: Duration) -> bool {
self.p99() > threshold
}
}
pub struct TimingGuard<'a, F>
where
F: FnOnce(Duration),
{
start: Instant,
callback: Option<F>,
_marker: std::marker::PhantomData<&'a ()>,
}
impl<F> TimingGuard<'_, F>
where
F: FnOnce(Duration),
{
pub fn new(callback: F) -> Self {
Self {
start: Instant::now(),
callback: Some(callback),
_marker: std::marker::PhantomData,
}
}
#[must_use]
pub fn elapsed(&self) -> Duration {
self.start.elapsed()
}
}
impl<F> Drop for TimingGuard<'_, F>
where
F: FnOnce(Duration),
{
fn drop(&mut self) {
if let Some(cb) = self.callback.take() {
cb(self.start.elapsed());
}
}
}