1use std::sync::atomic::{AtomicU64, Ordering};
4use std::sync::Mutex;
5
6use crate::moments;
7use crate::Sensor;
8
9pub struct Counter {
13 label: &'static str,
14 count: AtomicU64,
15}
16
17impl Counter {
18 pub const fn new(label: &'static str) -> Counter {
20 Counter {
21 label,
22 count: AtomicU64::new(0),
23 }
24 }
25
26 #[inline(always)]
28 pub fn click(&self) {
29 self.count(1)
30 }
31
32 #[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
53const GAUGE_INIT: u64 = 0;
56
57pub struct Gauge {
59 label: &'static str,
60 value: AtomicU64,
61}
62
63impl Gauge {
64 pub const fn new(label: &'static str) -> Gauge {
66 Gauge {
67 label,
68 value: AtomicU64::new(GAUGE_INIT),
69 }
70 }
71
72 #[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
94pub struct Moments {
98 label: &'static str,
99 value: Mutex<moments::Moments>,
100}
101
102impl Moments {
103 pub const fn new(label: &'static str) -> Self {
105 Self {
106 label,
107 value: Mutex::new(moments::Moments::new()),
108 }
109 }
110
111 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
133pub 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#[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}