biometrics/
sensors.rs

1//! Sensors that implement the [Sensor] trait.
2
3use std::sync::atomic::{AtomicU64, Ordering};
4use std::sync::Mutex;
5
6use crate::moments;
7use crate::Sensor;
8
9////////////////////////////////////////////// Counter /////////////////////////////////////////////
10
11/// [Counter] captures a monotonically increasing value.
12pub struct Counter {
13    label: &'static str,
14    count: AtomicU64,
15}
16
17impl Counter {
18    /// Create a new counter with the provided label.
19    pub const fn new(label: &'static str) -> Counter {
20        Counter {
21            label,
22            count: AtomicU64::new(0),
23        }
24    }
25
26    /// Increment the counter by one.
27    #[inline(always)]
28    pub fn click(&self) {
29        self.count(1)
30    }
31
32    /// Increment the counter by `x`.
33    #[inline(always)]
34    pub fn count(&self, x: u64) {
35        self.count.fetch_add(x, Ordering::Relaxed);
36    }
37}
38
39impl Sensor for Counter {
40    type Reading = u64;
41
42    #[inline(always)]
43    fn label(&self) -> &'static str {
44        self.label
45    }
46
47    #[inline(always)]
48    fn read(&self) -> u64 {
49        self.count.load(Ordering::Relaxed)
50    }
51}
52
53/////////////////////////////////////////////// Gauge //////////////////////////////////////////////
54
55const GAUGE_INIT: u64 = 0;
56
57/// [Gauge] captures a floating point value.
58pub struct Gauge {
59    label: &'static str,
60    value: AtomicU64,
61}
62
63impl Gauge {
64    /// Create a new Gauge from the provided label.
65    pub const fn new(label: &'static str) -> Gauge {
66        Gauge {
67            label,
68            value: AtomicU64::new(GAUGE_INIT),
69        }
70    }
71
72    /// Set the value of the gauge.
73    #[inline(always)]
74    pub fn set(&self, x: f64) {
75        self.value.store(x.to_bits(), Ordering::Relaxed);
76    }
77}
78
79impl Sensor for Gauge {
80    type Reading = f64;
81
82    #[inline(always)]
83    fn label(&self) -> &'static str {
84        self.label
85    }
86
87    #[inline(always)]
88    fn read(&self) -> f64 {
89        let u = self.value.load(Ordering::Relaxed);
90        f64::from_bits(u)
91    }
92}
93
94////////////////////////////////////////////// Moments /////////////////////////////////////////////
95
96/// [Moments] captures mean, stdev, skewness, and kurtosis.
97pub struct Moments {
98    label: &'static str,
99    value: Mutex<moments::Moments>,
100}
101
102impl Moments {
103    /// Create a new set of moments with the provided label.
104    pub const fn new(label: &'static str) -> Self {
105        Self {
106            label,
107            value: Mutex::new(moments::Moments::new()),
108        }
109    }
110
111    /// Add the provided f64 to the accumulated moments.
112    pub fn add(&self, x: f64) {
113        let mut value = self.value.lock().unwrap();
114        value.push(x);
115    }
116}
117
118impl Sensor for Moments {
119    type Reading = moments::Moments;
120
121    #[inline(always)]
122    fn label(&self) -> &'static str {
123        self.label
124    }
125
126    #[inline(always)]
127    fn read(&self) -> moments::Moments {
128        let value = self.value.lock().unwrap();
129        *value
130    }
131}
132
133///////////////////////////////////////////// Histogram ////////////////////////////////////////////
134
135pub trait HistogramImpl: Send + Sync {
136    fn observe(&self, x: f64) -> Result<(), sig_fig_histogram::Error>;
137    fn observe_n(&self, x: f64, n: u64) -> Result<(), sig_fig_histogram::Error>;
138    fn to_histogram(&self) -> sig_fig_histogram::Histogram;
139}
140
141impl<const N: usize> HistogramImpl for sig_fig_histogram::LockFreeHistogram<N> {
142    fn observe(&self, x: f64) -> Result<(), sig_fig_histogram::Error> {
143        sig_fig_histogram::LockFreeHistogram::<N>::observe(self, x)
144    }
145
146    fn observe_n(&self, x: f64, n: u64) -> Result<(), sig_fig_histogram::Error> {
147        sig_fig_histogram::LockFreeHistogram::<N>::observe_n(self, x, n)
148    }
149
150    fn to_histogram(&self) -> sig_fig_histogram::Histogram {
151        sig_fig_histogram::LockFreeHistogram::<N>::to_histogram(self)
152    }
153}
154
155pub struct Histogram {
156    label: &'static str,
157    histogram: &'static dyn HistogramImpl,
158    exceeds_max: Counter,
159    is_negative: Counter,
160}
161
162impl Histogram {
163    pub const fn new(label: &'static str, histogram: &'static dyn HistogramImpl) -> Self {
164        let exceeds_max = Counter::new(label);
165        let is_negative = Counter::new(label);
166        Self {
167            label,
168            histogram,
169            exceeds_max,
170            is_negative,
171        }
172    }
173}
174
175impl Histogram {
176    pub fn exceeds_max(&self) -> &Counter {
177        &self.exceeds_max
178    }
179
180    pub fn is_negative(&self) -> &Counter {
181        &self.is_negative
182    }
183
184    pub fn observe(&self, x: f64) {
185        match self.histogram.observe(x) {
186            Ok(()) => {}
187            Err(sig_fig_histogram::Error::ExceedsMax) => {
188                self.exceeds_max.click();
189            }
190            Err(sig_fig_histogram::Error::IsNegative) => {
191                self.is_negative.click();
192            }
193        }
194    }
195
196    pub fn observe_n(&self, x: f64, n: u64) {
197        match self.histogram.observe_n(x, n) {
198            Ok(()) => {}
199            Err(sig_fig_histogram::Error::ExceedsMax) => {
200                self.exceeds_max.click();
201            }
202            Err(sig_fig_histogram::Error::IsNegative) => {
203                self.is_negative.click();
204            }
205        }
206    }
207}
208
209impl Sensor for Histogram {
210    type Reading = sig_fig_histogram::Histogram;
211
212    #[inline(always)]
213    fn label(&self) -> &'static str {
214        self.label
215    }
216
217    #[inline(always)]
218    fn read(&self) -> sig_fig_histogram::Histogram {
219        self.histogram.to_histogram()
220    }
221}
222
223/////////////////////////////////////////////// tests //////////////////////////////////////////////
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228
229    #[test]
230    fn gauge_init() {
231        let x: f64 = 0.0;
232        let y: u64 = x.to_bits();
233        assert_eq!(y, GAUGE_INIT);
234    }
235
236    #[test]
237    fn counter_may_be_static() {
238        static _COUNTER: Counter = Counter::new("counter.may.be.static");
239        _COUNTER.click();
240    }
241
242    #[test]
243    fn gauge_may_be_static() {
244        static _GAUGE: Gauge = Gauge::new("gauge.may.be.static");
245    }
246
247    #[test]
248    fn sync_moments_may_be_static() {
249        static _MOMENTS: Moments = Moments::new("sync.moments.may.be.static");
250    }
251
252    #[test]
253    fn sync_moments_multiple_add() {
254        static MOMENTS: Moments = Moments::new("sync.moments.multiple.add");
255        MOMENTS.add(0.0);
256        MOMENTS.add(5.0);
257        MOMENTS.add(10.0);
258        assert_eq!(MOMENTS.read().n(), 3);
259        assert_eq!(MOMENTS.read().mean(), 5.0);
260    }
261
262    #[test]
263    fn histogram() {
264        static HISTOGRAM: sig_fig_histogram::LockFreeHistogram<1000> =
265            sig_fig_histogram::LockFreeHistogram::new(3);
266        static HISTOGRAM_SENSOR: Histogram = Histogram::new("histogram", &HISTOGRAM);
267        HISTOGRAM_SENSOR.observe(0.0);
268        HISTOGRAM_SENSOR.observe(5.0);
269        HISTOGRAM_SENSOR.observe(10.0);
270    }
271}