#[cfg(test)]
mod tests;
#[cfg(feature = "timer")]
use core::{fmt, time::Duration};
#[cfg(feature = "timer")]
use std::{
cell::RefCell,
rc::Rc,
sync::atomic::{AtomicBool, AtomicUsize, Ordering},
time::Instant,
};
#[cfg(feature = "timer")]
use colored::{ColoredString, Colorize};
#[cfg(feature = "timer")]
pub static NUM_INDENT: AtomicUsize = AtomicUsize::new(0);
#[cfg(feature = "timer")]
pub const PAD_CHAR: &str = " ";
#[cfg(feature = "timer")]
pub struct Timer<'name> {
start_time: Instant,
last_lap_time: Rc<RefCell<Instant>>,
#[allow(dead_code)]
module_path: &'static str,
file: &'static str,
line: u32,
name: &'name str,
indent: usize,
finished: AtomicBool,
extra_info: Option<String>,
}
#[cfg(not(feature = "timer"))]
#[derive(Default)]
pub struct Timer<'name> {
_marker: std::marker::PhantomData<&'name ()>,
}
impl<'name> Timer<'name> {
#[cfg(feature = "timer")]
pub fn new(
file: &'static str,
module_path: &'static str,
line: u32,
name: &'name str,
extra_info: Option<String>,
) -> Option<Self> {
let start_time = Instant::now();
let timer = Timer {
start_time,
last_lap_time: Rc::new(RefCell::new(start_time)),
module_path,
file,
line,
name,
indent: NUM_INDENT.fetch_add(0, Ordering::Relaxed),
finished: AtomicBool::new(false),
extra_info,
};
timer.print(TimerState::Start, None);
NUM_INDENT.fetch_add(1, Ordering::Relaxed);
Some(timer)
}
#[cfg(not(feature = "timer"))]
pub fn noop(&self) {}
#[cfg(feature = "timer")]
pub fn elapsed(&self, elapsed: Duration) -> String {
let secs = elapsed.as_secs();
let millis = elapsed.subsec_millis();
let micros = elapsed.subsec_micros() % 1000;
let nanos = elapsed.subsec_nanos() % 1000;
if secs != 0 {
format!("{}.{:0>3}s", secs, millis)
} else if millis > 0 {
format!("{}.{:0>3}ms", millis, micros)
} else if micros > 0 {
format!("{}.{:0>3}µs", micros, nanos)
} else {
format!("{}ns", elapsed.subsec_nanos())
}
}
#[cfg(feature = "timer")]
pub fn elapsed_colored(&self, elapsed: Duration) -> ColoredString {
let secs = elapsed.as_secs();
let millis = elapsed.subsec_millis();
let micros = elapsed.subsec_micros() % 1000;
let nanos = elapsed.subsec_nanos() % 1000;
if secs != 0 {
format!("{}.{:0>3}s", secs, millis).magenta().bold()
} else if millis > 0 {
format!("{}.{:0>3}ms", millis, micros).yellow().bold()
} else if micros > 0 {
format!("{}.{:0>3}µs", micros, nanos).cyan().bold()
} else {
format!("{}ns", elapsed.subsec_nanos()).green().bold()
}
}
#[cfg(feature = "timer")]
pub fn lap(&self, args: Option<fmt::Arguments>) {
self.print(TimerState::Lap, args);
}
#[cfg(feature = "timer")]
pub fn finish(&self, args: Option<fmt::Arguments>) {
if !self.finished.load(Ordering::SeqCst) {
NUM_INDENT.fetch_sub(1, Ordering::Relaxed);
self.finished.store(true, Ordering::SeqCst);
self.print(TimerState::Finish, args);
}
}
#[cfg(feature = "timer")]
fn print(&self, state: TimerState, args: Option<fmt::Arguments>) {
println!("{}", self.format(state, args));
}
#[cfg(feature = "timer")]
fn format(&self, status: TimerState, args: Option<fmt::Arguments>) -> String {
let user_message = match (self.extra_info.as_ref(), args) {
(Some(info), Some(args)) => format!("{}, {}, {}", self.name, info, args),
(Some(info), None) => format!("{}, {}", self.name, info),
(None, Some(args)) => format!("{}, {}", self.name, args),
(None, None) => format!("{}", self.name),
};
match status {
TimerState::Start => {
let indentation_amount = self.indent * 4;
let mut indentation = String::new();
for _ in 0..indentation_amount {
indentation.push_str(&PAD_CHAR);
}
let message = format!("{} ({})", Self::status(status, self.indent), user_message);
let metadata = format!(" [{} L{}]", self.file, self.line).bold();
format!(" {indentation}{:<30} {:.>55}", message, metadata)
}
TimerState::Lap => {
let indentation_amount = (self.indent + 1) * 4;
let mut indentation = String::new();
for _ in 0..indentation_amount {
indentation.push_str(&PAD_CHAR);
}
let message = format!("{} ({})", Self::status(status, self.indent + 1), user_message);
let elapsed = self.elapsed_colored(self.last_lap_time.borrow().elapsed());
*(*self.last_lap_time).borrow_mut() = Instant::now();
format!(" {indentation}{:<30} {:.>55}", message, elapsed)
}
TimerState::Finish => {
let indentation_amount = self.indent * 4;
let mut indentation = String::new();
for _ in 0..indentation_amount {
indentation.push_str(&PAD_CHAR);
}
let message = format!("{} ({})", Self::status(status, self.indent), user_message);
let elapsed = self.elapsed(self.start_time.elapsed());
format!(" {indentation}{:<50} {:.>25}", message, elapsed)
}
}
}
#[cfg(feature = "timer")]
fn status(status: TimerState, indent: usize) -> ColoredString {
let status = match status {
TimerState::Start => "Start",
TimerState::Lap => "Lap",
TimerState::Finish => "Finish",
};
match indent % 5 {
0 => Colorize::green(status).bold(),
1 => Colorize::cyan(status).bold(),
2 => Colorize::yellow(status).bold(),
3 => Colorize::magenta(status).bold(),
4 => Colorize::red(status).bold(),
_ => Colorize::white(status).bold(),
}
}
}
#[cfg(feature = "timer")]
impl<'a> Drop for Timer<'a> {
fn drop(&mut self) {
self.finish(None);
}
}
#[cfg(feature = "timer")]
#[derive(Debug, Copy, Clone)]
enum TimerState {
Start,
Lap,
Finish,
}
#[cfg(feature = "timer")]
#[macro_export]
macro_rules! timer {
($name:expr) => {
{
$crate::Timer::new(
file!(),
module_path!(),
line!(),
$name,
None,
)
}
};
($name:expr, $format:tt) => {
{
$crate::Timer::new(
file!(),
module_path!(),
line!(),
$name,
Some(format!($format)),
)
}
};
($name:expr, $format:tt, $($arg:expr),*) => {
{
$crate::Timer::new(
file!(),
module_path!(),
line!(),
$name,
Some(format!($format, $($arg), *)),
)
}
};
}
#[cfg(feature = "timer")]
#[macro_export]
macro_rules! lap {
($timer:expr) => ({
if let Some(ref timer) = $timer {
timer.lap(None);
}
});
($timer:expr, $format:tt) => ({
if let Some(ref timer) = $timer {
timer.lap(Some(format_args!($format)))
}
});
($timer:expr, $format:tt, $($arg:expr),*) => ({
if let Some(ref timer) = $timer {
timer.lap(Some(format_args!($format, $($arg), *)))
}
})
}
#[cfg(feature = "timer")]
#[macro_export]
macro_rules! finish {
($timer:expr) => ({
if let Some(ref timer) = $timer {
timer.finish(None)
}
});
($timer:expr, $format:tt) => ({
if let Some(ref timer) = $timer {
timer.finish(Some(format_args!($format)))
}
});
($timer:expr, $format:tt, $($arg:expr),*) => ({
if let Some(ref timer) = $timer {
timer.finish(Some(format_args!($format, $($arg), *)))
}
})
}
#[cfg(not(feature = "timer"))]
#[macro_export]
macro_rules! timer {
($name:expr) => {
$crate::Timer::default()
};
($name:expr, $format:tt) => {
$crate::Timer::default()
};
($name:expr, $format:tt, $($arg:expr),*) => {
$crate::Timer::default()
};
}
#[cfg(not(feature = "timer"))]
#[macro_export]
macro_rules! lap {
($timer:expr) => {
$timer.noop()
};
($timer:expr, $format:tt) => {
$timer.noop()
};
($timer:expr, $format:tt, $($arg:expr),*) => {
$timer.noop()
};
}
#[cfg(not(feature = "timer"))]
#[macro_export]
macro_rules! finish {
($timer:expr) => {
$timer.noop()
};
($timer:expr, $format:tt) => {
$timer.noop()
};
($timer:expr, $format:tt, $($arg:expr),*) => {
$timer.noop()
};
}