rustmeter_beacon_function_monitor/lib.rs
1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{
4 Ident, ItemFn, LitStr, Result, Token,
5 parse::{Parse, ParseStream},
6 parse_macro_input,
7};
8
9extern crate proc_macro;
10
11/// Helper struct to parse arguments for the `monitor_fn` attribute macro
12struct MonitorArgs {
13 name: Option<String>,
14}
15
16impl Parse for MonitorArgs {
17 fn parse(input: ParseStream) -> Result<Self> {
18 let mut name = None;
19 if input.is_empty() {
20 return Ok(MonitorArgs { name });
21 }
22
23 // Case 1: #[monitor_fn("Name")]
24 // `lookahead` checks if the next token is a string literal
25 if input.peek(syn::LitStr) {
26 let lit: LitStr = input.parse()?;
27 name = Some(lit.value());
28 }
29 // Case 2: Key-Value Pair: #[monitor_fn(name = "Name")]
30 else if input.peek(syn::Ident) {
31 let key: Ident = input.parse()?;
32 if key == "name" {
33 input.parse::<Token![=]>()?; // Consume the '='
34 let lit: LitStr = input.parse()?;
35 name = Some(lit.value());
36 } else {
37 return Err(syn::Error::new(
38 key.span(),
39 "Unknown argument (expected 'name')",
40 ));
41 }
42 }
43
44 // More arguments could be parsed here in the future
45
46 Ok(MonitorArgs { name })
47 }
48}
49
50/// Instruments a function to log execution for rustmeter
51///
52/// This attribute macro wraps the decorated function to log specific `@EVENT_MONITOR`
53/// messages before execution starts and after it finishes. It captures the function name
54/// (or a custom name) and the current core ID.
55///
56/// It supports both synchronous and `async` functions.
57///
58/// # Arguments
59///
60/// The macro accepts an optional name argument to override the default function name in the logs.
61///
62/// * `#[monitor_fn]` - Uses the name of the function.
63/// * `#[monitor_fn("custom_name")]` - Uses the provided string literal.
64/// * `#[monitor_fn(name = "custom_name")]` - Explicit key-value syntax.
65///
66/// # Examples
67///
68/// Basic usage using the function's name:
69///
70/// ```rust
71/// #[monitor_fn]
72/// fn process_data(data: u8) {
73/// // Function implementation
74/// }
75/// ```
76#[proc_macro_attribute]
77pub fn monitor_fn(attr: TokenStream, item: TokenStream) -> TokenStream {
78 let input = parse_macro_input!(item as ItemFn);
79 let args = parse_macro_input!(attr as MonitorArgs);
80
81 let fn_name = &input.sig.ident;
82 let vis = &input.vis;
83 let sig = &input.sig;
84 let block = &input.block;
85 let attrs = &input.attrs; // Important: Keep other attributes (e.g., #[inline])
86
87 let mut output_name = fn_name.to_string();
88
89 // Handle output name from args (if provided)
90 if let Some(custom_name) = args.name {
91 output_name = custom_name;
92 }
93
94 // TODO: Send one event when function is done and measure duration from start ==> less defmt messages BUT long computation inside function do not get logged when
95 // function is running for a long time and we exit while it is still running
96 // - which timestamp method to use for that?
97
98 if input.sig.asyncness.is_some() {
99 // ASYNC FUNCTION
100 quote! {
101 let core_id = rustmeter_beacon::get_current_core_id();
102 async move {
103 defmt::info!("@EVENT_MONITOR_START(function_name={=istr},core_id={})", defmt::intern!(#output_name), core_id);
104 let result = { #block };
105 defmt::info!("@EVENT_MONITOR_END(function_name={=istr},core_id={})", defmt::intern!(#output_name), core_id);
106 result
107 }
108 }.into()
109 } else {
110 // SYNC FUNCTION
111 quote! {
112 #(#attrs)*
113 #vis #sig {
114 let core_id = rustmeter_beacon::get_current_core_id();
115 defmt::info!("@EVENT_MONITOR_START(function_name={=istr},core_id={})", defmt::intern!(#output_name), core_id);
116 let result = (move || { #block })();
117 defmt::info!("@EVENT_MONITOR_END(function_name={=istr},core_id={})", defmt::intern!(#output_name), core_id);
118 result
119 }
120 }.into()
121 }
122}