use crate::{Histogram, TimePoint};
use std::sync::Arc;
pub struct Timer {
start: TimePoint,
histogram: Option<Arc<Histogram>>,
}
impl Timer {
#[inline(always)]
pub fn start() -> Self {
Self {
start: TimePoint::now(),
histogram: None,
}
}
#[inline(always)]
pub fn start_with_histogram(histogram: Arc<Histogram>) -> Self {
Self {
start: TimePoint::now(),
histogram: Some(histogram),
}
}
#[inline(always)]
pub fn elapsed_ns(&self) -> u64 {
self.start.elapsed_ns()
}
#[inline(always)]
pub fn elapsed_micros(&self) -> u64 {
self.elapsed_ns() / 1000
}
#[inline(always)]
pub fn elapsed_millis(&self) -> u64 {
self.elapsed_ns() / 1_000_000
}
#[inline(always)]
pub fn stop(self) -> u64 {
let elapsed = self.elapsed_ns();
if let Some(histogram) = &self.histogram {
histogram.record(elapsed);
}
elapsed
}
#[inline(always)]
pub fn restart(&mut self) {
self.start = TimePoint::now();
}
}
pub struct TimerGuard {
start: TimePoint,
histogram: Arc<Histogram>,
#[cfg_attr(not(debug_assertions), allow(dead_code))]
name: Option<&'static str>,
}
impl TimerGuard {
#[inline(always)]
pub fn new(histogram: Arc<Histogram>) -> Self {
Self {
start: TimePoint::now(),
histogram,
name: None,
}
}
#[inline(always)]
pub fn named(histogram: Arc<Histogram>, name: &'static str) -> Self {
Self {
start: TimePoint::now(),
histogram,
name: Some(name),
}
}
#[inline(always)]
pub fn elapsed_ns(&self) -> u64 {
self.start.elapsed_ns()
}
}
impl Drop for TimerGuard {
#[inline(always)]
fn drop(&mut self) {
let elapsed = self.start.elapsed_ns();
self.histogram.record(elapsed);
#[cfg(debug_assertions)]
if let Some(name) = self.name {
if elapsed > 1_000_000 {
eprintln!("slow operation '{}': {}μs", name, elapsed / 1000);
}
}
}
}
pub struct ScopedTimer {
histogram: Arc<Histogram>,
threshold_ns: u64,
}
impl ScopedTimer {
pub fn new(histogram: Arc<Histogram>) -> Self {
Self {
histogram,
threshold_ns: 1_000_000, }
}
pub fn with_threshold(mut self, threshold_ns: u64) -> Self {
self.threshold_ns = threshold_ns;
self
}
#[inline(always)]
pub fn time<F, R>(&self, f: F) -> R
where
F: FnOnce() -> R,
{
let start = TimePoint::now();
let result = f();
let elapsed = start.elapsed_ns();
self.histogram.record(elapsed);
#[cfg(debug_assertions)]
if elapsed > self.threshold_ns {
eprintln!("operation exceeded threshold: {}μs", elapsed / 1000);
}
result
}
#[inline(always)]
pub fn time_named<F, R>(&self, _name: &str, f: F) -> R
where
F: FnOnce() -> R,
{
let start = TimePoint::now();
let result = f();
let elapsed = start.elapsed_ns();
self.histogram.record(elapsed);
#[cfg(debug_assertions)]
if elapsed > self.threshold_ns {
eprintln!(
"operation '{}' exceeded threshold: {}μs",
_name,
elapsed / 1000
);
}
result
}
#[inline(always)]
pub fn guard(&self) -> TimerGuard {
TimerGuard::new(Arc::clone(&self.histogram))
}
#[inline(always)]
pub fn guard_named(&self, name: &'static str) -> TimerGuard {
TimerGuard::named(Arc::clone(&self.histogram), name)
}
}
pub mod global {
use super::*;
use std::collections::HashMap;
use std::sync::{OnceLock, RwLock};
static HISTOGRAMS: OnceLock<RwLock<HashMap<String, Arc<Histogram>>>> = OnceLock::new();
fn get_registry() -> &'static RwLock<HashMap<String, Arc<Histogram>>> {
HISTOGRAMS.get_or_init(|| RwLock::new(HashMap::new()))
}
pub fn histogram(name: &str) -> Arc<Histogram> {
{
let histograms = get_registry().read().unwrap();
if let Some(hist) = histograms.get(name) {
return Arc::clone(hist);
}
}
let mut histograms = get_registry().write().unwrap();
histograms
.entry(name.to_string())
.or_insert_with(|| Arc::new(Histogram::new()))
.clone()
}
pub fn report_all() {
let histograms = get_registry().read().unwrap();
for (name, hist) in histograms.iter() {
let stats = hist.stats();
if stats.count > 0 {
eprintln!("{}: {}", name, stats.format_micros());
}
}
}
pub fn clear_all() {
let histograms = get_registry().read().unwrap();
for hist in histograms.values() {
hist.clear();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_timer() {
let mut timer = Timer::start();
std::thread::sleep(std::time::Duration::from_millis(1));
let elapsed = timer.elapsed_ns();
assert!(elapsed > 1_000_000);
timer.restart();
let elapsed2 = timer.elapsed_ns();
assert!(elapsed2 < elapsed); }
#[test]
fn test_timer_guard() {
let histogram = Arc::new(Histogram::new());
{
let _guard = TimerGuard::new(Arc::clone(&histogram));
std::thread::sleep(std::time::Duration::from_millis(1));
}
assert_eq!(histogram.count(), 1);
assert!(histogram.max() > 1_000_000); }
#[test]
fn test_scoped_timer() {
let histogram = Arc::new(Histogram::new());
let timer = ScopedTimer::new(Arc::clone(&histogram));
let result = timer.time(|| {
std::thread::sleep(std::time::Duration::from_millis(1));
42
});
assert_eq!(result, 42);
assert_eq!(histogram.count(), 1);
}
}