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)]
46pub struct TimerStats {
47 pub count: u64,
49 pub total: Duration,
51 pub average: Duration,
53 pub min: Duration,
55 pub max: Duration,
57 pub age: Duration,
59 pub rate_per_second: f64,
61}
62
63impl Timer {
64 #[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 #[inline(always)]
91 pub fn try_record_ns(&self, duration_ns: u64) -> Result<()> {
92 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 let cnt = self.count.load(Ordering::Relaxed);
100 if cnt == u64::MAX {
101 return Err(MetricsError::Overflow);
102 }
103
104 self.total_nanos.fetch_add(duration_ns, Ordering::Relaxed);
106 self.count.fetch_add(1, Ordering::Relaxed);
107
108 self.update_min(duration_ns);
110 self.update_max(duration_ns);
111 Ok(())
112 }
113
114 #[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 #[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 #[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 #[inline(always)]
151 pub fn record_ns(&self, duration_ns: u64) {
152 self.total_nanos.fetch_add(duration_ns, Ordering::Relaxed);
154 self.count.fetch_add(1, Ordering::Relaxed);
155
156 self.update_min(duration_ns);
158
159 self.update_max(duration_ns);
161 }
162
163 #[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 #[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 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 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 #[must_use]
251 #[inline(always)]
252 pub fn count(&self) -> u64 {
253 self.count.load(Ordering::Relaxed)
254 }
255
256 #[must_use]
261 #[inline]
262 pub fn total(&self) -> Duration {
263 Duration::from_nanos(self.total_nanos.load(Ordering::Relaxed))
264 }
265
266 #[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 #[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 #[must_use]
300 #[inline]
301 pub fn max(&self) -> Duration {
302 Duration::from_nanos(self.max_nanos.load(Ordering::Relaxed))
303 }
304
305 #[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 #[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 #[must_use]
363 #[inline]
364 pub fn age(&self) -> Duration {
365 self.created_at.elapsed()
366 }
367
368 #[must_use]
372 #[inline]
373 pub fn is_empty(&self) -> bool {
374 self.count() == 0
375 }
376
377 #[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 #[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; }
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, }
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; }
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, }
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
462impl<'a> RunningTimer<'a> {
467 #[must_use]
472 #[inline]
473 pub fn elapsed(&self) -> Duration {
474 self.start_time.elapsed()
475 }
476
477 #[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 }
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
499pub mod utils {
501 use super::*;
502
503 #[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 #[inline]
514 pub fn time_and_record<T>(timer: &Timer, f: impl FnOnce() -> T) -> T {
515 let _timing_guard = timer.start();
516 f() }
518
519 #[inline]
521 pub fn scoped_timer(timer: &Timer) -> RunningTimer<'_> {
522 timer.start()
523 }
524
525 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#[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]
557macro_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 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)); 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 } 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(); 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 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)); 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 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)); }
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)); }
759
760 #[test]
761 fn test_utility_functions() {
762 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 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 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 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 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 } } assert_eq!(timer.count(), 2);
853
854 let rate = timer.rate_per_second();
856 assert!(rate >= 0.0);
857
858 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 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 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 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 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 assert!(elapsed.as_nanos() / iterations < 300);
943 }
944}