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