Skip to main content

ark_std/
perf_trace.rs

1#![allow(unused_imports)]
2//! This module contains macros for logging to stdout a trace of wall-clock time
3//! required 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
17//! apparent 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    #[macro_export]
163    macro_rules! add_single_trace {
164        ($title:expr) => {{
165            use $crate::perf_trace::{
166                compute_indent, compute_indent_whitespace, format, AtomicUsize, Colorize, Instant,
167                Ordering, ToString, NUM_INDENT, PAD_CHAR,
168            };
169
170            let start_msg = "Trace".blue().bold();
171            let title = $title();
172            let start_msg = format!("{}:   {}", start_msg, title);
173
174            let indent_amount = 2 * NUM_INDENT.fetch_add(0, Ordering::Relaxed);
175            let indent = compute_indent(indent_amount);
176
177            // Todo: Recursively ensure that *entire* string is of appropriate
178            // width (not just message).
179            $crate::perf_trace::println!("{}{}", indent, start_msg);
180        }};
181    }
182
183    pub fn compute_indent_whitespace(indent_amount: usize) -> String {
184        let mut indent = String::new();
185        for _ in 0..indent_amount {
186            indent.push_str(" ");
187        }
188        indent
189    }
190
191    pub fn compute_indent(indent_amount: usize) -> String {
192        let mut indent = String::new();
193        for _ in 0..indent_amount {
194            indent.push_str(&PAD_CHAR.white());
195        }
196        indent
197    }
198}
199
200#[macro_use]
201#[cfg(not(feature = "print-trace"))]
202mod inner {
203    pub struct TimerInfo;
204
205    #[macro_export]
206    macro_rules! start_timer {
207        ($msg:expr) => {{
208            let _ = $msg;
209            $crate::perf_trace::TimerInfo
210        }};
211    }
212    #[macro_export]
213    macro_rules! add_to_trace {
214        ($title:expr, $msg:expr) => {
215            let _ = $msg;
216            let _ = $title;
217        };
218    }
219    #[macro_export]
220    macro_rules! add_single_trace {
221        ($msg:expr) => {{
222            let _ = $msg;
223            $crate::perf_trace::TimerInfo
224        }};
225    }
226    #[macro_export]
227    macro_rules! end_timer {
228        ($time:expr, $msg:expr) => {
229            let _ = $msg;
230            let _ = $time;
231        };
232        ($time:expr) => {
233            let _ = $time;
234        };
235    }
236}
237
238mod tests {
239    use super::*;
240
241    #[test]
242    fn print_start_end() {
243        let start = start_timer!(|| "Hello");
244        end_timer!(start);
245    }
246
247    #[test]
248    fn print_add() {
249        add_single_trace!(|| "Hello");
250        let start = start_timer!(|| "Hello");
251        add_single_trace!(|| "HelloWorld");
252        add_to_trace!(|| "HelloMsg", || "Hello, I\nAm\nA\nMessage");
253        end_timer!(start);
254    }
255}