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(1, Ordering::Relaxed);
70            let indent = compute_indent(indent_amount);
71
72            $crate::perf_trace::println!("{}{:8} {}", indent, start_info, msg);
73            $crate::perf_trace::TimerInfo {
74                msg: msg.to_string(),
75                time: Instant::now(),
76            }
77        }};
78    }
79
80    #[macro_export]
81    macro_rules! end_timer {
82        ($time:expr) => {{
83            end_timer!($time, || "");
84        }};
85        ($time:expr, $msg:expr) => {{
86            use $crate::perf_trace::inner::{
87                compute_indent, format, AtomicUsize, Colorize, Instant, Ordering, ToString,
88                NUM_INDENT, PAD_CHAR,
89            };
90
91            let time = $time.time;
92            let final_time = time.elapsed();
93            let final_time = {
94                let secs = final_time.as_secs();
95                let millis = final_time.subsec_millis();
96                let micros = final_time.subsec_micros() % 1000;
97                let nanos = final_time.subsec_nanos() % 1000;
98                if secs != 0 {
99                    format!("{}.{:03}s", secs, millis).bold()
100                } else if millis > 0 {
101                    format!("{}.{:03}ms", millis, micros).bold()
102                } else if micros > 0 {
103                    format!("{}.{:03}µs", micros, nanos).bold()
104                } else {
105                    format!("{}ns", final_time.subsec_nanos()).bold()
106                }
107            };
108
109            let end_info = "End:".green().bold();
110            let message = format!("{} {}", $time.msg, $msg());
111
112            let indent_amount = 2 * NUM_INDENT.fetch_sub(1, Ordering::Relaxed);
113            let indent = compute_indent(indent_amount);
114
115            // Todo: Recursively ensure that *entire* string is of appropriate
116            // width (not just message).
117            $crate::perf_trace::println!(
118                "{}{:8} {:.<pad$}{}",
119                indent,
120                end_info,
121                message,
122                final_time,
123                pad = 75 - indent_amount
124            );
125        }};
126    }
127
128    #[macro_export]
129    macro_rules! add_to_trace {
130        ($title:expr, $msg:expr) => {{
131            use $crate::perf_trace::{
132                compute_indent, compute_indent_whitespace, format, AtomicUsize, Colorize, Instant,
133                Ordering, ToString, NUM_INDENT, PAD_CHAR,
134            };
135
136            let start_msg = "StartMsg".yellow().bold();
137            let end_msg = "EndMsg".green().bold();
138            let title = $title();
139            let start_msg = format!("{}: {}", start_msg, title);
140            let end_msg = format!("{}: {}", end_msg, title);
141
142            let start_indent_amount = 2 * NUM_INDENT.fetch_add(0, Ordering::Relaxed);
143            let start_indent = compute_indent(start_indent_amount);
144
145            let msg_indent_amount = start_indent_amount + 2;
146            let msg_indent = compute_indent_whitespace(msg_indent_amount);
147            let mut final_message = "\n".to_string();
148            for line in $msg().lines() {
149                final_message += &format!("{}{}\n", msg_indent, line,);
150            }
151
152            // Todo: Recursively ensure that *entire* string is of appropriate
153            // width (not just message).
154            $crate::perf_trace::println!("{}{}", start_indent, start_msg);
155            $crate::perf_trace::println!("{}{}", msg_indent, final_message,);
156            $crate::perf_trace::println!("{}{}", start_indent, end_msg);
157        }};
158    }
159
160    pub fn compute_indent_whitespace(indent_amount: usize) -> String {
161        let mut indent = String::new();
162        for _ in 0..indent_amount {
163            indent.push_str(" ");
164        }
165        indent
166    }
167
168    pub fn compute_indent(indent_amount: usize) -> String {
169        let mut indent = String::new();
170        for _ in 0..indent_amount {
171            indent.push_str(&PAD_CHAR.white());
172        }
173        indent
174    }
175}
176
177#[macro_use]
178#[cfg(not(feature = "print-trace"))]
179mod inner {
180    pub struct TimerInfo;
181
182    #[macro_export]
183    macro_rules! start_timer {
184        ($msg:expr) => {{
185            let _ = $msg;
186            $crate::perf_trace::TimerInfo
187        }};
188    }
189    #[macro_export]
190    macro_rules! add_to_trace {
191        ($title:expr, $msg:expr) => {
192            let _ = $msg;
193            let _ = $title;
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}