1#[cfg(not(feature = "no-std"))]
7use std::collections::HashMap;
8#[cfg(not(feature = "no-std"))]
9use std::fmt;
10#[cfg(not(feature = "no-std"))]
11use std::string::ToString;
12#[cfg(not(feature = "no-std"))]
13use std::sync::{Arc, Mutex, RwLock};
14#[cfg(not(feature = "no-std"))]
15use std::thread::ThreadId;
16#[cfg(not(feature = "no-std"))]
17use std::time::{Duration, Instant};
18
19#[cfg(feature = "no-std")]
20use alloc::{
21 collections::BTreeMap as HashMap,
22 string::{String, ToString},
23 sync::Arc,
24 vec,
25 vec::Vec,
26};
27#[cfg(feature = "no-std")]
28use core::fmt;
29#[cfg(feature = "no-std")]
30use spin::{Mutex, RwLock};
31
32#[cfg(feature = "no-std")]
35pub type ThreadId = u64; #[cfg(feature = "no-std")]
37#[derive(Debug, Clone, Copy, Default)]
38pub struct Duration(u64); #[cfg(feature = "no-std")]
40#[derive(Debug, Clone, Copy)]
41pub struct Instant; #[cfg(feature = "no-std")]
44impl Instant {
45 pub fn now() -> Self {
46 Instant }
48}
49
50#[cfg(feature = "no-std")]
51impl Duration {
52 pub fn from_millis(_millis: u64) -> Self {
53 Duration(0) }
55
56 pub fn from_nanos(_nanos: u64) -> Self {
57 Duration(0) }
59
60 pub fn as_nanos(&self) -> u128 {
61 self.0 as u128 * 1000 }
63}
64
65#[cfg(feature = "no-std")]
66impl core::ops::Div<u32> for Duration {
67 type Output = Duration;
68
69 fn div(self, rhs: u32) -> Self::Output {
70 Duration(if rhs == 0 {
71 self.0
72 } else {
73 self.0 / rhs as u64
74 })
75 }
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
80pub enum HookPhase {
81 BeforeOperation,
82 AfterOperation,
83 OnError,
84 OnOptimization,
85}
86
87#[derive(Debug, Clone)]
89pub struct PerformanceEvent {
90 pub operation_name: String,
91 pub phase: HookPhase,
92 pub timestamp: Instant,
93 pub thread_id: ThreadId,
94 pub input_size: usize,
95 pub output_size: usize,
96 pub execution_time: Option<Duration>,
97 pub error_message: Option<String>,
98 pub metadata: HashMap<String, String>,
99}
100
101impl PerformanceEvent {
102 pub fn new(
103 operation_name: String,
104 phase: HookPhase,
105 input_size: usize,
106 output_size: usize,
107 ) -> Self {
108 Self {
109 operation_name,
110 phase,
111 timestamp: Instant::now(),
112 #[cfg(not(feature = "no-std"))]
113 thread_id: std::thread::current().id(),
114 #[cfg(feature = "no-std")]
115 thread_id: 0, input_size,
117 output_size,
118 execution_time: None,
119 error_message: None,
120 metadata: HashMap::new(),
121 }
122 }
123
124 pub fn with_execution_time(mut self, duration: Duration) -> Self {
125 self.execution_time = Some(duration);
126 self
127 }
128
129 pub fn with_error(mut self, error: String) -> Self {
130 self.error_message = Some(error);
131 self
132 }
133
134 pub fn with_metadata(mut self, key: String, value: String) -> Self {
135 self.metadata.insert(key, value);
136 self
137 }
138}
139
140pub trait PerformanceHook: Send + Sync {
142 fn on_event(&self, event: &PerformanceEvent);
144
145 fn name(&self) -> &str;
147
148 fn interested_phases(&self) -> Vec<HookPhase> {
150 vec![HookPhase::BeforeOperation, HookPhase::AfterOperation]
151 }
152
153 fn should_handle(&self, operation_name: &str) -> bool {
155 let _ = operation_name; true
157 }
158}
159
160pub struct HookManager {
162 hooks: RwLock<HashMap<String, Arc<dyn PerformanceHook>>>,
163 enabled: RwLock<bool>,
164 event_buffer: Mutex<Vec<PerformanceEvent>>,
165 max_buffer_size: usize,
166}
167
168impl Default for HookManager {
169 fn default() -> Self {
170 Self::new()
171 }
172}
173
174impl HookManager {
175 pub fn new() -> Self {
177 Self {
178 hooks: RwLock::new(HashMap::new()),
179 enabled: RwLock::new(true),
180 event_buffer: Mutex::new(Vec::new()),
181 max_buffer_size: 10000,
182 }
183 }
184
185 #[cfg(not(feature = "no-std"))]
187 fn read_hooks(
188 &self,
189 ) -> std::sync::RwLockReadGuard<'_, HashMap<String, Arc<dyn PerformanceHook>>> {
190 self.hooks.read().expect("operation should succeed")
191 }
192
193 #[cfg(feature = "no-std")]
194 fn read_hooks(&self) -> spin::RwLockReadGuard<'_, HashMap<String, Arc<dyn PerformanceHook>>> {
195 self.hooks.read()
196 }
197
198 #[cfg(not(feature = "no-std"))]
200 fn write_hooks(
201 &self,
202 ) -> std::sync::RwLockWriteGuard<'_, HashMap<String, Arc<dyn PerformanceHook>>> {
203 self.hooks.write().expect("operation should succeed")
204 }
205
206 #[cfg(feature = "no-std")]
207 fn write_hooks(&self) -> spin::RwLockWriteGuard<'_, HashMap<String, Arc<dyn PerformanceHook>>> {
208 self.hooks.write()
209 }
210
211 #[cfg(not(feature = "no-std"))]
213 fn read_enabled(&self) -> std::sync::RwLockReadGuard<'_, bool> {
214 self.enabled.read().expect("operation should succeed")
215 }
216
217 #[cfg(feature = "no-std")]
218 fn read_enabled(&self) -> spin::RwLockReadGuard<'_, bool> {
219 self.enabled.read()
220 }
221
222 #[cfg(not(feature = "no-std"))]
224 fn write_enabled(&self) -> std::sync::RwLockWriteGuard<'_, bool> {
225 self.enabled.write().expect("operation should succeed")
226 }
227
228 #[cfg(feature = "no-std")]
229 fn write_enabled(&self) -> spin::RwLockWriteGuard<'_, bool> {
230 self.enabled.write()
231 }
232
233 #[cfg(not(feature = "no-std"))]
235 fn lock_event_buffer(&self) -> std::sync::MutexGuard<'_, Vec<PerformanceEvent>> {
236 self.event_buffer
237 .lock()
238 .expect("lock should not be poisoned")
239 }
240
241 #[cfg(feature = "no-std")]
242 fn lock_event_buffer(&self) -> spin::MutexGuard<'_, Vec<PerformanceEvent>, spin::Spin> {
243 self.event_buffer.lock()
244 }
245
246 pub fn register_hook(&self, hook: Arc<dyn PerformanceHook>) -> Result<(), HookError> {
248 let name = hook.name().to_string();
249 let mut hooks = self.write_hooks();
250
251 if hooks.contains_key(&name) {
252 return Err(HookError::AlreadyRegistered(name));
253 }
254
255 hooks.insert(name, hook);
256 Ok(())
257 }
258
259 pub fn unregister_hook(&self, name: &str) -> Result<(), HookError> {
261 let mut hooks = self.write_hooks();
262 if hooks.remove(name).is_none() {
263 return Err(HookError::NotFound(name.to_string()));
264 }
265 Ok(())
266 }
267
268 pub fn fire_event(&self, event: PerformanceEvent) {
270 if !*self.read_enabled() {
272 return;
273 }
274
275 {
277 let mut buffer = self.lock_event_buffer();
278 buffer.push(event.clone());
279
280 if buffer.len() > self.max_buffer_size {
282 buffer.remove(0);
283 }
284 }
285
286 let hooks = self.read_hooks();
288 for hook in hooks.values() {
289 if hook.should_handle(&event.operation_name)
290 && hook.interested_phases().contains(&event.phase)
291 {
292 hook.on_event(&event);
293 }
294 }
295 }
296
297 pub fn set_enabled(&self, enabled: bool) {
299 *self.write_enabled() = enabled;
300 }
301
302 pub fn is_enabled(&self) -> bool {
304 *self.read_enabled()
305 }
306
307 pub fn get_events(&self) -> Vec<PerformanceEvent> {
309 self.lock_event_buffer().clone()
310 }
311
312 pub fn clear_events(&self) {
314 self.lock_event_buffer().clear();
315 }
316
317 pub fn get_hook_stats(&self) -> HookStats {
319 let hooks = self.read_hooks();
320 let buffer = self.lock_event_buffer();
321
322 HookStats {
323 total_hooks: hooks.len(),
324 hook_names: hooks.keys().cloned().collect(),
325 total_events: buffer.len(),
326 is_enabled: self.is_enabled(),
327 }
328 }
329
330 pub fn set_max_buffer_size(&mut self, size: usize) {
332 self.max_buffer_size = size;
333 }
334}
335
336#[derive(Debug, Clone)]
338pub struct HookStats {
339 pub total_hooks: usize,
340 pub hook_names: Vec<String>,
341 pub total_events: usize,
342 pub is_enabled: bool,
343}
344
345#[derive(Debug, Clone)]
347pub enum HookError {
348 AlreadyRegistered(String),
349 NotFound(String),
350 ExecutionFailed(String),
351}
352
353impl fmt::Display for HookError {
354 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
355 match self {
356 HookError::AlreadyRegistered(name) => {
357 write!(f, "Hook '{}' is already registered", name)
358 }
359 HookError::NotFound(name) => write!(f, "Hook '{}' not found", name),
360 HookError::ExecutionFailed(msg) => write!(f, "Hook execution failed: {}", msg),
361 }
362 }
363}
364
365#[cfg(not(feature = "no-std"))]
366impl std::error::Error for HookError {}
367
368#[cfg(feature = "no-std")]
369impl core::error::Error for HookError {}
370
371pub static GLOBAL_HOOK_MANAGER: once_cell::sync::Lazy<HookManager> =
373 once_cell::sync::Lazy::new(HookManager::new);
374
375pub mod global {
377 use super::*;
378
379 pub fn register_hook(hook: Arc<dyn PerformanceHook>) -> Result<(), HookError> {
381 GLOBAL_HOOK_MANAGER.register_hook(hook)
382 }
383
384 pub fn fire_event(event: PerformanceEvent) {
386 GLOBAL_HOOK_MANAGER.fire_event(event);
387 }
388
389 pub fn set_enabled(enabled: bool) {
391 GLOBAL_HOOK_MANAGER.set_enabled(enabled);
392 }
393
394 pub fn get_stats() -> HookStats {
396 GLOBAL_HOOK_MANAGER.get_hook_stats()
397 }
398
399 pub fn clear_events() {
401 GLOBAL_HOOK_MANAGER.clear_events();
402 }
403}
404
405#[macro_export]
407macro_rules! perf_scope {
408 ($operation_name:expr, $input_size:expr, $output_size:expr, $body:expr) => {{
409 use $crate::performance_hooks::{global, HookPhase, PerformanceEvent};
410
411 let before_event = PerformanceEvent::new(
413 $operation_name.to_string(),
414 HookPhase::BeforeOperation,
415 $input_size,
416 $output_size,
417 );
418 global::fire_event(before_event);
419
420 let start_time = Instant::now();
422 let result = $body;
423 let execution_time = start_time.elapsed();
424
425 let after_event = PerformanceEvent::new(
427 $operation_name.to_string(),
428 HookPhase::AfterOperation,
429 $input_size,
430 $output_size,
431 )
432 .with_execution_time(execution_time);
433
434 global::fire_event(after_event);
435
436 result
437 }};
438}
439
440pub mod builtin_hooks {
442 use super::*;
443 #[cfg(feature = "no-std")]
444 use core::sync::atomic::{AtomicU64, Ordering};
445 #[cfg(not(feature = "no-std"))]
446 use std::sync::atomic::{AtomicU64, Ordering};
447
448 pub struct LoggingHook {
450 name: String,
451 }
452
453 impl LoggingHook {
454 pub fn new(name: String) -> Self {
455 Self { name }
456 }
457 }
458
459 impl PerformanceHook for LoggingHook {
460 fn on_event(&self, event: &PerformanceEvent) {
461 match event.phase {
462 HookPhase::BeforeOperation => {
463 #[cfg(not(feature = "no-std"))]
464 println!(
465 "[{}] Starting {} (input: {}, output: {})",
466 self.name, event.operation_name, event.input_size, event.output_size
467 );
468 }
469 HookPhase::AfterOperation =>
470 {
471 #[cfg(not(feature = "no-std"))]
472 if let Some(time) = event.execution_time {
473 println!(
474 "[{}] Finished {} in {:?}",
475 self.name, event.operation_name, time
476 );
477 }
478 }
479 HookPhase::OnError =>
480 {
481 #[cfg(not(feature = "no-std"))]
482 if let Some(error) = &event.error_message {
483 println!(
484 "[{}] Error in {}: {}",
485 self.name, event.operation_name, error
486 );
487 }
488 }
489 HookPhase::OnOptimization => {
490 #[cfg(not(feature = "no-std"))]
491 println!(
492 "[{}] Optimization applied to {}",
493 self.name, event.operation_name
494 );
495 }
496 }
497 }
498
499 fn name(&self) -> &str {
500 &self.name
501 }
502 }
503
504 pub struct StatsHook {
506 name: String,
507 operation_counts: RwLock<HashMap<String, AtomicU64>>,
508 total_execution_time: RwLock<HashMap<String, AtomicU64>>, total_elements_processed: RwLock<HashMap<String, AtomicU64>>,
510 }
511
512 impl StatsHook {
513 pub fn new(name: String) -> Self {
514 Self {
515 name,
516 operation_counts: RwLock::new(HashMap::new()),
517 total_execution_time: RwLock::new(HashMap::new()),
518 total_elements_processed: RwLock::new(HashMap::new()),
519 }
520 }
521
522 #[cfg(not(feature = "no-std"))]
524 fn read_counts(&self) -> std::sync::RwLockReadGuard<'_, HashMap<String, AtomicU64>> {
525 self.operation_counts
526 .read()
527 .expect("operation should succeed")
528 }
529
530 #[cfg(feature = "no-std")]
531 fn read_counts(&self) -> spin::RwLockReadGuard<'_, HashMap<String, AtomicU64>> {
532 self.operation_counts.read()
533 }
534
535 #[cfg(not(feature = "no-std"))]
537 fn write_counts(&self) -> std::sync::RwLockWriteGuard<'_, HashMap<String, AtomicU64>> {
538 self.operation_counts
539 .write()
540 .expect("operation should succeed")
541 }
542
543 #[cfg(feature = "no-std")]
544 fn write_counts(&self) -> spin::RwLockWriteGuard<'_, HashMap<String, AtomicU64>> {
545 self.operation_counts.write()
546 }
547
548 #[cfg(not(feature = "no-std"))]
550 fn read_times(&self) -> std::sync::RwLockReadGuard<'_, HashMap<String, AtomicU64>> {
551 self.total_execution_time
552 .read()
553 .expect("operation should succeed")
554 }
555
556 #[cfg(feature = "no-std")]
557 fn read_times(&self) -> spin::RwLockReadGuard<'_, HashMap<String, AtomicU64>> {
558 self.total_execution_time.read()
559 }
560
561 #[cfg(not(feature = "no-std"))]
563 fn write_times(&self) -> std::sync::RwLockWriteGuard<'_, HashMap<String, AtomicU64>> {
564 self.total_execution_time
565 .write()
566 .expect("operation should succeed")
567 }
568
569 #[cfg(feature = "no-std")]
570 fn write_times(&self) -> spin::RwLockWriteGuard<'_, HashMap<String, AtomicU64>> {
571 self.total_execution_time.write()
572 }
573
574 #[cfg(not(feature = "no-std"))]
576 fn read_elements(&self) -> std::sync::RwLockReadGuard<'_, HashMap<String, AtomicU64>> {
577 self.total_elements_processed
578 .read()
579 .expect("operation should succeed")
580 }
581
582 #[cfg(feature = "no-std")]
583 fn read_elements(&self) -> spin::RwLockReadGuard<'_, HashMap<String, AtomicU64>> {
584 self.total_elements_processed.read()
585 }
586
587 #[cfg(not(feature = "no-std"))]
589 fn write_elements(&self) -> std::sync::RwLockWriteGuard<'_, HashMap<String, AtomicU64>> {
590 self.total_elements_processed
591 .write()
592 .expect("operation should succeed")
593 }
594
595 #[cfg(feature = "no-std")]
596 fn write_elements(&self) -> spin::RwLockWriteGuard<'_, HashMap<String, AtomicU64>> {
597 self.total_elements_processed.write()
598 }
599
600 pub fn get_stats(&self) -> HashMap<String, OperationStats> {
601 let counts = self.read_counts();
602 let times = self.read_times();
603 let elements = self.read_elements();
604
605 let mut stats = HashMap::new();
606 for (op_name, count) in counts.iter() {
607 let count_val = count.load(Ordering::Relaxed);
608 let time_val = times
609 .get(op_name)
610 .map(|t| Duration::from_nanos(t.load(Ordering::Relaxed)))
611 .unwrap_or_default();
612 let elements_val = elements
613 .get(op_name)
614 .map(|e| e.load(Ordering::Relaxed))
615 .unwrap_or(0);
616
617 stats.insert(
618 op_name.clone(),
619 OperationStats {
620 call_count: count_val,
621 total_time: time_val,
622 total_elements: elements_val,
623 avg_time: if count_val > 0 {
624 time_val / count_val as u32
625 } else {
626 Duration::default()
627 },
628 },
629 );
630 }
631 stats
632 }
633
634 pub fn reset_stats(&self) {
635 self.write_counts().clear();
636 self.write_times().clear();
637 self.write_elements().clear();
638 }
639 }
640
641 impl PerformanceHook for StatsHook {
642 fn on_event(&self, event: &PerformanceEvent) {
643 if event.phase == HookPhase::AfterOperation {
644 let op_name = &event.operation_name;
645
646 {
648 let mut counts = self.write_counts();
649 counts
650 .entry(op_name.clone())
651 .or_insert_with(|| AtomicU64::new(0))
652 .fetch_add(1, Ordering::Relaxed);
653 }
654
655 if let Some(time) = event.execution_time {
657 let mut times = self.write_times();
658 times
659 .entry(op_name.clone())
660 .or_insert_with(|| AtomicU64::new(0))
661 .fetch_add(time.as_nanos() as u64, Ordering::Relaxed);
662 }
663
664 {
666 let mut elements = self.write_elements();
667 elements
668 .entry(op_name.clone())
669 .or_insert_with(|| AtomicU64::new(0))
670 .fetch_add(event.input_size as u64, Ordering::Relaxed);
671 }
672 }
673 }
674
675 fn name(&self) -> &str {
676 &self.name
677 }
678
679 fn interested_phases(&self) -> Vec<HookPhase> {
680 vec![HookPhase::AfterOperation]
681 }
682 }
683
684 #[derive(Debug, Clone)]
685 pub struct OperationStats {
686 pub call_count: u64,
687 pub total_time: Duration,
688 pub total_elements: u64,
689 pub avg_time: Duration,
690 }
691}
692
693#[allow(non_snake_case)]
694#[cfg(all(test, not(feature = "no-std")))]
695mod tests {
696 use super::builtin_hooks::*;
697 use super::*;
698
699 #[test]
700 fn test_hook_registration() {
701 let manager = HookManager::new();
702 let hook = Arc::new(LoggingHook::new("test_hook".to_string()));
703
704 assert!(manager.register_hook(hook).is_ok());
705
706 let stats = manager.get_hook_stats();
707 assert_eq!(stats.total_hooks, 1);
708 assert!(stats.hook_names.contains(&"test_hook".to_string()));
709 }
710
711 #[test]
712 fn test_event_firing() {
713 let manager = HookManager::new();
714 let hook = Arc::new(LoggingHook::new("test_hook".to_string()));
715 manager
716 .register_hook(hook)
717 .expect("operation should succeed");
718
719 let event = PerformanceEvent::new(
720 "test_operation".to_string(),
721 HookPhase::BeforeOperation,
722 100,
723 100,
724 );
725
726 manager.fire_event(event);
727
728 let events = manager.get_events();
729 assert_eq!(events.len(), 1);
730 assert_eq!(events[0].operation_name, "test_operation");
731 }
732
733 #[test]
734 fn test_stats_hook() {
735 let manager = HookManager::new();
736 let stats_hook = Arc::new(StatsHook::new("stats".to_string()));
737 let stats_hook_clone = stats_hook.clone();
738
739 manager
740 .register_hook(stats_hook)
741 .expect("operation should succeed");
742
743 for i in 0..5 {
745 let event =
746 PerformanceEvent::new("test_op".to_string(), HookPhase::AfterOperation, 100, 100)
747 .with_execution_time(Duration::from_millis(i));
748
749 manager.fire_event(event);
750 }
751
752 let stats = stats_hook_clone.get_stats();
753 assert!(stats.contains_key("test_op"));
754
755 let op_stats = &stats["test_op"];
756 assert_eq!(op_stats.call_count, 5);
757 assert_eq!(op_stats.total_elements, 500);
758 }
759
760 #[test]
761 fn test_global_hooks() {
762 let hook = Arc::new(LoggingHook::new("global_test".to_string()));
763 global::register_hook(hook).expect("operation should succeed");
764
765 let event = PerformanceEvent::new(
766 "global_test_op".to_string(),
767 HookPhase::BeforeOperation,
768 50,
769 50,
770 );
771
772 global::fire_event(event);
773
774 let stats = global::get_stats();
775 assert!(stats.hook_names.contains(&"global_test".to_string()));
776 }
777
778 #[test]
779 fn test_enable_disable() {
780 let manager = HookManager::new();
781 assert!(manager.is_enabled());
782
783 manager.set_enabled(false);
784 assert!(!manager.is_enabled());
785
786 manager.set_enabled(true);
787 assert!(manager.is_enabled());
788 }
789
790 #[test]
791 fn test_error_handling() {
792 let manager = HookManager::new();
793 let hook1 = Arc::new(LoggingHook::new("duplicate".to_string()));
794 let hook2 = Arc::new(LoggingHook::new("duplicate".to_string()));
795
796 assert!(manager.register_hook(hook1).is_ok());
797 assert!(matches!(
798 manager.register_hook(hook2),
799 Err(HookError::AlreadyRegistered(_))
800 ));
801
802 assert!(matches!(
803 manager.unregister_hook("nonexistent"),
804 Err(HookError::NotFound(_))
805 ));
806 }
807}