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