Skip to main content

rustmeter_beacon_target/
monitors.rs

1use crate::numeric_registry::NumericRegistry;
2
3pub static VALUE_MONITOR_REGISTRY: NumericRegistry = NumericRegistry::new();
4pub static CODE_MONITOR_REGISTRY: NumericRegistry = NumericRegistry::new();
5
6#[macro_export]
7macro_rules! get_static_id_by_registry {
8    ($registry:expr) => {{
9        use rustmeter_beacon::_private::portable_atomic::{AtomicUsize, Ordering};
10        static LOCAL_MONITOR_VALUE_ID: AtomicUsize = AtomicUsize::new(0);
11
12        // Get or allocate monitor ID
13        match LOCAL_MONITOR_VALUE_ID.load(Ordering::Relaxed) {
14            0 => {
15                // Allocate new ID
16                let id = VALUE_MONITOR_REGISTRY.allocate_new_id();
17                let res = LOCAL_MONITOR_VALUE_ID.compare_exchange(
18                    0,
19                    id,
20                    Ordering::Relaxed,
21                    Ordering::Relaxed,
22                );
23
24                match res {
25                    Ok(_) => {
26                        // Successfully stored this ID as LOCAL_MONITOR_VALUE_ID
27                        (id, true)
28                    }
29                    Err(actual) => {
30                        // Another thread stored an ID in the meantime
31                        (actual, false)
32                    }
33                }
34            }
35            id => (id, false),
36        }
37    }};
38}
39
40#[allow(unused_variables)]
41pub fn defmt_trace_new_monitored_value(name: &str, local_id: usize) {
42    #[cfg(feature = "defmt")]
43    defmt::trace!(
44        "Registered new monitored value: {} with id {}",
45        name,
46        local_id
47    );
48}
49
50#[macro_export]
51/// Macro to monitor a numeric value with Rustmeter Beacon.
52/// 
53/// ## Parameters
54/// - $name: A string literal representing the name of the value to be monitored (max 20 characters).
55/// - $val: The numeric value to be monitored. Must be convertible to rustmeter_beacon::protocol::MonitorValue (any primitive).
56/// 
57/// 
58/// # Examples
59/// ```rust,no_run
60/// let temperature: f32 = read_temperature_sensor();
61/// monitor_value!("temperature", temperature);
62/// ```
63macro_rules! monitor_value {
64    ($name:literal, $val:expr) => {
65        // TODO: Check that val is numeric
66
67        // Limit name length to 20 characters (BufferWriter is only 32 bytes and we need space for TimeDelta and other fields)
68        const _: () = {
69            core::assert!($name.len() <= 20, "Name of value to be monitored must be 20 characters or less");
70        };
71
72        use crate::monitors::{CODE_MONITOR_REGISTRY, VALUE_MONITOR_REGISTRY};
73        use rustmeter_beacon::protocol::MonitorValuePayload;
74
75        let (local_id, registered_newly) = get_static_id_by_registry!(VALUE_MONITOR_REGISTRY);
76
77        // Send TypeDefinition event if newly registered
78        if registered_newly {
79            let payload = rustmeter_beacon::protocol::TypeDefinitionPayload::ValueMonitor {
80                value_id: local_id as u8,
81                name: $name,
82            };
83            rustmeter_beacon::tracing::write_tracing_event(rustmeter_beacon::protocol::EventPayload::TypeDefinition(payload));
84
85            rustmeter_beacon::monitors::defmt_trace_new_monitored_value($name, local_id);
86        }
87
88        // Send MonitorValue event
89        rustmeter_beacon::tracing::write_tracing_event(rustmeter_beacon::protocol::EventPayload::MonitorValue {
90            value_id: local_id as u8,
91            value: $val.into(),
92        });
93    };
94}
95
96/// A guard that runs a function when dropped. Used in monitors to catch scope exits via return and other control flow statements.
97pub struct DropGuard<F: FnOnce()> {
98    drop_fn: Option<F>,
99}
100
101impl<F: FnOnce()> DropGuard<F> {
102    pub fn new(drop_fn: F) -> Self {
103        Self {
104            drop_fn: Some(drop_fn),
105        }
106    }
107}
108
109impl<F: FnOnce()> Drop for DropGuard<F> {
110    fn drop(&mut self) {
111        if let Some(f) = self.drop_fn.take() {
112            f();
113        }
114    }
115}
116
117#[allow(unused_variables)]
118pub fn defmt_trace_new_scope(name: &str, local_id: usize) {
119    #[cfg(feature = "defmt")]
120    defmt::trace!(
121        "Registered new scope monitor: {} with id {}",
122        name,
123        local_id
124    );
125}
126
127#[macro_export]
128/// Macro to monitor a code scope with Rustmeter Beacon.
129/// ## Parameters
130/// - $name: A string literal representing the name of the scope to be monitored (max 20 characters).
131/// - $body: A block of code representing the scope to be monitored. Must be synchronous.
132/// 
133/// # Examples
134/// ```rust,no_run
135/// fn matrix_multiply(a: &Matrix, b: &Matrix) -> Matrix {
136///     // prepare or anything
137/// 
138///     let result = monitor_scoped!("matrix_mul", {
139///         a * b
140///     });
141/// 
142///     // finalize or anything
143///     result
144/// }
145macro_rules! monitor_scoped {
146    ($name:literal, $body:block) => {{
147        // Limit name length to 20 characters (BufferWriter is only 32 bytes and we need space for TimeDelta and other fields)
148        const _: () = {
149            core::assert!($name.len() <= 20, "Scope name must be 20 characters or less");
150        };
151
152        use rustmeter_beacon::monitors::{CODE_MONITOR_REGISTRY, VALUE_MONITOR_REGISTRY};
153        use rustmeter_beacon::get_static_id_by_registry;
154        use rustmeter_beacon::tracing::write_tracing_event;
155
156        let (local_id, registered_newly) = get_static_id_by_registry!(CODE_MONITOR_REGISTRY);
157
158        // Send TypeDefinition event if newly registered
159        if registered_newly {
160            let payload = rustmeter_beacon::protocol::TypeDefinitionPayload::ScopeMonitor {
161                monitor_id: local_id as u8,
162                name: $name,
163            };
164            write_tracing_event(rustmeter_beacon::protocol::EventPayload::TypeDefinition(payload));
165
166            rustmeter_beacon::monitors::defmt_trace_new_scope($name, local_id);
167        }
168
169        // Create guard to signal end of scope
170        let _guard = rustmeter_beacon::monitors::DropGuard::new(|| {
171            rustmeter_beacon::protocol::raw_writers::write_monitor_end();
172        });
173
174        // Send MonitorStart event (after guard-created to lower tracing impact on measured scope)
175        rustmeter_beacon::protocol::raw_writers::write_monitor_start(local_id as u8);
176
177        { $body }
178    }};
179}
180
181// Call from proc-macro when a new function monitor is registered
182#[allow(unused_variables)]
183pub fn defmt_trace_new_function_monitor(name: &str, local_id: usize) {
184    #[cfg(feature = "defmt")]
185    defmt::trace!(
186        "Registered new function monitor: {} with id {}",
187        name,
188        local_id
189    );
190}