Skip to main content

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// Timer is composed of `AtomicU64` fields (Send + Sync) and `Instant`
463// (Send + Sync). The compiler derives Send + Sync automatically.
464
465/// Running timer implementation (RAII)
466impl<'a> RunningTimer<'a> {
467    /// Get elapsed time without stopping the timer
468    ///
469    /// Note: `#[must_use]`. The elapsed duration is typically used in logic or
470    /// reporting; ignoring it may indicate a bug.
471    #[must_use]
472    #[inline]
473    pub fn elapsed(&self) -> Duration {
474        self.start_time.elapsed()
475    }
476
477    /// Stop the timer manually and record the duration
478    #[inline]
479    pub fn stop(mut self) {
480        if !self.stopped {
481            let elapsed = self.start_time.elapsed();
482            self.timer.record(elapsed);
483            self.stopped = true;
484        }
485        // Timer is consumed here, preventing double recording
486    }
487}
488
489impl<'a> Drop for RunningTimer<'a> {
490    #[inline]
491    fn drop(&mut self) {
492        if !self.stopped {
493            let elapsed = self.start_time.elapsed();
494            self.timer.record(elapsed);
495        }
496    }
497}
498
499/// Utility functions for timing
500pub mod utils {
501    use super::*;
502
503    /// Time a function execution and return the result + duration
504    #[inline]
505    pub fn time_fn<T>(f: impl FnOnce() -> T) -> (T, Duration) {
506        let start = Instant::now();
507        let result = f();
508        let duration = start.elapsed();
509        (result, duration)
510    }
511
512    /// Time a function and record the duration in the provided timer
513    #[inline]
514    pub fn time_and_record<T>(timer: &Timer, f: impl FnOnce() -> T) -> T {
515        let _timing_guard = timer.start();
516        f() // Timer automatically records when guard drops
517    }
518
519    /// Create a scoped timer that records to the provided timer
520    #[inline]
521    pub fn scoped_timer(timer: &Timer) -> RunningTimer<'_> {
522        timer.start()
523    }
524
525    /// Benchmark a function with multiple iterations
526    pub fn benchmark<F>(name: &str, iterations: usize, f: F) -> Duration
527    where
528        F: Fn(),
529    {
530        let timer = Timer::new();
531
532        for _ in 0..iterations {
533            let _guard = timer.start();
534            f();
535        }
536
537        let stats = timer.stats();
538        println!(
539            "Benchmark '{}': {} iterations, avg: {:?}, total: {:?}",
540            name, iterations, stats.average, stats.total
541        );
542
543        stats.average
544    }
545}
546
547/// Convenience macros
548#[macro_export]
549macro_rules! time_block {
550    ($timer:expr, $block:block) => {{
551        let _timing_guard = $timer.start();
552        $block
553    }};
554}
555
556#[macro_export]
557/// Macro to time a function call and record the result
558///
559/// # Examples
560///
561/// ```rust
562/// # use metrics_lib::time_fn;
563/// let (result, duration) = time_fn!({
564///     // Some work to time
565///     std::thread::sleep(std::time::Duration::from_millis(10));
566///     "done"
567/// });
568/// assert!(duration >= std::time::Duration::from_millis(10));
569/// assert_eq!(result, "done");
570/// ```
571macro_rules! time_fn {
572    ($func:expr) => {{
573        let start = std::time::Instant::now();
574        let result = $func;
575        let duration = start.elapsed();
576        (result, duration)
577    }};
578}
579
580#[cfg(test)]
581mod tests {
582    use super::*;
583    use std::sync::Arc;
584    use std::thread;
585
586    #[test]
587    fn test_basic_operations() {
588        let timer = Timer::new();
589
590        assert!(timer.is_empty());
591        assert_eq!(timer.count(), 0);
592        assert_eq!(timer.total(), Duration::ZERO);
593        assert_eq!(timer.average(), Duration::ZERO);
594
595        // Record a duration
596        timer.record(Duration::from_millis(100));
597
598        assert!(!timer.is_empty());
599        assert_eq!(timer.count(), 1);
600        assert!(timer.total() >= Duration::from_millis(99)); // Account for precision
601        assert!(timer.average() >= Duration::from_millis(99));
602    }
603
604    #[test]
605    fn test_running_timer() {
606        let timer = Timer::new();
607
608        {
609            let running = timer.start();
610            thread::sleep(Duration::from_millis(10));
611            assert!(running.elapsed() >= Duration::from_millis(9));
612        } // Automatically recorded when dropped
613
614        assert_eq!(timer.count(), 1);
615        assert!(timer.average() >= Duration::from_millis(9));
616    }
617
618    #[test]
619    fn test_manual_stop() {
620        let timer = Timer::new();
621
622        let running = timer.start();
623        thread::sleep(Duration::from_millis(5));
624        running.stop(); // Manual stop
625
626        assert_eq!(timer.count(), 1);
627        assert!(timer.average() >= Duration::from_millis(4));
628    }
629
630    #[test]
631    fn test_batch_recording() {
632        let timer = Timer::new();
633
634        let durations = vec![
635            Duration::from_millis(10),
636            Duration::from_millis(20),
637            Duration::from_millis(30),
638        ];
639
640        timer.record_batch(&durations);
641
642        assert_eq!(timer.count(), 3);
643        assert_eq!(timer.min(), Duration::from_millis(10));
644        assert_eq!(timer.max(), Duration::from_millis(30));
645        assert_eq!(timer.total(), Duration::from_millis(60));
646        assert_eq!(timer.average(), Duration::from_millis(20));
647    }
648
649    #[test]
650    fn test_min_max_tracking() {
651        let timer = Timer::new();
652
653        timer.record(Duration::from_millis(50));
654        timer.record(Duration::from_millis(10));
655        timer.record(Duration::from_millis(100));
656        timer.record(Duration::from_millis(25));
657
658        assert_eq!(timer.count(), 4);
659        assert_eq!(timer.min(), Duration::from_millis(10));
660        assert_eq!(timer.max(), Duration::from_millis(100));
661
662        // Check average is approximately 46.25ms (allowing for precision differences)
663        let avg = timer.average();
664        assert!(
665            avg >= Duration::from_millis(46) && avg <= Duration::from_millis(47),
666            "Average {avg:?} should be between 46ms and 47ms",
667        );
668    }
669
670    #[test]
671    fn test_reset() {
672        let timer = Timer::new();
673
674        timer.record(Duration::from_millis(100));
675        assert!(!timer.is_empty());
676
677        timer.reset();
678        assert!(timer.is_empty());
679        assert_eq!(timer.count(), 0);
680        assert_eq!(timer.total(), Duration::ZERO);
681        assert_eq!(timer.min(), Duration::ZERO);
682        assert_eq!(timer.max(), Duration::ZERO);
683    }
684
685    #[test]
686    fn test_statistics() {
687        let timer = Timer::new();
688
689        for i in 1..=10 {
690            timer.record(Duration::from_millis(i * 10));
691        }
692
693        let stats = timer.stats();
694        assert_eq!(stats.count, 10);
695        assert_eq!(stats.total, Duration::from_millis(550)); // Sum of 10+20+...+100
696        assert_eq!(stats.average, Duration::from_millis(55));
697        assert_eq!(stats.min, Duration::from_millis(10));
698        assert_eq!(stats.max, Duration::from_millis(100));
699        assert!(stats.rate_per_second > 0.0);
700        assert!(stats.age > Duration::from_nanos(0));
701    }
702
703    #[test]
704    fn test_high_concurrency() {
705        let timer = Arc::new(Timer::new());
706        let num_threads = 50;
707        let recordings_per_thread = 1000;
708
709        let handles: Vec<_> = (0..num_threads)
710            .map(|thread_id| {
711                let timer = Arc::clone(&timer);
712                thread::spawn(move || {
713                    for i in 0..recordings_per_thread {
714                        // Record varying durations
715                        let duration_ms = (thread_id * recordings_per_thread + i) % 100 + 1;
716                        timer.record(Duration::from_millis(duration_ms));
717                    }
718                })
719            })
720            .collect();
721
722        for handle in handles {
723            handle.join().unwrap();
724        }
725
726        let stats = timer.stats();
727        assert_eq!(stats.count, num_threads * recordings_per_thread);
728        assert!(stats.min > Duration::ZERO);
729        assert!(stats.max > Duration::ZERO);
730        assert!(stats.average > Duration::ZERO);
731        assert!(stats.rate_per_second > 0.0);
732    }
733
734    #[test]
735    fn test_concurrent_timing() {
736        let timer = Arc::new(Timer::new());
737        let num_threads = 20;
738
739        let handles: Vec<_> = (0..num_threads)
740            .map(|_| {
741                let timer = Arc::clone(&timer);
742                thread::spawn(move || {
743                    for _ in 0..100 {
744                        let _guard = timer.start();
745                        thread::sleep(Duration::from_micros(100)); // Very short sleep
746                    }
747                })
748            })
749            .collect();
750
751        for handle in handles {
752            handle.join().unwrap();
753        }
754
755        let stats = timer.stats();
756        assert_eq!(stats.count, num_threads * 100);
757        assert!(stats.average >= Duration::from_micros(50)); // Should be at least 50μs
758    }
759
760    #[test]
761    fn test_utility_functions() {
762        // Test time_fn
763        let (result, duration) = utils::time_fn(|| {
764            thread::sleep(Duration::from_millis(10));
765            42
766        });
767
768        assert_eq!(result, 42);
769        assert!(duration >= Duration::from_millis(9));
770
771        // Test time_and_record
772        let timer = Timer::new();
773        let result = utils::time_and_record(&timer, || {
774            thread::sleep(Duration::from_millis(5));
775            "hello"
776        });
777
778        assert_eq!(result, "hello");
779        assert_eq!(timer.count(), 1);
780        assert!(timer.average() >= Duration::from_millis(4));
781
782        // Test benchmark
783        let avg_duration = utils::benchmark("test_function", 10, || {
784            thread::sleep(Duration::from_millis(1));
785        });
786
787        assert!(avg_duration >= Duration::from_millis(1));
788    }
789
790    #[test]
791    fn test_macros() {
792        let timer = Timer::new();
793
794        // Test time_block macro
795        let result = time_block!(timer, {
796            thread::sleep(Duration::from_millis(5));
797            "result"
798        });
799
800        assert_eq!(result, "result");
801        assert_eq!(timer.count(), 1);
802
803        // Test time_fn macro
804        let (result, duration) = time_fn!({
805            thread::sleep(Duration::from_millis(2));
806            100
807        });
808
809        assert_eq!(result, 100);
810        assert!(duration >= Duration::from_millis(1));
811    }
812
813    #[test]
814    fn test_display_and_debug() {
815        let timer = Timer::new();
816        timer.record(Duration::from_millis(100));
817
818        let display_str = format!("{timer}");
819        assert!(display_str.contains("Timer"));
820        assert!(display_str.contains("count: 1"));
821
822        let debug_str = format!("{timer:?}");
823        assert!(debug_str.contains("Timer"));
824        assert!(debug_str.contains("count"));
825        assert!(debug_str.contains("average"));
826    }
827
828    #[test]
829    fn test_empty_batch_handling() {
830        let timer = Timer::new();
831        timer.record_batch(&[]);
832        assert!(timer.is_empty());
833
834        assert!(timer.try_record_batch(&[]).is_ok());
835        assert!(timer.is_empty());
836    }
837
838    #[test]
839    fn test_nested_timers_and_rate() {
840        let timer = Timer::new();
841
842        {
843            let _outer = timer.start();
844            thread::sleep(Duration::from_millis(2));
845            {
846                let _inner = timer.start();
847                thread::sleep(Duration::from_millis(2));
848            } // inner drop -> record
849        } // outer drop -> record
850
851        // Two recordings (outer + inner)
852        assert_eq!(timer.count(), 2);
853
854        // Non-zero rate per second (age should be > 0)
855        let rate = timer.rate_per_second();
856        assert!(rate >= 0.0);
857
858        // stats.rate_per_second should mirror rate_per_second()
859        let stats = timer.stats();
860        assert!(stats.rate_per_second >= 0.0);
861    }
862}
863
864#[cfg(all(test, feature = "bench-tests", not(tarpaulin)))]
865#[allow(unused_imports)]
866mod benchmarks {
867    use super::*;
868    use std::time::Instant;
869
870    #[cfg_attr(not(feature = "bench-tests"), ignore)]
871    #[test]
872    fn bench_timer_record() {
873        let timer = Timer::new();
874        let duration = Duration::from_nanos(1000);
875        let iterations = 1_000_000;
876
877        let start = Instant::now();
878        for _ in 0..iterations {
879            timer.record(duration);
880        }
881        let elapsed = start.elapsed();
882
883        println!(
884            "Timer record: {:.2} ns/op",
885            elapsed.as_nanos() as f64 / iterations as f64
886        );
887
888        assert_eq!(timer.count(), iterations);
889        // Should be under 300ns per record (relaxed from 200ns)
890        assert!(elapsed.as_nanos() / (iterations as u128) < 300);
891    }
892
893    #[cfg_attr(not(feature = "bench-tests"), ignore)]
894    #[test]
895    fn bench_running_timer() {
896        let timer = Timer::new();
897        let iterations = 100_000;
898
899        let start = Instant::now();
900        for _ in 0..iterations {
901            let guard = timer.start();
902            // Do minimal work
903            let _ = guard.elapsed();
904            guard.stop();
905        }
906        let elapsed = start.elapsed();
907
908        println!(
909            "Running timer: {:.2} ns/op",
910            elapsed.as_nanos() as f64 / iterations as f64
911        );
912
913        assert_eq!(timer.count(), iterations);
914        // Should be under 1500ns per timer operation (relaxed from 1000ns)
915        assert!(elapsed.as_nanos() / (iterations as u128) < 1500);
916    }
917
918    #[cfg_attr(not(feature = "bench-tests"), ignore)]
919    #[test]
920    fn bench_timer_stats() {
921        let timer = Timer::new();
922
923        // Fill timer with data
924        for i in 0..1000 {
925            timer.record(Duration::from_nanos(i * 1000));
926        }
927
928        let iterations = 1_000_000;
929        let start = Instant::now();
930
931        for _ in 0..iterations {
932            let _ = timer.stats();
933        }
934
935        let elapsed = start.elapsed();
936        println!(
937            "Timer stats: {:.2} ns/op",
938            elapsed.as_nanos() as f64 / iterations as f64
939        );
940
941        // Should be very fast since it's just atomic loads (relaxed from 100ns to 300ns)
942        assert!(elapsed.as_nanos() / iterations < 300);
943    }
944}