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