opentelemetry_sdk/metrics/
instrument.rs

1use std::{borrow::Cow, collections::HashSet, sync::Arc};
2
3use opentelemetry::{
4    metrics::{AsyncInstrument, SyncInstrument},
5    InstrumentationScope, Key, KeyValue,
6};
7
8use crate::metrics::{aggregation::Aggregation, internal::Measure};
9
10use super::Temporality;
11
12/// The identifier of a group of instruments that all perform the same function.
13#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
14pub enum InstrumentKind {
15    /// Identifies a group of instruments that record increasing values synchronously
16    /// with the code path they are measuring.
17    Counter,
18    /// A group of instruments that record increasing and decreasing values
19    /// synchronously with the code path they are measuring.
20    UpDownCounter,
21    /// A group of instruments that record a distribution of values synchronously with
22    /// the code path they are measuring.
23    Histogram,
24    /// A group of instruments that record increasing values in an asynchronous
25    /// callback.
26    ObservableCounter,
27    /// A group of instruments that record increasing and decreasing values in an
28    /// asynchronous callback.
29    ObservableUpDownCounter,
30
31    /// a group of instruments that record current value synchronously with
32    /// the code path they are measuring.
33    Gauge,
34    ///
35    /// a group of instruments that record current values in an asynchronous callback.
36    ObservableGauge,
37}
38
39impl InstrumentKind {
40    /// Select the [Temporality] preference based on [InstrumentKind]
41    ///
42    /// [exporter-docs]: https://github.com/open-telemetry/opentelemetry-specification/blob/a1c13d59bb7d0fb086df2b3e1eaec9df9efef6cc/specification/metrics/sdk_exporters/otlp.md#additional-configuration
43    pub(crate) fn temporality_preference(&self, temporality: Temporality) -> Temporality {
44        match temporality {
45            Temporality::Cumulative => Temporality::Cumulative,
46            Temporality::Delta => match self {
47                Self::Counter
48                | Self::Histogram
49                | Self::ObservableCounter
50                | Self::Gauge
51                | Self::ObservableGauge => Temporality::Delta,
52                Self::UpDownCounter | InstrumentKind::ObservableUpDownCounter => {
53                    Temporality::Cumulative
54                }
55            },
56            Temporality::LowMemory => match self {
57                Self::Counter | InstrumentKind::Histogram => Temporality::Delta,
58                Self::ObservableCounter
59                | Self::Gauge
60                | Self::ObservableGauge
61                | Self::UpDownCounter
62                | Self::ObservableUpDownCounter => Temporality::Cumulative,
63            },
64        }
65    }
66}
67
68/// Describes properties an instrument is created with, also used for filtering
69/// in [View](crate::metrics::View)s.
70///
71/// # Example
72///
73/// Instruments can be used as criteria for views.
74///
75/// ```
76/// use opentelemetry_sdk::metrics::{new_view, Aggregation, Instrument, Stream};
77///
78/// let criteria = Instrument::new().name("counter_*");
79/// let mask = Stream::new().aggregation(Aggregation::Sum);
80///
81/// let view = new_view(criteria, mask);
82/// # drop(view);
83/// ```
84#[derive(Clone, Default, Debug, PartialEq)]
85#[non_exhaustive]
86#[allow(unreachable_pub)]
87pub struct Instrument {
88    /// The human-readable identifier of the instrument.
89    pub name: Cow<'static, str>,
90    /// describes the purpose of the instrument.
91    pub description: Cow<'static, str>,
92    /// The functional group of the instrument.
93    pub kind: Option<InstrumentKind>,
94    /// Unit is the unit of measurement recorded by the instrument.
95    pub unit: Cow<'static, str>,
96    /// The instrumentation that created the instrument.
97    pub scope: InstrumentationScope,
98}
99
100#[cfg(feature = "spec_unstable_metrics_views")]
101impl Instrument {
102    /// Create a new instrument with default values
103    pub fn new() -> Self {
104        Instrument::default()
105    }
106
107    /// Set the instrument name.
108    pub fn name(mut self, name: impl Into<Cow<'static, str>>) -> Self {
109        self.name = name.into();
110        self
111    }
112
113    /// Set the instrument description.
114    pub fn description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
115        self.description = description.into();
116        self
117    }
118
119    /// Set the instrument unit.
120    pub fn unit(mut self, unit: impl Into<Cow<'static, str>>) -> Self {
121        self.unit = unit.into();
122        self
123    }
124
125    /// Set the instrument scope.
126    pub fn scope(mut self, scope: InstrumentationScope) -> Self {
127        self.scope = scope;
128        self
129    }
130
131    /// empty returns if all fields of i are their default-value.
132    pub(crate) fn is_empty(&self) -> bool {
133        self.name == ""
134            && self.description == ""
135            && self.kind.is_none()
136            && self.unit == ""
137            && self.scope == InstrumentationScope::default()
138    }
139
140    pub(crate) fn matches(&self, other: &Instrument) -> bool {
141        self.matches_name(other)
142            && self.matches_description(other)
143            && self.matches_kind(other)
144            && self.matches_unit(other)
145            && self.matches_scope(other)
146    }
147
148    pub(crate) fn matches_name(&self, other: &Instrument) -> bool {
149        self.name.is_empty() || self.name.as_ref() == other.name.as_ref()
150    }
151
152    pub(crate) fn matches_description(&self, other: &Instrument) -> bool {
153        self.description.is_empty() || self.description.as_ref() == other.description.as_ref()
154    }
155
156    pub(crate) fn matches_kind(&self, other: &Instrument) -> bool {
157        self.kind.is_none() || self.kind == other.kind
158    }
159
160    pub(crate) fn matches_unit(&self, other: &Instrument) -> bool {
161        self.unit.is_empty() || self.unit.as_ref() == other.unit.as_ref()
162    }
163
164    pub(crate) fn matches_scope(&self, other: &Instrument) -> bool {
165        (self.scope.name().is_empty() || self.scope.name() == other.scope.name())
166            && (self.scope.version().is_none()
167                || self.scope.version().as_ref() == other.scope.version().as_ref())
168            && (self.scope.schema_url().is_none()
169                || self.scope.schema_url().as_ref() == other.scope.schema_url().as_ref())
170    }
171}
172
173/// Describes the stream of data an instrument produces.
174///
175/// # Example
176///
177/// Streams can be used as masks in views.
178///
179/// ```
180/// use opentelemetry_sdk::metrics::{new_view, Aggregation, Instrument, Stream};
181///
182/// let criteria = Instrument::new().name("counter_*");
183/// let mask = Stream::new().aggregation(Aggregation::Sum);
184///
185/// let view = new_view(criteria, mask);
186/// # drop(view);
187/// ```
188#[derive(Default, Debug)]
189#[non_exhaustive]
190#[allow(unreachable_pub)]
191pub struct Stream {
192    /// The human-readable identifier of the stream.
193    pub name: Cow<'static, str>,
194    /// Describes the purpose of the data.
195    pub description: Cow<'static, str>,
196    /// the unit of measurement recorded.
197    pub unit: Cow<'static, str>,
198    /// Aggregation the stream uses for an instrument.
199    pub aggregation: Option<Aggregation>,
200    /// An allow-list of attribute keys that will be preserved for the stream.
201    ///
202    /// Any attribute recorded for the stream with a key not in this set will be
203    /// dropped. If the set is empty, all attributes will be dropped, if `None` all
204    /// attributes will be kept.
205    pub allowed_attribute_keys: Option<Arc<HashSet<Key>>>,
206}
207
208#[cfg(feature = "spec_unstable_metrics_views")]
209impl Stream {
210    /// Create a new stream with empty values.
211    pub fn new() -> Self {
212        Stream::default()
213    }
214
215    /// Set the stream name.
216    pub fn name(mut self, name: impl Into<Cow<'static, str>>) -> Self {
217        self.name = name.into();
218        self
219    }
220
221    /// Set the stream description.
222    pub fn description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
223        self.description = description.into();
224        self
225    }
226
227    /// Set the stream unit.
228    pub fn unit(mut self, unit: impl Into<Cow<'static, str>>) -> Self {
229        self.unit = unit.into();
230        self
231    }
232
233    /// Set the stream aggregation.
234    pub fn aggregation(mut self, aggregation: Aggregation) -> Self {
235        self.aggregation = Some(aggregation);
236        self
237    }
238
239    /// Set the stream allowed attribute keys.
240    ///
241    /// Any attribute recorded for the stream with a key not in this set will be
242    /// dropped. If this set is empty all attributes will be dropped.
243    pub fn allowed_attribute_keys(mut self, attribute_keys: impl IntoIterator<Item = Key>) -> Self {
244        self.allowed_attribute_keys = Some(Arc::new(attribute_keys.into_iter().collect()));
245
246        self
247    }
248}
249
250/// The identifying properties of an instrument.
251#[derive(Debug, PartialEq, Eq, Hash)]
252pub(crate) struct InstrumentId {
253    /// The human-readable identifier of the instrument.
254    pub(crate) name: Cow<'static, str>,
255    /// Describes the purpose of the data.
256    pub(crate) description: Cow<'static, str>,
257    /// Defines the functional group of the instrument.
258    pub(crate) kind: InstrumentKind,
259    /// The unit of measurement recorded.
260    pub(crate) unit: Cow<'static, str>,
261    /// Number is the underlying data type of the instrument.
262    pub(crate) number: Cow<'static, str>,
263}
264
265impl InstrumentId {
266    /// Instrument names are considered case-insensitive ASCII.
267    ///
268    /// Standardize the instrument name to always be lowercase so it can be compared
269    /// via hash.
270    ///
271    /// See [naming syntax] for full requirements.
272    ///
273    /// [naming syntax]: https://github.com/open-telemetry/opentelemetry-specification/blob/v1.21.0/specification/metrics/api.md#instrument-name-syntax
274    pub(crate) fn normalize(&mut self) {
275        if self.name.chars().any(|c| c.is_ascii_uppercase()) {
276            self.name = self.name.to_ascii_lowercase().into();
277        }
278    }
279}
280
281pub(crate) struct ResolvedMeasures<T> {
282    pub(crate) measures: Vec<Arc<dyn Measure<T>>>,
283}
284
285impl<T: Copy + 'static> SyncInstrument<T> for ResolvedMeasures<T> {
286    fn measure(&self, val: T, attrs: &[KeyValue]) {
287        for measure in &self.measures {
288            measure.call(val, attrs)
289        }
290    }
291}
292
293#[derive(Clone)]
294pub(crate) struct Observable<T> {
295    measures: Vec<Arc<dyn Measure<T>>>,
296}
297
298impl<T> Observable<T> {
299    pub(crate) fn new(measures: Vec<Arc<dyn Measure<T>>>) -> Self {
300        Self { measures }
301    }
302}
303
304impl<T: Copy + Send + Sync + 'static> AsyncInstrument<T> for Observable<T> {
305    fn observe(&self, measurement: T, attrs: &[KeyValue]) {
306        for measure in &self.measures {
307            measure.call(measurement, attrs)
308        }
309    }
310}