Skip to main content

scirs2_core/profiling/
instrumentation.rs

1//! # Instrumentation Framework for SciRS2 v0.2.0
2//!
3//! This module provides a comprehensive instrumentation framework for adding
4//! observability to SciRS2 operations with minimal code changes.
5//!
6//! # Features
7//!
8//! - **Automatic Instrumentation**: Attribute macros for automatic function instrumentation
9//! - **Custom Metrics**: Easy-to-use metrics collection
10//! - **Performance Zones**: Mark critical sections for profiling
11//! - **Event Recording**: Record significant events with context
12//! - **Zero Overhead**: Compile-time elimination when disabled
13//!
14//! # Example
15//!
16//! ```rust,no_run
17//! use scirs2_core::{instrument, profiling::instrumentation::record_event};
18//!
19//! fn compute_fft(data: &[f64]) -> Vec<f64> {
20//!     instrument!("compute_fft");
21//!     record_event("fft_started", &[("size", &data.len())]);
22//!     // ... computation ...
23//!     record_event("fft_completed", &[]);
24//!     vec![]
25//! }
26//! ```
27
28#[cfg(feature = "instrumentation")]
29use crate::CoreResult;
30#[cfg(feature = "instrumentation")]
31use std::collections::HashMap;
32#[cfg(feature = "instrumentation")]
33use std::sync::Arc;
34#[cfg(feature = "instrumentation")]
35use std::time::{Duration, Instant};
36
37/// Event recorder for instrumentation
38#[cfg(feature = "instrumentation")]
39#[derive(Clone)]
40pub struct InstrumentationEvent {
41    /// Event name
42    pub name: String,
43    /// Event timestamp
44    pub timestamp: Instant,
45    /// Event attributes
46    pub attributes: HashMap<String, String>,
47    /// Event duration (if applicable)
48    pub duration: Option<Duration>,
49}
50
51#[cfg(feature = "instrumentation")]
52impl InstrumentationEvent {
53    /// Create a new event
54    pub fn new(name: impl Into<String>) -> Self {
55        Self {
56            name: name.into(),
57            timestamp: Instant::now(),
58            attributes: HashMap::new(),
59            duration: None,
60        }
61    }
62
63    /// Add an attribute to the event
64    pub fn with_attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
65        self.attributes.insert(key.into(), value.into());
66        self
67    }
68
69    /// Set the duration of the event
70    pub fn with_duration(mut self, duration: Duration) -> Self {
71        self.duration = Some(duration);
72        self
73    }
74}
75
76/// Global event recorder
77#[cfg(feature = "instrumentation")]
78static EVENT_RECORDER: once_cell::sync::Lazy<Arc<parking_lot::Mutex<Vec<InstrumentationEvent>>>> =
79    once_cell::sync::Lazy::new(|| Arc::new(parking_lot::Mutex::new(Vec::new())));
80
81/// Record an instrumentation event
82#[cfg(feature = "instrumentation")]
83pub fn record_event(name: &str, attributes: &[(&str, &dyn std::fmt::Display)]) {
84    let mut event = InstrumentationEvent::new(name);
85
86    for (key, value) in attributes {
87        event = event.with_attribute(*key, value.to_string());
88    }
89
90    EVENT_RECORDER.lock().push(event);
91}
92
93/// Record an event with duration
94#[cfg(feature = "instrumentation")]
95pub fn record_event_with_duration(
96    name: &str,
97    duration: Duration,
98    attributes: &[(&str, &dyn std::fmt::Display)],
99) {
100    let mut event = InstrumentationEvent::new(name).with_duration(duration);
101
102    for (key, value) in attributes {
103        event = event.with_attribute(*key, value.to_string());
104    }
105
106    EVENT_RECORDER.lock().push(event);
107}
108
109/// Get all recorded events
110#[cfg(feature = "instrumentation")]
111pub fn get_events() -> Vec<InstrumentationEvent> {
112    EVENT_RECORDER.lock().clone()
113}
114
115/// Clear all recorded events
116#[cfg(feature = "instrumentation")]
117pub fn clear_events() {
118    EVENT_RECORDER.lock().clear();
119}
120
121/// Instrumentation scope for automatic timing
122#[cfg(feature = "instrumentation")]
123pub struct InstrumentationScope {
124    name: String,
125    start: Instant,
126    attributes: HashMap<String, String>,
127}
128
129#[cfg(feature = "instrumentation")]
130impl InstrumentationScope {
131    /// Create a new instrumentation scope
132    pub fn new(name: impl Into<String>) -> Self {
133        Self {
134            name: name.into(),
135            start: Instant::now(),
136            attributes: HashMap::new(),
137        }
138    }
139
140    /// Add an attribute to the scope
141    pub fn add_attribute(&mut self, key: impl Into<String>, value: impl Into<String>) {
142        self.attributes.insert(key.into(), value.into());
143    }
144
145    /// End the scope manually and return duration
146    pub fn end(self) -> Duration {
147        let duration = self.start.elapsed();
148
149        // Record the event
150        let mut event = InstrumentationEvent::new(&self.name).with_duration(duration);
151        for (key, value) in self.attributes.clone() {
152            event = event.with_attribute(key, value);
153        }
154
155        EVENT_RECORDER.lock().push(event);
156
157        duration
158    }
159}
160
161#[cfg(feature = "instrumentation")]
162impl Drop for InstrumentationScope {
163    fn drop(&mut self) {
164        let duration = self.start.elapsed();
165
166        // Record the event
167        let mut event = InstrumentationEvent::new(&self.name).with_duration(duration);
168        for (key, value) in self.attributes.clone() {
169            event = event.with_attribute(key, value);
170        }
171
172        EVENT_RECORDER.lock().push(event);
173    }
174}
175
176/// Macro for instrumenting a function
177#[macro_export]
178#[cfg(feature = "instrumentation")]
179macro_rules! instrument {
180    ($name:expr) => {
181        let _scope = $crate::profiling::instrumentation::InstrumentationScope::new($name);
182    };
183    ($name:expr, $($attr_key:expr => $attr_value:expr),* $(,)?) => {
184        let mut _scope = $crate::profiling::instrumentation::InstrumentationScope::new($name);
185        $(
186            _scope.add_attribute($attr_key, $attr_value.to_string());
187        )*
188    };
189}
190
191/// Macro for instrumenting a code block
192#[macro_export]
193#[cfg(feature = "instrumentation")]
194macro_rules! instrument_block {
195    ($name:expr, $body:block) => {{
196        let _scope = $crate::profiling::instrumentation::InstrumentationScope::new($name);
197        $body
198    }};
199}
200
201/// Performance counter for tracking metrics
202#[cfg(feature = "instrumentation")]
203pub struct PerformanceCounter {
204    name: String,
205    count: parking_lot::Mutex<u64>,
206    total_duration: parking_lot::Mutex<Duration>,
207}
208
209#[cfg(feature = "instrumentation")]
210impl PerformanceCounter {
211    /// Create a new performance counter
212    pub fn new(name: impl Into<String>) -> Self {
213        Self {
214            name: name.into(),
215            count: parking_lot::Mutex::new(0),
216            total_duration: parking_lot::Mutex::new(Duration::ZERO),
217        }
218    }
219
220    /// Increment the counter
221    pub fn increment(&self) {
222        *self.count.lock() += 1;
223    }
224
225    /// Add duration to the counter
226    pub fn add_duration(&self, duration: Duration) {
227        *self.total_duration.lock() += duration;
228        self.increment();
229    }
230
231    /// Get the current count
232    pub fn count(&self) -> u64 {
233        *self.count.lock()
234    }
235
236    /// Get the total duration
237    pub fn total_duration(&self) -> Duration {
238        *self.total_duration.lock()
239    }
240
241    /// Get the average duration
242    pub fn average_duration(&self) -> Option<Duration> {
243        let count = self.count();
244        if count == 0 {
245            None
246        } else {
247            Some(self.total_duration() / count as u32)
248        }
249    }
250
251    /// Reset the counter
252    pub fn reset(&self) {
253        *self.count.lock() = 0;
254        *self.total_duration.lock() = Duration::ZERO;
255    }
256
257    /// Get a summary of the counter
258    pub fn summary(&self) -> String {
259        let count = self.count();
260        let total = self.total_duration();
261        let avg = self.average_duration();
262
263        format!(
264            "{}: count={}, total={:?}, avg={:?}",
265            self.name,
266            count,
267            total,
268            avg.unwrap_or(Duration::ZERO)
269        )
270    }
271}
272
273/// Global performance counters registry
274#[cfg(feature = "instrumentation")]
275static COUNTERS: once_cell::sync::Lazy<
276    Arc<parking_lot::Mutex<HashMap<String, Arc<PerformanceCounter>>>>,
277> = once_cell::sync::Lazy::new(|| Arc::new(parking_lot::Mutex::new(HashMap::new())));
278
279/// Get or create a performance counter
280#[cfg(feature = "instrumentation")]
281pub fn get_counter(name: &str) -> Arc<PerformanceCounter> {
282    let mut counters = COUNTERS.lock();
283    counters
284        .entry(name.to_string())
285        .or_insert_with(|| Arc::new(PerformanceCounter::new(name)))
286        .clone()
287}
288
289/// Get all performance counters
290#[cfg(feature = "instrumentation")]
291pub fn get_all_counters() -> HashMap<String, Arc<PerformanceCounter>> {
292    COUNTERS.lock().clone()
293}
294
295/// Print summary of all counters
296#[cfg(feature = "instrumentation")]
297pub fn print_counter_summary() {
298    let counters = get_all_counters();
299    println!("Performance Counter Summary:");
300    println!("===========================");
301
302    for (name, counter) in counters {
303        println!("{}", counter.summary());
304    }
305}
306
307/// Stub implementations when instrumentation feature is disabled
308#[cfg(not(feature = "instrumentation"))]
309#[allow(unused_imports)]
310use crate::CoreResult;
311
312#[cfg(not(feature = "instrumentation"))]
313pub fn record_event(_name: &str, _attributes: &[(&str, &dyn std::fmt::Display)]) {}
314
315#[cfg(not(feature = "instrumentation"))]
316pub fn clear_events() {}
317
318#[cfg(test)]
319#[cfg(feature = "instrumentation")]
320mod tests {
321    use super::*;
322
323    #[test]
324    fn test_event_recording() {
325        clear_events();
326
327        record_event("test_event", &[("key", &"value")]);
328
329        let events = get_events();
330        assert_eq!(events.len(), 1);
331        assert_eq!(events[0].name, "test_event");
332        assert_eq!(events[0].attributes.get("key"), Some(&"value".to_string()));
333    }
334
335    #[test]
336    fn test_instrumentation_scope() {
337        clear_events();
338
339        {
340            let _scope = InstrumentationScope::new("test_scope");
341            std::thread::sleep(Duration::from_millis(10));
342        }
343
344        let events = get_events();
345        assert_eq!(events.len(), 1);
346        assert_eq!(events[0].name, "test_scope");
347        assert!(events[0].duration.is_some());
348    }
349
350    #[test]
351    fn test_performance_counter() {
352        let counter = PerformanceCounter::new("test_counter");
353
354        counter.increment();
355        counter.add_duration(Duration::from_millis(100));
356
357        assert_eq!(counter.count(), 2);
358        assert!(counter.total_duration() >= Duration::from_millis(100));
359
360        let avg = counter.average_duration();
361        assert!(avg.is_some());
362    }
363
364    #[test]
365    fn test_global_counter() {
366        let counter1 = get_counter("global_test");
367        let counter2 = get_counter("global_test");
368
369        counter1.increment();
370        assert_eq!(counter2.count(), 1);
371    }
372
373    #[test]
374    fn test_counter_summary() {
375        let counter = PerformanceCounter::new("summary_test");
376        counter.add_duration(Duration::from_millis(50));
377        counter.add_duration(Duration::from_millis(150));
378
379        let summary = counter.summary();
380        assert!(summary.contains("summary_test"));
381        assert!(summary.contains("count=2"));
382    }
383}