1use std::sync::atomic::{AtomicU64, Ordering};
14use std::time::{Duration, Instant};
15
16#[repr(align(64))]
21pub struct Gauge {
22 value: AtomicU64,
24 created_at: Instant,
26}
27
28#[derive(Debug, Clone)]
30pub struct GaugeStats {
31 pub value: f64,
33 pub age: Duration,
35 pub updates: Option<u64>,
37}
38
39impl Gauge {
40 #[inline]
42 pub fn new() -> Self {
43 Self {
44 value: AtomicU64::new(0.0_f64.to_bits()),
45 created_at: Instant::now(),
46 }
47 }
48
49 #[inline]
51 pub fn with_value(initial: f64) -> Self {
52 Self {
53 value: AtomicU64::new(initial.to_bits()),
54 created_at: Instant::now(),
55 }
56 }
57
58 #[inline(always)]
66 pub fn set(&self, value: f64) {
67 self.value.store(value.to_bits(), Ordering::Relaxed);
68 }
69
70 #[inline(always)]
72 pub fn get(&self) -> f64 {
73 f64::from_bits(self.value.load(Ordering::Relaxed))
74 }
75
76 #[inline]
80 pub fn add(&self, delta: f64) {
81 if delta == 0.0 {
82 return;
83 }
84
85 loop {
86 let current_bits = self.value.load(Ordering::Relaxed);
87 let current_value = f64::from_bits(current_bits);
88 let new_value = current_value + delta;
89 let new_bits = new_value.to_bits();
90
91 match self.value.compare_exchange_weak(
92 current_bits,
93 new_bits,
94 Ordering::Relaxed,
95 Ordering::Relaxed,
96 ) {
97 Ok(_) => break,
98 Err(_) => continue, }
100 }
101 }
102
103 #[inline]
105 pub fn sub(&self, delta: f64) {
106 self.add(-delta);
107 }
108
109 #[inline]
111 pub fn set_max(&self, value: f64) {
112 loop {
113 let current_bits = self.value.load(Ordering::Relaxed);
114 let current_value = f64::from_bits(current_bits);
115
116 if value <= current_value {
117 break; }
119
120 let new_bits = value.to_bits();
121 match self.value.compare_exchange_weak(
122 current_bits,
123 new_bits,
124 Ordering::Relaxed,
125 Ordering::Relaxed,
126 ) {
127 Ok(_) => break,
128 Err(_) => continue, }
130 }
131 }
132
133 #[inline]
135 pub fn set_min(&self, value: f64) {
136 loop {
137 let current_bits = self.value.load(Ordering::Relaxed);
138 let current_value = f64::from_bits(current_bits);
139
140 if value >= current_value {
141 break; }
143
144 let new_bits = value.to_bits();
145 match self.value.compare_exchange_weak(
146 current_bits,
147 new_bits,
148 Ordering::Relaxed,
149 Ordering::Relaxed,
150 ) {
151 Ok(_) => break,
152 Err(_) => continue, }
154 }
155 }
156
157 #[inline]
161 pub fn compare_and_swap(&self, expected: f64, new: f64) -> Result<f64, f64> {
162 let expected_bits = expected.to_bits();
163 let new_bits = new.to_bits();
164
165 match self.value.compare_exchange(
166 expected_bits,
167 new_bits,
168 Ordering::SeqCst,
169 Ordering::SeqCst,
170 ) {
171 Ok(prev_bits) => Ok(f64::from_bits(prev_bits)),
172 Err(current_bits) => Err(f64::from_bits(current_bits)),
173 }
174 }
175
176 #[inline]
178 pub fn reset(&self) {
179 self.set(0.0);
180 }
181
182 #[inline]
184 pub fn multiply(&self, factor: f64) {
185 if factor == 1.0 {
186 return;
187 }
188
189 loop {
190 let current_bits = self.value.load(Ordering::Relaxed);
191 let current_value = f64::from_bits(current_bits);
192 let new_value = current_value * factor;
193 let new_bits = new_value.to_bits();
194
195 match self.value.compare_exchange_weak(
196 current_bits,
197 new_bits,
198 Ordering::Relaxed,
199 Ordering::Relaxed,
200 ) {
201 Ok(_) => break,
202 Err(_) => continue,
203 }
204 }
205 }
206
207 #[inline]
209 pub fn divide(&self, divisor: f64) {
210 if divisor == 0.0 || divisor == 1.0 {
211 return;
212 }
213 self.multiply(1.0 / divisor);
214 }
215
216 #[inline]
218 pub fn abs(&self) {
219 loop {
220 let current_bits = self.value.load(Ordering::Relaxed);
221 let current_value = f64::from_bits(current_bits);
222
223 if current_value >= 0.0 {
224 break; }
226
227 let abs_value = current_value.abs();
228 let abs_bits = abs_value.to_bits();
229
230 match self.value.compare_exchange_weak(
231 current_bits,
232 abs_bits,
233 Ordering::Relaxed,
234 Ordering::Relaxed,
235 ) {
236 Ok(_) => break,
237 Err(_) => continue,
238 }
239 }
240 }
241
242 #[inline]
244 pub fn clamp(&self, min: f64, max: f64) {
245 loop {
246 let current_bits = self.value.load(Ordering::Relaxed);
247 let current_value = f64::from_bits(current_bits);
248 let clamped_value = current_value.clamp(min, max);
249
250 if (current_value - clamped_value).abs() < f64::EPSILON {
251 break; }
253
254 let clamped_bits = clamped_value.to_bits();
255
256 match self.value.compare_exchange_weak(
257 current_bits,
258 clamped_bits,
259 Ordering::Relaxed,
260 Ordering::Relaxed,
261 ) {
262 Ok(_) => break,
263 Err(_) => continue,
264 }
265 }
266 }
267
268 #[inline]
272 pub fn update_ema(&self, sample: f64, alpha: f64) {
273 let alpha = alpha.clamp(0.0, 1.0);
274
275 loop {
276 let current_bits = self.value.load(Ordering::Relaxed);
277 let current_value = f64::from_bits(current_bits);
278 let new_value = alpha * sample + (1.0 - alpha) * current_value;
279 let new_bits = new_value.to_bits();
280
281 match self.value.compare_exchange_weak(
282 current_bits,
283 new_bits,
284 Ordering::Relaxed,
285 Ordering::Relaxed,
286 ) {
287 Ok(_) => break,
288 Err(_) => continue,
289 }
290 }
291 }
292
293 pub fn stats(&self) -> GaugeStats {
295 GaugeStats {
296 value: self.get(),
297 age: self.created_at.elapsed(),
298 updates: None, }
300 }
301
302 #[inline]
304 pub fn age(&self) -> Duration {
305 self.created_at.elapsed()
306 }
307
308 #[inline]
310 pub fn is_zero(&self) -> bool {
311 self.get() == 0.0
312 }
313
314 #[inline]
316 pub fn is_positive(&self) -> bool {
317 self.get() > 0.0
318 }
319
320 #[inline]
322 pub fn is_negative(&self) -> bool {
323 self.get() < 0.0
324 }
325
326 #[inline]
328 pub fn is_finite(&self) -> bool {
329 self.get().is_finite()
330 }
331}
332
333impl Default for Gauge {
334 #[inline]
335 fn default() -> Self {
336 Self::new()
337 }
338}
339
340impl std::fmt::Display for Gauge {
341 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
342 write!(f, "Gauge({})", self.get())
343 }
344}
345
346impl std::fmt::Debug for Gauge {
347 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
348 f.debug_struct("Gauge")
349 .field("value", &self.get())
350 .field("age", &self.age())
351 .field("is_finite", &self.is_finite())
352 .finish()
353 }
354}
355
356unsafe impl Send for Gauge {}
358unsafe impl Sync for Gauge {}
359
360pub mod specialized {
362 use super::*;
363
364 #[repr(align(64))]
366 pub struct PercentageGauge {
367 inner: Gauge,
368 }
369
370 impl PercentageGauge {
371 #[inline]
373 pub fn new() -> Self {
374 Self {
375 inner: Gauge::new(),
376 }
377 }
378
379 #[inline(always)]
381 pub fn set_percentage(&self, percentage: f64) {
382 let clamped = percentage.clamp(0.0, 100.0);
383 self.inner.set(clamped);
384 }
385
386 #[inline(always)]
388 pub fn get_percentage(&self) -> f64 {
389 self.inner.get()
390 }
391
392 #[inline(always)]
394 pub fn set_ratio(&self, ratio: f64) {
395 let percentage = (ratio * 100.0).clamp(0.0, 100.0);
396 self.inner.set(percentage);
397 }
398
399 #[inline(always)]
401 pub fn get_ratio(&self) -> f64 {
402 self.inner.get() / 100.0
403 }
404
405 #[inline]
407 pub fn is_full(&self) -> bool {
408 (self.inner.get() - 100.0).abs() < f64::EPSILON
409 }
410
411 #[inline]
413 pub fn is_empty(&self) -> bool {
414 self.inner.get() < f64::EPSILON
415 }
416
417 #[inline]
419 pub fn add_percentage(&self, delta: f64) {
420 loop {
421 let current = self.get_percentage();
422 let new_value = (current + delta).clamp(0.0, 100.0);
423
424 if (current - new_value).abs() < f64::EPSILON {
425 break; }
427
428 match self.inner.compare_and_swap(current, new_value) {
429 Ok(_) => break,
430 Err(_) => continue, }
432 }
433 }
434
435 pub fn stats(&self) -> GaugeStats {
437 self.inner.stats()
438 }
439 }
440
441 impl Default for PercentageGauge {
442 fn default() -> Self {
443 Self::new()
444 }
445 }
446
447 impl std::fmt::Display for PercentageGauge {
448 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
449 write!(f, "PercentageGauge({}%)", self.get_percentage())
450 }
451 }
452
453 pub type CpuGauge = PercentageGauge;
455
456 #[repr(align(64))]
458 pub struct MemoryGauge {
459 bytes: Gauge,
460 }
461
462 impl MemoryGauge {
463 #[inline]
465 pub fn new() -> Self {
466 Self {
467 bytes: Gauge::new(),
468 }
469 }
470
471 #[inline(always)]
473 pub fn set_bytes(&self, bytes: u64) {
474 self.bytes.set(bytes as f64);
475 }
476
477 #[inline]
479 pub fn get_bytes(&self) -> u64 {
480 self.bytes.get() as u64
481 }
482
483 #[inline]
485 pub fn get_kb(&self) -> f64 {
486 self.bytes.get() / 1024.0
487 }
488
489 #[inline]
491 pub fn get_mb(&self) -> f64 {
492 self.bytes.get() / (1024.0 * 1024.0)
493 }
494
495 #[inline]
497 pub fn get_gb(&self) -> f64 {
498 self.bytes.get() / (1024.0 * 1024.0 * 1024.0)
499 }
500
501 #[inline]
503 pub fn add_bytes(&self, bytes: i64) {
504 self.bytes.add(bytes as f64);
505 }
506
507 pub fn stats(&self) -> GaugeStats {
509 self.bytes.stats()
510 }
511 }
512
513 impl Default for MemoryGauge {
514 fn default() -> Self {
515 Self::new()
516 }
517 }
518
519 impl std::fmt::Display for MemoryGauge {
520 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
521 let mb = self.get_mb();
522 if mb >= 1024.0 {
523 write!(f, "MemoryGauge({:.2} GB)", self.get_gb())
524 } else {
525 write!(f, "MemoryGauge({mb:.2} MB)")
526 }
527 }
528 }
529}
530
531#[cfg(test)]
532mod tests {
533 use super::*;
534 use std::sync::Arc;
535 use std::thread;
536
537 #[test]
538 fn test_basic_operations() {
539 let gauge = Gauge::new();
540
541 assert_eq!(gauge.get(), 0.0);
542 assert!(gauge.is_zero());
543 assert!(gauge.is_finite());
544
545 gauge.set(42.5);
546 assert_eq!(gauge.get(), 42.5);
547 assert!(!gauge.is_zero());
548 assert!(gauge.is_positive());
549
550 gauge.add(7.5);
551 assert_eq!(gauge.get(), 50.0);
552
553 gauge.sub(10.0);
554 assert_eq!(gauge.get(), 40.0);
555
556 gauge.reset();
557 assert_eq!(gauge.get(), 0.0);
558 }
559
560 #[test]
561 fn test_mathematical_operations() {
562 let gauge = Gauge::with_value(10.0);
563
564 gauge.multiply(2.0);
565 assert_eq!(gauge.get(), 20.0);
566
567 gauge.divide(4.0);
568 assert_eq!(gauge.get(), 5.0);
569
570 gauge.set(-15.0);
571 assert!(gauge.is_negative());
572
573 gauge.abs();
574 assert_eq!(gauge.get(), 15.0);
575 assert!(gauge.is_positive());
576
577 gauge.clamp(5.0, 10.0);
578 assert_eq!(gauge.get(), 10.0);
579 }
580
581 #[test]
582 fn test_min_max_operations() {
583 let gauge = Gauge::with_value(10.0);
584
585 gauge.set_max(15.0);
586 assert_eq!(gauge.get(), 15.0);
587
588 gauge.set_max(12.0); assert_eq!(gauge.get(), 15.0);
590
591 gauge.set_min(8.0);
592 assert_eq!(gauge.get(), 8.0);
593
594 gauge.set_min(12.0); assert_eq!(gauge.get(), 8.0);
596 }
597
598 #[test]
599 fn test_compare_and_swap() {
600 let gauge = Gauge::with_value(10.0);
601
602 assert_eq!(gauge.compare_and_swap(10.0, 20.0), Ok(10.0));
604 assert_eq!(gauge.get(), 20.0);
605
606 assert_eq!(gauge.compare_and_swap(10.0, 30.0), Err(20.0));
608 assert_eq!(gauge.get(), 20.0);
609 }
610
611 #[test]
612 fn test_ema_update() {
613 let gauge = Gauge::with_value(10.0);
614
615 gauge.update_ema(20.0, 0.5);
617 assert_eq!(gauge.get(), 15.0);
618
619 gauge.update_ema(30.0, 0.3);
621 assert_eq!(gauge.get(), 19.5);
622 }
623
624 #[test]
625 fn test_percentage_gauge() {
626 let gauge = specialized::PercentageGauge::new();
627
628 gauge.set_percentage(75.5);
629 assert_eq!(gauge.get_percentage(), 75.5);
630 assert!((gauge.get_ratio() - 0.755).abs() < f64::EPSILON);
631
632 gauge.set_ratio(0.9);
633 assert_eq!(gauge.get_percentage(), 90.0);
634
635 gauge.set_percentage(150.0);
637 assert_eq!(gauge.get_percentage(), 100.0);
638 assert!(gauge.is_full());
639
640 gauge.set_percentage(-10.0);
641 assert_eq!(gauge.get_percentage(), 0.0);
642 assert!(gauge.is_empty());
643
644 gauge.set_percentage(95.0);
646 gauge.add_percentage(10.0);
647 assert_eq!(gauge.get_percentage(), 100.0);
648 }
649
650 #[test]
651 fn test_memory_gauge() {
652 let gauge = specialized::MemoryGauge::new();
653
654 gauge.set_bytes(1024 * 1024 * 1024); assert_eq!(gauge.get_bytes(), 1024 * 1024 * 1024);
657 assert!((gauge.get_mb() - 1024.0).abs() < 0.1);
658 assert!((gauge.get_gb() - 1.0).abs() < 0.001);
659
660 gauge.add_bytes(1024 * 1024); assert!(gauge.get_mb() > 1024.0);
662 }
663
664 #[test]
665 fn test_statistics() {
666 let gauge = Gauge::with_value(42.0);
667
668 let stats = gauge.stats();
669 assert_eq!(stats.value, 42.0);
670 assert!(stats.age > Duration::from_nanos(0));
671 assert!(stats.updates.is_none()); }
673
674 #[test]
675 fn test_high_concurrency() {
676 let gauge = Arc::new(Gauge::new());
677 let num_threads = 100;
678 let operations_per_thread = 1000;
679
680 let handles: Vec<_> = (0..num_threads)
681 .map(|thread_id| {
682 let gauge = Arc::clone(&gauge);
683 thread::spawn(move || {
684 for i in 0..operations_per_thread {
685 let value = (thread_id * operations_per_thread + i) as f64;
686 gauge.set(value);
687 gauge.add(0.1);
688 gauge.multiply(1.001);
689 }
690 })
691 })
692 .collect();
693
694 for handle in handles {
695 handle.join().unwrap();
696 }
697
698 let final_value = gauge.get();
700 assert!(final_value.is_finite());
701
702 let stats = gauge.stats();
703 assert!(stats.age > Duration::from_nanos(0));
704 }
705
706 #[test]
707 fn test_special_values() {
708 let gauge = Gauge::new();
709
710 gauge.set(f64::INFINITY);
712 assert!(!gauge.is_finite());
713
714 gauge.set(f64::NAN);
716 assert!(!gauge.is_finite());
717
718 gauge.set(42.0);
720 assert!(gauge.is_finite());
721 }
722
723 #[test]
724 fn test_display_and_debug() {
725 let gauge = Gauge::with_value(42.5);
726
727 let display_str = format!("{}", gauge);
728 assert!(display_str.contains("42.5"));
729
730 let debug_str = format!("{:?}", gauge);
731 assert!(debug_str.contains("Gauge"));
732 assert!(debug_str.contains("42.5"));
733 }
734}
735
736#[cfg(all(test, feature = "bench-tests", not(tarpaulin)))]
737#[allow(unused_imports)]
738mod benchmarks {
739 use super::*;
740 use std::time::Instant;
741
742 #[cfg_attr(not(feature = "bench-tests"), ignore)]
743 #[test]
744 fn bench_gauge_set() {
745 let gauge = Gauge::new();
746 let iterations = 10_000_000;
747
748 let start = Instant::now();
749 for i in 0..iterations {
750 gauge.set(i as f64);
751 }
752 let elapsed = start.elapsed();
753
754 println!(
755 "Gauge set: {:.2} ns/op",
756 elapsed.as_nanos() as f64 / iterations as f64
757 );
758
759 assert!(elapsed.as_nanos() / iterations < 100);
761 assert_eq!(gauge.get(), (iterations - 1) as f64);
762 }
763
764 #[cfg_attr(not(feature = "bench-tests"), ignore)]
765 #[test]
766 fn bench_gauge_add() {
767 let gauge = Gauge::new();
768 let iterations = 1_000_000;
769
770 let start = Instant::now();
771 for _ in 0..iterations {
772 gauge.add(1.0);
773 }
774 let elapsed = start.elapsed();
775
776 println!(
777 "Gauge add: {:.2} ns/op",
778 elapsed.as_nanos() as f64 / iterations as f64
779 );
780
781 assert!(elapsed.as_nanos() / iterations < 300);
783 assert_eq!(gauge.get(), iterations as f64);
784 }
785
786 #[cfg_attr(not(feature = "bench-tests"), ignore)]
787 #[test]
788 fn bench_gauge_get() {
789 let gauge = Gauge::with_value(42.5);
790 let iterations = 100_000_000;
791
792 let start = Instant::now();
793 let mut sum = 0.0;
794 for _ in 0..iterations {
795 sum += gauge.get();
796 }
797 let elapsed = start.elapsed();
798
799 println!(
800 "Gauge get: {:.2} ns/op",
801 elapsed.as_nanos() as f64 / iterations as f64
802 );
803
804 assert_eq!(sum, 42.5 * iterations as f64);
806
807 assert!(elapsed.as_nanos() / iterations < 50);
809 }
810}