metrics_lib/
timer.rs

1//! # High-Precision Timer
2//!
3//! Ultra-fast timer implementation with nanosecond precision.
4//!
5//! ## Features
6//!
7//! - **Nanosecond precision** - Using system high-resolution clocks
8//! - **Zero allocation** - Pure stack operations
9//! - **RAII support** - Automatic timing with scoped timers
10//! - **Lock-free** - Never blocks, never waits
11//! - **Cache optimized** - Aligned to prevent false sharing
12
13use crate::{MetricsError, Result};
14use std::sync::atomic::{AtomicU64, Ordering};
15use std::time::{Duration, Instant};
16
17/// High-precision timer with automatic statistics
18///
19/// Optimized for sub-10ns timing operations.
20/// Cache-line aligned to prevent false sharing.
21#[repr(align(64))]
22pub struct Timer {
23    /// Number of timing samples
24    count: AtomicU64,
25    /// Sum of all recorded durations in nanoseconds
26    total_nanos: AtomicU64,
27    /// Minimum duration in nanoseconds
28    min_nanos: AtomicU64,
29    /// Maximum duration in nanoseconds
30    max_nanos: AtomicU64,
31    /// Creation timestamp
32    created_at: Instant,
33}
34
35/// Running timer instance (RAII)
36///
37/// When dropped, automatically records the elapsed time
38pub struct RunningTimer<'a> {
39    timer: &'a Timer,
40    start_time: Instant,
41    stopped: bool,
42}
43
44/// Timer statistics
45#[derive(Debug, Clone)]
46pub struct TimerStats {
47    /// Total number of timing samples
48    pub count: u64,
49    /// Sum of all durations
50    pub total: Duration,
51    /// Average duration
52    pub average: Duration,
53    /// Minimum duration recorded
54    pub min: Duration,
55    /// Maximum duration recorded
56    pub max: Duration,
57    /// Time since timer creation
58    pub age: Duration,
59    /// Rate of samples per second
60    pub rate_per_second: f64,
61}
62
63impl Timer {
64    /// Create new timer
65    #[inline]
66    pub fn new() -> Self {
67        Self {
68            count: AtomicU64::new(0),
69            total_nanos: AtomicU64::new(0),
70            min_nanos: AtomicU64::new(u64::MAX),
71            max_nanos: AtomicU64::new(0),
72            created_at: Instant::now(),
73        }
74    }
75
76    /// Try to record nanoseconds directly with overflow checks
77    ///
78    /// Returns `Err(MetricsError::Overflow)` if adding `duration_ns` would
79    /// overflow internal counters (`total_nanos` or `count`). On success
80    /// updates min/max as needed.
81    ///
82    /// Example
83    /// ```
84    /// use metrics_lib::{Timer, MetricsError};
85    /// let t = Timer::new();
86    /// t.try_record_ns(1_000).unwrap();
87    /// assert_eq!(t.count(), 1);
88    /// assert!(matches!(t.try_record_ns(u64::MAX), Err(MetricsError::Overflow)));
89    /// ```
90    #[inline(always)]
91    pub fn try_record_ns(&self, duration_ns: u64) -> Result<()> {
92        // Check total_nanos overflow
93        let total = self.total_nanos.load(Ordering::Relaxed);
94        if total.checked_add(duration_ns).is_none() {
95            return Err(MetricsError::Overflow);
96        }
97
98        // Check count overflow
99        let cnt = self.count.load(Ordering::Relaxed);
100        if cnt == u64::MAX {
101            return Err(MetricsError::Overflow);
102        }
103
104        // Apply updates
105        self.total_nanos.fetch_add(duration_ns, Ordering::Relaxed);
106        self.count.fetch_add(1, Ordering::Relaxed);
107
108        // Update min and max
109        self.update_min(duration_ns);
110        self.update_max(duration_ns);
111        Ok(())
112    }
113
114    /// Start timing - returns RAII guard that auto-records on drop
115    #[inline(always)]
116    pub fn start(&self) -> RunningTimer<'_> {
117        RunningTimer {
118            timer: self,
119            start_time: Instant::now(),
120            stopped: false,
121        }
122    }
123
124    /// Record a duration manually
125    #[inline]
126    pub fn record(&self, duration: Duration) {
127        let duration_ns = duration.as_nanos() as u64;
128        self.record_ns(duration_ns);
129    }
130
131    /// Try to record a duration with overflow checks
132    ///
133    /// Returns `Err(MetricsError::Overflow)` if adding this sample would overflow
134    /// internal counters.
135    ///
136    /// Example
137    /// ```
138    /// use metrics_lib::{Timer, MetricsError};
139    /// use std::time::Duration;
140    /// let t = Timer::new();
141    /// assert!(t.try_record(Duration::from_millis(1)).is_ok());
142    /// ```
143    #[inline]
144    pub fn try_record(&self, duration: Duration) -> Result<()> {
145        let duration_ns = duration.as_nanos() as u64;
146        self.try_record_ns(duration_ns)
147    }
148
149    /// Record nanoseconds directly - fastest path
150    #[inline(always)]
151    pub fn record_ns(&self, duration_ns: u64) {
152        // Update total and count
153        self.total_nanos.fetch_add(duration_ns, Ordering::Relaxed);
154        self.count.fetch_add(1, Ordering::Relaxed);
155
156        // Update min (compare-and-swap loop)
157        self.update_min(duration_ns);
158
159        // Update max (compare-and-swap loop)
160        self.update_max(duration_ns);
161    }
162
163    /// Record multiple durations at once
164    #[inline]
165    pub fn record_batch(&self, durations: &[Duration]) {
166        if durations.is_empty() {
167            return;
168        }
169
170        let mut total_ns = 0u64;
171        let mut local_min = u64::MAX;
172        let mut local_max = 0u64;
173
174        for duration in durations {
175            let ns = duration.as_nanos() as u64;
176            total_ns += ns;
177            local_min = local_min.min(ns);
178            local_max = local_max.max(ns);
179        }
180
181        self.total_nanos.fetch_add(total_ns, Ordering::Relaxed);
182        self.count
183            .fetch_add(durations.len() as u64, Ordering::Relaxed);
184
185        if local_min < u64::MAX {
186            self.update_min(local_min);
187        }
188        if local_max > 0 {
189            self.update_max(local_max);
190        }
191    }
192
193    /// Try to record multiple durations at once with overflow checks
194    ///
195    /// Returns `Err(MetricsError::Overflow)` if the aggregated additions would
196    /// overflow internal counters. Updates min/max based on batch extrema.
197    ///
198    /// Example
199    /// ```
200    /// use metrics_lib::Timer;
201    /// use std::time::Duration;
202    /// let t = Timer::new();
203    /// t.try_record_batch(&[Duration::from_micros(5), Duration::from_micros(10)]).unwrap();
204    /// assert_eq!(t.count(), 2);
205    /// ```
206    #[inline]
207    pub fn try_record_batch(&self, durations: &[Duration]) -> Result<()> {
208        if durations.is_empty() {
209            return Ok(());
210        }
211
212        let mut total_ns: u64 = 0;
213        let mut local_min = u64::MAX;
214        let mut local_max = 0u64;
215
216        for d in durations {
217            let ns = d.as_nanos() as u64;
218            total_ns = total_ns.checked_add(ns).ok_or(MetricsError::Overflow)?;
219            local_min = local_min.min(ns);
220            local_max = local_max.max(ns);
221        }
222
223        // Check counters will not overflow
224        let current_total = self.total_nanos.load(Ordering::Relaxed);
225        if current_total.checked_add(total_ns).is_none() {
226            return Err(MetricsError::Overflow);
227        }
228        let current_count = self.count.load(Ordering::Relaxed);
229        let add_count = durations.len() as u64;
230        if current_count.checked_add(add_count).is_none() {
231            return Err(MetricsError::Overflow);
232        }
233
234        // Apply updates
235        self.total_nanos.fetch_add(total_ns, Ordering::Relaxed);
236        self.count.fetch_add(add_count, Ordering::Relaxed);
237        if local_min < u64::MAX {
238            self.update_min(local_min);
239        }
240        if local_max > 0 {
241            self.update_max(local_max);
242        }
243        Ok(())
244    }
245
246    /// Get current count of samples
247    ///
248    /// Note: `#[must_use]`. The count informs control flow and sanity checks;
249    /// ignoring it may indicate a logic bug.
250    #[must_use]
251    #[inline(always)]
252    pub fn count(&self) -> u64 {
253        self.count.load(Ordering::Relaxed)
254    }
255
256    /// Get total accumulated time
257    ///
258    /// Note: `#[must_use]`. The total duration is commonly used for reporting
259    /// and ratios; dropping it may indicate a bug.
260    #[must_use]
261    #[inline]
262    pub fn total(&self) -> Duration {
263        Duration::from_nanos(self.total_nanos.load(Ordering::Relaxed))
264    }
265
266    /// Get average duration
267    ///
268    /// Note: `#[must_use]`. The average is a derived metric; call this only
269    /// when you consume the result.
270    #[must_use]
271    #[inline]
272    pub fn average(&self) -> Duration {
273        let count = self.count();
274        if count == 0 {
275            return Duration::ZERO;
276        }
277
278        let total_ns = self.total_nanos.load(Ordering::Relaxed);
279        Duration::from_nanos(total_ns / count)
280    }
281
282    /// Get minimum duration
283    ///
284    /// Note: `#[must_use]`. Often used for alerting/thresholds.
285    #[must_use]
286    #[inline]
287    pub fn min(&self) -> Duration {
288        let min_ns = self.min_nanos.load(Ordering::Relaxed);
289        if min_ns == u64::MAX {
290            Duration::ZERO
291        } else {
292            Duration::from_nanos(min_ns)
293        }
294    }
295
296    /// Get maximum duration
297    ///
298    /// Note: `#[must_use]`. Often used for alerting/thresholds.
299    #[must_use]
300    #[inline]
301    pub fn max(&self) -> Duration {
302        Duration::from_nanos(self.max_nanos.load(Ordering::Relaxed))
303    }
304
305    /// Reset all statistics
306    #[inline]
307    pub fn reset(&self) {
308        self.total_nanos.store(0, Ordering::SeqCst);
309        self.count.store(0, Ordering::SeqCst);
310        self.min_nanos.store(u64::MAX, Ordering::SeqCst);
311        self.max_nanos.store(0, Ordering::SeqCst);
312    }
313
314    /// Get comprehensive statistics
315    ///
316    /// Note: `#[must_use]`. Statistics summarize current state; dropping the
317    /// result may indicate a logic bug.
318    #[must_use]
319    pub fn stats(&self) -> TimerStats {
320        let count = self.count();
321        let total_ns = self.total_nanos.load(Ordering::Relaxed);
322        let min_ns = self.min_nanos.load(Ordering::Relaxed);
323        let max_ns = self.max_nanos.load(Ordering::Relaxed);
324
325        let total = Duration::from_nanos(total_ns);
326        let average = if count > 0 {
327            Duration::from_nanos(total_ns / count)
328        } else {
329            Duration::ZERO
330        };
331
332        let min = if min_ns == u64::MAX {
333            Duration::ZERO
334        } else {
335            Duration::from_nanos(min_ns)
336        };
337
338        let max = Duration::from_nanos(max_ns);
339        let age = self.created_at.elapsed();
340
341        let rate_per_second = if age.as_secs_f64() > 0.0 {
342            count as f64 / age.as_secs_f64()
343        } else {
344            0.0
345        };
346
347        TimerStats {
348            count,
349            total,
350            average,
351            min,
352            max,
353            age,
354            rate_per_second,
355        }
356    }
357
358    /// Get age since creation
359    ///
360    /// Note: `#[must_use]`. Age is used in rate calculations; don't call for
361    /// side effects.
362    #[must_use]
363    #[inline]
364    pub fn age(&self) -> Duration {
365        self.created_at.elapsed()
366    }
367
368    /// Check if timer has recorded any samples
369    ///
370    /// Note: `#[must_use]`. The boolean result determines control flow.
371    #[must_use]
372    #[inline]
373    pub fn is_empty(&self) -> bool {
374        self.count() == 0
375    }
376
377    /// Get samples per second rate
378    ///
379    /// Note: `#[must_use]`. Derived metric; consume the result.
380    #[must_use]
381    #[inline]
382    pub fn rate_per_second(&self) -> f64 {
383        let age_seconds = self.age().as_secs_f64();
384        if age_seconds > 0.0 {
385            self.count() as f64 / age_seconds
386        } else {
387            0.0
388        }
389    }
390
391    // Internal helper methods
392
393    #[inline(always)]
394    fn update_min(&self, value: u64) {
395        loop {
396            let current = self.min_nanos.load(Ordering::Relaxed);
397            if value >= current {
398                break; // Current min is already smaller
399            }
400
401            match self.min_nanos.compare_exchange_weak(
402                current,
403                value,
404                Ordering::Relaxed,
405                Ordering::Relaxed,
406            ) {
407                Ok(_) => break,
408                Err(_) => continue, // Retry
409            }
410        }
411    }
412
413    #[inline(always)]
414    fn update_max(&self, value: u64) {
415        loop {
416            let current = self.max_nanos.load(Ordering::Relaxed);
417            if value <= current {
418                break; // Current max is already larger
419            }
420
421            match self.max_nanos.compare_exchange_weak(
422                current,
423                value,
424                Ordering::Relaxed,
425                Ordering::Relaxed,
426            ) {
427                Ok(_) => break,
428                Err(_) => continue, // Retry
429            }
430        }
431    }
432}
433
434impl Default for Timer {
435    #[inline]
436    fn default() -> Self {
437        Self::new()
438    }
439}
440
441impl std::fmt::Display for Timer {
442    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
443        let stats = self.stats();
444        write!(f, "Timer(count: {}, avg: {:?})", stats.count, stats.average)
445    }
446}
447
448impl std::fmt::Debug for Timer {
449    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
450        let stats = self.stats();
451        f.debug_struct("Timer")
452            .field("count", &stats.count)
453            .field("total", &stats.total)
454            .field("average", &stats.average)
455            .field("min", &stats.min)
456            .field("max", &stats.max)
457            .field("rate_per_second", &stats.rate_per_second)
458            .finish()
459    }
460}
461
462// Thread safety
463unsafe impl Send for Timer {}
464unsafe impl Sync for Timer {}
465
466/// Running timer implementation (RAII)
467impl<'a> RunningTimer<'a> {
468    /// Get elapsed time without stopping the timer
469    ///
470    /// Note: `#[must_use]`. The elapsed duration is typically used in logic or
471    /// reporting; ignoring it may indicate a bug.
472    #[must_use]
473    #[inline]
474    pub fn elapsed(&self) -> Duration {
475        self.start_time.elapsed()
476    }
477
478    /// Stop the timer manually and record the duration
479    #[inline]
480    pub fn stop(mut self) {
481        if !self.stopped {
482            let elapsed = self.start_time.elapsed();
483            self.timer.record(elapsed);
484            self.stopped = true;
485        }
486        // Timer is consumed here, preventing double recording
487    }
488}
489
490impl<'a> Drop for RunningTimer<'a> {
491    #[inline]
492    fn drop(&mut self) {
493        if !self.stopped {
494            let elapsed = self.start_time.elapsed();
495            self.timer.record(elapsed);
496        }
497    }
498}
499
500/// Utility functions for timing
501pub mod utils {
502    use super::*;
503
504    /// Time a function execution and return the result + duration
505    #[inline]
506    pub fn time_fn<T>(f: impl FnOnce() -> T) -> (T, Duration) {
507        let start = Instant::now();
508        let result = f();
509        let duration = start.elapsed();
510        (result, duration)
511    }
512
513    /// Time a function and record the duration in the provided timer
514    #[inline]
515    pub fn time_and_record<T>(timer: &Timer, f: impl FnOnce() -> T) -> T {
516        let _timing_guard = timer.start();
517        f() // Timer automatically records when guard drops
518    }
519
520    /// Create a scoped timer that records to the provided timer
521    #[inline]
522    pub fn scoped_timer(timer: &Timer) -> RunningTimer<'_> {
523        timer.start()
524    }
525
526    /// Benchmark a function with multiple iterations
527    pub fn benchmark<F>(name: &str, iterations: usize, f: F) -> Duration
528    where
529        F: Fn(),
530    {
531        let timer = Timer::new();
532
533        for _ in 0..iterations {
534            let _guard = timer.start();
535            f();
536        }
537
538        let stats = timer.stats();
539        println!(
540            "Benchmark '{}': {} iterations, avg: {:?}, total: {:?}",
541            name, iterations, stats.average, stats.total
542        );
543
544        stats.average
545    }
546}
547
548/// Convenience macros
549#[macro_export]
550macro_rules! time_block {
551    ($timer:expr, $block:block) => {{
552        let _timing_guard = $timer.start();
553        $block
554    }};
555}
556
557#[macro_export]
558/// Macro to time a function call and record the result
559///
560/// # Examples
561///
562/// ```rust
563/// # use metrics_lib::time_fn;
564/// let (result, duration) = time_fn!({
565///     // Some work to time
566///     std::thread::sleep(std::time::Duration::from_millis(10));
567///     "done"
568/// });
569/// assert!(duration >= std::time::Duration::from_millis(10));
570/// assert_eq!(result, "done");
571/// ```
572macro_rules! time_fn {
573    ($func:expr) => {{
574        let start = std::time::Instant::now();
575        let result = $func;
576        let duration = start.elapsed();
577        (result, duration)
578    }};
579}
580
581#[cfg(test)]
582mod tests {
583    use super::*;
584    use std::sync::Arc;
585    use std::thread;
586
587    #[test]
588    fn test_basic_operations() {
589        let timer = Timer::new();
590
591        assert!(timer.is_empty());
592        assert_eq!(timer.count(), 0);
593        assert_eq!(timer.total(), Duration::ZERO);
594        assert_eq!(timer.average(), Duration::ZERO);
595
596        // Record a duration
597        timer.record(Duration::from_millis(100));
598
599        assert!(!timer.is_empty());
600        assert_eq!(timer.count(), 1);
601        assert!(timer.total() >= Duration::from_millis(99)); // Account for precision
602        assert!(timer.average() >= Duration::from_millis(99));
603    }
604
605    #[test]
606    fn test_running_timer() {
607        let timer = Timer::new();
608
609        {
610            let running = timer.start();
611            thread::sleep(Duration::from_millis(10));
612            assert!(running.elapsed() >= Duration::from_millis(9));
613        } // Automatically recorded when dropped
614
615        assert_eq!(timer.count(), 1);
616        assert!(timer.average() >= Duration::from_millis(9));
617    }
618
619    #[test]
620    fn test_manual_stop() {
621        let timer = Timer::new();
622
623        let running = timer.start();
624        thread::sleep(Duration::from_millis(5));
625        running.stop(); // Manual stop
626
627        assert_eq!(timer.count(), 1);
628        assert!(timer.average() >= Duration::from_millis(4));
629    }
630
631    #[test]
632    fn test_batch_recording() {
633        let timer = Timer::new();
634
635        let durations = vec![
636            Duration::from_millis(10),
637            Duration::from_millis(20),
638            Duration::from_millis(30),
639        ];
640
641        timer.record_batch(&durations);
642
643        assert_eq!(timer.count(), 3);
644        assert_eq!(timer.min(), Duration::from_millis(10));
645        assert_eq!(timer.max(), Duration::from_millis(30));
646        assert_eq!(timer.total(), Duration::from_millis(60));
647        assert_eq!(timer.average(), Duration::from_millis(20));
648    }
649
650    #[test]
651    fn test_min_max_tracking() {
652        let timer = Timer::new();
653
654        timer.record(Duration::from_millis(50));
655        timer.record(Duration::from_millis(10));
656        timer.record(Duration::from_millis(100));
657        timer.record(Duration::from_millis(25));
658
659        assert_eq!(timer.count(), 4);
660        assert_eq!(timer.min(), Duration::from_millis(10));
661        assert_eq!(timer.max(), Duration::from_millis(100));
662
663        // Check average is approximately 46.25ms (allowing for precision differences)
664        let avg = timer.average();
665        assert!(
666            avg >= Duration::from_millis(46) && avg <= Duration::from_millis(47),
667            "Average {avg:?} should be between 46ms and 47ms",
668        );
669    }
670
671    #[test]
672    fn test_reset() {
673        let timer = Timer::new();
674
675        timer.record(Duration::from_millis(100));
676        assert!(!timer.is_empty());
677
678        timer.reset();
679        assert!(timer.is_empty());
680        assert_eq!(timer.count(), 0);
681        assert_eq!(timer.total(), Duration::ZERO);
682        assert_eq!(timer.min(), Duration::ZERO);
683        assert_eq!(timer.max(), Duration::ZERO);
684    }
685
686    #[test]
687    fn test_statistics() {
688        let timer = Timer::new();
689
690        for i in 1..=10 {
691            timer.record(Duration::from_millis(i * 10));
692        }
693
694        let stats = timer.stats();
695        assert_eq!(stats.count, 10);
696        assert_eq!(stats.total, Duration::from_millis(550)); // Sum of 10+20+...+100
697        assert_eq!(stats.average, Duration::from_millis(55));
698        assert_eq!(stats.min, Duration::from_millis(10));
699        assert_eq!(stats.max, Duration::from_millis(100));
700        assert!(stats.rate_per_second > 0.0);
701        assert!(stats.age > Duration::from_nanos(0));
702    }
703
704    #[test]
705    fn test_high_concurrency() {
706        let timer = Arc::new(Timer::new());
707        let num_threads = 50;
708        let recordings_per_thread = 1000;
709
710        let handles: Vec<_> = (0..num_threads)
711            .map(|thread_id| {
712                let timer = Arc::clone(&timer);
713                thread::spawn(move || {
714                    for i in 0..recordings_per_thread {
715                        // Record varying durations
716                        let duration_ms = (thread_id * recordings_per_thread + i) % 100 + 1;
717                        timer.record(Duration::from_millis(duration_ms));
718                    }
719                })
720            })
721            .collect();
722
723        for handle in handles {
724            handle.join().unwrap();
725        }
726
727        let stats = timer.stats();
728        assert_eq!(stats.count, num_threads * recordings_per_thread);
729        assert!(stats.min > Duration::ZERO);
730        assert!(stats.max > Duration::ZERO);
731        assert!(stats.average > Duration::ZERO);
732        assert!(stats.rate_per_second > 0.0);
733    }
734
735    #[test]
736    fn test_concurrent_timing() {
737        let timer = Arc::new(Timer::new());
738        let num_threads = 20;
739
740        let handles: Vec<_> = (0..num_threads)
741            .map(|_| {
742                let timer = Arc::clone(&timer);
743                thread::spawn(move || {
744                    for _ in 0..100 {
745                        let _guard = timer.start();
746                        thread::sleep(Duration::from_micros(100)); // Very short sleep
747                    }
748                })
749            })
750            .collect();
751
752        for handle in handles {
753            handle.join().unwrap();
754        }
755
756        let stats = timer.stats();
757        assert_eq!(stats.count, num_threads * 100);
758        assert!(stats.average >= Duration::from_micros(50)); // Should be at least 50μs
759    }
760
761    #[test]
762    fn test_utility_functions() {
763        // Test time_fn
764        let (result, duration) = utils::time_fn(|| {
765            thread::sleep(Duration::from_millis(10));
766            42
767        });
768
769        assert_eq!(result, 42);
770        assert!(duration >= Duration::from_millis(9));
771
772        // Test time_and_record
773        let timer = Timer::new();
774        let result = utils::time_and_record(&timer, || {
775            thread::sleep(Duration::from_millis(5));
776            "hello"
777        });
778
779        assert_eq!(result, "hello");
780        assert_eq!(timer.count(), 1);
781        assert!(timer.average() >= Duration::from_millis(4));
782
783        // Test benchmark
784        let avg_duration = utils::benchmark("test_function", 10, || {
785            thread::sleep(Duration::from_millis(1));
786        });
787
788        assert!(avg_duration >= Duration::from_millis(1));
789    }
790
791    #[test]
792    fn test_macros() {
793        let timer = Timer::new();
794
795        // Test time_block macro
796        let result = time_block!(timer, {
797            thread::sleep(Duration::from_millis(5));
798            "result"
799        });
800
801        assert_eq!(result, "result");
802        assert_eq!(timer.count(), 1);
803
804        // Test time_fn macro
805        let (result, duration) = time_fn!({
806            thread::sleep(Duration::from_millis(2));
807            100
808        });
809
810        assert_eq!(result, 100);
811        assert!(duration >= Duration::from_millis(1));
812    }
813
814    #[test]
815    fn test_display_and_debug() {
816        let timer = Timer::new();
817        timer.record(Duration::from_millis(100));
818
819        let display_str = format!("{timer}");
820        assert!(display_str.contains("Timer"));
821        assert!(display_str.contains("count: 1"));
822
823        let debug_str = format!("{timer:?}");
824        assert!(debug_str.contains("Timer"));
825        assert!(debug_str.contains("count"));
826        assert!(debug_str.contains("average"));
827    }
828
829    #[test]
830    fn test_empty_batch_handling() {
831        let timer = Timer::new();
832        timer.record_batch(&[]);
833        assert!(timer.is_empty());
834
835        assert!(timer.try_record_batch(&[]).is_ok());
836        assert!(timer.is_empty());
837    }
838
839    #[test]
840    fn test_nested_timers_and_rate() {
841        let timer = Timer::new();
842
843        {
844            let _outer = timer.start();
845            thread::sleep(Duration::from_millis(2));
846            {
847                let _inner = timer.start();
848                thread::sleep(Duration::from_millis(2));
849            } // inner drop -> record
850        } // outer drop -> record
851
852        // Two recordings (outer + inner)
853        assert_eq!(timer.count(), 2);
854
855        // Non-zero rate per second (age should be > 0)
856        let rate = timer.rate_per_second();
857        assert!(rate >= 0.0);
858
859        // stats.rate_per_second should mirror rate_per_second()
860        let stats = timer.stats();
861        assert!(stats.rate_per_second >= 0.0);
862    }
863}
864
865#[cfg(all(test, feature = "bench-tests", not(tarpaulin)))]
866#[allow(unused_imports)]
867mod benchmarks {
868    use super::*;
869    use std::time::Instant;
870
871    #[cfg_attr(not(feature = "bench-tests"), ignore)]
872    #[test]
873    fn bench_timer_record() {
874        let timer = Timer::new();
875        let duration = Duration::from_nanos(1000);
876        let iterations = 1_000_000;
877
878        let start = Instant::now();
879        for _ in 0..iterations {
880            timer.record(duration);
881        }
882        let elapsed = start.elapsed();
883
884        println!(
885            "Timer record: {:.2} ns/op",
886            elapsed.as_nanos() as f64 / iterations as f64
887        );
888
889        assert_eq!(timer.count(), iterations);
890        // Should be under 300ns per record (relaxed from 200ns)
891        assert!(elapsed.as_nanos() / (iterations as u128) < 300);
892    }
893
894    #[cfg_attr(not(feature = "bench-tests"), ignore)]
895    #[test]
896    fn bench_running_timer() {
897        let timer = Timer::new();
898        let iterations = 100_000;
899
900        let start = Instant::now();
901        for _ in 0..iterations {
902            let guard = timer.start();
903            // Do minimal work
904            let _ = guard.elapsed();
905            guard.stop();
906        }
907        let elapsed = start.elapsed();
908
909        println!(
910            "Running timer: {:.2} ns/op",
911            elapsed.as_nanos() as f64 / iterations as f64
912        );
913
914        assert_eq!(timer.count(), iterations);
915        // Should be under 1500ns per timer operation (relaxed from 1000ns)
916        assert!(elapsed.as_nanos() / (iterations as u128) < 1500);
917    }
918
919    #[cfg_attr(not(feature = "bench-tests"), ignore)]
920    #[test]
921    fn bench_timer_stats() {
922        let timer = Timer::new();
923
924        // Fill timer with data
925        for i in 0..1000 {
926            timer.record(Duration::from_nanos(i * 1000));
927        }
928
929        let iterations = 1_000_000;
930        let start = Instant::now();
931
932        for _ in 0..iterations {
933            let _ = timer.stats();
934        }
935
936        let elapsed = start.elapsed();
937        println!(
938            "Timer stats: {:.2} ns/op",
939            elapsed.as_nanos() as f64 / iterations as f64
940        );
941
942        // Should be very fast since it's just atomic loads (relaxed from 100ns to 300ns)
943        assert!(elapsed.as_nanos() / iterations < 300);
944    }
945}