Skip to main content

fast_telemetry/metric/
max_gauge_f64.rs

1//! Thread-sharded maximum gauge for `f64` values.
2//!
3//! Values are encoded into a sortable `u64` representation so the hot path can
4//! use atomic min/max operations while preserving numeric ordering, including
5//! for negative values.
6
7use crate::thread_id::thread_id;
8use crossbeam_utils::CachePadded;
9use std::fmt;
10use std::sync::atomic::{AtomicU64, Ordering};
11
12const SIGN_MASK: u64 = 1u64 << 63;
13
14#[inline]
15fn encode_ordered_f64(value: f64) -> u64 {
16    let bits = value.to_bits();
17    if bits & SIGN_MASK != 0 {
18        !bits
19    } else {
20        bits ^ SIGN_MASK
21    }
22}
23
24#[inline]
25fn decode_ordered_f64(encoded: u64) -> f64 {
26    let bits = if encoded & SIGN_MASK != 0 {
27        encoded ^ SIGN_MASK
28    } else {
29        !encoded
30    };
31    f64::from_bits(bits)
32}
33
34/// A thread-sharded maximum tracker for floating-point values.
35///
36/// `NaN` observations are ignored.
37pub struct MaxGaugeF64 {
38    cells: Vec<CachePadded<AtomicU64>>,
39    reset_value: u64,
40}
41
42impl MaxGaugeF64 {
43    /// Create a new max gauge with all shards initialized to zero.
44    pub fn new(shard_count: usize) -> Self {
45        Self::with_value(shard_count, 0.0)
46    }
47
48    /// Create a new max gauge with all shards initialized to `initial`.
49    pub fn with_value(shard_count: usize, initial: f64) -> Self {
50        let shard_count = shard_count.next_power_of_two();
51        let reset_value = encode_ordered_f64(initial);
52        Self {
53            cells: (0..shard_count)
54                .map(|_| CachePadded::new(AtomicU64::new(reset_value)))
55                .collect(),
56            reset_value,
57        }
58    }
59
60    /// Record a candidate value for the maximum.
61    ///
62    /// `NaN` is ignored.
63    #[inline]
64    pub fn observe(&self, value: f64) {
65        if value.is_nan() {
66            return;
67        }
68        let idx = thread_id() & (self.cells.len() - 1);
69        let cell = if cfg!(debug_assertions) {
70            self.cells.get(idx).expect("index out of bounds")
71        } else {
72            unsafe { self.cells.get_unchecked(idx) }
73        };
74        cell.fetch_max(encode_ordered_f64(value), Ordering::Relaxed);
75    }
76
77    /// Return the current maximum across all shards.
78    #[inline]
79    pub fn get(&self) -> f64 {
80        decode_ordered_f64(
81            self.cells
82                .iter()
83                .map(|cell| cell.load(Ordering::Relaxed))
84                .max()
85                .unwrap_or(self.reset_value),
86        )
87    }
88
89    /// Reset all shards to the original value configured at construction.
90    pub fn reset(&self) {
91        for cell in &self.cells {
92            cell.store(self.reset_value, Ordering::Relaxed);
93        }
94    }
95
96    /// Reset all shards and return the previous maximum.
97    pub fn swap_reset(&self) -> f64 {
98        decode_ordered_f64(
99            self.cells
100                .iter()
101                .map(|cell| cell.swap(self.reset_value, Ordering::Relaxed))
102                .max()
103                .unwrap_or(self.reset_value),
104        )
105    }
106}
107
108impl Default for MaxGaugeF64 {
109    fn default() -> Self {
110        Self::new(4)
111    }
112}
113
114impl fmt::Debug for MaxGaugeF64 {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        f.debug_struct("MaxGaugeF64")
117            .field("max", &self.get())
118            .field("cells", &self.cells.len())
119            .finish()
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn basic_observe() {
129        let gauge = MaxGaugeF64::new(4);
130        gauge.observe(3.5);
131        gauge.observe(7.25);
132        gauge.observe(5.0);
133        assert!((gauge.get() - 7.25).abs() < f64::EPSILON);
134    }
135
136    #[test]
137    fn negative_values_order_correctly() {
138        let gauge = MaxGaugeF64::with_value(4, -10.0);
139        gauge.observe(-3.25);
140        gauge.observe(-8.0);
141        assert!((gauge.get() - (-3.25)).abs() < f64::EPSILON);
142    }
143
144    #[test]
145    fn nan_is_ignored() {
146        let gauge = MaxGaugeF64::with_value(4, 1.0);
147        gauge.observe(f64::NAN);
148        assert!((gauge.get() - 1.0).abs() < f64::EPSILON);
149    }
150}