opentelemetry/metrics/
mod.rs

1//! # OpenTelemetry Metrics API
2
3use 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
25/// A specialized `Result` type for metric operations.
26pub type Result<T> = result::Result<T, MetricsError>;
27
28/// Errors returned by the metrics API.
29#[derive(Error, Debug)]
30#[non_exhaustive]
31pub enum MetricsError {
32    /// Other errors not covered by specific cases.
33    #[error("Metrics error: {0}")]
34    Other(String),
35    /// Invalid configuration
36    #[error("Config error {0}")]
37    Config(String),
38    /// Fail to export metrics
39    #[error("Metrics exporter {} failed with {0}", .0.exporter_name())]
40    ExportErr(Box<dyn ExportError>),
41    /// Invalid instrument configuration such invalid instrument name, invalid instrument description, invalid instrument unit, etc.
42    /// See [spec](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#general-characteristics)
43    /// for full list of requirements.
44    #[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
100/// Ordering is based on the key only.
101impl Ord for KeyValue {
102    fn cmp(&self, other: &Self) -> Ordering {
103        self.key.cmp(&other.key)
104    }
105}
106
107impl Eq for KeyValue {}
108
109/// SDK implemented trait for creating instruments
110pub trait InstrumentProvider {
111    /// creates an instrument for recording increasing values.
112    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    /// creates an instrument for recording increasing values.
122    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    /// creates an instrument for recording increasing values via callback.
132    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    /// creates an instrument for recording increasing values via callback.
145    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    /// creates an instrument for recording changes of a value.
158    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    /// creates an instrument for recording changes of a value.
170    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    /// creates an instrument for recording changes of a value.
182    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    /// creates an instrument for recording changes of a value via callback.
195    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    /// creates an instrument for recording independent values.
208    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    /// creates an instrument for recording independent values.
218    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    /// creates an instrument for recording independent values.
228    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    /// creates an instrument for recording the current value via callback.
238    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    /// creates an instrument for recording the current value via callback.
251    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    /// creates an instrument for recording the current value via callback.
264    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    /// creates an instrument for recording a distribution of values.
277    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    /// creates an instrument for recording a distribution of values.
287    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    /// Captures the function that will be called during data collection.
297    ///
298    /// It is only valid to call `observe` within the scope of the passed function.
299    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        // TODO: Extend this test to all value types, not just F64
385        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}