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
462unsafe impl Send for Timer {}
464unsafe impl Sync for Timer {}
465
466impl<'a> RunningTimer<'a> {
468 #[must_use]
473 #[inline]
474 pub fn elapsed(&self) -> Duration {
475 self.start_time.elapsed()
476 }
477
478 #[inline]
480 pub fn stop(mut self) {
481 if !self.stopped {
482 let elapsed = self.start_time.elapsed();
483 self.timer.record(elapsed);
484 self.stopped = true;
485 }
486 }
488}
489
490impl<'a> Drop for RunningTimer<'a> {
491 #[inline]
492 fn drop(&mut self) {
493 if !self.stopped {
494 let elapsed = self.start_time.elapsed();
495 self.timer.record(elapsed);
496 }
497 }
498}
499
500pub mod utils {
502 use super::*;
503
504 #[inline]
506 pub fn time_fn<T>(f: impl FnOnce() -> T) -> (T, Duration) {
507 let start = Instant::now();
508 let result = f();
509 let duration = start.elapsed();
510 (result, duration)
511 }
512
513 #[inline]
515 pub fn time_and_record<T>(timer: &Timer, f: impl FnOnce() -> T) -> T {
516 let _timing_guard = timer.start();
517 f() }
519
520 #[inline]
522 pub fn scoped_timer(timer: &Timer) -> RunningTimer<'_> {
523 timer.start()
524 }
525
526 pub fn benchmark<F>(name: &str, iterations: usize, f: F) -> Duration
528 where
529 F: Fn(),
530 {
531 let timer = Timer::new();
532
533 for _ in 0..iterations {
534 let _guard = timer.start();
535 f();
536 }
537
538 let stats = timer.stats();
539 println!(
540 "Benchmark '{}': {} iterations, avg: {:?}, total: {:?}",
541 name, iterations, stats.average, stats.total
542 );
543
544 stats.average
545 }
546}
547
548#[macro_export]
550macro_rules! time_block {
551 ($timer:expr, $block:block) => {{
552 let _timing_guard = $timer.start();
553 $block
554 }};
555}
556
557#[macro_export]
558macro_rules! time_fn {
573 ($func:expr) => {{
574 let start = std::time::Instant::now();
575 let result = $func;
576 let duration = start.elapsed();
577 (result, duration)
578 }};
579}
580
581#[cfg(test)]
582mod tests {
583 use super::*;
584 use std::sync::Arc;
585 use std::thread;
586
587 #[test]
588 fn test_basic_operations() {
589 let timer = Timer::new();
590
591 assert!(timer.is_empty());
592 assert_eq!(timer.count(), 0);
593 assert_eq!(timer.total(), Duration::ZERO);
594 assert_eq!(timer.average(), Duration::ZERO);
595
596 timer.record(Duration::from_millis(100));
598
599 assert!(!timer.is_empty());
600 assert_eq!(timer.count(), 1);
601 assert!(timer.total() >= Duration::from_millis(99)); assert!(timer.average() >= Duration::from_millis(99));
603 }
604
605 #[test]
606 fn test_running_timer() {
607 let timer = Timer::new();
608
609 {
610 let running = timer.start();
611 thread::sleep(Duration::from_millis(10));
612 assert!(running.elapsed() >= Duration::from_millis(9));
613 } assert_eq!(timer.count(), 1);
616 assert!(timer.average() >= Duration::from_millis(9));
617 }
618
619 #[test]
620 fn test_manual_stop() {
621 let timer = Timer::new();
622
623 let running = timer.start();
624 thread::sleep(Duration::from_millis(5));
625 running.stop(); assert_eq!(timer.count(), 1);
628 assert!(timer.average() >= Duration::from_millis(4));
629 }
630
631 #[test]
632 fn test_batch_recording() {
633 let timer = Timer::new();
634
635 let durations = vec![
636 Duration::from_millis(10),
637 Duration::from_millis(20),
638 Duration::from_millis(30),
639 ];
640
641 timer.record_batch(&durations);
642
643 assert_eq!(timer.count(), 3);
644 assert_eq!(timer.min(), Duration::from_millis(10));
645 assert_eq!(timer.max(), Duration::from_millis(30));
646 assert_eq!(timer.total(), Duration::from_millis(60));
647 assert_eq!(timer.average(), Duration::from_millis(20));
648 }
649
650 #[test]
651 fn test_min_max_tracking() {
652 let timer = Timer::new();
653
654 timer.record(Duration::from_millis(50));
655 timer.record(Duration::from_millis(10));
656 timer.record(Duration::from_millis(100));
657 timer.record(Duration::from_millis(25));
658
659 assert_eq!(timer.count(), 4);
660 assert_eq!(timer.min(), Duration::from_millis(10));
661 assert_eq!(timer.max(), Duration::from_millis(100));
662
663 let avg = timer.average();
665 assert!(
666 avg >= Duration::from_millis(46) && avg <= Duration::from_millis(47),
667 "Average {avg:?} should be between 46ms and 47ms",
668 );
669 }
670
671 #[test]
672 fn test_reset() {
673 let timer = Timer::new();
674
675 timer.record(Duration::from_millis(100));
676 assert!(!timer.is_empty());
677
678 timer.reset();
679 assert!(timer.is_empty());
680 assert_eq!(timer.count(), 0);
681 assert_eq!(timer.total(), Duration::ZERO);
682 assert_eq!(timer.min(), Duration::ZERO);
683 assert_eq!(timer.max(), Duration::ZERO);
684 }
685
686 #[test]
687 fn test_statistics() {
688 let timer = Timer::new();
689
690 for i in 1..=10 {
691 timer.record(Duration::from_millis(i * 10));
692 }
693
694 let stats = timer.stats();
695 assert_eq!(stats.count, 10);
696 assert_eq!(stats.total, Duration::from_millis(550)); assert_eq!(stats.average, Duration::from_millis(55));
698 assert_eq!(stats.min, Duration::from_millis(10));
699 assert_eq!(stats.max, Duration::from_millis(100));
700 assert!(stats.rate_per_second > 0.0);
701 assert!(stats.age > Duration::from_nanos(0));
702 }
703
704 #[test]
705 fn test_high_concurrency() {
706 let timer = Arc::new(Timer::new());
707 let num_threads = 50;
708 let recordings_per_thread = 1000;
709
710 let handles: Vec<_> = (0..num_threads)
711 .map(|thread_id| {
712 let timer = Arc::clone(&timer);
713 thread::spawn(move || {
714 for i in 0..recordings_per_thread {
715 let duration_ms = (thread_id * recordings_per_thread + i) % 100 + 1;
717 timer.record(Duration::from_millis(duration_ms));
718 }
719 })
720 })
721 .collect();
722
723 for handle in handles {
724 handle.join().unwrap();
725 }
726
727 let stats = timer.stats();
728 assert_eq!(stats.count, num_threads * recordings_per_thread);
729 assert!(stats.min > Duration::ZERO);
730 assert!(stats.max > Duration::ZERO);
731 assert!(stats.average > Duration::ZERO);
732 assert!(stats.rate_per_second > 0.0);
733 }
734
735 #[test]
736 fn test_concurrent_timing() {
737 let timer = Arc::new(Timer::new());
738 let num_threads = 20;
739
740 let handles: Vec<_> = (0..num_threads)
741 .map(|_| {
742 let timer = Arc::clone(&timer);
743 thread::spawn(move || {
744 for _ in 0..100 {
745 let _guard = timer.start();
746 thread::sleep(Duration::from_micros(100)); }
748 })
749 })
750 .collect();
751
752 for handle in handles {
753 handle.join().unwrap();
754 }
755
756 let stats = timer.stats();
757 assert_eq!(stats.count, num_threads * 100);
758 assert!(stats.average >= Duration::from_micros(50)); }
760
761 #[test]
762 fn test_utility_functions() {
763 let (result, duration) = utils::time_fn(|| {
765 thread::sleep(Duration::from_millis(10));
766 42
767 });
768
769 assert_eq!(result, 42);
770 assert!(duration >= Duration::from_millis(9));
771
772 let timer = Timer::new();
774 let result = utils::time_and_record(&timer, || {
775 thread::sleep(Duration::from_millis(5));
776 "hello"
777 });
778
779 assert_eq!(result, "hello");
780 assert_eq!(timer.count(), 1);
781 assert!(timer.average() >= Duration::from_millis(4));
782
783 let avg_duration = utils::benchmark("test_function", 10, || {
785 thread::sleep(Duration::from_millis(1));
786 });
787
788 assert!(avg_duration >= Duration::from_millis(1));
789 }
790
791 #[test]
792 fn test_macros() {
793 let timer = Timer::new();
794
795 let result = time_block!(timer, {
797 thread::sleep(Duration::from_millis(5));
798 "result"
799 });
800
801 assert_eq!(result, "result");
802 assert_eq!(timer.count(), 1);
803
804 let (result, duration) = time_fn!({
806 thread::sleep(Duration::from_millis(2));
807 100
808 });
809
810 assert_eq!(result, 100);
811 assert!(duration >= Duration::from_millis(1));
812 }
813
814 #[test]
815 fn test_display_and_debug() {
816 let timer = Timer::new();
817 timer.record(Duration::from_millis(100));
818
819 let display_str = format!("{timer}");
820 assert!(display_str.contains("Timer"));
821 assert!(display_str.contains("count: 1"));
822
823 let debug_str = format!("{timer:?}");
824 assert!(debug_str.contains("Timer"));
825 assert!(debug_str.contains("count"));
826 assert!(debug_str.contains("average"));
827 }
828
829 #[test]
830 fn test_empty_batch_handling() {
831 let timer = Timer::new();
832 timer.record_batch(&[]);
833 assert!(timer.is_empty());
834
835 assert!(timer.try_record_batch(&[]).is_ok());
836 assert!(timer.is_empty());
837 }
838
839 #[test]
840 fn test_nested_timers_and_rate() {
841 let timer = Timer::new();
842
843 {
844 let _outer = timer.start();
845 thread::sleep(Duration::from_millis(2));
846 {
847 let _inner = timer.start();
848 thread::sleep(Duration::from_millis(2));
849 } } assert_eq!(timer.count(), 2);
854
855 let rate = timer.rate_per_second();
857 assert!(rate >= 0.0);
858
859 let stats = timer.stats();
861 assert!(stats.rate_per_second >= 0.0);
862 }
863}
864
865#[cfg(all(test, feature = "bench-tests", not(tarpaulin)))]
866#[allow(unused_imports)]
867mod benchmarks {
868 use super::*;
869 use std::time::Instant;
870
871 #[cfg_attr(not(feature = "bench-tests"), ignore)]
872 #[test]
873 fn bench_timer_record() {
874 let timer = Timer::new();
875 let duration = Duration::from_nanos(1000);
876 let iterations = 1_000_000;
877
878 let start = Instant::now();
879 for _ in 0..iterations {
880 timer.record(duration);
881 }
882 let elapsed = start.elapsed();
883
884 println!(
885 "Timer record: {:.2} ns/op",
886 elapsed.as_nanos() as f64 / iterations as f64
887 );
888
889 assert_eq!(timer.count(), iterations);
890 assert!(elapsed.as_nanos() / (iterations as u128) < 300);
892 }
893
894 #[cfg_attr(not(feature = "bench-tests"), ignore)]
895 #[test]
896 fn bench_running_timer() {
897 let timer = Timer::new();
898 let iterations = 100_000;
899
900 let start = Instant::now();
901 for _ in 0..iterations {
902 let guard = timer.start();
903 let _ = guard.elapsed();
905 guard.stop();
906 }
907 let elapsed = start.elapsed();
908
909 println!(
910 "Running timer: {:.2} ns/op",
911 elapsed.as_nanos() as f64 / iterations as f64
912 );
913
914 assert_eq!(timer.count(), iterations);
915 assert!(elapsed.as_nanos() / (iterations as u128) < 1500);
917 }
918
919 #[cfg_attr(not(feature = "bench-tests"), ignore)]
920 #[test]
921 fn bench_timer_stats() {
922 let timer = Timer::new();
923
924 for i in 0..1000 {
926 timer.record(Duration::from_nanos(i * 1000));
927 }
928
929 let iterations = 1_000_000;
930 let start = Instant::now();
931
932 for _ in 0..iterations {
933 let _ = timer.stats();
934 }
935
936 let elapsed = start.elapsed();
937 println!(
938 "Timer stats: {:.2} ns/op",
939 elapsed.as_nanos() as f64 / iterations as f64
940 );
941
942 assert!(elapsed.as_nanos() / iterations < 300);
944 }
945}