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