ark_std/
perf_trace.rs

1#![allow(unused_imports)]
2//! This module contains macros for logging to stdout a trace of wall-clock time required
3//! to execute annotated code. One can use this code as follows:
4//! ```
5//! use ark_std::{start_timer, end_timer};
6//! let start = start_timer!(|| "Addition of two integers");
7//! let c = 5 + 7;
8//! end_timer!(start);
9//! ```
10//! The foregoing code should log the following to stdout.
11//! ```text
12//! Start: Addition of two integers
13//! End: Addition of two integers... 1ns
14//! ```
15//!
16//! These macros can be arbitrarily nested, and the nested nature is made apparent
17//! in the output. For example, the following snippet:
18//! ```
19//! use ark_std::{start_timer, end_timer};
20//! let start = start_timer!(|| "Addition of two integers");
21//! let start2 = start_timer!(|| "Inner");
22//! let c = 5 + 7;
23//! end_timer!(start2);
24//! end_timer!(start);
25//! ```
26//! should print out the following:
27//! ```text
28//! Start: Addition of two integers
29//!     Start: Inner
30//!     End: Inner               ... 1ns
31//! End: Addition of two integers... 1ns
32//! ```
33//!
34//! Additionally, one can use the `add_to_trace` macro to log additional context
35//! in the output.
36pub use self::inner::*;
37
38#[macro_use]
39#[cfg(feature = "print-trace")]
40pub mod inner {
41    pub use colored::Colorize;
42
43    // print-trace requires std, so these imports are well-defined
44    pub use std::{
45        format, println,
46        string::{String, ToString},
47        sync::atomic::{AtomicUsize, Ordering},
48        time::Instant,
49    };
50
51    pub static NUM_INDENT: AtomicUsize = AtomicUsize::new(0);
52    pub const PAD_CHAR: &str = "·";
53
54    pub struct TimerInfo {
55        pub msg: String,
56        pub time: Instant,
57    }
58
59    #[macro_export]
60    macro_rules! start_timer {
61        ($msg:expr) => {{
62            use $crate::perf_trace::inner::{
63                compute_indent, AtomicUsize, Colorize, Instant, Ordering, ToString, NUM_INDENT,
64                PAD_CHAR,
65            };
66
67            let msg = $msg();
68            let start_info = "Start:".yellow().bold();
69            let indent_amount = 2 * NUM_INDENT.fetch_add(0, Ordering::Relaxed);
70            let indent = compute_indent(indent_amount);
71
72            $crate::perf_trace::println!("{}{:8} {}", indent, start_info, msg);
73            NUM_INDENT.fetch_add(1, Ordering::Relaxed);
74            $crate::perf_trace::TimerInfo {
75                msg: msg.to_string(),
76                time: Instant::now(),
77            }
78        }};
79    }
80
81    #[macro_export]
82    macro_rules! end_timer {
83        ($time:expr) => {{
84            end_timer!($time, || "");
85        }};
86        ($time:expr, $msg:expr) => {{
87            use $crate::perf_trace::inner::{
88                compute_indent, format, AtomicUsize, Colorize, Instant, Ordering, ToString,
89                NUM_INDENT, PAD_CHAR,
90            };
91
92            let time = $time.time;
93            let final_time = time.elapsed();
94            let final_time = {
95                let secs = final_time.as_secs();
96                let millis = final_time.subsec_millis();
97                let micros = final_time.subsec_micros() % 1000;
98                let nanos = final_time.subsec_nanos() % 1000;
99                if secs != 0 {
100                    format!("{}.{:03}s", secs, millis).bold()
101                } else if millis > 0 {
102                    format!("{}.{:03}ms", millis, micros).bold()
103                } else if micros > 0 {
104                    format!("{}.{:03}µs", micros, nanos).bold()
105                } else {
106                    format!("{}ns", final_time.subsec_nanos()).bold()
107                }
108            };
109
110            let end_info = "End:".green().bold();
111            let message = format!("{} {}", $time.msg, $msg());
112
113            NUM_INDENT.fetch_sub(1, Ordering::Relaxed);
114            let indent_amount = 2 * NUM_INDENT.fetch_add(0, Ordering::Relaxed);
115            let indent = compute_indent(indent_amount);
116
117            // Todo: Recursively ensure that *entire* string is of appropriate
118            // width (not just message).
119            $crate::perf_trace::println!(
120                "{}{:8} {:.<pad$}{}",
121                indent,
122                end_info,
123                message,
124                final_time,
125                pad = 75 - indent_amount
126            );
127        }};
128    }
129
130    #[macro_export]
131    macro_rules! add_to_trace {
132        ($title:expr, $msg:expr) => {{
133            use $crate::perf_trace::{
134                compute_indent, compute_indent_whitespace, format, AtomicUsize, Colorize, Instant,
135                Ordering, ToString, NUM_INDENT, PAD_CHAR,
136            };
137
138            let start_msg = "StartMsg".yellow().bold();
139            let end_msg = "EndMsg".green().bold();
140            let title = $title();
141            let start_msg = format!("{}: {}", start_msg, title);
142            let end_msg = format!("{}: {}", end_msg, title);
143
144            let start_indent_amount = 2 * NUM_INDENT.fetch_add(0, Ordering::Relaxed);
145            let start_indent = compute_indent(start_indent_amount);
146
147            let msg_indent_amount = 2 * NUM_INDENT.fetch_add(0, Ordering::Relaxed) + 2;
148            let msg_indent = compute_indent_whitespace(msg_indent_amount);
149            let mut final_message = "\n".to_string();
150            for line in $msg().lines() {
151                final_message += &format!("{}{}\n", msg_indent, line,);
152            }
153
154            // Todo: Recursively ensure that *entire* string is of appropriate
155            // width (not just message).
156            $crate::perf_trace::println!("{}{}", start_indent, start_msg);
157            $crate::perf_trace::println!("{}{}", msg_indent, final_message,);
158            $crate::perf_trace::println!("{}{}", start_indent, end_msg);
159        }};
160    }
161
162    pub fn compute_indent_whitespace(indent_amount: usize) -> String {
163        let mut indent = String::new();
164        for _ in 0..indent_amount {
165            indent.push_str(" ");
166        }
167        indent
168    }
169
170    pub fn compute_indent(indent_amount: usize) -> String {
171        let mut indent = String::new();
172        for _ in 0..indent_amount {
173            indent.push_str(&PAD_CHAR.white());
174        }
175        indent
176    }
177}
178
179#[macro_use]
180#[cfg(not(feature = "print-trace"))]
181mod inner {
182    pub struct TimerInfo;
183
184    #[macro_export]
185    macro_rules! start_timer {
186        ($msg:expr) => {
187            $crate::perf_trace::TimerInfo
188        };
189    }
190    #[macro_export]
191    macro_rules! add_to_trace {
192        ($title:expr, $msg:expr) => {
193            let _ = $msg;
194        };
195    }
196
197    #[macro_export]
198    macro_rules! end_timer {
199        ($time:expr, $msg:expr) => {
200            let _ = $msg;
201            let _ = $time;
202        };
203        ($time:expr) => {
204            let _ = $time;
205        };
206    }
207}
208
209mod tests {
210    use super::*;
211
212    #[test]
213    fn print_start_end() {
214        let start = start_timer!(|| "Hello");
215        end_timer!(start);
216    }
217
218    #[test]
219    fn print_add() {
220        let start = start_timer!(|| "Hello");
221        add_to_trace!(|| "HelloMsg", || "Hello, I\nAm\nA\nMessage");
222        end_timer!(start);
223    }
224}