time_graph_macros/
lib.rs

1//! A procedural macro attribute for instrumenting functions with
2//! [`time-graph`].
3//!
4//! [`time-graph`] provides always-on profiling for your code, allowing to
5//! record the execution time of functions, spans inside these functions and the
6//! actual call graph at run-time. This crate provides the
7//! [`#[instrument]`][instrument] procedural macro attribute.
8//!
9//! Note that this macro is also re-exported by the main `time-graph` crate.
10//!
11//!
12//! ## Usage
13//!
14//! First, add this to your `Cargo.toml`:
15//!
16//! ```toml
17//! [dependencies]
18//! time-graph-macros = "0.1.0"
19//! ```
20//!
21//! The [`#[instrument]`][instrument] attribute can now be added to a function
22//! to automatically create a `time-graph` [callsite], and enter the
23//! corresponding [span] when that function is called. For example:
24//!
25//! ```
26//! use time_graph_macros::instrument;
27//!
28//! #[instrument]
29//! pub fn my_function(my_arg: usize) {
30//!     // ...
31//! }
32//!
33//! # fn main() {}
34//! ```
35//!
36//! [`time-graph`]: https://crates.io/crates/time-graph
37//! [instrument]: macro@instrument
38//! [callsite]: https://docs.rs/time-graph/latest/time_graph/struct.CallSite.html
39//! [span]: https://docs.rs/time-graph/latest/time_graph/struct.Span.html
40
41#![allow(clippy::needless_return)]
42
43extern crate proc_macro;
44use proc_macro::TokenStream;
45
46use quote::quote;
47use syn::parse::{Parse, ParseStream};
48use syn::{ItemFn, Signature, LitStr, Token};
49
50
51/// Instruments a function to create and enter a [`time-graph`] [span] every
52/// time the function is called.
53///
54/// # Examples
55/// Instrumenting a function:
56/// ```
57/// # use time_graph_macros::instrument;
58/// #[instrument]
59/// pub fn my_function(my_arg: usize) {
60///     // ...
61/// }
62///
63/// ```
64/// Overriding the generated span's name:
65/// ```
66/// # use time_graph_macros::instrument;
67/// #[instrument(name = "another name")]
68/// pub fn my_function() {
69///     // ...
70/// }
71/// ```
72///
73/// [span]: https://docs.rs/time-graph/latest/time_graph/struct.Span.html
74/// [`time-graph`]: https://github.com/luthaf/time-graph
75#[proc_macro_attribute]
76pub fn instrument(args: TokenStream, tokens: TokenStream) -> TokenStream {
77    let input: ItemFn = syn::parse_macro_input!(tokens as ItemFn);
78    let args: TimedArgs = syn::parse_macro_input!(args as TimedArgs);
79
80    let name = args.name.unwrap_or_else(|| input.sig.ident.to_string());
81
82    let ItemFn {
83        attrs,
84        vis,
85        block,
86        sig,
87        ..
88    } = input;
89
90    let Signature {
91        output: return_type,
92        inputs: params,
93        unsafety,
94        asyncness,
95        constness,
96        abi,
97        ident,
98        generics:
99            syn::Generics {
100                params: gen_params,
101                where_clause,
102                ..
103            },
104        ..
105    } = sig;
106
107    let stream = quote!(
108        #(#attrs) *
109        #vis #constness #unsafety #asyncness #abi fn #ident<#gen_params>(#params) #return_type
110        #where_clause
111        {
112            time_graph::spanned!(#name, {
113                #block
114            })
115        }
116    );
117
118    return stream.into();
119}
120
121struct TimedArgs {
122    name: Option<String>,
123}
124
125mod kw {
126    syn::custom_keyword!(name);
127}
128
129impl Parse for TimedArgs {
130    fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
131        let mut args = TimedArgs {
132            name: None,
133        };
134        while !input.is_empty() {
135            let lookahead = input.lookahead1();
136            if lookahead.peek(kw::name) {
137                if args.name.is_some() {
138                    return Err(input.error("expected only a single `name` argument"));
139                }
140                let _ = input.parse::<kw::name>()?;
141                let _ = input.parse::<Token![=]>()?;
142                args.name = Some(input.parse::<LitStr>()?.value());
143            } else if lookahead.peek(LitStr) {
144                if args.name.is_some() {
145                    return Err(input.error("expected only a single `name` argument"));
146                }
147                args.name = Some(input.parse::<LitStr>()?.value());
148            } else {
149                return Err(lookahead.error());
150            }
151        }
152        Ok(args)
153    }
154}