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