1use std::any::Any;
4use std::cmp::Ordering;
5use std::hash::{Hash, Hasher};
6use std::result;
7use std::sync::PoisonError;
8use std::{borrow::Cow, sync::Arc};
9use thiserror::Error;
10
11mod instruments;
12mod meter;
13pub mod noop;
14
15use crate::{Array, ExportError, KeyValue, Value};
16pub use instruments::{
17 counter::{Counter, ObservableCounter, SyncCounter},
18 gauge::{Gauge, ObservableGauge, SyncGauge},
19 histogram::{Histogram, SyncHistogram},
20 up_down_counter::{ObservableUpDownCounter, SyncUpDownCounter, UpDownCounter},
21 AsyncInstrument, AsyncInstrumentBuilder, Callback, InstrumentBuilder,
22};
23pub use meter::{CallbackRegistration, Meter, MeterProvider, Observer};
24
25pub type Result<T> = result::Result<T, MetricsError>;
27
28#[derive(Error, Debug)]
30#[non_exhaustive]
31pub enum MetricsError {
32 #[error("Metrics error: {0}")]
34 Other(String),
35 #[error("Config error {0}")]
37 Config(String),
38 #[error("Metrics exporter {} failed with {0}", .0.exporter_name())]
40 ExportErr(Box<dyn ExportError>),
41 #[error("Invalid instrument configuration: {0}")]
45 InvalidInstrumentConfiguration(&'static str),
46}
47
48impl<T: ExportError> From<T> for MetricsError {
49 fn from(err: T) -> Self {
50 MetricsError::ExportErr(Box::new(err))
51 }
52}
53
54impl<T> From<PoisonError<T>> for MetricsError {
55 fn from(err: PoisonError<T>) -> Self {
56 MetricsError::Other(err.to_string())
57 }
58}
59
60struct F64Hashable(f64);
61
62impl PartialEq for F64Hashable {
63 fn eq(&self, other: &Self) -> bool {
64 self.0.to_bits() == other.0.to_bits()
65 }
66}
67
68impl Eq for F64Hashable {}
69
70impl Hash for F64Hashable {
71 fn hash<H: Hasher>(&self, state: &mut H) {
72 self.0.to_bits().hash(state);
73 }
74}
75
76impl Hash for KeyValue {
77 fn hash<H: Hasher>(&self, state: &mut H) {
78 self.key.hash(state);
79 match &self.value {
80 Value::F64(f) => F64Hashable(*f).hash(state),
81 Value::Array(a) => match a {
82 Array::Bool(b) => b.hash(state),
83 Array::I64(i) => i.hash(state),
84 Array::F64(f) => f.iter().for_each(|f| F64Hashable(*f).hash(state)),
85 Array::String(s) => s.hash(state),
86 },
87 Value::Bool(b) => b.hash(state),
88 Value::I64(i) => i.hash(state),
89 Value::String(s) => s.hash(state),
90 };
91 }
92}
93
94impl PartialOrd for KeyValue {
95 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
96 Some(self.cmp(other))
97 }
98}
99
100impl Ord for KeyValue {
102 fn cmp(&self, other: &Self) -> Ordering {
103 self.key.cmp(&other.key)
104 }
105}
106
107impl Eq for KeyValue {}
108
109pub trait InstrumentProvider {
111 fn u64_counter(
113 &self,
114 _name: Cow<'static, str>,
115 _description: Option<Cow<'static, str>>,
116 _unit: Option<Cow<'static, str>>,
117 ) -> Result<Counter<u64>> {
118 Ok(Counter::new(Arc::new(noop::NoopSyncInstrument::new())))
119 }
120
121 fn f64_counter(
123 &self,
124 _name: Cow<'static, str>,
125 _description: Option<Cow<'static, str>>,
126 _unit: Option<Cow<'static, str>>,
127 ) -> Result<Counter<f64>> {
128 Ok(Counter::new(Arc::new(noop::NoopSyncInstrument::new())))
129 }
130
131 fn u64_observable_counter(
133 &self,
134 _name: Cow<'static, str>,
135 _description: Option<Cow<'static, str>>,
136 _unit: Option<Cow<'static, str>>,
137 _callback: Vec<Callback<u64>>,
138 ) -> Result<ObservableCounter<u64>> {
139 Ok(ObservableCounter::new(Arc::new(
140 noop::NoopAsyncInstrument::new(),
141 )))
142 }
143
144 fn f64_observable_counter(
146 &self,
147 _name: Cow<'static, str>,
148 _description: Option<Cow<'static, str>>,
149 _unit: Option<Cow<'static, str>>,
150 _callback: Vec<Callback<f64>>,
151 ) -> Result<ObservableCounter<f64>> {
152 Ok(ObservableCounter::new(Arc::new(
153 noop::NoopAsyncInstrument::new(),
154 )))
155 }
156
157 fn i64_up_down_counter(
159 &self,
160 _name: Cow<'static, str>,
161 _description: Option<Cow<'static, str>>,
162 _unit: Option<Cow<'static, str>>,
163 ) -> Result<UpDownCounter<i64>> {
164 Ok(UpDownCounter::new(
165 Arc::new(noop::NoopSyncInstrument::new()),
166 ))
167 }
168
169 fn f64_up_down_counter(
171 &self,
172 _name: Cow<'static, str>,
173 _description: Option<Cow<'static, str>>,
174 _unit: Option<Cow<'static, str>>,
175 ) -> Result<UpDownCounter<f64>> {
176 Ok(UpDownCounter::new(
177 Arc::new(noop::NoopSyncInstrument::new()),
178 ))
179 }
180
181 fn i64_observable_up_down_counter(
183 &self,
184 _name: Cow<'static, str>,
185 _description: Option<Cow<'static, str>>,
186 _unit: Option<Cow<'static, str>>,
187 _callback: Vec<Callback<i64>>,
188 ) -> Result<ObservableUpDownCounter<i64>> {
189 Ok(ObservableUpDownCounter::new(Arc::new(
190 noop::NoopAsyncInstrument::new(),
191 )))
192 }
193
194 fn f64_observable_up_down_counter(
196 &self,
197 _name: Cow<'static, str>,
198 _description: Option<Cow<'static, str>>,
199 _unit: Option<Cow<'static, str>>,
200 _callback: Vec<Callback<f64>>,
201 ) -> Result<ObservableUpDownCounter<f64>> {
202 Ok(ObservableUpDownCounter::new(Arc::new(
203 noop::NoopAsyncInstrument::new(),
204 )))
205 }
206
207 fn u64_gauge(
209 &self,
210 _name: Cow<'static, str>,
211 _description: Option<Cow<'static, str>>,
212 _unit: Option<Cow<'static, str>>,
213 ) -> Result<Gauge<u64>> {
214 Ok(Gauge::new(Arc::new(noop::NoopSyncInstrument::new())))
215 }
216
217 fn f64_gauge(
219 &self,
220 _name: Cow<'static, str>,
221 _description: Option<Cow<'static, str>>,
222 _unit: Option<Cow<'static, str>>,
223 ) -> Result<Gauge<f64>> {
224 Ok(Gauge::new(Arc::new(noop::NoopSyncInstrument::new())))
225 }
226
227 fn i64_gauge(
229 &self,
230 _name: Cow<'static, str>,
231 _description: Option<Cow<'static, str>>,
232 _unit: Option<Cow<'static, str>>,
233 ) -> Result<Gauge<i64>> {
234 Ok(Gauge::new(Arc::new(noop::NoopSyncInstrument::new())))
235 }
236
237 fn u64_observable_gauge(
239 &self,
240 _name: Cow<'static, str>,
241 _description: Option<Cow<'static, str>>,
242 _unit: Option<Cow<'static, str>>,
243 _callback: Vec<Callback<u64>>,
244 ) -> Result<ObservableGauge<u64>> {
245 Ok(ObservableGauge::new(Arc::new(
246 noop::NoopAsyncInstrument::new(),
247 )))
248 }
249
250 fn i64_observable_gauge(
252 &self,
253 _name: Cow<'static, str>,
254 _description: Option<Cow<'static, str>>,
255 _unit: Option<Cow<'static, str>>,
256 _callback: Vec<Callback<i64>>,
257 ) -> Result<ObservableGauge<i64>> {
258 Ok(ObservableGauge::new(Arc::new(
259 noop::NoopAsyncInstrument::new(),
260 )))
261 }
262
263 fn f64_observable_gauge(
265 &self,
266 _name: Cow<'static, str>,
267 _description: Option<Cow<'static, str>>,
268 _unit: Option<Cow<'static, str>>,
269 _callback: Vec<Callback<f64>>,
270 ) -> Result<ObservableGauge<f64>> {
271 Ok(ObservableGauge::new(Arc::new(
272 noop::NoopAsyncInstrument::new(),
273 )))
274 }
275
276 fn f64_histogram(
278 &self,
279 _name: Cow<'static, str>,
280 _description: Option<Cow<'static, str>>,
281 _unit: Option<Cow<'static, str>>,
282 ) -> Result<Histogram<f64>> {
283 Ok(Histogram::new(Arc::new(noop::NoopSyncInstrument::new())))
284 }
285
286 fn u64_histogram(
288 &self,
289 _name: Cow<'static, str>,
290 _description: Option<Cow<'static, str>>,
291 _unit: Option<Cow<'static, str>>,
292 ) -> Result<Histogram<u64>> {
293 Ok(Histogram::new(Arc::new(noop::NoopSyncInstrument::new())))
294 }
295
296 fn register_callback(
300 &self,
301 instruments: &[Arc<dyn Any>],
302 callbacks: Box<MultiInstrumentCallback>,
303 ) -> Result<Box<dyn CallbackRegistration>>;
304}
305
306type MultiInstrumentCallback = dyn Fn(&dyn Observer) + Send + Sync;
307
308#[cfg(test)]
309mod tests {
310 use rand::Rng;
311
312 use crate::KeyValue;
313 use std::collections::hash_map::DefaultHasher;
314 use std::f64;
315 use std::hash::{Hash, Hasher};
316
317 #[test]
318 fn kv_float_equality() {
319 let kv1 = KeyValue::new("key", 1.0);
320 let kv2 = KeyValue::new("key", 1.0);
321 assert_eq!(kv1, kv2);
322
323 let kv1 = KeyValue::new("key", 1.0);
324 let kv2 = KeyValue::new("key", 1.01);
325 assert_ne!(kv1, kv2);
326
327 let kv1 = KeyValue::new("key", f64::NAN);
328 let kv2 = KeyValue::new("key", f64::NAN);
329 assert_ne!(kv1, kv2, "NAN is not equal to itself");
330
331 for float_val in [
332 f64::INFINITY,
333 f64::NEG_INFINITY,
334 f64::MAX,
335 f64::MIN,
336 f64::MIN_POSITIVE,
337 ]
338 .iter()
339 {
340 let kv1 = KeyValue::new("key", *float_val);
341 let kv2 = KeyValue::new("key", *float_val);
342 assert_eq!(kv1, kv2);
343 }
344
345 let mut rng = rand::thread_rng();
346
347 for _ in 0..100 {
348 let random_value = rng.gen::<f64>();
349 let kv1 = KeyValue::new("key", random_value);
350 let kv2 = KeyValue::new("key", random_value);
351 assert_eq!(kv1, kv2);
352 }
353 }
354
355 #[test]
356 fn kv_float_hash() {
357 for float_val in [
358 f64::NAN,
359 f64::INFINITY,
360 f64::NEG_INFINITY,
361 f64::MAX,
362 f64::MIN,
363 f64::MIN_POSITIVE,
364 ]
365 .iter()
366 {
367 let kv1 = KeyValue::new("key", *float_val);
368 let kv2 = KeyValue::new("key", *float_val);
369 assert_eq!(hash_helper(&kv1), hash_helper(&kv2));
370 }
371
372 let mut rng = rand::thread_rng();
373
374 for _ in 0..100 {
375 let random_value = rng.gen::<f64>();
376 let kv1 = KeyValue::new("key", random_value);
377 let kv2 = KeyValue::new("key", random_value);
378 assert_eq!(hash_helper(&kv1), hash_helper(&kv2));
379 }
380 }
381
382 #[test]
383 fn kv_float_order() {
384 let float_vals = [
386 0.0,
387 1.0,
388 -1.0,
389 f64::INFINITY,
390 f64::NEG_INFINITY,
391 f64::NAN,
392 f64::MIN,
393 f64::MAX,
394 ];
395
396 for v in float_vals {
397 let kv1 = KeyValue::new("a", v);
398 let kv2 = KeyValue::new("b", v);
399 assert!(kv1 < kv2, "Order is solely based on key!");
400 }
401 }
402
403 fn hash_helper<T: Hash>(item: &T) -> u64 {
404 let mut hasher = DefaultHasher::new();
405 item.hash(&mut hasher);
406 hasher.finish()
407 }
408}