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