├── .gitignore ├── examples └── simple.rs ├── Cargo.toml ├── README.md └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | #[funtime::timed] 2 | fn foo(y: i32) -> i32 { 3 | let mut x = 1; 4 | let d = 1_000; 5 | x += d; 6 | x += y; 7 | x 8 | } 9 | 10 | #[funtime::timed] 11 | fn main() { 12 | foo(23); 13 | } 14 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "funtime" 3 | version = "0.3.1" 4 | authors = ["Jacob Brown "] 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | description = "A proc-macro helper to time Rust functions" 8 | readme = "README.md" 9 | keywords = ["stopwatch", "timing"] 10 | 11 | [dependencies] 12 | syn = { version = "1", features = ["full", "extra-traits"] } 13 | quote = "1" 14 | proc-macro2 = "1" 15 | 16 | 17 | [lib] 18 | proc-macro = true 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # funtime 2 | 3 | [![Docs](https://docs.rs/funtime/badge.svg)](https://docs.rs/crate/funtime/) 4 | [![Crates.io](https://img.shields.io/crates/v/funtime.svg)](https://crates.io/crates/funtime) 5 | 6 | A small proc-macro helper to time every statement in a given function (or item method). 7 | 8 | ## Usage 9 | 10 | ```rust 11 | #[funtime::timed] 12 | fn foo(y: i32) -> i32 { 13 | let mut x = 1; 14 | let d = 1_000; 15 | x += d; 16 | x += y; 17 | x 18 | } 19 | 20 | #[funtime::timed] 21 | fn main() { 22 | foo(23); 23 | } 24 | ``` 25 | 26 | Prints: 27 | 28 | ``` 29 | funtime start: `foo` 30 | took 1µs: `let mut x = 1 ;` 31 | took 5µs: `let d = 1_000 ;` 32 | took 2µs: `x += d ;` 33 | took 2µs: `x += y ;` 34 | took 3µs: `x` 35 | funtime end: `foo` took 12µs 36 | funtime start: `main` 37 | took 49µs: `foo (23) ;` 38 | funtime end: `main` took 56µs 39 | ``` 40 | 41 | Current version: `0.3.0`. 42 | 43 | Supports rustc `1.31` and up. -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | 3 | use proc_macro::TokenStream; 4 | use quote::quote; 5 | use syn::*; 6 | 7 | #[proc_macro_attribute] 8 | pub fn timed(_attrs: TokenStream, item: TokenStream) -> TokenStream { 9 | if let Ok(mut fun) = parse::(item.clone()) { 10 | let new_stmts = rewrite_stmts(fun.sig.ident.to_string(), &mut fun.block.stmts); 11 | fun.block.stmts = new_stmts; 12 | return quote!(#fun).into(); 13 | } 14 | 15 | if let Ok(mut fun) = parse::(item.clone()) { 16 | if let Some(block) = fun.default.as_mut() { 17 | let new_stmts = rewrite_stmts(fun.sig.ident.to_string(), &mut block.stmts); 18 | block.stmts = new_stmts; 19 | return quote!(#fun).into(); 20 | } 21 | } 22 | 23 | if let Ok(mut fun) = parse::(item) { 24 | let new_stmts = rewrite_stmts(fun.sig.ident.to_string(), &mut fun.block.stmts); 25 | fun.block.stmts = new_stmts; 26 | return quote!(#fun).into(); 27 | } 28 | 29 | panic!("`funtime::timed` only works on functions") 30 | } 31 | 32 | fn rewrite_stmts(name: String, stmts: &mut Vec) -> Vec { 33 | 34 | fn truncate_stmt(stmt: &Stmt, len: usize) -> String { 35 | let short = 36 | format!("{}", quote::ToTokens::to_token_stream(stmt)).chars().collect::>(); 37 | 38 | let short = if short.len() > len { 39 | let mut short = short[..(len - 3)].into_iter().collect::(); 40 | short.push_str("..."); 41 | short 42 | } else { 43 | short.into_iter().collect::() 44 | }; 45 | 46 | short 47 | } 48 | 49 | let setup: Block = parse_quote! {{ 50 | struct FuntimeTimer { 51 | start: std::time::Instant, 52 | name: &'static str, 53 | buffer: String, 54 | prev_mark: Option, 55 | } 56 | 57 | 58 | impl Drop for FuntimeTimer { 59 | fn drop(&mut self) { 60 | use std::fmt::Write; 61 | writeln!(&mut self.buffer, "funtime end: `{}` took {:?}", self.name, self.start.elapsed()).unwrap(); 62 | print!("{}", &self.buffer); 63 | } 64 | } 65 | 66 | impl FuntimeTimer { 67 | fn new(name: &'static str) -> Self { 68 | use std::fmt::Write; 69 | let mut buffer = String::new(); 70 | writeln!(&mut buffer, "funtime start: `{}`", name).unwrap(); 71 | FuntimeTimer { 72 | start: std::time::Instant::now(), 73 | name, 74 | buffer, 75 | prev_mark: None, 76 | } 77 | } 78 | 79 | fn mark_elapsed(&mut self, short: &str) { 80 | use std::fmt::Write; 81 | let mut elapsed = self.start.elapsed(); 82 | if let Some(prev) = self.prev_mark.replace(elapsed) { 83 | elapsed = elapsed - prev; 84 | } 85 | writeln!(&mut self.buffer, " took {:?}: `{}`", elapsed, short).unwrap(); 86 | } 87 | } 88 | 89 | let mut funtime_timer = FuntimeTimer::new(#name); 90 | 91 | }}; 92 | 93 | let mut new_stmts = setup.stmts; 94 | 95 | let last = stmts.pop(); 96 | 97 | for stmt in stmts.drain(..) { 98 | let short = truncate_stmt(&stmt, 40); 99 | 100 | let next_stmt = parse_quote!(funtime_timer.mark_elapsed(#short);); 101 | 102 | new_stmts.push(stmt); 103 | new_stmts.push(next_stmt); 104 | } 105 | 106 | if let Some(stmt) = last { 107 | let short = truncate_stmt(&stmt, 40); 108 | let new_stmt = parse_quote! { 109 | let funtime_return_val = { 110 | #stmt 111 | }; 112 | }; 113 | 114 | let next_stmt = parse_quote!(funtime_timer.mark_elapsed(#short);); 115 | let return_stmt = parse_quote!(return funtime_return_val;); 116 | 117 | new_stmts.push(new_stmt); 118 | new_stmts.push(next_stmt); 119 | new_stmts.push(return_stmt); 120 | } 121 | 122 | new_stmts 123 | } 124 | --------------------------------------------------------------------------------