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}