use super::{raw, Histogram, MetricsExt as _};
use crate::{Clock, Metrics};
use std::{sync::Arc, time::SystemTime};
pub trait HistogramExt {
fn observe_between(&self, start: SystemTime, end: SystemTime);
}
impl HistogramExt for raw::Histogram {
fn observe_between(&self, start: SystemTime, end: SystemTime) {
let duration = end
.duration_since(start)
.map_or(0.0, |duration| duration.as_secs_f64());
self.observe(duration);
}
}
pub struct Buckets;
impl Buckets {
pub const NETWORK: [f64; 13] = [
0.010, 0.020, 0.050, 0.100, 0.200, 0.500, 1.0, 2.0, 5.0, 10.0, 30.0, 60.0, 300.0,
];
pub const LOCAL: [f64; 12] = [
3e-6, 1e-5, 3e-5, 1e-4, 3e-4, 0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 1.0,
];
pub const CRYPTOGRAPHY: [f64; 16] = [
3e-6, 1e-5, 3e-5, 1e-4, 3e-4, 0.001, 0.002, 0.003, 0.005, 0.01, 0.015, 0.02, 0.025, 0.03,
0.1, 0.2,
];
}
#[derive(Clone)]
pub struct Timed {
histogram: Histogram,
}
impl Timed {
pub const fn new(histogram: Histogram) -> Self {
Self { histogram }
}
pub fn timer<C: Clock>(&self, clock: &C) -> Timer {
let start = clock.current();
Timer {
histogram: self.histogram.clone(),
start,
}
}
pub fn time_some<C: Clock, T, F: FnOnce() -> Option<T>>(&self, clock: &C, f: F) -> Option<T> {
let start = clock.current();
let result = f();
if result.is_some() {
self.histogram.observe_between(start, clock.current());
}
result
}
}
pub struct Timer {
histogram: Histogram,
start: SystemTime,
}
impl Timer {
pub fn observe<C: Clock>(self, clock: &C) {
self.histogram.observe_between(self.start, clock.current());
}
}
pub struct ScopedTimer<C: Clock> {
timer: Option<Timer>,
clock: Arc<C>,
}
impl<C: Clock> ScopedTimer<C> {
pub fn cancel(mut self) {
self.timer = None;
}
}
impl<C: Clock> Drop for ScopedTimer<C> {
fn drop(&mut self) {
if let Some(timer) = self.timer.take() {
timer.observe(self.clock.as_ref());
}
}
}
impl Timed {
pub fn scoped<C: Clock>(&self, clock: &Arc<C>) -> ScopedTimer<C> {
ScopedTimer {
timer: Some(self.timer(clock.as_ref())),
clock: clock.clone(),
}
}
}
pub fn duration_histogram<M: Metrics>(
context: &M,
name: &'static str,
help: &'static str,
) -> Histogram {
context.histogram(name, help, Buckets::LOCAL)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{deterministic, Runner as _, Supervisor as _};
use std::time::Duration;
#[test]
fn duration_records_all_calls() {
deterministic::Runner::default().start(|context| async move {
let histogram = duration_histogram(&context, "test_duration", "test duration");
let timed = Timed::new(histogram);
let clock = Arc::new(context.child("timer"));
{
let _timer = timed.scoped(&clock);
context.sleep(Duration::from_millis(1)).await;
let result: Result<(), ()> = Ok(());
assert!(result.is_ok());
}
{
let _timer = timed.scoped(&clock);
context.sleep(Duration::from_millis(1)).await;
let result: Result<(), ()> = Err(());
assert!(result.is_err());
}
{
let _timer = timed.scoped(&clock);
context.sleep(Duration::from_millis(1)).await;
}
let metrics = context.encode();
assert!(
metrics.contains("test_duration_count 3"),
"unexpected metrics: {metrics}"
);
});
}
}