1use crate::{MetricsError, Result};
14use std::sync::atomic::{AtomicU64, Ordering};
15use std::time::{Duration, Instant};
16
17#[repr(align(64))]
22pub struct Gauge {
23 value: AtomicU64,
25 created_at: Instant,
27}
28
29#[derive(Debug, Clone)]
31pub struct GaugeStats {
32 pub value: f64,
34 pub age: Duration,
36 pub updates: Option<u64>,
38}
39
40impl Gauge {
41 #[inline]
43 pub fn new() -> Self {
44 Self {
45 value: AtomicU64::new(0.0_f64.to_bits()),
46 created_at: Instant::now(),
47 }
48 }
49
50 #[inline]
65 pub fn try_add(&self, delta: f64) -> Result<()> {
66 if delta == 0.0 {
67 return Ok(());
68 }
69 if !delta.is_finite() {
70 return Err(MetricsError::InvalidValue {
71 reason: "delta is not finite",
72 });
73 }
74
75 loop {
76 let current_bits = self.value.load(Ordering::Relaxed);
77 let current_value = f64::from_bits(current_bits);
78 let new_value = current_value + delta;
79 if !new_value.is_finite() {
80 return Err(MetricsError::Overflow);
81 }
82 let new_bits = new_value.to_bits();
83
84 match self.value.compare_exchange_weak(
85 current_bits,
86 new_bits,
87 Ordering::Relaxed,
88 Ordering::Relaxed,
89 ) {
90 Ok(_) => return Ok(()),
91 Err(_) => continue,
92 }
93 }
94 }
95
96 #[inline]
109 pub fn try_sub(&self, delta: f64) -> Result<()> {
110 self.try_add(-delta)
111 }
112
113 #[inline]
115 pub fn with_value(initial: f64) -> Self {
116 Self {
117 value: AtomicU64::new(initial.to_bits()),
118 created_at: Instant::now(),
119 }
120 }
121
122 #[inline(always)]
130 pub fn set(&self, value: f64) {
131 self.value.store(value.to_bits(), Ordering::Relaxed);
132 }
133
134 #[inline]
146 pub fn try_set(&self, value: f64) -> Result<()> {
147 if !value.is_finite() {
148 return Err(MetricsError::InvalidValue {
149 reason: "value is not finite",
150 });
151 }
152 self.set(value);
153 Ok(())
154 }
155
156 #[must_use]
161 #[inline(always)]
162 pub fn get(&self) -> f64 {
163 f64::from_bits(self.value.load(Ordering::Relaxed))
164 }
165
166 #[inline]
174 pub fn add(&self, delta: f64) {
175 if delta == 0.0 || !delta.is_finite() {
176 return;
177 }
178
179 loop {
180 let current_bits = self.value.load(Ordering::Relaxed);
181 let current_value = f64::from_bits(current_bits);
182 let new_value = current_value + delta;
183 if !new_value.is_finite() {
184 return; }
186 let new_bits = new_value.to_bits();
187
188 match self.value.compare_exchange_weak(
189 current_bits,
190 new_bits,
191 Ordering::Relaxed,
192 Ordering::Relaxed,
193 ) {
194 Ok(_) => break,
195 Err(_) => continue, }
197 }
198 }
199
200 #[inline]
204 pub fn sub(&self, delta: f64) {
205 self.add(-delta);
206 }
207
208 #[inline]
210 pub fn set_max(&self, value: f64) {
211 loop {
212 let current_bits = self.value.load(Ordering::Relaxed);
213 let current_value = f64::from_bits(current_bits);
214
215 if value <= current_value {
216 break; }
218
219 let new_bits = value.to_bits();
220 match self.value.compare_exchange_weak(
221 current_bits,
222 new_bits,
223 Ordering::Relaxed,
224 Ordering::Relaxed,
225 ) {
226 Ok(_) => break,
227 Err(_) => continue, }
229 }
230 }
231
232 #[inline]
246 pub fn try_set_max(&self, value: f64) -> Result<()> {
247 if !value.is_finite() {
248 return Err(MetricsError::InvalidValue {
249 reason: "value is not finite",
250 });
251 }
252 loop {
253 let current_bits = self.value.load(Ordering::Relaxed);
254 let current_value = f64::from_bits(current_bits);
255 if value <= current_value {
256 return Ok(());
257 }
258 let new_bits = value.to_bits();
259 match self.value.compare_exchange_weak(
260 current_bits,
261 new_bits,
262 Ordering::Relaxed,
263 Ordering::Relaxed,
264 ) {
265 Ok(_) => return Ok(()),
266 Err(_) => continue,
267 }
268 }
269 }
270
271 #[inline]
273 pub fn set_min(&self, value: f64) {
274 loop {
275 let current_bits = self.value.load(Ordering::Relaxed);
276 let current_value = f64::from_bits(current_bits);
277
278 if value >= current_value {
279 break; }
281
282 let new_bits = value.to_bits();
283 match self.value.compare_exchange_weak(
284 current_bits,
285 new_bits,
286 Ordering::Relaxed,
287 Ordering::Relaxed,
288 ) {
289 Ok(_) => break,
290 Err(_) => continue, }
292 }
293 }
294
295 #[inline]
309 pub fn try_set_min(&self, value: f64) -> Result<()> {
310 if !value.is_finite() {
311 return Err(MetricsError::InvalidValue {
312 reason: "value is not finite",
313 });
314 }
315 loop {
316 let current_bits = self.value.load(Ordering::Relaxed);
317 let current_value = f64::from_bits(current_bits);
318 if value >= current_value {
319 return Ok(());
320 }
321 let new_bits = value.to_bits();
322 match self.value.compare_exchange_weak(
323 current_bits,
324 new_bits,
325 Ordering::Relaxed,
326 Ordering::Relaxed,
327 ) {
328 Ok(_) => return Ok(()),
329 Err(_) => continue,
330 }
331 }
332 }
333
334 #[inline]
338 pub fn compare_and_swap(&self, expected: f64, new: f64) -> core::result::Result<f64, f64> {
339 let expected_bits = expected.to_bits();
340 let new_bits = new.to_bits();
341
342 match self.value.compare_exchange(
343 expected_bits,
344 new_bits,
345 Ordering::SeqCst,
346 Ordering::SeqCst,
347 ) {
348 Ok(prev_bits) => Ok(f64::from_bits(prev_bits)),
349 Err(current_bits) => Err(f64::from_bits(current_bits)),
350 }
351 }
352
353 #[inline]
355 pub fn reset(&self) {
356 self.set(0.0);
357 }
358
359 #[inline]
364 pub fn multiply(&self, factor: f64) {
365 if factor == 1.0 || !factor.is_finite() {
366 return;
367 }
368
369 loop {
370 let current_bits = self.value.load(Ordering::Relaxed);
371 let current_value = f64::from_bits(current_bits);
372 let new_value = current_value * factor;
373 if !new_value.is_finite() {
374 return; }
376 let new_bits = new_value.to_bits();
377
378 match self.value.compare_exchange_weak(
379 current_bits,
380 new_bits,
381 Ordering::Relaxed,
382 Ordering::Relaxed,
383 ) {
384 Ok(_) => break,
385 Err(_) => continue,
386 }
387 }
388 }
389
390 #[inline]
396 pub fn divide(&self, divisor: f64) {
397 if divisor == 0.0 || divisor == 1.0 || !divisor.is_finite() {
398 return;
399 }
400 self.multiply(1.0 / divisor);
401 }
402
403 #[inline]
405 pub fn abs(&self) {
406 loop {
407 let current_bits = self.value.load(Ordering::Relaxed);
408 let current_value = f64::from_bits(current_bits);
409
410 if current_value >= 0.0 {
411 break; }
413
414 let abs_value = current_value.abs();
415 let abs_bits = abs_value.to_bits();
416
417 match self.value.compare_exchange_weak(
418 current_bits,
419 abs_bits,
420 Ordering::Relaxed,
421 Ordering::Relaxed,
422 ) {
423 Ok(_) => break,
424 Err(_) => continue,
425 }
426 }
427 }
428
429 #[inline]
431 pub fn clamp(&self, min: f64, max: f64) {
432 loop {
433 let current_bits = self.value.load(Ordering::Relaxed);
434 let current_value = f64::from_bits(current_bits);
435 let clamped_value = current_value.clamp(min, max);
436
437 if (current_value - clamped_value).abs() < f64::EPSILON {
438 break; }
440
441 let clamped_bits = clamped_value.to_bits();
442
443 match self.value.compare_exchange_weak(
444 current_bits,
445 clamped_bits,
446 Ordering::Relaxed,
447 Ordering::Relaxed,
448 ) {
449 Ok(_) => break,
450 Err(_) => continue,
451 }
452 }
453 }
454
455 #[inline]
459 pub fn update_ema(&self, sample: f64, alpha: f64) {
460 let alpha = alpha.clamp(0.0, 1.0);
461
462 loop {
463 let current_bits = self.value.load(Ordering::Relaxed);
464 let current_value = f64::from_bits(current_bits);
465 let new_value = alpha * sample + (1.0 - alpha) * current_value;
466 let new_bits = new_value.to_bits();
467
468 match self.value.compare_exchange_weak(
469 current_bits,
470 new_bits,
471 Ordering::Relaxed,
472 Ordering::Relaxed,
473 ) {
474 Ok(_) => break,
475 Err(_) => continue,
476 }
477 }
478 }
479
480 #[must_use]
485 pub fn stats(&self) -> GaugeStats {
486 GaugeStats {
487 value: self.get(),
488 age: self.created_at.elapsed(),
489 updates: None, }
491 }
492
493 #[must_use]
498 #[inline]
499 pub fn age(&self) -> Duration {
500 self.created_at.elapsed()
501 }
502
503 #[must_use]
507 #[inline]
508 pub fn is_zero(&self) -> bool {
509 self.get() == 0.0
510 }
511
512 #[must_use]
516 #[inline]
517 pub fn is_positive(&self) -> bool {
518 self.get() > 0.0
519 }
520
521 #[must_use]
525 #[inline]
526 pub fn is_negative(&self) -> bool {
527 self.get() < 0.0
528 }
529
530 #[must_use]
534 #[inline]
535 pub fn is_finite(&self) -> bool {
536 self.get().is_finite()
537 }
538}
539
540impl Default for Gauge {
541 #[inline]
542 fn default() -> Self {
543 Self::new()
544 }
545}
546
547impl std::fmt::Display for Gauge {
548 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
549 write!(f, "Gauge({})", self.get())
550 }
551}
552
553impl std::fmt::Debug for Gauge {
554 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
555 f.debug_struct("Gauge")
556 .field("value", &self.get())
557 .field("age", &self.age())
558 .field("is_finite", &self.is_finite())
559 .finish()
560 }
561}
562
563pub mod specialized {
569 use super::*;
570
571 #[repr(align(64))]
573 pub struct PercentageGauge {
574 inner: Gauge,
575 }
576
577 impl PercentageGauge {
578 #[inline]
580 pub fn new() -> Self {
581 Self {
582 inner: Gauge::new(),
583 }
584 }
585
586 #[inline(always)]
588 pub fn set_percentage(&self, percentage: f64) {
589 let clamped = percentage.clamp(0.0, 100.0);
590 self.inner.set(clamped);
591 }
592
593 #[inline(always)]
595 pub fn get_percentage(&self) -> f64 {
596 self.inner.get()
597 }
598
599 #[inline(always)]
601 pub fn set_ratio(&self, ratio: f64) {
602 let percentage = (ratio * 100.0).clamp(0.0, 100.0);
603 self.inner.set(percentage);
604 }
605
606 #[inline(always)]
608 pub fn get_ratio(&self) -> f64 {
609 self.inner.get() / 100.0
610 }
611
612 #[inline]
614 pub fn is_full(&self) -> bool {
615 (self.inner.get() - 100.0).abs() < f64::EPSILON
616 }
617
618 #[inline]
620 pub fn is_empty(&self) -> bool {
621 self.inner.get() < f64::EPSILON
622 }
623
624 #[inline]
626 pub fn add_percentage(&self, delta: f64) {
627 loop {
628 let current = self.get_percentage();
629 let new_value = (current + delta).clamp(0.0, 100.0);
630
631 if (current - new_value).abs() < f64::EPSILON {
632 break; }
634
635 match self.inner.compare_and_swap(current, new_value) {
636 Ok(_) => break,
637 Err(_) => continue, }
639 }
640 }
641
642 pub fn stats(&self) -> GaugeStats {
644 self.inner.stats()
645 }
646 }
647
648 impl Default for PercentageGauge {
649 fn default() -> Self {
650 Self::new()
651 }
652 }
653
654 impl std::fmt::Display for PercentageGauge {
655 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
656 write!(f, "PercentageGauge({}%)", self.get_percentage())
657 }
658 }
659
660 pub type CpuGauge = PercentageGauge;
662
663 #[repr(align(64))]
665 pub struct MemoryGauge {
666 bytes: Gauge,
667 }
668
669 impl MemoryGauge {
670 #[inline]
672 pub fn new() -> Self {
673 Self {
674 bytes: Gauge::new(),
675 }
676 }
677
678 #[inline(always)]
680 pub fn set_bytes(&self, bytes: u64) {
681 self.bytes.set(bytes as f64);
682 }
683
684 #[inline]
686 pub fn get_bytes(&self) -> u64 {
687 self.bytes.get() as u64
688 }
689
690 #[inline]
692 pub fn get_kb(&self) -> f64 {
693 self.bytes.get() / 1024.0
694 }
695
696 #[inline]
698 pub fn get_mb(&self) -> f64 {
699 self.bytes.get() / (1024.0 * 1024.0)
700 }
701
702 #[inline]
704 pub fn get_gb(&self) -> f64 {
705 self.bytes.get() / (1024.0 * 1024.0 * 1024.0)
706 }
707
708 #[inline]
710 pub fn add_bytes(&self, bytes: i64) {
711 self.bytes.add(bytes as f64);
712 }
713
714 pub fn stats(&self) -> GaugeStats {
716 self.bytes.stats()
717 }
718 }
719
720 impl Default for MemoryGauge {
721 fn default() -> Self {
722 Self::new()
723 }
724 }
725
726 impl std::fmt::Display for MemoryGauge {
727 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
728 let mb = self.get_mb();
729 if mb >= 1024.0 {
730 write!(f, "MemoryGauge({:.2} GB)", self.get_gb())
731 } else {
732 write!(f, "MemoryGauge({mb:.2} MB)")
733 }
734 }
735 }
736}
737
738#[cfg(test)]
739mod tests {
740 use super::*;
741 use std::sync::Arc;
742 use std::thread;
743
744 #[test]
745 fn test_basic_operations() {
746 let gauge = Gauge::new();
747
748 assert_eq!(gauge.get(), 0.0);
749 assert!(gauge.is_zero());
750 assert!(gauge.is_finite());
751
752 gauge.set(42.5);
753 assert_eq!(gauge.get(), 42.5);
754 assert!(!gauge.is_zero());
755 assert!(gauge.is_positive());
756
757 gauge.add(7.5);
758 assert_eq!(gauge.get(), 50.0);
759
760 gauge.sub(10.0);
761 assert_eq!(gauge.get(), 40.0);
762
763 gauge.reset();
764 assert_eq!(gauge.get(), 0.0);
765 }
766
767 #[test]
768 fn test_mathematical_operations() {
769 let gauge = Gauge::with_value(10.0);
770
771 gauge.multiply(2.0);
772 assert_eq!(gauge.get(), 20.0);
773
774 gauge.divide(4.0);
775 assert_eq!(gauge.get(), 5.0);
776
777 gauge.set(-15.0);
778 assert!(gauge.is_negative());
779
780 gauge.abs();
781 assert_eq!(gauge.get(), 15.0);
782 assert!(gauge.is_positive());
783
784 gauge.clamp(5.0, 10.0);
785 assert_eq!(gauge.get(), 10.0);
786 }
787
788 #[test]
789 fn test_min_max_operations() {
790 let gauge = Gauge::with_value(10.0);
791
792 gauge.set_max(15.0);
793 assert_eq!(gauge.get(), 15.0);
794
795 gauge.set_max(12.0); assert_eq!(gauge.get(), 15.0);
797
798 gauge.set_min(8.0);
799 assert_eq!(gauge.get(), 8.0);
800
801 gauge.set_min(12.0); assert_eq!(gauge.get(), 8.0);
803 }
804
805 #[test]
806 fn test_compare_and_swap() {
807 let gauge = Gauge::with_value(10.0);
808
809 assert_eq!(gauge.compare_and_swap(10.0, 20.0), Ok(10.0));
811 assert_eq!(gauge.get(), 20.0);
812
813 assert_eq!(gauge.compare_and_swap(10.0, 30.0), Err(20.0));
815 assert_eq!(gauge.get(), 20.0);
816 }
817
818 #[test]
819 fn test_ema_update() {
820 let gauge = Gauge::with_value(10.0);
821
822 gauge.update_ema(20.0, 0.5);
824 assert_eq!(gauge.get(), 15.0);
825
826 gauge.update_ema(30.0, 0.3);
828 assert_eq!(gauge.get(), 19.5);
829 }
830
831 #[test]
832 fn test_percentage_gauge() {
833 let gauge = specialized::PercentageGauge::new();
834
835 gauge.set_percentage(75.5);
836 assert_eq!(gauge.get_percentage(), 75.5);
837 assert!((gauge.get_ratio() - 0.755).abs() < f64::EPSILON);
838
839 gauge.set_ratio(0.9);
840 assert_eq!(gauge.get_percentage(), 90.0);
841
842 gauge.set_percentage(150.0);
844 assert_eq!(gauge.get_percentage(), 100.0);
845 assert!(gauge.is_full());
846
847 gauge.set_percentage(-10.0);
848 assert_eq!(gauge.get_percentage(), 0.0);
849 assert!(gauge.is_empty());
850
851 gauge.set_percentage(95.0);
853 gauge.add_percentage(10.0);
854 assert_eq!(gauge.get_percentage(), 100.0);
855 }
856
857 #[test]
858 fn test_memory_gauge() {
859 let gauge = specialized::MemoryGauge::new();
860
861 gauge.set_bytes(1024 * 1024 * 1024); assert_eq!(gauge.get_bytes(), 1024 * 1024 * 1024);
864 assert!((gauge.get_mb() - 1024.0).abs() < 0.1);
865 assert!((gauge.get_gb() - 1.0).abs() < 0.001);
866
867 gauge.add_bytes(1024 * 1024); assert!(gauge.get_mb() > 1024.0);
869 }
870
871 #[test]
872 fn test_statistics() {
873 let gauge = Gauge::with_value(42.0);
874
875 let stats = gauge.stats();
876 assert_eq!(stats.value, 42.0);
877 assert!(stats.age <= gauge.age());
878 assert!(stats.updates.is_none()); }
880
881 #[test]
882 fn test_high_concurrency() {
883 let gauge = Arc::new(Gauge::new());
884 let num_threads = 100;
885 let operations_per_thread = 1000;
886
887 let handles: Vec<_> = (0..num_threads)
888 .map(|thread_id| {
889 let gauge = Arc::clone(&gauge);
890 thread::spawn(move || {
891 for i in 0..operations_per_thread {
892 let value = (thread_id * operations_per_thread + i) as f64;
893 gauge.set(value);
894 gauge.add(0.1);
895 gauge.multiply(1.001);
896 }
897 })
898 })
899 .collect();
900
901 for handle in handles {
902 handle.join().unwrap();
903 }
904
905 let final_value = gauge.get();
907 assert!(final_value.is_finite());
908
909 let stats = gauge.stats();
910 assert!(stats.age <= gauge.age());
911 }
912
913 #[test]
914 fn test_special_values() {
915 let gauge = Gauge::new();
916
917 gauge.set(f64::INFINITY);
919 assert!(!gauge.is_finite());
920
921 gauge.set(f64::NAN);
923 assert!(!gauge.is_finite());
924
925 gauge.set(42.0);
927 assert!(gauge.is_finite());
928 }
929
930 #[test]
931 fn test_display_and_debug() {
932 let gauge = Gauge::with_value(42.5);
933
934 let display_str = format!("{gauge}");
935 assert!(display_str.contains("42.5"));
936
937 let debug_str = format!("{gauge:?}");
938 assert!(debug_str.contains("Gauge"));
939 assert!(debug_str.contains("42.5"));
940 }
941
942 #[test]
943 fn test_try_add_validation_and_overflow() {
944 let gauge = Gauge::new();
945
946 assert!(matches!(
948 gauge.try_add(f64::NAN),
949 Err(MetricsError::InvalidValue { .. })
950 ));
951 assert!(matches!(
952 gauge.try_add(f64::INFINITY),
953 Err(MetricsError::InvalidValue { .. })
954 ));
955
956 let gauge2 = Gauge::with_value(f64::MAX / 2.0);
958 assert!(matches!(
959 gauge2.try_add(f64::MAX),
960 Err(MetricsError::Overflow)
961 ));
962 }
963
964 #[test]
965 fn test_try_set_and_min_max_validation() {
966 let gauge = Gauge::new();
967 assert!(matches!(
968 gauge.try_set(f64::NAN),
969 Err(MetricsError::InvalidValue { .. })
970 ));
971
972 assert!(matches!(
974 gauge.try_set_max(f64::INFINITY),
975 Err(MetricsError::InvalidValue { .. })
976 ));
977
978 assert!(matches!(
980 gauge.try_set_min(f64::NAN),
981 Err(MetricsError::InvalidValue { .. })
982 ));
983 }
984
985 #[test]
986 fn test_ema_alpha_boundaries() {
987 let gauge = Gauge::with_value(10.0);
988
989 gauge.update_ema(100.0, -1.0);
991 assert_eq!(gauge.get(), 10.0);
992
993 gauge.update_ema(100.0, 2.0);
995 assert_eq!(gauge.get(), 100.0);
996
997 gauge.set(5.0);
999 gauge.update_ema(20.0, 0.0);
1000 assert_eq!(gauge.get(), 5.0);
1001 gauge.update_ema(20.0, 1.0);
1002 assert_eq!(gauge.get(), 20.0);
1003 }
1004
1005 #[test]
1006 fn test_non_finite_math_helpers_are_noops() {
1007 let gauge = Gauge::with_value(12.0);
1008
1009 gauge.add(f64::NAN);
1010 assert_eq!(gauge.get(), 12.0);
1011
1012 gauge.multiply(f64::INFINITY);
1013 assert_eq!(gauge.get(), 12.0);
1014
1015 gauge.divide(0.0);
1016 assert_eq!(gauge.get(), 12.0);
1017
1018 let huge = Gauge::with_value(f64::MAX / 2.0);
1019 huge.multiply(4.0);
1020 assert_eq!(huge.get(), f64::MAX / 2.0);
1021 }
1022}
1023
1024#[cfg(all(test, feature = "bench-tests", not(tarpaulin)))]
1025#[allow(unused_imports)]
1026mod benchmarks {
1027 use super::*;
1028 use std::time::Instant;
1029
1030 #[cfg_attr(not(feature = "bench-tests"), ignore)]
1031 #[test]
1032 fn bench_gauge_set() {
1033 let gauge = Gauge::new();
1034 let iterations = 10_000_000;
1035
1036 let start = Instant::now();
1037 for i in 0..iterations {
1038 gauge.set(i as f64);
1039 }
1040 let elapsed = start.elapsed();
1041
1042 println!(
1043 "Gauge set: {:.2} ns/op",
1044 elapsed.as_nanos() as f64 / iterations as f64
1045 );
1046
1047 assert!(elapsed.as_nanos() / iterations < 100);
1049 assert_eq!(gauge.get(), (iterations - 1) as f64);
1050 }
1051
1052 #[cfg_attr(not(feature = "bench-tests"), ignore)]
1053 #[test]
1054 fn bench_gauge_add() {
1055 let gauge = Gauge::new();
1056 let iterations = 1_000_000;
1057
1058 let start = Instant::now();
1059 for _ in 0..iterations {
1060 gauge.add(1.0);
1061 }
1062 let elapsed = start.elapsed();
1063
1064 println!(
1065 "Gauge add: {:.2} ns/op",
1066 elapsed.as_nanos() as f64 / iterations as f64
1067 );
1068
1069 assert!(elapsed.as_nanos() / iterations < 300);
1071 assert_eq!(gauge.get(), iterations as f64);
1072 }
1073
1074 #[cfg_attr(not(feature = "bench-tests"), ignore)]
1075 #[test]
1076 fn bench_gauge_get() {
1077 let gauge = Gauge::with_value(42.5);
1078 let iterations = 100_000_000;
1079
1080 let start = Instant::now();
1081 let mut sum = 0.0;
1082 for _ in 0..iterations {
1083 sum += gauge.get();
1084 }
1085 let elapsed = start.elapsed();
1086
1087 println!(
1088 "Gauge get: {:.2} ns/op",
1089 elapsed.as_nanos() as f64 / iterations as f64
1090 );
1091
1092 assert_eq!(sum, 42.5 * iterations as f64);
1094
1095 assert!(elapsed.as_nanos() / iterations < 50);
1097 }
1098}