1#[cfg(test)]
22mod tests;
23
24#[cfg(feature = "timer")]
25use core::{fmt, time::Duration};
26#[cfg(feature = "timer")]
27use std::{
28 cell::RefCell,
29 rc::Rc,
30 sync::atomic::{AtomicBool, AtomicUsize, Ordering},
31 time::Instant,
32};
33
34#[cfg(feature = "timer")]
35use colored::{ColoredString, Colorize};
36
37#[cfg(feature = "timer")]
38pub static NUM_INDENT: AtomicUsize = AtomicUsize::new(0);
39#[cfg(feature = "timer")]
40pub const PAD_CHAR: &str = " ";
41
42#[cfg(feature = "timer")]
45pub struct Timer<'name> {
46 start_time: Instant,
48 last_lap_time: Rc<RefCell<Instant>>,
50 #[allow(dead_code)]
52 module_path: &'static str,
53 file: &'static str,
55 line: u32,
57 name: &'name str,
59 indent: usize,
61 finished: AtomicBool,
64 extra_info: Option<String>,
68}
69
70#[cfg(not(feature = "timer"))]
71#[derive(Default)]
72pub struct Timer<'name> {
73 _marker: std::marker::PhantomData<&'name ()>,
74}
75
76impl<'name> Timer<'name> {
77 #[cfg(feature = "timer")]
80 pub fn new(
81 file: &'static str,
82 module_path: &'static str,
83 line: u32,
84 name: &'name str,
85 extra_info: Option<String>,
86 ) -> Option<Self> {
87 let start_time = Instant::now();
88 let timer = Timer {
89 start_time,
90 last_lap_time: Rc::new(RefCell::new(start_time)),
91 module_path,
92 file,
93 line,
94 name,
95 indent: NUM_INDENT.fetch_add(0, Ordering::Relaxed),
96 finished: AtomicBool::new(false),
97 extra_info,
98 };
99 timer.print(TimerState::Start, None);
101 NUM_INDENT.fetch_add(1, Ordering::Relaxed);
103 Some(timer)
104 }
105
106 #[cfg(not(feature = "timer"))]
107 pub fn noop(&self) {}
108
109 #[cfg(feature = "timer")]
111 pub fn elapsed(&self, elapsed: Duration) -> String {
112 let secs = elapsed.as_secs();
113 let millis = elapsed.subsec_millis();
114 let micros = elapsed.subsec_micros() % 1000;
115 let nanos = elapsed.subsec_nanos() % 1000;
116 if secs != 0 {
117 format!("{}.{:0>3}s", secs, millis)
118 } else if millis > 0 {
119 format!("{}.{:0>3}ms", millis, micros)
120 } else if micros > 0 {
121 format!("{}.{:0>3}µs", micros, nanos)
122 } else {
123 format!("{}ns", elapsed.subsec_nanos())
124 }
125 }
126
127 #[cfg(feature = "timer")]
129 pub fn elapsed_colored(&self, elapsed: Duration) -> ColoredString {
130 let secs = elapsed.as_secs();
131 let millis = elapsed.subsec_millis();
132 let micros = elapsed.subsec_micros() % 1000;
133 let nanos = elapsed.subsec_nanos() % 1000;
134 if secs != 0 {
135 format!("{}.{:0>3}s", secs, millis).magenta().bold()
136 } else if millis > 0 {
137 format!("{}.{:0>3}ms", millis, micros).yellow().bold()
138 } else if micros > 0 {
139 format!("{}.{:0>3}µs", micros, nanos).cyan().bold()
140 } else {
141 format!("{}ns", elapsed.subsec_nanos()).green().bold()
142 }
143 }
144
145 #[cfg(feature = "timer")]
150 pub fn lap(&self, args: Option<fmt::Arguments>) {
151 self.print(TimerState::Lap, args);
152 }
153
154 #[cfg(feature = "timer")]
159 pub fn finish(&self, args: Option<fmt::Arguments>) {
160 if !self.finished.load(Ordering::SeqCst) {
161 NUM_INDENT.fetch_sub(1, Ordering::Relaxed);
163 self.finished.store(true, Ordering::SeqCst);
164 self.print(TimerState::Finish, args);
165 }
166 }
167
168 #[cfg(feature = "timer")]
169 fn print(&self, state: TimerState, args: Option<fmt::Arguments>) {
170 println!("{}", self.format(state, args));
171 }
172
173 #[cfg(feature = "timer")]
174 fn format(&self, status: TimerState, args: Option<fmt::Arguments>) -> String {
175 let user_message = match (self.extra_info.as_ref(), args) {
177 (Some(info), Some(args)) => format!("{}, {}, {}", self.name, info, args),
178 (Some(info), None) => format!("{}, {}", self.name, info),
179 (None, Some(args)) => format!("{}, {}", self.name, args),
180 (None, None) => format!("{}", self.name),
181 };
182
183 match status {
185 TimerState::Start => {
186 let indentation_amount = self.indent * 4;
188 let mut indentation = String::new();
189 for _ in 0..indentation_amount {
190 indentation.push_str(&PAD_CHAR);
191 }
192
193 let message = format!("{} ({})", Self::status(status, self.indent), user_message);
194 let metadata = format!(" [{} L{}]", self.file, self.line).bold();
196
197 format!(" {indentation}{:<30} {:.>55}", message, metadata)
198 }
199 TimerState::Lap => {
200 let indentation_amount = (self.indent + 1) * 4;
202 let mut indentation = String::new();
203 for _ in 0..indentation_amount {
204 indentation.push_str(&PAD_CHAR);
205 }
206
207 let message = format!("{} ({})", Self::status(status, self.indent + 1), user_message);
208 let elapsed = self.elapsed_colored(self.last_lap_time.borrow().elapsed());
209
210 *(*self.last_lap_time).borrow_mut() = Instant::now();
212
213 format!(" {indentation}{:<30} {:.>55}", message, elapsed)
214 }
215 TimerState::Finish => {
216 let indentation_amount = self.indent * 4;
218 let mut indentation = String::new();
219 for _ in 0..indentation_amount {
220 indentation.push_str(&PAD_CHAR);
221 }
222
223 let message = format!("{} ({})", Self::status(status, self.indent), user_message);
224 let elapsed = self.elapsed(self.start_time.elapsed());
225
226 format!(" {indentation}{:<50} {:.>25}", message, elapsed)
227 }
228 }
229 }
230
231 #[cfg(feature = "timer")]
233 fn status(status: TimerState, indent: usize) -> ColoredString {
234 let status = match status {
235 TimerState::Start => "Start",
236 TimerState::Lap => "Lap",
237 TimerState::Finish => "Finish",
238 };
239
240 match indent % 5 {
241 0 => Colorize::green(status).bold(),
242 1 => Colorize::cyan(status).bold(),
243 2 => Colorize::yellow(status).bold(),
244 3 => Colorize::magenta(status).bold(),
245 4 => Colorize::red(status).bold(),
246 _ => Colorize::white(status).bold(),
247 }
248 }
249}
250
251#[cfg(feature = "timer")]
252impl<'a> Drop for Timer<'a> {
253 fn drop(&mut self) {
256 self.finish(None);
257 }
258}
259
260#[cfg(feature = "timer")]
261#[derive(Debug, Copy, Clone)]
262enum TimerState {
263 Start,
264 Lap,
265 Finish,
266}
267
268#[cfg(feature = "timer")]
281#[macro_export]
282macro_rules! timer {
283 ($name:expr) => {
284 {
285 $crate::Timer::new(
286 file!(),
287 module_path!(),
288 line!(),
289 $name,
290 None,
291 )
292 }
293 };
294
295 ($name:expr, $format:tt) => {
296 {
297 $crate::Timer::new(
298 file!(),
299 module_path!(),
300 line!(),
301 $name,
302 Some(format!($format)),
303 )
304 }
305 };
306
307 ($name:expr, $format:tt, $($arg:expr),*) => {
308 {
309 $crate::Timer::new(
310 file!(),
311 module_path!(),
312 line!(),
313 $name,
314 Some(format!($format, $($arg), *)),
315 )
316 }
317 };
318}
319
320#[cfg(feature = "timer")]
323#[macro_export]
324macro_rules! lap {
325 ($timer:expr) => ({
326 if let Some(ref timer) = $timer {
327 timer.lap(None);
328 }
329 });
330
331 ($timer:expr, $format:tt) => ({
332 if let Some(ref timer) = $timer {
333 timer.lap(Some(format_args!($format)))
334 }
335 });
336
337 ($timer:expr, $format:tt, $($arg:expr),*) => ({
338 if let Some(ref timer) = $timer {
339 timer.lap(Some(format_args!($format, $($arg), *)))
340 }
341 })
342}
343
344#[cfg(feature = "timer")]
348#[macro_export]
349macro_rules! finish {
350 ($timer:expr) => ({
351 if let Some(ref timer) = $timer {
352 timer.finish(None)
353 }
354 });
355
356 ($timer:expr, $format:tt) => ({
357 if let Some(ref timer) = $timer {
358 timer.finish(Some(format_args!($format)))
359 }
360 });
361
362 ($timer:expr, $format:tt, $($arg:expr),*) => ({
363 if let Some(ref timer) = $timer {
364 timer.finish(Some(format_args!($format, $($arg), *)))
365 }
366 })
367}
368
369#[cfg(not(feature = "timer"))]
370#[macro_export]
371macro_rules! timer {
372 ($name:expr) => {
373 $crate::Timer::default()
374 };
375
376 ($name:expr, $format:tt) => {
377 $crate::Timer::default()
378 };
379
380 ($name:expr, $format:tt, $($arg:expr),*) => {
381 $crate::Timer::default()
382 };
383}
384
385#[cfg(not(feature = "timer"))]
386#[macro_export]
387macro_rules! lap {
388 ($timer:expr) => {
389 $timer.noop()
390 };
391
392 ($timer:expr, $format:tt) => {
393 $timer.noop()
394 };
395
396 ($timer:expr, $format:tt, $($arg:expr),*) => {
397 $timer.noop()
398 };
399}
400
401#[cfg(not(feature = "timer"))]
402#[macro_export]
403macro_rules! finish {
404 ($timer:expr) => {
405 $timer.noop()
406 };
407
408 ($timer:expr, $format:tt) => {
409 $timer.noop()
410 };
411
412 ($timer:expr, $format:tt, $($arg:expr),*) => {
413 $timer.noop()
414 };
415}