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