1use crate::{MetricsError, Result};
14use std::sync::atomic::{AtomicU64, Ordering};
15use std::time::{Duration, Instant};
16
17#[repr(align(64))]
22pub struct Timer {
23 count: AtomicU64,
25 total_nanos: AtomicU64,
27 min_nanos: AtomicU64,
29 max_nanos: AtomicU64,
31 created_at: Instant,
33}
34
35pub struct RunningTimer<'a> {
39 timer: &'a Timer,
40 start_time: Instant,
41 stopped: bool,
42}
43
44#[derive(Debug, Clone)]
46#[cfg_attr(feature = "serde", derive(serde::Serialize))]
47pub struct TimerStats {
48 pub count: u64,
50 pub total: Duration,
52 pub average: Duration,
54 pub min: Duration,
56 pub max: Duration,
58 pub age: Duration,
60 pub rate_per_second: f64,
62}
63
64impl Timer {
65 #[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 #[inline(always)]
92 pub fn try_record_ns(&self, duration_ns: u64) -> Result<()> {
93 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 let cnt = self.count.load(Ordering::Relaxed);
101 if cnt == u64::MAX {
102 return Err(MetricsError::Overflow);
103 }
104
105 self.total_nanos.fetch_add(duration_ns, Ordering::Relaxed);
107 self.count.fetch_add(1, Ordering::Relaxed);
108
109 self.update_min(duration_ns);
111 self.update_max(duration_ns);
112 Ok(())
113 }
114
115 #[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 #[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 #[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 #[inline(always)]
152 pub fn record_ns(&self, duration_ns: u64) {
153 self.total_nanos.fetch_add(duration_ns, Ordering::Relaxed);
155 self.count.fetch_add(1, Ordering::Relaxed);
156
157 self.update_min(duration_ns);
159
160 self.update_max(duration_ns);
162 }
163
164 #[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 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 #[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 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 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 #[must_use]
255 #[inline(always)]
256 pub fn count(&self) -> u64 {
257 self.count.load(Ordering::Relaxed)
258 }
259
260 #[must_use]
265 #[inline]
266 pub fn total(&self) -> Duration {
267 Duration::from_nanos(self.total_nanos.load(Ordering::Relaxed))
268 }
269
270 #[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 #[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 #[must_use]
303 #[inline]
304 pub fn max(&self) -> Duration {
305 Duration::from_nanos(self.max_nanos.load(Ordering::Relaxed))
306 }
307
308 #[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 #[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 #[must_use]
365 #[inline]
366 pub fn age(&self) -> Duration {
367 self.created_at.elapsed()
368 }
369
370 #[must_use]
374 #[inline]
375 pub fn is_empty(&self) -> bool {
376 self.count() == 0
377 }
378
379 #[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 #[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; }
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, }
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; }
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, }
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
464impl<'a> RunningTimer<'a> {
469 #[must_use]
474 #[inline]
475 pub fn elapsed(&self) -> Duration {
476 self.start_time.elapsed()
477 }
478
479 #[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 }
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
501pub mod utils {
503 use super::*;
504
505 #[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 #[inline]
516 pub fn time_and_record<T>(timer: &Timer, f: impl FnOnce() -> T) -> T {
517 let _timing_guard = timer.start();
518 f() }
520
521 #[inline]
523 pub fn scoped_timer(timer: &Timer) -> RunningTimer<'_> {
524 timer.start()
525 }
526
527 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#[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]
559macro_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 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)); 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 } 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(); 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 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)); 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 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)); }
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)); }
761
762 #[test]
763 fn test_utility_functions() {
764 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 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 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 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 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 let timer = Timer::new();
836 let huge = Duration::from_nanos(u64::MAX / 2);
837 timer.record_batch(&[huge, huge, huge]);
839 assert_eq!(timer.count(), 3);
840 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 } } assert_eq!(timer.count(), 2);
869
870 let rate = timer.rate_per_second();
872 assert!(rate >= 0.0);
873
874 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);
906 }
907
908 #[cfg_attr(not(feature = "bench-tests"), ignore)]
909 #[test]
910 fn bench_running_timer() {
911 let timer = Timer::new();
912 let iterations = 100_000;
913
914 let start = Instant::now();
915 for _ in 0..iterations {
916 let guard = timer.start();
917 let _ = guard.elapsed();
919 guard.stop();
920 }
921 let elapsed = start.elapsed();
922
923 println!(
924 "Running timer: {:.2} ns/op",
925 elapsed.as_nanos() as f64 / iterations as f64
926 );
927
928 assert_eq!(timer.count(), iterations);
930 }
931
932 #[cfg_attr(not(feature = "bench-tests"), ignore)]
933 #[test]
934 fn bench_timer_stats() {
935 let timer = Timer::new();
936
937 for i in 0..1000 {
939 timer.record(Duration::from_nanos(i * 1000));
940 }
941
942 let iterations = 1_000_000;
943 let start = Instant::now();
944
945 for _ in 0..iterations {
946 let _ = timer.stats();
947 }
948
949 let elapsed = start.elapsed();
950 println!(
951 "Timer stats: {:.2} ns/op",
952 elapsed.as_nanos() as f64 / iterations as f64
953 );
954 }
956}