Skip to main content

aleo_std_timer/
lib.rs

1// Copyright (c) 2019-2026 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16// With credits to PhilipDaniels/logging_timer.
17
18//! This crate implements a straightforward timer to conveniently time code blocks.
19
20#[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/// When this struct is dropped, it logs a message stating its name and how long
42/// the execution time was. Can be used to time functions or other critical areas.
43#[cfg(feature = "timer")]
44pub struct Timer<'name> {
45    /// The instant, in UTC, that the timer was instantiated.
46    start_time: Instant,
47    /// The last lap time, in UTC, that the timer was called.
48    last_lap_time: Rc<RefCell<Instant>>,
49    /// Set by the module_path!() macro to the module where the timer is instantiated.
50    #[allow(dead_code)]
51    module_path: &'static str,
52    /// Set by the file!() macro to the name of the file where the timer is instantiated.
53    file: &'static str,
54    /// Set by the line!() macro to the line number where the timer is instantiated.
55    line: u32,
56    /// The name of the timer. Used in messages to identify it.
57    name: &'name str,
58    /// The level of indentation for this timer context.
59    indent: usize,
60    /// A flag used to suppress printing of the 'Finish' message in the drop() function
61    /// It is set by the finish method.
62    finished: AtomicBool,
63    /// Any extra information to be logged along with the name. Unfortunately, due
64    /// to the lifetimes associated with a `format_args!` invocation, this currently allocates
65    /// if you use it.
66    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    /// Constructs a new `Timer` that prints a 'Start' and a 'Finish' message.
77    /// This method is not usually called directly, use the `timer!` macro instead.
78    #[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        // Print the start message.
99        timer.print(TimerState::Start, None);
100        // Increment the indentation by 1.
101        NUM_INDENT.fetch_add(1, Ordering::Relaxed);
102        Some(timer)
103    }
104
105    #[cfg(not(feature = "timer"))]
106    pub fn noop(&self) {}
107
108    /// Returns how long the timer has been running for.
109    #[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    /// Returns how long the timer has been running for.
127    #[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    /// Outputs a log message with a target of 'Lap' showing the current elapsed time, but does not
145    /// stop the timer. This method can be called multiple times.
146    /// The message can include further information via a `format_args!` approach.
147    /// This method is usually not called directly, it is easier to use the `lap!` macro.
148    #[cfg(feature = "timer")]
149    pub fn lap(&self, args: Option<fmt::Arguments>) {
150        self.print(TimerState::Lap, args);
151    }
152
153    /// Outputs a log message with a target of 'Finish' and suppresses the normal message
154    /// that is output when the timer is dropped. The message can include further `format_args!`
155    /// information. This method is normally called using the `finish!` macro. Calling
156    /// `finish()` again will have no effect.
157    #[cfg(feature = "timer")]
158    pub fn finish(&self, args: Option<fmt::Arguments>) {
159        if !self.finished.load(Ordering::SeqCst) {
160            // Decrement the indentation by 1.
161            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        // Construct the user message.
175        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        // Construct the main message.
183        match status {
184            TimerState::Start => {
185                // Compute the indentation.
186                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.module_path, self.file, self.line).bold();
194                let metadata = format!(" [{} L{}]", self.file, self.line).bold();
195
196                format!(" {indentation}{:<30} {:.>55}", message, metadata)
197            }
198            TimerState::Lap => {
199                // Compute the indentation.
200                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                // Update the last lap time.
210                *(*self.last_lap_time).borrow_mut() = Instant::now();
211
212                format!(" {indentation}{:<30} {:.>55}", message, elapsed)
213            }
214            TimerState::Finish => {
215                // Compute the indentation.
216                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    /// Returns the state of the timer, with coloring.
231    #[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    /// Drops the timer, outputting a log message with a target of `Finish`
253    /// if the `finish` method has not yet been called.
254    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/// Initializes a timer that logs a start and finish message.
268///
269/// # Examples
270/// Note that when specifying the log level you must use a semi-colon as a
271/// separator, this is to ensure disambiguous parsing of the macro arguments.
272///
273/// ```
274/// use aleo_std_timer::timer;
275///
276/// let _tmr1 = timer!("FIND_FILES");
277/// let _tmr2 = timer!("FIND_FILES", "Found {} files", 42);
278/// ```
279#[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/// Makes an existing timer output an 'lap' message.
320/// Can be called multiple times.
321#[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/// Makes an existing timer output a 'finished' message and suppresses
344/// the normal drop message.
345/// Only the first call has any effect, subsequent calls will be ignored.
346#[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}