quantum_pulse/
lib.rs

1//! # Quantum Pulse - Zero-Cost Profiling Library
2//!
3//! A profiling library that provides true zero-cost abstractions through compile-time
4//! feature selection. When disabled, all profiling code compiles to nothing.
5//!
6//! ## Architecture
7//!
8//! This library provides two implementations:
9//! - **Stub (default)**: Empty implementations that compile away completely
10//! - **Full (opt-in)**: Complete profiling with HDR histograms and reporting
11//!
12//! Both implementations expose the exact same API, allowing you to write clean,
13//! unconditional code that works in both development and production.
14//!
15//! ## Features
16//!
17//! - **True Zero-Cost**: Stub implementations are inlined and eliminated by the optimizer
18//! - **Type-Safe Categories**: Define custom categories with compile-time guarantees
19//! - **Percentile Statistics**: Accurate p50, p95, p99, p99.9 using HDR histograms (full mode)
20//! - **Clean API**: No conditional compilation needed in your code
21//! - **Async Support**: Full support for async/await patterns
22//! - **Thread-Safe**: Safe to use from multiple threads
23//!
24//! ## Quick Start
25//!
26//! ```rust
27//! use quantum_pulse::{Profiler, Category, profile};
28//!
29//! // Your code always looks the same, regardless of features
30//! let result = profile!("database_query" => {
31//!     expensive_database_query()
32//! });
33//!
34//! // With default features (stub): compiles to just `expensive_database_query()`
35//! // With "full" feature: includes timing and statistics
36//!
37//! // Generate a report (empty in stub mode, full in full mode)
38//! let report = Profiler::report();
39//! println!("{}", report);
40//! ```
41//!
42//! ## Zero-Cost Guarantee
43//!
44//! When the "full" feature is not enabled, all methods have empty bodies that
45//! are marked for inlining. The compiler's optimizer completely removes these
46//! calls, resulting in zero runtime overhead and minimal binary size impact.
47
48// Implementation selection based on features
49// When "full" feature is enabled, use the complete implementation
50// Otherwise, use stub implementations that compile to nothing
51
52// Full implementation modules - complete profiling functionality
53#[cfg(feature = "full")]
54pub mod category;
55#[cfg(feature = "full")]
56pub mod collector;
57#[cfg(feature = "full")]
58pub mod metrics;
59#[cfg(feature = "full")]
60pub mod reporter;
61#[cfg(feature = "full")]
62pub mod timer;
63
64// Stub implementation modules - zero-cost abstractions
65// These modules provide the same API but with empty implementations
66// that are completely optimized away by the compiler
67#[cfg(not(feature = "full"))]
68pub mod category {
69    pub trait Category: Clone + Eq + std::hash::Hash + Send + Sync + 'static {
70        fn description(&self) -> Option<&str> {
71            None
72        }
73        fn color_hint(&self) -> Option<&str> {
74            None
75        }
76        fn priority(&self) -> i32 {
77            0
78        }
79    }
80
81    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
82    pub enum DefaultCategory {
83        IO,
84        Compute,
85        Network,
86        Database,
87        Other,
88    }
89
90    impl Category for DefaultCategory {}
91}
92
93#[cfg(not(feature = "full"))]
94pub mod collector {
95    use super::category::Category;
96    use std::collections::HashMap;
97
98    #[derive(Debug, Clone, Default)]
99    pub struct OperationStats {
100        pub count: u64,
101        pub total_micros: u64,
102        pub min_micros: u64,
103        pub max_micros: u64,
104        pub mean_micros: u64,
105    }
106
107    pub struct ProfileCollector;
108
109    impl ProfileCollector {
110        pub fn record(_operation: &str, _duration_micros: u64) {}
111        pub fn record_with_category<C: Category>(
112            _operation: &str,
113            _category: C,
114            _duration_micros: u64,
115        ) {
116        }
117        pub fn get_stats(_operation: &str) -> Option<OperationStats> {
118            None
119        }
120        pub fn get_all_stats() -> HashMap<String, OperationStats> {
121            HashMap::new()
122        }
123        pub fn get_category<C: Category>(_operation: &str) -> Option<C> {
124            None
125        }
126        pub fn clear_all() {}
127        pub fn reset_all() {}
128        pub fn reset_operation(_operation: &str) {}
129        pub fn has_data() -> bool {
130            false
131        }
132        pub fn total_operations() -> u64 {
133            0
134        }
135        pub fn get_summary() -> SummaryStats {
136            SummaryStats::default()
137        }
138        pub fn get_stats_by_category<C: Category>() -> HashMap<C, Vec<(String, OperationStats)>> {
139            HashMap::new()
140        }
141    }
142
143    #[derive(Debug, Default)]
144    pub struct SummaryStats {
145        pub total_operations: u64,
146        pub unique_operations: usize,
147        pub total_time_micros: u64,
148    }
149}
150
151#[cfg(not(feature = "full"))]
152pub mod reporter {
153    use super::category::Category;
154    use super::collector::OperationStats;
155    use std::collections::HashMap;
156
157    #[derive(Debug, Clone, Copy)]
158    pub enum TimeFormat {
159        Microseconds,
160        Milliseconds,
161        Seconds,
162        Auto,
163    }
164
165    #[derive(Debug, Clone, Copy)]
166    pub enum SortMetric {
167        TotalTime,
168        MeanTime,
169        MaxTime,
170        CallCount,
171    }
172
173    #[derive(Debug, Clone, Copy)]
174    pub struct Percentile {
175        pub p50: u64,
176        pub p95: u64,
177        pub p99: u64,
178        pub p999: u64,
179    }
180
181    #[derive(Debug)]
182    pub struct ReportConfig {
183        pub include_percentiles: bool,
184        pub group_by_category: bool,
185        pub time_format: TimeFormat,
186        pub sort_by: SortMetric,
187        pub sort_by_time: bool,
188        pub min_samples: u64,
189    }
190
191    impl Default for ReportConfig {
192        fn default() -> Self {
193            Self {
194                include_percentiles: false,
195                group_by_category: false,
196                time_format: TimeFormat::Auto,
197                sort_by: SortMetric::TotalTime,
198                sort_by_time: false,
199                min_samples: 0,
200            }
201        }
202    }
203
204    pub struct ProfileReport<C: Category> {
205        pub stats: HashMap<String, OperationStats>,
206        pub categories: HashMap<String, C>,
207        pub config: ReportConfig,
208    }
209
210    impl<C: Category> ProfileReport<C> {
211        pub fn generate() -> Self {
212            Self {
213                stats: HashMap::new(),
214                categories: HashMap::new(),
215                config: ReportConfig::default(),
216            }
217        }
218
219        pub fn generate_with_config(_config: ReportConfig) -> Self {
220            Self {
221                stats: HashMap::new(),
222                categories: HashMap::new(),
223                config: ReportConfig::default(),
224            }
225        }
226
227        pub fn quick_summary(&self) -> String {
228            String::new()
229        }
230
231        pub fn summary_stats(&self) -> super::collector::SummaryStats {
232            super::collector::SummaryStats::default()
233        }
234
235        pub fn to_string(&self) -> String {
236            String::new()
237        }
238
239        pub fn top_operations_by(
240            &self,
241            _metric: SortMetric,
242            _limit: usize,
243        ) -> Vec<(String, OperationStats)> {
244            Vec::new()
245        }
246    }
247
248    impl<C: Category> std::fmt::Debug for ProfileReport<C> {
249        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250            write!(f, "")
251        }
252    }
253
254    pub struct ReportBuilder<C: Category> {
255        _phantom: std::marker::PhantomData<C>,
256    }
257
258    impl<C: Category> Default for ReportBuilder<C> {
259        fn default() -> Self {
260            Self::new()
261        }
262    }
263
264    impl<C: Category> ReportBuilder<C> {
265        pub fn new() -> Self {
266            Self {
267                _phantom: std::marker::PhantomData,
268            }
269        }
270
271        pub fn group_by_category(self, _enabled: bool) -> Self {
272            self
273        }
274        pub fn include_percentiles(self, _enabled: bool) -> Self {
275            self
276        }
277        pub fn time_format(self, _format: TimeFormat) -> Self {
278            self
279        }
280        pub fn sort_by_time(self, _enabled: bool) -> Self {
281            self
282        }
283        pub fn build(self) -> ProfileReport<C> {
284            ProfileReport::generate()
285        }
286    }
287}
288
289#[cfg(not(feature = "full"))]
290pub mod timer {
291    use super::category::Category;
292
293    pub struct ProfileTimer;
294
295    impl ProfileTimer {
296        pub fn new(_operation: &str) -> Self {
297            ProfileTimer
298        }
299
300        pub fn with_category<C: Category>(_operation: &str, _category: C) -> Self {
301            ProfileTimer
302        }
303    }
304
305    impl Drop for ProfileTimer {
306        fn drop(&mut self) {
307            // No-op in stub mode
308        }
309    }
310
311    pub struct PausableTimer;
312}
313
314// Re-export the appropriate implementations based on feature flags
315pub use category::{Category, DefaultCategory};
316pub use collector::{OperationStats, ProfileCollector};
317pub use reporter::{
318    Percentile, ProfileReport, ReportBuilder, ReportConfig, SortMetric, TimeFormat,
319};
320pub use timer::{PausableTimer, ProfileTimer};
321
322#[cfg(not(feature = "full"))]
323pub use collector::SummaryStats;
324#[cfg(feature = "full")]
325pub use collector::SummaryStats;
326
327use std::marker::PhantomData;
328
329/// Main profiling interface with customizable categories
330///
331/// This struct provides a unified API for profiling that works in both
332/// stub and full modes. In stub mode, all methods are no-ops that compile
333/// away. In full mode, complete profiling functionality is provided.
334///
335/// # Examples
336///
337/// ```rust
338/// use quantum_pulse::Profiler;
339///
340/// // This code works identically in both modes
341/// let result = Profiler::time("operation", || {
342///     perform_operation()
343/// });
344/// ```
345pub struct Profiler<C: Category = DefaultCategory> {
346    _phantom: PhantomData<C>,
347}
348
349impl<C: Category> Default for Profiler<C> {
350    fn default() -> Self {
351        Self::new()
352    }
353}
354
355impl<C: Category> Profiler<C> {
356    /// Create a new profiler with custom categories
357    pub fn new() -> Self {
358        Self {
359            _phantom: PhantomData,
360        }
361    }
362
363    /// Profile a code block and automatically record timing
364    ///
365    /// # Example
366    /// ```rust
367    /// use profile_timer::Profiler;
368    ///
369    /// let result = Profiler::time("my_operation", || {
370    ///     // Your code here
371    ///     expensive_operation()
372    /// });
373    /// ```
374    #[cfg(feature = "full")]
375    pub fn time<T, F>(operation: &str, f: F) -> T
376    where
377        F: FnOnce() -> T,
378    {
379        let _timer = ProfileTimer::new(operation);
380        f()
381    }
382
383    #[cfg(not(feature = "full"))]
384    pub fn time<T, F>(_operation: &str, f: F) -> T
385    where
386        F: FnOnce() -> T,
387    {
388        f()
389    }
390
391    /// Profile a code block with a specific category
392    #[cfg(feature = "full")]
393    pub fn time_with_category<T, F>(operation: &str, category: C, f: F) -> T
394    where
395        F: FnOnce() -> T,
396    {
397        let _timer = ProfileTimer::with_category(operation, category);
398        f()
399    }
400
401    #[cfg(not(feature = "full"))]
402    pub fn time_with_category<T, F>(_operation: &str, _category: C, f: F) -> T
403    where
404        F: FnOnce() -> T,
405    {
406        f()
407    }
408
409    /// Profile an async code block
410    #[cfg(feature = "full")]
411    pub async fn time_async<T, F, Fut>(operation: &str, f: F) -> T
412    where
413        F: FnOnce() -> Fut,
414        Fut: std::future::Future<Output = T>,
415    {
416        let _timer = ProfileTimer::new(operation);
417        f().await
418    }
419
420    #[cfg(not(feature = "full"))]
421    pub async fn time_async<T, F, Fut>(_operation: &str, f: F) -> T
422    where
423        F: FnOnce() -> Fut,
424        Fut: std::future::Future<Output = T>,
425    {
426        f().await
427    }
428
429    /// Profile an async code block with a specific category
430    #[cfg(feature = "full")]
431    pub async fn time_async_with_category<T, F, Fut>(operation: &str, category: C, f: F) -> T
432    where
433        F: FnOnce() -> Fut,
434        Fut: std::future::Future<Output = T>,
435    {
436        let _timer = ProfileTimer::with_category(operation, category);
437        f().await
438    }
439
440    #[cfg(not(feature = "full"))]
441    pub async fn time_async_with_category<T, F, Fut>(_operation: &str, _category: C, f: F) -> T
442    where
443        F: FnOnce() -> Fut,
444        Fut: std::future::Future<Output = T>,
445    {
446        f().await
447    }
448
449    /// Get a comprehensive profiling report
450    pub fn report() -> ProfileReport<C> {
451        ProfileReport::generate()
452    }
453
454    /// Get a report with custom configuration
455    pub fn report_with_config(config: ReportConfig) -> ProfileReport<C> {
456        ProfileReport::generate_with_config(config)
457    }
458
459    /// Reset all profiling metrics
460    pub fn reset() {
461        ProfileCollector::reset_all()
462    }
463
464    /// Reset metrics for a specific operation
465    pub fn reset_operation(operation: &str) {
466        ProfileCollector::reset_operation(operation)
467    }
468
469    /// Record a raw metric value (in microseconds)
470    pub fn record(operation: &str, value_micros: u64) {
471        ProfileCollector::record(operation, value_micros);
472    }
473
474    /// Record a metric with a specific category
475    pub fn record_with_category(operation: &str, category: C, value_micros: u64) {
476        ProfileCollector::record_with_category(operation, category, value_micros);
477    }
478
479    /// Check if any profiling data has been collected
480    pub fn has_data() -> bool {
481        ProfileCollector::has_data()
482    }
483
484    /// Get total number of operations recorded
485    pub fn total_operations() -> u64 {
486        ProfileCollector::total_operations()
487    }
488
489    /// Get statistics for a specific operation
490    pub fn get_stats(operation: &str) -> Option<OperationStats> {
491        ProfileCollector::get_stats(operation)
492    }
493
494    /// Get all statistics
495    pub fn get_all_stats() -> std::collections::HashMap<String, OperationStats> {
496        ProfileCollector::get_all_stats()
497    }
498}
499
500/// Builder for creating configured profiler instances
501pub struct ProfilerBuilder<C: Category = DefaultCategory> {
502    config: ReportConfig,
503    _phantom: PhantomData<C>,
504}
505
506impl<C: Category> ProfilerBuilder<C> {
507    /// Create a new profiler builder
508    pub fn new() -> Self {
509        Self {
510            config: ReportConfig::default(),
511            _phantom: PhantomData,
512        }
513    }
514
515    /// Set whether to include percentiles in reports
516    pub fn with_percentiles(mut self, include: bool) -> Self {
517        self.config.include_percentiles = include;
518        self
519    }
520
521    /// Set whether to sort operations by time in reports
522    pub fn sort_by_time(mut self, sort: bool) -> Self {
523        self.config.sort_by_time = sort;
524        self
525    }
526
527    /// Set minimum number of samples required for an operation to appear in reports
528    pub fn min_samples(mut self, min: u64) -> Self {
529        self.config.min_samples = min;
530        self
531    }
532
533    /// Set whether to group operations by category in reports
534    pub fn group_by_category(mut self, group: bool) -> Self {
535        self.config.group_by_category = group;
536        self
537    }
538
539    /// Set the time format for reports
540    pub fn time_format(mut self, format: TimeFormat) -> Self {
541        self.config.time_format = format;
542        self
543    }
544
545    /// Build the profiler
546    pub fn build(self) -> Profiler<C> {
547        Profiler::new()
548    }
549}
550
551impl<C: Category> Default for ProfilerBuilder<C> {
552    fn default() -> Self {
553        Self::new()
554    }
555}
556
557/// Convenience macro for profiling code blocks
558///
559/// # Example
560/// ```rust
561/// use profile_timer::profile;
562///
563/// let result = profile!("database_query" => {
564///     database.query("SELECT * FROM users")
565/// });
566/// ```
567#[macro_export]
568macro_rules! profile {
569    // Enum-based profiling with automatic Debug formatting and Category trait
570    ($operation:expr => $block:block) => {{
571        let op_name = format!("{:?}", $operation);
572        $crate::Profiler::time_with_category(&op_name, $operation, || $block)
573    }};
574    ($operation:expr => async $block:block) => {{
575        let op_name = format!("{:?}", $operation);
576        $crate::Profiler::time_async_with_category(&op_name, $operation, || async move $block).await
577    }};
578    // Legacy string-based profiling with explicit category (deprecated - use enums instead)
579    ($name:expr, $category:expr => $block:block) => {
580        $crate::Profiler::time_with_category($name, $category, || $block)
581    };
582    ($name:expr, $category:expr => async $block:block) => {
583        $crate::Profiler::time_async_with_category($name, $category, || async move $block).await
584    };
585}
586
587/// Conditional profiling that only activates when a condition is met
588#[macro_export]
589macro_rules! profile_if {
590    // Conditional enum-based profiling
591    ($condition:expr, $operation:expr => $block:block) => {
592        if $condition {
593            $crate::profile!($operation => $block)
594        } else {
595            $block
596        }
597    };
598    // Legacy conditional string-based profiling (deprecated)
599    ($condition:expr, $name:expr, $category:expr => $block:block) => {
600        if $condition {
601            $crate::profile!($name, $category => $block)
602        } else {
603            $block
604        }
605    };
606}
607
608/// Create a scoped timer that records on drop
609#[macro_export]
610macro_rules! scoped_timer {
611    ($name:expr) => {
612        let _timer = $crate::ProfileTimer::<$crate::DefaultCategory>::new($name);
613    };
614    ($name:expr, $category:expr) => {
615        let _timer = $crate::ProfileTimer::with_category($name, $category);
616    };
617}
618
619#[cfg(test)]
620mod tests {
621    use super::*;
622
623    #[test]
624    fn test_basic_profiling() {
625        Profiler::<DefaultCategory>::reset();
626
627        let result = Profiler::<DefaultCategory>::time("test_operation", || {
628            std::thread::sleep(std::time::Duration::from_millis(1));
629            42
630        });
631
632        assert_eq!(result, 42);
633        assert!(Profiler::<DefaultCategory>::has_data());
634
635        let stats = Profiler::<DefaultCategory>::get_stats("test_operation");
636        assert!(stats.is_some());
637        assert_eq!(stats.unwrap().count, 1);
638    }
639
640    #[test]
641    fn test_custom_category() {
642        #[derive(Debug, Clone, PartialEq, Eq, Hash)]
643        enum TestCategory {
644            Fast,
645            Slow,
646        }
647
648        impl Category for TestCategory {}
649
650        Profiler::<TestCategory>::reset();
651
652        Profiler::<TestCategory>::record_with_category("op1", TestCategory::Fast, 100);
653        Profiler::<TestCategory>::record_with_category("op2", TestCategory::Slow, 1000);
654
655        assert_eq!(Profiler::<TestCategory>::total_operations(), 2);
656    }
657
658    #[tokio::test]
659    async fn test_async_profiling() {
660        Profiler::<DefaultCategory>::reset();
661
662        let result = Profiler::<DefaultCategory>::time_async("async_test", || async {
663            tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
664            "async_result"
665        })
666        .await;
667
668        assert_eq!(result, "async_result");
669        assert!(Profiler::<DefaultCategory>::has_data());
670    }
671
672    #[test]
673    fn test_profile_macro() {
674        Profiler::<DefaultCategory>::reset();
675
676        let result = profile!("macro_test", DefaultCategory::Other => {
677            std::thread::sleep(std::time::Duration::from_millis(1));
678            100
679        });
680
681        assert_eq!(result, 100);
682        assert!(Profiler::<DefaultCategory>::get_stats("macro_test").is_some());
683    }
684
685    #[test]
686    fn test_conditional_profiling() {
687        Profiler::<DefaultCategory>::reset();
688
689        let should_profile = true;
690        let result = profile_if!(should_profile, "conditional_test", DefaultCategory::Other => {
691            42
692        });
693
694        assert_eq!(result, 42);
695        assert!(Profiler::<DefaultCategory>::get_stats("conditional_test").is_some());
696
697        let should_not_profile = false;
698        let result2 = profile_if!(should_not_profile, "conditional_test2", DefaultCategory::Other => {
699            84
700        });
701
702        assert_eq!(result2, 84);
703        assert!(Profiler::<DefaultCategory>::get_stats("conditional_test2").is_none());
704    }
705}