elapsed_printer/
lib.rs

1/*! # elapsed-printer
2Very simple macro for printing time elapsed to execute a function.
3
4## Feature
5`elapsed-printer` is crate holding just *one* macro, `print_elapsed` using Rust standard library *`std::time`* to check elapsed during function(also method) execution.
6
7## Attributes
8`print_elapsed` can have three types of attributes. Using attributes is optional and, if not specified, uses the default attributes. The order of attributes and the use of quotes do not matter.
9### (1) Stream to print time
101. `stdout` - Print output to standard output stream.
112. `stderr` - Print output to standard error stream.
123. `both` - Print output to both standard output and error stream.
13* `Default`: `stdout`
14### (2) Unit of time
151. `auto` - Print output in the form defined in *`Debug`* trait in *`std::time::Duration`* structure.
162. `s` - Print output in units of second.
173. `ms` - Print output in units of millisecond.
184. `us` - Print output in units of microsecond.
195. `ns` - Print output in units of nanosecond.
20* `Default`: `auto`
21### (3) Features list
22* \[*features_list*\]
23  * If any of the features in list are activated, print output.
24  * If empty, print output always.
25* `Default`: *not specified (=print always)*
26
27## Example
28### Use Cases
29```rust
30use elapsed_printer::print_elapsed;
31
32#[print_elapsed]
33fn func_to_print_elapsed_default() {}
34
35#[print_elapsed(stdout, auto)]
36// Same as default
37// Print always regardless of feature activation.
38fn func_to_print_elapsed_same_as_default() {}
39
40#[print_elapsed(ms, "stdout")]
41// Attribute order does not matter.
42// Use of quotes does not matter.
43fn func_to_print_elapsed_same_with_ms() {}
44
45#[print_elapsed("ms", stderr, [feature_1])]
46// Print when using `feature_1`
47fn func_to_print_elapsed_when_using_feature_1() {}
48
49#[print_elapsed([feature_1, feature_2], ns, stderr)]
50// Print when using `feature_1` or `feature_2`
51fn func_to_print_elapsed_when_using_feature_1_or_feature_2() {}
52
53struct MyStruct;
54impl MyStruct {
55    #[print_elapsed]
56    // Can be applied to method
57    pub fn method(&self) {}
58}
59```
60### Sample code and output
61Code
62```rust
63use elapsed_printer::print_elapsed;
64use std::time::Duration;
65use std::thread;
66
67#[print_elapsed]
68fn function_name_1() {
69    thread::sleep(Duration::from_millis(10));
70}
71#[print_elapsed(stdout, ns)]
72fn function_name_2() {
73    //
74}
75#[print_elapsed(stdout, us)]
76fn function_name_3() {
77    function_name_1()
78}
79
80fn main() {
81    function_name_1();
82    function_name_2();
83    function_name_3();
84    function_name_1();
85}
86```
87Output
88```ignore
89function_name_1, 12.527014ms
90function_name_2, 32ns
91function_name_1, 10.070776ms
92function_name_3, 10097us
93```
94*/
95
96use proc_macro::TokenStream;
97use proc_macro2::{TokenStream as TokenStream2, TokenTree};
98use quote::quote;
99
100use std::collections::HashSet;
101
102struct PrintOption {
103    printer: Printer,
104    time_unit: TimeUnit,
105    features: Vec<String>,
106    function_name: String,
107    original_function: syn::ItemFn,
108}
109impl PrintOption {
110    // Parse from token stream
111    fn parse(attr: TokenStream, function: TokenStream) -> Self {
112        let (printer, time_unit, features) = Self::parse_attributes(attr);
113        let (function_name, original_function) = Self::parse_function(function);
114
115        Self {
116            printer,
117            time_unit,
118            features,
119            function_name,
120            original_function,
121        }
122    }
123    fn parse_function(function: TokenStream) -> (String, syn::ItemFn) {
124        let original_function: syn::ItemFn = match syn::parse(function) {
125            Ok(item_fn) => item_fn,
126            Err(err) => panic!("{}", err)
127        };
128        let function_name = original_function.sig.ident.to_string();
129
130        (function_name, original_function)
131    }
132    fn parse_attributes(attr: TokenStream) -> (Printer, TimeUnit, Vec<String>) {
133        let mut optional_printer: Option<Printer> = None;
134        let mut optional_time_unit: Option<TimeUnit> = None;
135        let mut features = Vec::new();
136
137        let attr = proc_macro2::TokenStream::from(attr);
138        for (index, token_tree) in attr.into_iter().enumerate() {
139            if index % 2 == 1 { // Delimiter checker
140                let is_comma = Self::token_is_comma(token_tree);
141                if !is_comma {
142                    panic!("Commas(,) are used as attribute delimiter.")
143                }
144                continue;
145            }
146
147            match token_tree {
148                TokenTree::Ident(ident) => {
149                    let tag = ident.to_string();
150                    Self::parse_from_tag(tag, &mut optional_printer, &mut optional_time_unit);
151                },
152                TokenTree::Literal(literal) => {
153                    let tag = literal.to_string();
154                    let tag = tag.replace("\"", ""); // Remove quotes
155                    Self::parse_from_tag(tag, &mut optional_printer, &mut optional_time_unit);
156                },
157                TokenTree::Group(group) => {
158                    features = Self::parse_features(group);
159                },
160                _ => {
161                    panic!("Unknown attributes")
162                },
163            }            
164        }
165
166        let printer = match optional_printer {
167            Some(printer) => printer,
168            None => Printer::default(),
169        };
170        let time_unit = match optional_time_unit {
171            Some(time_unit) => time_unit,
172            None => TimeUnit::default(),
173        };
174
175        (printer, time_unit, features)
176    }
177    fn parse_from_tag(
178        tag: String,
179        optional_printer: &mut Option<Printer>,
180        optional_time_unit: &mut Option<TimeUnit>,
181    ) {
182        // Parse printer
183        let printer = match tag.as_ref() {
184            "stdout" => Some(Printer::StdOut),
185            "stderr" => Some(Printer::StdErr),
186            "both" => Some(Printer::Both),
187            _ => None,
188        };
189        if printer.is_some() {
190            if optional_printer.is_none() {
191                *optional_printer = printer;
192                return
193            } else {
194                panic!("Printer attribute is assigned multiple times.")
195            }
196        }
197
198        // Parse time unit
199        let time_unit = match tag.as_ref() {
200            "auto" => Some(TimeUnit::Auto),
201            "s" => Some(TimeUnit::S),
202            "ms" => Some(TimeUnit::Ms),
203            "us" => Some(TimeUnit::Us),
204            "ns" => Some(TimeUnit::Ns),
205            _ => {
206                panic!("Attribute allows printer settings(stdout, stderr, both) and time unit(auto, s, ms, us, ns)")
207            }
208        };
209        if time_unit.is_some() {
210            if optional_time_unit.is_none() {
211                *optional_time_unit = time_unit;
212                return
213            } else {
214                panic!("Time unit attribute is assigned multiple times.")
215            }
216        }
217    }
218    fn parse_features(group: proc_macro2::Group) -> Vec<String> {
219        let mut features_set = HashSet::new();
220
221        for (index, token_tree) in group.stream().into_iter().enumerate() {
222            if index % 2 == 1 {
223                let is_comma = Self::token_is_comma(token_tree);
224                if !is_comma {
225                    panic!("Commas(,) are used as feature delimiter.")
226                }
227                continue;
228            }
229
230            let feature = match token_tree {
231                TokenTree::Ident(ident) => {
232                    ident.to_string()
233                },
234                TokenTree::Literal(literal) => {
235                    let tag = literal.to_string();
236                    tag.replace("\"", "")
237                },
238                _ => {
239                    panic!("Features allows only attribute");
240                },
241            };
242            features_set.insert(feature);
243        }
244
245        features_set.into_iter().collect()
246    }
247    fn token_is_comma(token_tree: TokenTree) -> bool {
248        if let TokenTree::Punct(punct) = token_tree {
249            if punct.to_string() == "," {
250                true
251            } else {
252                false
253            }
254        } else {
255            false
256        }
257    }
258
259    // Generate new token stream
260    fn new_token_stream(&self) -> TokenStream {
261        let function_name = &self.function_name;
262
263        let attrs = &self.original_function.attrs;
264        let vis = &self.original_function.vis;
265        let constness = &self.original_function.sig.constness;
266        let asyncness = &self.original_function.sig.asyncness;
267        let unsafety = &self.original_function.sig.unsafety;
268        let abi = &self.original_function.sig.abi;
269        let ident = &self.original_function.sig.ident;
270        let generics = &self.original_function.sig.generics;
271        let inputs = &self.original_function.sig.inputs;
272        let variadic = &self.original_function.sig.variadic;
273        let output = &self.original_function.sig.output;
274        let block = &self.original_function.block;
275
276        let duration_token = self.time_unit.duration_token();
277        let print_token = self.printer.print_token(&self.time_unit, function_name);
278
279        let features = &self.features;
280
281        let tokens = if features.len() == 0 {
282            quote! {
283                #(#attrs),*
284                #vis #constness #asyncness #unsafety #abi fn #ident #generics(#inputs #variadic) #output {
285                    let start = std::time::Instant::now();
286                    let result = #block;
287                    #duration_token
288                    #print_token
289                    result
290                }
291            }
292        } else {
293            quote! {
294                #(#attrs),*
295                #vis #constness #asyncness #unsafety #abi fn #ident #generics(#inputs #variadic) #output {
296                    #[cfg(any(#(feature=#features),*))]
297                    {
298                        let start = std::time::Instant::now();
299                        let result = #block;
300                        #duration_token
301                        #print_token
302                        result
303                    }
304                    #[cfg(not(any(#(feature=#features),*)))]
305                    #block
306                }
307            }
308        };
309
310        tokens.into()
311    }
312}
313
314enum Printer {
315    StdOut,
316    StdErr,
317    Both,
318}
319impl Default for Printer {
320    fn default() -> Self {
321        Self::StdOut
322    }
323}
324impl Printer {
325    fn print_token(&self, time_unit: &TimeUnit, function_name: &String) -> TokenStream2 {
326        match self {
327            Self::StdOut => {
328                time_unit.print_to_stdout_token(function_name)
329            },
330            Self::StdErr => {
331                time_unit.print_to_stderr_token(function_name)
332            },
333            Self::Both => {
334                let print_to_stdout_token = time_unit.print_to_stdout_token(function_name);
335                let print_to_stderr_token = time_unit.print_to_stderr_token(function_name);
336                quote! {
337                    #print_to_stdout_token
338                    #print_to_stderr_token
339                }
340            },
341        }
342    }
343}
344
345enum TimeUnit {
346    Auto,
347    S,
348    Ms,
349    Us,
350    Ns,
351}
352impl Default for TimeUnit {
353    fn default() -> Self {
354        Self::Auto
355    }
356}
357impl TimeUnit {
358    fn duration_token(&self) -> TokenStream2 {
359        match self {
360            Self::Auto => {
361                quote! {
362                    let duration = start.elapsed();
363                }
364            },
365            Self::S => {
366                quote! {
367                    let duration = start.elapsed().as_secs();
368                }
369            },
370            Self::Ms => {
371                quote! {
372                    let duration = start.elapsed().as_millis();
373                }
374            },
375            Self::Us => {
376                quote! {
377                    let duration = start.elapsed().as_micros();
378                }
379            },
380            Self::Ns => {
381                quote! {
382                    let duration = start.elapsed().as_nanos();
383                }
384            },
385        }
386    }
387    fn print_to_stdout_token(&self, function_name: &String) -> TokenStream2 {
388        match self {
389            Self::Auto => {
390                quote! {
391                    println!("{}, {:?}", #function_name, duration);
392                }
393            },
394            Self::S => {
395                quote! {
396                    println!("{}, {}s", #function_name, duration);
397                }
398            },
399            Self::Ms => {
400                quote! {
401                    println!("{}, {}ms", #function_name, duration);
402                }
403            },
404            Self::Us => {
405                quote! {
406                    println!("{}, {}us", #function_name, duration);
407                }
408            },
409            Self::Ns => {
410                quote! {
411                    println!("{}, {}ns", #function_name, duration);
412                }
413            },
414        }
415    }
416    fn print_to_stderr_token(&self, function_name: &String) -> TokenStream2 {
417        match self {
418            Self::Auto => {
419                quote! {
420                    eprintln!("{}, {:?}", #function_name, duration);
421                }
422            },
423            Self::S => {
424                quote! {
425                    eprintln!("{}, {}s", #function_name, duration);
426                }
427            },
428            Self::Ms => {
429                quote! {
430                    eprintln!("{}, {}ms", #function_name, duration);
431                }
432            },
433            Self::Us => {
434                quote! {
435                    eprintln!("{}, {}us", #function_name, duration);
436                }
437            },
438            Self::Ns => {
439                quote! {
440                    eprintln!("{}, {}ns", #function_name, duration);
441                }
442            },
443        }
444    }
445}
446
447/// Attributes
448/// (1) stdout, stderr, both (default: stdout)
449/// (2) auto, s, ms, us, ns (default: auto)
450/// (3) [features] (e.g. [feature_1, "feature_2", feature_3])
451#[proc_macro_attribute]
452pub fn print_elapsed(attr: TokenStream, function: TokenStream) -> TokenStream {
453    let print_option = PrintOption::parse(attr, function);
454    let new_token_stream = print_option.new_token_stream();
455    new_token_stream
456}