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]
170 pub fn add(&self, delta: f64) {
171 if delta == 0.0 {
172 return;
173 }
174
175 loop {
176 let current_bits = self.value.load(Ordering::Relaxed);
177 let current_value = f64::from_bits(current_bits);
178 let new_value = current_value + delta;
179 let new_bits = new_value.to_bits();
180
181 match self.value.compare_exchange_weak(
182 current_bits,
183 new_bits,
184 Ordering::Relaxed,
185 Ordering::Relaxed,
186 ) {
187 Ok(_) => break,
188 Err(_) => continue, }
190 }
191 }
192
193 #[inline]
195 pub fn sub(&self, delta: f64) {
196 self.add(-delta);
197 }
198
199 #[inline]
201 pub fn set_max(&self, value: f64) {
202 loop {
203 let current_bits = self.value.load(Ordering::Relaxed);
204 let current_value = f64::from_bits(current_bits);
205
206 if value <= current_value {
207 break; }
209
210 let new_bits = value.to_bits();
211 match self.value.compare_exchange_weak(
212 current_bits,
213 new_bits,
214 Ordering::Relaxed,
215 Ordering::Relaxed,
216 ) {
217 Ok(_) => break,
218 Err(_) => continue, }
220 }
221 }
222
223 #[inline]
237 pub fn try_set_max(&self, value: f64) -> Result<()> {
238 if !value.is_finite() {
239 return Err(MetricsError::InvalidValue {
240 reason: "value is not finite",
241 });
242 }
243 loop {
244 let current_bits = self.value.load(Ordering::Relaxed);
245 let current_value = f64::from_bits(current_bits);
246 if value <= current_value {
247 return Ok(());
248 }
249 let new_bits = value.to_bits();
250 match self.value.compare_exchange_weak(
251 current_bits,
252 new_bits,
253 Ordering::Relaxed,
254 Ordering::Relaxed,
255 ) {
256 Ok(_) => return Ok(()),
257 Err(_) => continue,
258 }
259 }
260 }
261
262 #[inline]
264 pub fn set_min(&self, value: f64) {
265 loop {
266 let current_bits = self.value.load(Ordering::Relaxed);
267 let current_value = f64::from_bits(current_bits);
268
269 if value >= current_value {
270 break; }
272
273 let new_bits = value.to_bits();
274 match self.value.compare_exchange_weak(
275 current_bits,
276 new_bits,
277 Ordering::Relaxed,
278 Ordering::Relaxed,
279 ) {
280 Ok(_) => break,
281 Err(_) => continue, }
283 }
284 }
285
286 #[inline]
300 pub fn try_set_min(&self, value: f64) -> Result<()> {
301 if !value.is_finite() {
302 return Err(MetricsError::InvalidValue {
303 reason: "value is not finite",
304 });
305 }
306 loop {
307 let current_bits = self.value.load(Ordering::Relaxed);
308 let current_value = f64::from_bits(current_bits);
309 if value >= current_value {
310 return Ok(());
311 }
312 let new_bits = value.to_bits();
313 match self.value.compare_exchange_weak(
314 current_bits,
315 new_bits,
316 Ordering::Relaxed,
317 Ordering::Relaxed,
318 ) {
319 Ok(_) => return Ok(()),
320 Err(_) => continue,
321 }
322 }
323 }
324
325 #[inline]
329 pub fn compare_and_swap(&self, expected: f64, new: f64) -> core::result::Result<f64, f64> {
330 let expected_bits = expected.to_bits();
331 let new_bits = new.to_bits();
332
333 match self.value.compare_exchange(
334 expected_bits,
335 new_bits,
336 Ordering::SeqCst,
337 Ordering::SeqCst,
338 ) {
339 Ok(prev_bits) => Ok(f64::from_bits(prev_bits)),
340 Err(current_bits) => Err(f64::from_bits(current_bits)),
341 }
342 }
343
344 #[inline]
346 pub fn reset(&self) {
347 self.set(0.0);
348 }
349
350 #[inline]
352 pub fn multiply(&self, factor: f64) {
353 if factor == 1.0 {
354 return;
355 }
356
357 loop {
358 let current_bits = self.value.load(Ordering::Relaxed);
359 let current_value = f64::from_bits(current_bits);
360 let new_value = current_value * factor;
361 let new_bits = new_value.to_bits();
362
363 match self.value.compare_exchange_weak(
364 current_bits,
365 new_bits,
366 Ordering::Relaxed,
367 Ordering::Relaxed,
368 ) {
369 Ok(_) => break,
370 Err(_) => continue,
371 }
372 }
373 }
374
375 #[inline]
377 pub fn divide(&self, divisor: f64) {
378 if divisor == 0.0 || divisor == 1.0 {
379 return;
380 }
381 self.multiply(1.0 / divisor);
382 }
383
384 #[inline]
386 pub fn abs(&self) {
387 loop {
388 let current_bits = self.value.load(Ordering::Relaxed);
389 let current_value = f64::from_bits(current_bits);
390
391 if current_value >= 0.0 {
392 break; }
394
395 let abs_value = current_value.abs();
396 let abs_bits = abs_value.to_bits();
397
398 match self.value.compare_exchange_weak(
399 current_bits,
400 abs_bits,
401 Ordering::Relaxed,
402 Ordering::Relaxed,
403 ) {
404 Ok(_) => break,
405 Err(_) => continue,
406 }
407 }
408 }
409
410 #[inline]
412 pub fn clamp(&self, min: f64, max: f64) {
413 loop {
414 let current_bits = self.value.load(Ordering::Relaxed);
415 let current_value = f64::from_bits(current_bits);
416 let clamped_value = current_value.clamp(min, max);
417
418 if (current_value - clamped_value).abs() < f64::EPSILON {
419 break; }
421
422 let clamped_bits = clamped_value.to_bits();
423
424 match self.value.compare_exchange_weak(
425 current_bits,
426 clamped_bits,
427 Ordering::Relaxed,
428 Ordering::Relaxed,
429 ) {
430 Ok(_) => break,
431 Err(_) => continue,
432 }
433 }
434 }
435
436 #[inline]
440 pub fn update_ema(&self, sample: f64, alpha: f64) {
441 let alpha = alpha.clamp(0.0, 1.0);
442
443 loop {
444 let current_bits = self.value.load(Ordering::Relaxed);
445 let current_value = f64::from_bits(current_bits);
446 let new_value = alpha * sample + (1.0 - alpha) * current_value;
447 let new_bits = new_value.to_bits();
448
449 match self.value.compare_exchange_weak(
450 current_bits,
451 new_bits,
452 Ordering::Relaxed,
453 Ordering::Relaxed,
454 ) {
455 Ok(_) => break,
456 Err(_) => continue,
457 }
458 }
459 }
460
461 #[must_use]
466 pub fn stats(&self) -> GaugeStats {
467 GaugeStats {
468 value: self.get(),
469 age: self.created_at.elapsed(),
470 updates: None, }
472 }
473
474 #[must_use]
479 #[inline]
480 pub fn age(&self) -> Duration {
481 self.created_at.elapsed()
482 }
483
484 #[must_use]
488 #[inline]
489 pub fn is_zero(&self) -> bool {
490 self.get() == 0.0
491 }
492
493 #[must_use]
497 #[inline]
498 pub fn is_positive(&self) -> bool {
499 self.get() > 0.0
500 }
501
502 #[must_use]
506 #[inline]
507 pub fn is_negative(&self) -> bool {
508 self.get() < 0.0
509 }
510
511 #[must_use]
515 #[inline]
516 pub fn is_finite(&self) -> bool {
517 self.get().is_finite()
518 }
519}
520
521impl Default for Gauge {
522 #[inline]
523 fn default() -> Self {
524 Self::new()
525 }
526}
527
528impl std::fmt::Display for Gauge {
529 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
530 write!(f, "Gauge({})", self.get())
531 }
532}
533
534impl std::fmt::Debug for Gauge {
535 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
536 f.debug_struct("Gauge")
537 .field("value", &self.get())
538 .field("age", &self.age())
539 .field("is_finite", &self.is_finite())
540 .finish()
541 }
542}
543
544unsafe impl Send for Gauge {}
546unsafe impl Sync for Gauge {}
547
548pub mod specialized {
550 use super::*;
551
552 #[repr(align(64))]
554 pub struct PercentageGauge {
555 inner: Gauge,
556 }
557
558 impl PercentageGauge {
559 #[inline]
561 pub fn new() -> Self {
562 Self {
563 inner: Gauge::new(),
564 }
565 }
566
567 #[inline(always)]
569 pub fn set_percentage(&self, percentage: f64) {
570 let clamped = percentage.clamp(0.0, 100.0);
571 self.inner.set(clamped);
572 }
573
574 #[inline(always)]
576 pub fn get_percentage(&self) -> f64 {
577 self.inner.get()
578 }
579
580 #[inline(always)]
582 pub fn set_ratio(&self, ratio: f64) {
583 let percentage = (ratio * 100.0).clamp(0.0, 100.0);
584 self.inner.set(percentage);
585 }
586
587 #[inline(always)]
589 pub fn get_ratio(&self) -> f64 {
590 self.inner.get() / 100.0
591 }
592
593 #[inline]
595 pub fn is_full(&self) -> bool {
596 (self.inner.get() - 100.0).abs() < f64::EPSILON
597 }
598
599 #[inline]
601 pub fn is_empty(&self) -> bool {
602 self.inner.get() < f64::EPSILON
603 }
604
605 #[inline]
607 pub fn add_percentage(&self, delta: f64) {
608 loop {
609 let current = self.get_percentage();
610 let new_value = (current + delta).clamp(0.0, 100.0);
611
612 if (current - new_value).abs() < f64::EPSILON {
613 break; }
615
616 match self.inner.compare_and_swap(current, new_value) {
617 Ok(_) => break,
618 Err(_) => continue, }
620 }
621 }
622
623 pub fn stats(&self) -> GaugeStats {
625 self.inner.stats()
626 }
627 }
628
629 impl Default for PercentageGauge {
630 fn default() -> Self {
631 Self::new()
632 }
633 }
634
635 impl std::fmt::Display for PercentageGauge {
636 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
637 write!(f, "PercentageGauge({}%)", self.get_percentage())
638 }
639 }
640
641 pub type CpuGauge = PercentageGauge;
643
644 #[repr(align(64))]
646 pub struct MemoryGauge {
647 bytes: Gauge,
648 }
649
650 impl MemoryGauge {
651 #[inline]
653 pub fn new() -> Self {
654 Self {
655 bytes: Gauge::new(),
656 }
657 }
658
659 #[inline(always)]
661 pub fn set_bytes(&self, bytes: u64) {
662 self.bytes.set(bytes as f64);
663 }
664
665 #[inline]
667 pub fn get_bytes(&self) -> u64 {
668 self.bytes.get() as u64
669 }
670
671 #[inline]
673 pub fn get_kb(&self) -> f64 {
674 self.bytes.get() / 1024.0
675 }
676
677 #[inline]
679 pub fn get_mb(&self) -> f64 {
680 self.bytes.get() / (1024.0 * 1024.0)
681 }
682
683 #[inline]
685 pub fn get_gb(&self) -> f64 {
686 self.bytes.get() / (1024.0 * 1024.0 * 1024.0)
687 }
688
689 #[inline]
691 pub fn add_bytes(&self, bytes: i64) {
692 self.bytes.add(bytes as f64);
693 }
694
695 pub fn stats(&self) -> GaugeStats {
697 self.bytes.stats()
698 }
699 }
700
701 impl Default for MemoryGauge {
702 fn default() -> Self {
703 Self::new()
704 }
705 }
706
707 impl std::fmt::Display for MemoryGauge {
708 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
709 let mb = self.get_mb();
710 if mb >= 1024.0 {
711 write!(f, "MemoryGauge({:.2} GB)", self.get_gb())
712 } else {
713 write!(f, "MemoryGauge({mb:.2} MB)")
714 }
715 }
716 }
717}
718
719#[cfg(test)]
720mod tests {
721 use super::*;
722 use std::sync::Arc;
723 use std::thread;
724
725 #[test]
726 fn test_basic_operations() {
727 let gauge = Gauge::new();
728
729 assert_eq!(gauge.get(), 0.0);
730 assert!(gauge.is_zero());
731 assert!(gauge.is_finite());
732
733 gauge.set(42.5);
734 assert_eq!(gauge.get(), 42.5);
735 assert!(!gauge.is_zero());
736 assert!(gauge.is_positive());
737
738 gauge.add(7.5);
739 assert_eq!(gauge.get(), 50.0);
740
741 gauge.sub(10.0);
742 assert_eq!(gauge.get(), 40.0);
743
744 gauge.reset();
745 assert_eq!(gauge.get(), 0.0);
746 }
747
748 #[test]
749 fn test_mathematical_operations() {
750 let gauge = Gauge::with_value(10.0);
751
752 gauge.multiply(2.0);
753 assert_eq!(gauge.get(), 20.0);
754
755 gauge.divide(4.0);
756 assert_eq!(gauge.get(), 5.0);
757
758 gauge.set(-15.0);
759 assert!(gauge.is_negative());
760
761 gauge.abs();
762 assert_eq!(gauge.get(), 15.0);
763 assert!(gauge.is_positive());
764
765 gauge.clamp(5.0, 10.0);
766 assert_eq!(gauge.get(), 10.0);
767 }
768
769 #[test]
770 fn test_min_max_operations() {
771 let gauge = Gauge::with_value(10.0);
772
773 gauge.set_max(15.0);
774 assert_eq!(gauge.get(), 15.0);
775
776 gauge.set_max(12.0); assert_eq!(gauge.get(), 15.0);
778
779 gauge.set_min(8.0);
780 assert_eq!(gauge.get(), 8.0);
781
782 gauge.set_min(12.0); assert_eq!(gauge.get(), 8.0);
784 }
785
786 #[test]
787 fn test_compare_and_swap() {
788 let gauge = Gauge::with_value(10.0);
789
790 assert_eq!(gauge.compare_and_swap(10.0, 20.0), Ok(10.0));
792 assert_eq!(gauge.get(), 20.0);
793
794 assert_eq!(gauge.compare_and_swap(10.0, 30.0), Err(20.0));
796 assert_eq!(gauge.get(), 20.0);
797 }
798
799 #[test]
800 fn test_ema_update() {
801 let gauge = Gauge::with_value(10.0);
802
803 gauge.update_ema(20.0, 0.5);
805 assert_eq!(gauge.get(), 15.0);
806
807 gauge.update_ema(30.0, 0.3);
809 assert_eq!(gauge.get(), 19.5);
810 }
811
812 #[test]
813 fn test_percentage_gauge() {
814 let gauge = specialized::PercentageGauge::new();
815
816 gauge.set_percentage(75.5);
817 assert_eq!(gauge.get_percentage(), 75.5);
818 assert!((gauge.get_ratio() - 0.755).abs() < f64::EPSILON);
819
820 gauge.set_ratio(0.9);
821 assert_eq!(gauge.get_percentage(), 90.0);
822
823 gauge.set_percentage(150.0);
825 assert_eq!(gauge.get_percentage(), 100.0);
826 assert!(gauge.is_full());
827
828 gauge.set_percentage(-10.0);
829 assert_eq!(gauge.get_percentage(), 0.0);
830 assert!(gauge.is_empty());
831
832 gauge.set_percentage(95.0);
834 gauge.add_percentage(10.0);
835 assert_eq!(gauge.get_percentage(), 100.0);
836 }
837
838 #[test]
839 fn test_memory_gauge() {
840 let gauge = specialized::MemoryGauge::new();
841
842 gauge.set_bytes(1024 * 1024 * 1024); assert_eq!(gauge.get_bytes(), 1024 * 1024 * 1024);
845 assert!((gauge.get_mb() - 1024.0).abs() < 0.1);
846 assert!((gauge.get_gb() - 1.0).abs() < 0.001);
847
848 gauge.add_bytes(1024 * 1024); assert!(gauge.get_mb() > 1024.0);
850 }
851
852 #[test]
853 fn test_statistics() {
854 let gauge = Gauge::with_value(42.0);
855
856 let stats = gauge.stats();
857 assert_eq!(stats.value, 42.0);
858 assert!(stats.age > Duration::from_nanos(0));
859 assert!(stats.updates.is_none()); }
861
862 #[test]
863 fn test_high_concurrency() {
864 let gauge = Arc::new(Gauge::new());
865 let num_threads = 100;
866 let operations_per_thread = 1000;
867
868 let handles: Vec<_> = (0..num_threads)
869 .map(|thread_id| {
870 let gauge = Arc::clone(&gauge);
871 thread::spawn(move || {
872 for i in 0..operations_per_thread {
873 let value = (thread_id * operations_per_thread + i) as f64;
874 gauge.set(value);
875 gauge.add(0.1);
876 gauge.multiply(1.001);
877 }
878 })
879 })
880 .collect();
881
882 for handle in handles {
883 handle.join().unwrap();
884 }
885
886 let final_value = gauge.get();
888 assert!(final_value.is_finite());
889
890 let stats = gauge.stats();
891 assert!(stats.age > Duration::from_nanos(0));
892 }
893
894 #[test]
895 fn test_special_values() {
896 let gauge = Gauge::new();
897
898 gauge.set(f64::INFINITY);
900 assert!(!gauge.is_finite());
901
902 gauge.set(f64::NAN);
904 assert!(!gauge.is_finite());
905
906 gauge.set(42.0);
908 assert!(gauge.is_finite());
909 }
910
911 #[test]
912 fn test_display_and_debug() {
913 let gauge = Gauge::with_value(42.5);
914
915 let display_str = format!("{gauge}");
916 assert!(display_str.contains("42.5"));
917
918 let debug_str = format!("{gauge:?}");
919 assert!(debug_str.contains("Gauge"));
920 assert!(debug_str.contains("42.5"));
921 }
922
923 #[test]
924 fn test_try_add_validation_and_overflow() {
925 let gauge = Gauge::new();
926
927 assert!(matches!(
929 gauge.try_add(f64::NAN),
930 Err(MetricsError::InvalidValue { .. })
931 ));
932 assert!(matches!(
933 gauge.try_add(f64::INFINITY),
934 Err(MetricsError::InvalidValue { .. })
935 ));
936
937 let gauge2 = Gauge::with_value(f64::MAX / 2.0);
939 assert!(matches!(
940 gauge2.try_add(f64::MAX),
941 Err(MetricsError::Overflow)
942 ));
943 }
944
945 #[test]
946 fn test_try_set_and_min_max_validation() {
947 let gauge = Gauge::new();
948 assert!(matches!(
949 gauge.try_set(f64::NAN),
950 Err(MetricsError::InvalidValue { .. })
951 ));
952
953 assert!(matches!(
955 gauge.try_set_max(f64::INFINITY),
956 Err(MetricsError::InvalidValue { .. })
957 ));
958
959 assert!(matches!(
961 gauge.try_set_min(f64::NAN),
962 Err(MetricsError::InvalidValue { .. })
963 ));
964 }
965
966 #[test]
967 fn test_ema_alpha_boundaries() {
968 let gauge = Gauge::with_value(10.0);
969
970 gauge.update_ema(100.0, -1.0);
972 assert_eq!(gauge.get(), 10.0);
973
974 gauge.update_ema(100.0, 2.0);
976 assert_eq!(gauge.get(), 100.0);
977
978 gauge.set(5.0);
980 gauge.update_ema(20.0, 0.0);
981 assert_eq!(gauge.get(), 5.0);
982 gauge.update_ema(20.0, 1.0);
983 assert_eq!(gauge.get(), 20.0);
984 }
985}
986
987#[cfg(all(test, feature = "bench-tests", not(tarpaulin)))]
988#[allow(unused_imports)]
989mod benchmarks {
990 use super::*;
991 use std::time::Instant;
992
993 #[cfg_attr(not(feature = "bench-tests"), ignore)]
994 #[test]
995 fn bench_gauge_set() {
996 let gauge = Gauge::new();
997 let iterations = 10_000_000;
998
999 let start = Instant::now();
1000 for i in 0..iterations {
1001 gauge.set(i as f64);
1002 }
1003 let elapsed = start.elapsed();
1004
1005 println!(
1006 "Gauge set: {:.2} ns/op",
1007 elapsed.as_nanos() as f64 / iterations as f64
1008 );
1009
1010 assert!(elapsed.as_nanos() / iterations < 100);
1012 assert_eq!(gauge.get(), (iterations - 1) as f64);
1013 }
1014
1015 #[cfg_attr(not(feature = "bench-tests"), ignore)]
1016 #[test]
1017 fn bench_gauge_add() {
1018 let gauge = Gauge::new();
1019 let iterations = 1_000_000;
1020
1021 let start = Instant::now();
1022 for _ in 0..iterations {
1023 gauge.add(1.0);
1024 }
1025 let elapsed = start.elapsed();
1026
1027 println!(
1028 "Gauge add: {:.2} ns/op",
1029 elapsed.as_nanos() as f64 / iterations as f64
1030 );
1031
1032 assert!(elapsed.as_nanos() / iterations < 300);
1034 assert_eq!(gauge.get(), iterations as f64);
1035 }
1036
1037 #[cfg_attr(not(feature = "bench-tests"), ignore)]
1038 #[test]
1039 fn bench_gauge_get() {
1040 let gauge = Gauge::with_value(42.5);
1041 let iterations = 100_000_000;
1042
1043 let start = Instant::now();
1044 let mut sum = 0.0;
1045 for _ in 0..iterations {
1046 sum += gauge.get();
1047 }
1048 let elapsed = start.elapsed();
1049
1050 println!(
1051 "Gauge get: {:.2} ns/op",
1052 elapsed.as_nanos() as f64 / iterations as f64
1053 );
1054
1055 assert_eq!(sum, 42.5 * iterations as f64);
1057
1058 assert!(elapsed.as_nanos() / iterations < 50);
1060 }
1061}