Skip to main content

opentelemetry/metrics/instruments/
mod.rs

1use gauge::{Gauge, ObservableGauge};
2
3use crate::metrics::Meter;
4use crate::KeyValue;
5use core::fmt;
6use std::borrow::Cow;
7use std::marker;
8
9use super::{
10    Counter, Histogram, InstrumentProvider, ObservableCounter, ObservableUpDownCounter,
11    UpDownCounter,
12};
13
14pub(super) mod counter;
15pub(super) mod gauge;
16pub(super) mod histogram;
17pub(super) mod up_down_counter;
18
19/// An SDK implemented instrument that records measurements via callback.
20pub trait AsyncInstrument<T>: Send + Sync {
21    /// Observes the state of the instrument.
22    ///
23    /// It is only valid to call this within a callback.
24    fn observe(&self, measurement: T, attributes: &[KeyValue]);
25}
26
27/// An SDK implemented instrument that records measurements synchronously.
28pub trait SyncInstrument<T>: Send + Sync {
29    /// Records a measurement synchronously.
30    fn measure(&self, measurement: T, attributes: &[KeyValue]);
31
32    /// Binds this instrument to a fixed set of attributes, returning a handle
33    /// that records measurements without per-call attribute lookup.
34    ///
35    /// The default implementation returns a no-op handle so that custom
36    /// `SyncInstrument` impls that have not opted into bound instruments
37    /// degrade gracefully rather than panicking on the user's hot path.
38    #[cfg(feature = "experimental_metrics_bound_instruments")]
39    fn bind(&self, _attributes: &[KeyValue]) -> Box<dyn BoundSyncInstrument<T> + Send + Sync> {
40        crate::otel_debug!(
41            name: "SyncInstrument.BindNotImplemented",
42            message = "bind() called on a SyncInstrument implementation that does not override the default; measurements through the returned handle will be dropped"
43        );
44        Box::new(crate::metrics::noop::NoopBoundSyncInstrument::new())
45    }
46}
47
48/// A pre-bound synchronous instrument that records measurements without attributes.
49/// Created by calling `bind()` on a `Counter`, `UpDownCounter`, `Histogram`,
50/// or `Gauge` with a fixed attribute set.
51#[cfg(feature = "experimental_metrics_bound_instruments")]
52pub trait BoundSyncInstrument<T>: Send + Sync {
53    /// Records a measurement. The attributes were fixed at bind time.
54    fn measure(&self, measurement: T);
55}
56
57/// Configuration for building a Histogram.
58#[non_exhaustive] // We expect to add more configuration fields in the future
59pub struct HistogramBuilder<'a, T> {
60    /// Instrument provider is used to create the instrument.
61    pub instrument_provider: &'a dyn InstrumentProvider,
62
63    /// Name of the Histogram.
64    pub name: Cow<'static, str>,
65
66    /// Description of the Histogram.
67    pub description: Option<Cow<'static, str>>,
68
69    /// Unit of the Histogram.
70    pub unit: Option<Cow<'static, str>>,
71
72    /// Bucket boundaries for the histogram.
73    pub boundaries: Option<Vec<f64>>,
74
75    // boundaries: Vec<T>,
76    _marker: marker::PhantomData<T>,
77}
78
79impl<'a, T> HistogramBuilder<'a, T> {
80    /// Create a new instrument builder
81    pub(crate) fn new(meter: &'a Meter, name: Cow<'static, str>) -> Self {
82        HistogramBuilder {
83            instrument_provider: meter.instrument_provider.as_ref(),
84            name,
85            description: None,
86            unit: None,
87            boundaries: None,
88            _marker: marker::PhantomData,
89        }
90    }
91
92    /// Set the description for this instrument
93    pub fn with_description<S: Into<Cow<'static, str>>>(mut self, description: S) -> Self {
94        self.description = Some(description.into());
95        self
96    }
97
98    /// Set the unit for this instrument.
99    ///
100    /// Unit is case sensitive(`kb` is not the same as `kB`).
101    ///
102    /// Unit must be:
103    /// - ASCII string
104    /// - No longer than 63 characters
105    pub fn with_unit<S: Into<Cow<'static, str>>>(mut self, unit: S) -> Self {
106        self.unit = Some(unit.into());
107        self
108    }
109
110    /// Set the boundaries for this histogram.
111    ///
112    /// Setting boundaries is optional. By default, the boundaries are set to:
113    ///
114    /// `[0.0, 5.0, 10.0, 25.0, 50.0, 75.0, 100.0, 250.0, 500.0, 750.0, 1000.0,
115    /// 2500.0, 5000.0, 7500.0, 10000.0]`
116    ///
117    /// # Notes
118    /// - Boundaries must not contain `f64::NAN`, `f64::INFINITY` or
119    ///   `f64::NEG_INFINITY`
120    /// - Values must be in strictly increasing order (e.g., each value must be
121    ///   greater than the previous).
122    /// - Boundaries must not contain duplicate values.
123    ///
124    /// If invalid boundaries are provided, the instrument will not report
125    /// measurements.
126    /// Providing an empty `vec![]` means no bucket information will be
127    /// calculated.
128    ///
129    /// # Warning
130    /// Using more buckets can improve the accuracy of percentile calculations in backends.
131    /// However, this comes at a cost, including increased memory, CPU, and network usage.
132    /// Choose the number of buckets carefully, considering your application's performance
133    /// and resource requirements.
134    pub fn with_boundaries(mut self, boundaries: Vec<f64>) -> Self {
135        self.boundaries = Some(boundaries);
136        self
137    }
138}
139
140impl HistogramBuilder<'_, Histogram<f64>> {
141    /// Creates a new instrument.
142    ///
143    /// Validates the instrument configuration and creates a new instrument. In
144    /// case of invalid configuration, a no-op instrument is returned
145    /// and an error is logged using internal logging.
146    pub fn build(self) -> Histogram<f64> {
147        self.instrument_provider.f64_histogram(self)
148    }
149}
150
151impl HistogramBuilder<'_, Histogram<u64>> {
152    /// Creates a new instrument.
153    ///
154    /// Validates the instrument configuration and creates a new instrument. In
155    /// case of invalid configuration, a no-op instrument is returned
156    /// and an error is logged using internal logging.
157    pub fn build(self) -> Histogram<u64> {
158        self.instrument_provider.u64_histogram(self)
159    }
160}
161
162/// Configuration for building a sync instrument.
163#[non_exhaustive] // We expect to add more configuration fields in the future
164pub struct InstrumentBuilder<'a, T> {
165    /// Instrument provider is used to create the instrument.
166    pub instrument_provider: &'a dyn InstrumentProvider,
167
168    /// Name of the instrument.
169    pub name: Cow<'static, str>,
170
171    /// Description of the instrument.
172    pub description: Option<Cow<'static, str>>,
173
174    /// Unit of the instrument.
175    pub unit: Option<Cow<'static, str>>,
176
177    _marker: marker::PhantomData<T>,
178}
179
180impl<'a, T> InstrumentBuilder<'a, T> {
181    /// Create a new instrument builder
182    pub(crate) fn new(meter: &'a Meter, name: Cow<'static, str>) -> Self {
183        InstrumentBuilder {
184            instrument_provider: meter.instrument_provider.as_ref(),
185            name,
186            description: None,
187            unit: None,
188            _marker: marker::PhantomData,
189        }
190    }
191
192    /// Set the description for this instrument
193    pub fn with_description<S: Into<Cow<'static, str>>>(mut self, description: S) -> Self {
194        self.description = Some(description.into());
195        self
196    }
197
198    /// Set the unit for this instrument.
199    ///
200    /// Unit is case sensitive(`kb` is not the same as `kB`).
201    ///
202    /// Unit must be:
203    /// - ASCII string
204    /// - No longer than 63 characters
205    pub fn with_unit<S: Into<Cow<'static, str>>>(mut self, unit: S) -> Self {
206        self.unit = Some(unit.into());
207        self
208    }
209}
210
211macro_rules! build_instrument {
212    ($name:ident, $inst:ty) => {
213        impl<'a> InstrumentBuilder<'a, $inst> {
214            #[doc = concat!("Validates the instrument configuration and creates a new `",  stringify!($inst), "`.")]
215            /// In case of invalid configuration, a no-op instrument is returned
216            /// and an error is logged using internal logging.
217            pub fn build(self) -> $inst {
218                self.instrument_provider.$name(self)
219            }
220        }
221    };
222}
223
224build_instrument!(u64_counter, Counter<u64>);
225build_instrument!(f64_counter, Counter<f64>);
226build_instrument!(u64_gauge, Gauge<u64>);
227build_instrument!(f64_gauge, Gauge<f64>);
228build_instrument!(i64_gauge, Gauge<i64>);
229build_instrument!(i64_up_down_counter, UpDownCounter<i64>);
230build_instrument!(f64_up_down_counter, UpDownCounter<f64>);
231
232impl<T> fmt::Debug for InstrumentBuilder<'_, T> {
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        f.debug_struct("InstrumentBuilder")
235            .field("name", &self.name)
236            .field("description", &self.description)
237            .field("unit", &self.unit)
238            .field("kind", &std::any::type_name::<T>())
239            .finish()
240    }
241}
242
243impl<T> fmt::Debug for HistogramBuilder<'_, T> {
244    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245        f.debug_struct("HistogramBuilder")
246            .field("name", &self.name)
247            .field("description", &self.description)
248            .field("unit", &self.unit)
249            .field("boundaries", &self.boundaries)
250            .field(
251                "kind",
252                &format!("Histogram<{}>", std::any::type_name::<T>()),
253            )
254            .finish()
255    }
256}
257
258/// A function registered with a [Meter] that makes observations for the
259/// instruments it is registered with.
260///
261/// The async instrument parameter is used to record measurement observations
262/// for these instruments.
263///
264/// The function needs to complete in a finite amount of time.
265pub type Callback<T> = Box<dyn Fn(&dyn AsyncInstrument<T>) + Send + Sync>;
266
267/// Configuration for building an async instrument.
268#[must_use = "Callbacks will not be invoked unless you call .build() on this async instrument builder."]
269#[non_exhaustive] // We expect to add more configuration fields in the future
270pub struct AsyncInstrumentBuilder<'a, I, M> {
271    /// Instrument provider is used to create the instrument.
272    pub instrument_provider: &'a dyn InstrumentProvider,
273
274    /// Name of the instrument.
275    pub name: Cow<'static, str>,
276
277    /// Description of the instrument.
278    pub description: Option<Cow<'static, str>>,
279
280    /// Unit of the instrument.
281    pub unit: Option<Cow<'static, str>>,
282
283    /// Callbacks to be called for this instrument.
284    pub callbacks: Vec<Callback<M>>,
285
286    _inst: marker::PhantomData<I>,
287}
288
289impl<'a, I, M> AsyncInstrumentBuilder<'a, I, M> {
290    /// Create a new instrument builder
291    pub(crate) fn new(meter: &'a Meter, name: Cow<'static, str>) -> Self {
292        AsyncInstrumentBuilder {
293            instrument_provider: meter.instrument_provider.as_ref(),
294            name,
295            description: None,
296            unit: None,
297            _inst: marker::PhantomData,
298            callbacks: Vec::new(),
299        }
300    }
301
302    /// Set the description for this instrument
303    pub fn with_description<S: Into<Cow<'static, str>>>(mut self, description: S) -> Self {
304        self.description = Some(description.into());
305        self
306    }
307
308    /// Set the unit for this instrument.
309    ///
310    /// Unit is case sensitive(`kb` is not the same as `kB`).
311    ///
312    /// Unit must be:
313    /// - ASCII string
314    /// - No longer than 63 characters
315    pub fn with_unit<S: Into<Cow<'static, str>>>(mut self, unit: S) -> Self {
316        self.unit = Some(unit.into());
317        self
318    }
319
320    /// Set the callback to be called for this instrument.
321    pub fn with_callback<F>(mut self, callback: F) -> Self
322    where
323        F: Fn(&dyn AsyncInstrument<M>) + Send + Sync + 'static,
324    {
325        self.callbacks.push(Box::new(callback));
326        self
327    }
328}
329
330macro_rules! build_async_instrument {
331    ($name:ident, $inst:ty, $measurement:ty) => {
332        impl<'a> AsyncInstrumentBuilder<'a, $inst, $measurement> {
333            #[doc = concat!("Validates the instrument configuration and creates a new `",  stringify!($inst), "`.")]
334            /// In case of invalid configuration, a no-op instrument is returned
335            /// and an error is logged using internal logging.
336            pub fn build(self) -> $inst {
337                self.instrument_provider.$name(self)
338            }
339        }
340    };
341}
342
343build_async_instrument!(u64_observable_counter, ObservableCounter<u64>, u64);
344build_async_instrument!(f64_observable_counter, ObservableCounter<f64>, f64);
345build_async_instrument!(u64_observable_gauge, ObservableGauge<u64>, u64);
346build_async_instrument!(f64_observable_gauge, ObservableGauge<f64>, f64);
347build_async_instrument!(i64_observable_gauge, ObservableGauge<i64>, i64);
348build_async_instrument!(
349    i64_observable_up_down_counter,
350    ObservableUpDownCounter<i64>,
351    i64
352);
353build_async_instrument!(
354    f64_observable_up_down_counter,
355    ObservableUpDownCounter<f64>,
356    f64
357);
358
359impl<I, M> fmt::Debug for AsyncInstrumentBuilder<'_, I, M>
360where
361    I: AsyncInstrument<M>,
362{
363    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
364        f.debug_struct("InstrumentBuilder")
365            .field("name", &self.name)
366            .field("description", &self.description)
367            .field("unit", &self.unit)
368            .field("kind", &std::any::type_name::<I>())
369            .field("callbacks_len", &self.callbacks.len())
370            .finish()
371    }
372}