#![doc = include_str!("../README.md")]
#![cfg_attr(
feature = "docsrs",
cfg_attr(doc, doc = ::document_features::document_features!())
)]
#![forbid(unsafe_code)]
#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
use std::fmt;
#[cfg(feature = "start-print-order")]
use indexmap::IndexMap as HashMap;
#[cfg(not(feature = "start-print-order"))]
use std::collections::HashMap;
#[cfg(not(feature = "minstant"))]
use std::time::{Duration, Instant};
#[cfg(feature = "minstant")]
use ::{minstant::Instant, std::time::Duration};
use tracing::Level;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum PrintOrder {
#[cfg(feature = "start-print-order")]
Start,
#[cfg(feature = "start-print-order")]
RevStart,
Key,
RevKey,
IncDuration,
DecDuration,
}
impl Default for PrintOrder {
#[cfg(feature = "start-print-order")]
fn default() -> Self {
Self::Start
}
#[cfg(not(feature = "start-print-order"))]
fn default() -> Self {
Self::DecDuration
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TimeReporterBuilder {
name: String,
level: Level,
print_order: PrintOrder,
width: usize,
precision: usize,
}
impl TimeReporterBuilder {
pub fn new<S: Into<String>>(name: S) -> Self {
Self {
name: name.into(),
level: Level::INFO,
print_order: PrintOrder::default(),
width: 11,
precision: 9,
}
}
#[must_use]
pub fn build(&self) -> TimeReporter {
TimeReporter {
times: HashMap::new(),
name: self.name.clone(),
cur_state_time: None,
level: self.level,
print_order: self.print_order,
width: self.width,
precision: self.precision,
}
}
pub fn level(&mut self, level: Level) -> &mut Self {
self.level = level;
self
}
pub fn print_order(&mut self, print_order: PrintOrder) -> &mut Self {
self.print_order = print_order;
self
}
pub fn width(&mut self, width: usize) -> &mut Self {
self.width = width;
self
}
pub fn precision(&mut self, precision: usize) -> &mut Self {
self.precision = precision;
self
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TimeReporter {
times: HashMap<&'static str, Duration>,
cur_state_time: Option<(&'static str, Instant)>,
name: String,
level: Level,
print_order: PrintOrder,
width: usize,
precision: usize,
}
impl TimeReporter {
pub fn new<S: Into<String>>(name: S) -> Self {
TimeReporterBuilder::new(name).build()
}
pub fn new_with_level<S: Into<String>>(name: S, level: Level) -> Self {
TimeReporterBuilder::new(name).level(level).build()
}
pub fn start(&mut self, key: &'static str) {
let now = Instant::now();
self.save_current(now);
self.cur_state_time = Some((key, now));
}
pub fn start_with<F, R>(&mut self, key: &'static str, f: F) -> R
where
F: FnOnce() -> R,
{
self.start(key);
f()
}
fn save_current(&mut self, now: Instant) {
if let Some((key, prev)) = self.cur_state_time.take() {
*self.times.entry(key).or_insert_with(|| Duration::new(0, 0)) += now - prev;
}
}
pub fn stop(&mut self) {
let now = Instant::now();
self.save_current(now);
}
#[allow(clippy::unused_self)]
pub fn finish(self) {}
}
fn get_times(
times: &HashMap<&'static str, Duration>,
print_order: PrintOrder,
) -> Vec<(&'static str, Duration)> {
cfg_if::cfg_if! {
if #[cfg(feature = "start-print-order")] {
if print_order == PrintOrder::RevStart {
times.iter().rev().map(|(&k, &v)| (k, v)).collect()
} else {
times.iter().map(|(&k, &v)| (k, v)).collect()
}
} else {
let _ = print_order;
times.iter().map(|(&k, &v)| (k, v)).collect()
}
}
}
impl<'a> fmt::Display for TimeReporter {
#[allow(clippy::cast_precision_loss)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut stats: Vec<(&'static str, Duration)> = get_times(&self.times, self.print_order);
match self.print_order {
#[cfg(feature = "start-print-order")]
PrintOrder::Start | PrintOrder::RevStart => {}
PrintOrder::Key => stats.sort_by_key(|s| s.0),
PrintOrder::RevKey => stats.sort_by(|a, b| b.0.cmp(a.0)),
PrintOrder::IncDuration => stats.sort_by_key(|s| s.1),
PrintOrder::DecDuration => stats.sort_by(|a, b| b.1.cmp(&a.1)),
}
write!(f, "name: {}", self.name)?;
let precision = self.precision;
let width = self.width;
for &(state, dur) in &stats {
let dur = dur.as_secs() as f64 + f64::from(dur.subsec_nanos()) / 1_000_000_000_f64;
write!(f, ", {}: {:<width$.precision$}", state, dur)?;
}
Ok(())
}
}
macro_rules! _span {
($lvl:expr, $($args:tt)*) => {{
match $lvl {
Level::ERROR => ::tracing::span!(Level::ERROR, $($args)*),
Level::WARN => ::tracing::span!(Level::WARN, $($args)*),
Level::INFO => ::tracing::span!(Level::INFO, $($args)*),
Level::DEBUG => ::tracing::span!(Level::DEBUG, $($args)*),
Level::TRACE => ::tracing::span!(Level::TRACE, $($args)*),
}
}};
}
macro_rules! _event {
(target: $target:expr, $lvl:expr, $($args:tt)*) => {{
match $lvl {
Level::ERROR => ::tracing::event!(target: $target, Level::ERROR, $($args)*),
Level::WARN => ::tracing::event!(target: $target, Level::WARN, $($args)*),
Level::INFO => ::tracing::event!(target: $target, Level::INFO, $($args)*),
Level::DEBUG => ::tracing::event!(target: $target, Level::DEBUG, $($args)*),
Level::TRACE => ::tracing::event!(target: $target, Level::TRACE, $($args)*),
}
}};
}
impl Drop for TimeReporter {
fn drop(&mut self) {
let _span = _span!(self.level, "time-report").entered();
_event!(target: "tracing-perf", self.level, "{}", self);
}
}