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