scirs2_core/profiling/
instrumentation.rs1#[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#[cfg(feature = "instrumentation")]
39#[derive(Clone)]
40pub struct InstrumentationEvent {
41 pub name: String,
43 pub timestamp: Instant,
45 pub attributes: HashMap<String, String>,
47 pub duration: Option<Duration>,
49}
50
51#[cfg(feature = "instrumentation")]
52impl InstrumentationEvent {
53 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 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 pub fn with_duration(mut self, duration: Duration) -> Self {
71 self.duration = Some(duration);
72 self
73 }
74}
75
76#[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#[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#[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#[cfg(feature = "instrumentation")]
111pub fn get_events() -> Vec<InstrumentationEvent> {
112 EVENT_RECORDER.lock().clone()
113}
114
115#[cfg(feature = "instrumentation")]
117pub fn clear_events() {
118 EVENT_RECORDER.lock().clear();
119}
120
121#[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 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 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 pub fn end(self) -> Duration {
147 let duration = self.start.elapsed();
148
149 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 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_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_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#[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 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 pub fn increment(&self) {
222 *self.count.lock() += 1;
223 }
224
225 pub fn add_duration(&self, duration: Duration) {
227 *self.total_duration.lock() += duration;
228 self.increment();
229 }
230
231 pub fn count(&self) -> u64 {
233 *self.count.lock()
234 }
235
236 pub fn total_duration(&self) -> Duration {
238 *self.total_duration.lock()
239 }
240
241 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 pub fn reset(&self) {
253 *self.count.lock() = 0;
254 *self.total_duration.lock() = Duration::ZERO;
255 }
256
257 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#[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#[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#[cfg(feature = "instrumentation")]
291pub fn get_all_counters() -> HashMap<String, Arc<PerformanceCounter>> {
292 COUNTERS.lock().clone()
293}
294
295#[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#[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}