opentelemetry_sdk/metrics/
meter.rs

1use core::fmt;
2use std::{any::Any, borrow::Cow, collections::HashSet, sync::Arc};
3
4use opentelemetry::{
5    global,
6    metrics::{
7        noop::{NoopAsyncInstrument, NoopRegistration},
8        AsyncInstrument, Callback, CallbackRegistration, Counter, Gauge, Histogram,
9        InstrumentProvider, MetricsError, ObservableCounter, ObservableGauge,
10        ObservableUpDownCounter, Observer as ApiObserver, Result, UpDownCounter,
11    },
12    KeyValue,
13};
14
15use crate::instrumentation::Scope;
16use crate::metrics::{
17    instrument::{
18        Instrument, InstrumentKind, Observable, ObservableId, ResolvedMeasures, EMPTY_MEASURE_MSG,
19    },
20    internal::{self, Number},
21    pipeline::{Pipelines, Resolver},
22};
23
24// maximum length of instrument name
25const INSTRUMENT_NAME_MAX_LENGTH: usize = 255;
26// maximum length of instrument unit name
27const INSTRUMENT_UNIT_NAME_MAX_LENGTH: usize = 63;
28const INSTRUMENT_NAME_ALLOWED_NON_ALPHANUMERIC_CHARS: [char; 4] = ['_', '.', '-', '/'];
29
30// instrument validation error strings
31const INSTRUMENT_NAME_EMPTY: &str = "instrument name must be non-empty";
32const INSTRUMENT_NAME_LENGTH: &str = "instrument name must be less than 256 characters";
33const INSTRUMENT_NAME_INVALID_CHAR: &str =
34    "characters in instrument name must be ASCII and belong to the alphanumeric characters, '_', '.', '-' and '/'";
35const INSTRUMENT_NAME_FIRST_ALPHABETIC: &str =
36    "instrument name must start with an alphabetic character";
37const INSTRUMENT_UNIT_LENGTH: &str = "instrument unit must be less than 64 characters";
38const INSTRUMENT_UNIT_INVALID_CHAR: &str = "characters in instrument unit must be ASCII";
39
40/// Handles the creation and coordination of all metric instruments.
41///
42/// A meter represents a single instrumentation scope; all metric telemetry
43/// produced by an instrumentation scope will use metric instruments from a
44/// single meter.
45///
46/// See the [Meter API] docs for usage.
47///
48/// [Meter API]: opentelemetry::metrics::Meter
49pub struct SdkMeter {
50    scope: Scope,
51    pipes: Arc<Pipelines>,
52    u64_resolver: Resolver<u64>,
53    i64_resolver: Resolver<i64>,
54    f64_resolver: Resolver<f64>,
55    validation_policy: InstrumentValidationPolicy,
56}
57
58impl SdkMeter {
59    pub(crate) fn new(scope: Scope, pipes: Arc<Pipelines>) -> Self {
60        let view_cache = Default::default();
61
62        SdkMeter {
63            scope,
64            pipes: Arc::clone(&pipes),
65            u64_resolver: Resolver::new(Arc::clone(&pipes), Arc::clone(&view_cache)),
66            i64_resolver: Resolver::new(Arc::clone(&pipes), Arc::clone(&view_cache)),
67            f64_resolver: Resolver::new(pipes, view_cache),
68            validation_policy: InstrumentValidationPolicy::HandleGlobalAndIgnore,
69        }
70    }
71
72    #[cfg(test)]
73    fn with_validation_policy(self, validation_policy: InstrumentValidationPolicy) -> Self {
74        Self {
75            validation_policy,
76            ..self
77        }
78    }
79}
80
81#[doc(hidden)]
82impl InstrumentProvider for SdkMeter {
83    fn u64_counter(
84        &self,
85        name: Cow<'static, str>,
86        description: Option<Cow<'static, str>>,
87        unit: Option<Cow<'static, str>>,
88    ) -> Result<Counter<u64>> {
89        validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
90        let p = InstrumentResolver::new(self, &self.u64_resolver);
91        p.lookup(InstrumentKind::Counter, name, description, unit)
92            .map(|i| Counter::new(Arc::new(i)))
93    }
94
95    fn f64_counter(
96        &self,
97        name: Cow<'static, str>,
98        description: Option<Cow<'static, str>>,
99        unit: Option<Cow<'static, str>>,
100    ) -> Result<Counter<f64>> {
101        validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
102        let p = InstrumentResolver::new(self, &self.f64_resolver);
103        p.lookup(InstrumentKind::Counter, name, description, unit)
104            .map(|i| Counter::new(Arc::new(i)))
105    }
106
107    fn u64_observable_counter(
108        &self,
109        name: Cow<'static, str>,
110        description: Option<Cow<'static, str>>,
111        unit: Option<Cow<'static, str>>,
112        callbacks: Vec<Callback<u64>>,
113    ) -> Result<ObservableCounter<u64>> {
114        validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
115        let p = InstrumentResolver::new(self, &self.u64_resolver);
116        let ms = p.measures(
117            InstrumentKind::ObservableCounter,
118            name.clone(),
119            description.clone(),
120            unit.clone(),
121        )?;
122        if ms.is_empty() {
123            return Ok(ObservableCounter::new(Arc::new(NoopAsyncInstrument::new())));
124        }
125
126        let observable = Arc::new(Observable::new(
127            self.scope.clone(),
128            InstrumentKind::ObservableCounter,
129            name,
130            description.unwrap_or_default(),
131            unit.unwrap_or_default(),
132            ms,
133        ));
134
135        for callback in callbacks {
136            let cb_inst = Arc::clone(&observable);
137            self.pipes
138                .register_callback(move || callback(cb_inst.as_ref()));
139        }
140
141        Ok(ObservableCounter::new(observable))
142    }
143
144    fn f64_observable_counter(
145        &self,
146        name: Cow<'static, str>,
147        description: Option<Cow<'static, str>>,
148        unit: Option<Cow<'static, str>>,
149        callbacks: Vec<Callback<f64>>,
150    ) -> Result<ObservableCounter<f64>> {
151        validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
152        let p = InstrumentResolver::new(self, &self.f64_resolver);
153        let ms = p.measures(
154            InstrumentKind::ObservableCounter,
155            name.clone(),
156            description.clone(),
157            unit.clone(),
158        )?;
159        if ms.is_empty() {
160            return Ok(ObservableCounter::new(Arc::new(NoopAsyncInstrument::new())));
161        }
162        let observable = Arc::new(Observable::new(
163            self.scope.clone(),
164            InstrumentKind::ObservableCounter,
165            name,
166            description.unwrap_or_default(),
167            unit.unwrap_or_default(),
168            ms,
169        ));
170
171        for callback in callbacks {
172            let cb_inst = Arc::clone(&observable);
173            self.pipes
174                .register_callback(move || callback(cb_inst.as_ref()));
175        }
176
177        Ok(ObservableCounter::new(observable))
178    }
179
180    fn i64_up_down_counter(
181        &self,
182        name: Cow<'static, str>,
183        description: Option<Cow<'static, str>>,
184        unit: Option<Cow<'static, str>>,
185    ) -> Result<UpDownCounter<i64>> {
186        validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
187        let p = InstrumentResolver::new(self, &self.i64_resolver);
188        p.lookup(InstrumentKind::UpDownCounter, name, description, unit)
189            .map(|i| UpDownCounter::new(Arc::new(i)))
190    }
191
192    fn f64_up_down_counter(
193        &self,
194        name: Cow<'static, str>,
195        description: Option<Cow<'static, str>>,
196        unit: Option<Cow<'static, str>>,
197    ) -> Result<UpDownCounter<f64>> {
198        validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
199        let p = InstrumentResolver::new(self, &self.f64_resolver);
200        p.lookup(InstrumentKind::UpDownCounter, name, description, unit)
201            .map(|i| UpDownCounter::new(Arc::new(i)))
202    }
203
204    fn i64_observable_up_down_counter(
205        &self,
206        name: Cow<'static, str>,
207        description: Option<Cow<'static, str>>,
208        unit: Option<Cow<'static, str>>,
209        callbacks: Vec<Callback<i64>>,
210    ) -> Result<ObservableUpDownCounter<i64>> {
211        validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
212        let p = InstrumentResolver::new(self, &self.i64_resolver);
213        let ms = p.measures(
214            InstrumentKind::ObservableUpDownCounter,
215            name.clone(),
216            description.clone(),
217            unit.clone(),
218        )?;
219        if ms.is_empty() {
220            return Ok(ObservableUpDownCounter::new(Arc::new(
221                NoopAsyncInstrument::new(),
222            )));
223        }
224
225        let observable = Arc::new(Observable::new(
226            self.scope.clone(),
227            InstrumentKind::ObservableUpDownCounter,
228            name,
229            description.unwrap_or_default(),
230            unit.unwrap_or_default(),
231            ms,
232        ));
233
234        for callback in callbacks {
235            let cb_inst = Arc::clone(&observable);
236            self.pipes
237                .register_callback(move || callback(cb_inst.as_ref()));
238        }
239
240        Ok(ObservableUpDownCounter::new(observable))
241    }
242
243    fn f64_observable_up_down_counter(
244        &self,
245        name: Cow<'static, str>,
246        description: Option<Cow<'static, str>>,
247        unit: Option<Cow<'static, str>>,
248        callbacks: Vec<Callback<f64>>,
249    ) -> Result<ObservableUpDownCounter<f64>> {
250        validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
251        let p = InstrumentResolver::new(self, &self.f64_resolver);
252        let ms = p.measures(
253            InstrumentKind::ObservableUpDownCounter,
254            name.clone(),
255            description.clone(),
256            unit.clone(),
257        )?;
258        if ms.is_empty() {
259            return Ok(ObservableUpDownCounter::new(Arc::new(
260                NoopAsyncInstrument::new(),
261            )));
262        }
263
264        let observable = Arc::new(Observable::new(
265            self.scope.clone(),
266            InstrumentKind::ObservableUpDownCounter,
267            name,
268            description.unwrap_or_default(),
269            unit.unwrap_or_default(),
270            ms,
271        ));
272
273        for callback in callbacks {
274            let cb_inst = Arc::clone(&observable);
275            self.pipes
276                .register_callback(move || callback(cb_inst.as_ref()));
277        }
278
279        Ok(ObservableUpDownCounter::new(observable))
280    }
281
282    fn u64_gauge(
283        &self,
284        name: Cow<'static, str>,
285        description: Option<Cow<'static, str>>,
286        unit: Option<Cow<'static, str>>,
287    ) -> Result<Gauge<u64>> {
288        validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
289        let p = InstrumentResolver::new(self, &self.u64_resolver);
290        p.lookup(InstrumentKind::Gauge, name, description, unit)
291            .map(|i| Gauge::new(Arc::new(i)))
292    }
293
294    fn f64_gauge(
295        &self,
296        name: Cow<'static, str>,
297        description: Option<Cow<'static, str>>,
298        unit: Option<Cow<'static, str>>,
299    ) -> Result<Gauge<f64>> {
300        validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
301        let p = InstrumentResolver::new(self, &self.f64_resolver);
302        p.lookup(InstrumentKind::Gauge, name, description, unit)
303            .map(|i| Gauge::new(Arc::new(i)))
304    }
305
306    fn i64_gauge(
307        &self,
308        name: Cow<'static, str>,
309        description: Option<Cow<'static, str>>,
310        unit: Option<Cow<'static, str>>,
311    ) -> Result<Gauge<i64>> {
312        validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
313        let p = InstrumentResolver::new(self, &self.i64_resolver);
314        p.lookup(InstrumentKind::Gauge, name, description, unit)
315            .map(|i| Gauge::new(Arc::new(i)))
316    }
317
318    fn u64_observable_gauge(
319        &self,
320        name: Cow<'static, str>,
321        description: Option<Cow<'static, str>>,
322        unit: Option<Cow<'static, str>>,
323        callbacks: Vec<Callback<u64>>,
324    ) -> Result<ObservableGauge<u64>> {
325        validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
326        let p = InstrumentResolver::new(self, &self.u64_resolver);
327        let ms = p.measures(
328            InstrumentKind::ObservableGauge,
329            name.clone(),
330            description.clone(),
331            unit.clone(),
332        )?;
333        if ms.is_empty() {
334            return Ok(ObservableGauge::new(Arc::new(NoopAsyncInstrument::new())));
335        }
336
337        let observable = Arc::new(Observable::new(
338            self.scope.clone(),
339            InstrumentKind::ObservableGauge,
340            name,
341            description.unwrap_or_default(),
342            unit.unwrap_or_default(),
343            ms,
344        ));
345
346        for callback in callbacks {
347            let cb_inst = Arc::clone(&observable);
348            self.pipes
349                .register_callback(move || callback(cb_inst.as_ref()));
350        }
351
352        Ok(ObservableGauge::new(observable))
353    }
354
355    fn i64_observable_gauge(
356        &self,
357        name: Cow<'static, str>,
358        description: Option<Cow<'static, str>>,
359        unit: Option<Cow<'static, str>>,
360        callbacks: Vec<Callback<i64>>,
361    ) -> Result<ObservableGauge<i64>> {
362        validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
363        let p = InstrumentResolver::new(self, &self.i64_resolver);
364        let ms = p.measures(
365            InstrumentKind::ObservableGauge,
366            name.clone(),
367            description.clone(),
368            unit.clone(),
369        )?;
370        if ms.is_empty() {
371            return Ok(ObservableGauge::new(Arc::new(NoopAsyncInstrument::new())));
372        }
373
374        let observable = Arc::new(Observable::new(
375            self.scope.clone(),
376            InstrumentKind::ObservableGauge,
377            name,
378            description.unwrap_or_default(),
379            unit.unwrap_or_default(),
380            ms,
381        ));
382
383        for callback in callbacks {
384            let cb_inst = Arc::clone(&observable);
385            self.pipes
386                .register_callback(move || callback(cb_inst.as_ref()));
387        }
388
389        Ok(ObservableGauge::new(observable))
390    }
391
392    fn f64_observable_gauge(
393        &self,
394        name: Cow<'static, str>,
395        description: Option<Cow<'static, str>>,
396        unit: Option<Cow<'static, str>>,
397        callbacks: Vec<Callback<f64>>,
398    ) -> Result<ObservableGauge<f64>> {
399        validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
400        let p = InstrumentResolver::new(self, &self.f64_resolver);
401        let ms = p.measures(
402            InstrumentKind::ObservableGauge,
403            name.clone(),
404            description.clone(),
405            unit.clone(),
406        )?;
407        if ms.is_empty() {
408            return Ok(ObservableGauge::new(Arc::new(NoopAsyncInstrument::new())));
409        }
410
411        let observable = Arc::new(Observable::new(
412            self.scope.clone(),
413            InstrumentKind::ObservableGauge,
414            name,
415            description.unwrap_or_default(),
416            unit.unwrap_or_default(),
417            ms,
418        ));
419
420        for callback in callbacks {
421            let cb_inst = Arc::clone(&observable);
422            self.pipes
423                .register_callback(move || callback(cb_inst.as_ref()));
424        }
425
426        Ok(ObservableGauge::new(observable))
427    }
428
429    fn f64_histogram(
430        &self,
431        name: Cow<'static, str>,
432        description: Option<Cow<'static, str>>,
433        unit: Option<Cow<'static, str>>,
434    ) -> Result<Histogram<f64>> {
435        validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
436        let p = InstrumentResolver::new(self, &self.f64_resolver);
437        p.lookup(InstrumentKind::Histogram, name, description, unit)
438            .map(|i| Histogram::new(Arc::new(i)))
439    }
440
441    fn u64_histogram(
442        &self,
443        name: Cow<'static, str>,
444        description: Option<Cow<'static, str>>,
445        unit: Option<Cow<'static, str>>,
446    ) -> Result<Histogram<u64>> {
447        validate_instrument_config(name.as_ref(), &unit, self.validation_policy)?;
448        let p = InstrumentResolver::new(self, &self.u64_resolver);
449        p.lookup(InstrumentKind::Histogram, name, description, unit)
450            .map(|i| Histogram::new(Arc::new(i)))
451    }
452
453    fn register_callback(
454        &self,
455        insts: &[Arc<dyn Any>],
456        callback: Box<dyn Fn(&dyn ApiObserver) + Send + Sync>,
457    ) -> Result<Box<dyn CallbackRegistration>> {
458        if insts.is_empty() {
459            return Ok(Box::new(NoopRegistration::new()));
460        }
461
462        let mut reg = Observer::default();
463        let mut errs = vec![];
464        for inst in insts {
465            if let Some(i64_obs) = inst.downcast_ref::<Observable<i64>>() {
466                if let Err(err) = i64_obs.registerable(&self.scope) {
467                    if !err.to_string().contains(EMPTY_MEASURE_MSG) {
468                        errs.push(err);
469                    }
470                    continue;
471                }
472                reg.register_i64(i64_obs.id.clone());
473            } else if let Some(u64_obs) = inst.downcast_ref::<Observable<u64>>() {
474                if let Err(err) = u64_obs.registerable(&self.scope) {
475                    if !err.to_string().contains(EMPTY_MEASURE_MSG) {
476                        errs.push(err);
477                    }
478                    continue;
479                }
480                reg.register_u64(u64_obs.id.clone());
481            } else if let Some(f64_obs) = inst.downcast_ref::<Observable<f64>>() {
482                if let Err(err) = f64_obs.registerable(&self.scope) {
483                    if !err.to_string().contains(EMPTY_MEASURE_MSG) {
484                        errs.push(err);
485                    }
486                    continue;
487                }
488                reg.register_f64(f64_obs.id.clone());
489            } else {
490                // Instrument external to the SDK.
491                return Err(MetricsError::Other(
492                    "invalid observable: from different implementation".into(),
493                ));
494            }
495        }
496
497        if !errs.is_empty() {
498            return Err(MetricsError::Other(format!("{errs:?}")));
499        }
500
501        if reg.is_empty() {
502            // All instruments use drop aggregation or are invalid.
503            return Ok(Box::new(NoopRegistration::new()));
504        }
505
506        self.pipes.register_multi_callback(move || callback(&reg))
507    }
508}
509
510/// Validation policy for instrument
511#[derive(Clone, Copy)]
512enum InstrumentValidationPolicy {
513    HandleGlobalAndIgnore,
514    /// Currently only for test
515    #[cfg(test)]
516    Strict,
517}
518
519fn validate_instrument_config(
520    name: &str,
521    unit: &Option<Cow<'static, str>>,
522    policy: InstrumentValidationPolicy,
523) -> Result<()> {
524    match validate_instrument_name(name).and_then(|_| validate_instrument_unit(unit)) {
525        Ok(_) => Ok(()),
526        Err(err) => match policy {
527            InstrumentValidationPolicy::HandleGlobalAndIgnore => {
528                global::handle_error(err);
529                Ok(())
530            }
531            #[cfg(test)]
532            InstrumentValidationPolicy::Strict => Err(err),
533        },
534    }
535}
536
537fn validate_instrument_name(name: &str) -> Result<()> {
538    if name.is_empty() {
539        return Err(MetricsError::InvalidInstrumentConfiguration(
540            INSTRUMENT_NAME_EMPTY,
541        ));
542    }
543    if name.len() > INSTRUMENT_NAME_MAX_LENGTH {
544        return Err(MetricsError::InvalidInstrumentConfiguration(
545            INSTRUMENT_NAME_LENGTH,
546        ));
547    }
548    if name.starts_with(|c: char| !c.is_ascii_alphabetic()) {
549        return Err(MetricsError::InvalidInstrumentConfiguration(
550            INSTRUMENT_NAME_FIRST_ALPHABETIC,
551        ));
552    }
553    if name.contains(|c: char| {
554        !c.is_ascii_alphanumeric() && !INSTRUMENT_NAME_ALLOWED_NON_ALPHANUMERIC_CHARS.contains(&c)
555    }) {
556        return Err(MetricsError::InvalidInstrumentConfiguration(
557            INSTRUMENT_NAME_INVALID_CHAR,
558        ));
559    }
560    Ok(())
561}
562
563fn validate_instrument_unit(unit: &Option<Cow<'static, str>>) -> Result<()> {
564    if let Some(unit) = unit {
565        if unit.len() > INSTRUMENT_UNIT_NAME_MAX_LENGTH {
566            return Err(MetricsError::InvalidInstrumentConfiguration(
567                INSTRUMENT_UNIT_LENGTH,
568            ));
569        }
570        if unit.contains(|c: char| !c.is_ascii()) {
571            return Err(MetricsError::InvalidInstrumentConfiguration(
572                INSTRUMENT_UNIT_INVALID_CHAR,
573            ));
574        }
575    }
576    Ok(())
577}
578
579#[derive(Default)]
580struct Observer {
581    f64s: HashSet<ObservableId<f64>>,
582    i64s: HashSet<ObservableId<i64>>,
583    u64s: HashSet<ObservableId<u64>>,
584}
585
586impl Observer {
587    fn is_empty(&self) -> bool {
588        self.f64s.is_empty() && self.i64s.is_empty() && self.u64s.is_empty()
589    }
590
591    pub(crate) fn register_i64(&mut self, id: ObservableId<i64>) {
592        self.i64s.insert(id);
593    }
594
595    pub(crate) fn register_f64(&mut self, id: ObservableId<f64>) {
596        self.f64s.insert(id);
597    }
598
599    pub(crate) fn register_u64(&mut self, id: ObservableId<u64>) {
600        self.u64s.insert(id);
601    }
602}
603
604impl ApiObserver for Observer {
605    fn observe_f64(&self, inst: &dyn AsyncInstrument<f64>, measurement: f64, attrs: &[KeyValue]) {
606        if let Some(f64_obs) = inst.as_any().downcast_ref::<Observable<f64>>() {
607            if self.f64s.contains(&f64_obs.id) {
608                f64_obs.observe(measurement, attrs)
609            } else {
610                global::handle_error(
611                    MetricsError::Other(format!("observable instrument not registered for callback, failed to record. name: {}, description: {}, unit: {:?}, number: f64",
612                    f64_obs.id.inner.name,
613                    f64_obs.id.inner.description,
614                    f64_obs.id.inner.unit,
615                )))
616            }
617        } else {
618            global::handle_error(MetricsError::Other(
619                "unknown observable instrument, failed to record.".into(),
620            ))
621        }
622    }
623
624    fn observe_u64(&self, inst: &dyn AsyncInstrument<u64>, measurement: u64, attrs: &[KeyValue]) {
625        if let Some(u64_obs) = inst.as_any().downcast_ref::<Observable<u64>>() {
626            if self.u64s.contains(&u64_obs.id) {
627                u64_obs.observe(measurement, attrs)
628            } else {
629                global::handle_error(
630                    MetricsError::Other(format!("observable instrument not registered for callback, failed to record. name: {}, description: {}, unit: {:?}, number: f64",
631                    u64_obs.id.inner.name,
632                    u64_obs.id.inner.description,
633                    u64_obs.id.inner.unit,
634                )))
635            }
636        } else {
637            global::handle_error(MetricsError::Other(
638                "unknown observable instrument, failed to record.".into(),
639            ))
640        }
641    }
642
643    fn observe_i64(&self, inst: &dyn AsyncInstrument<i64>, measurement: i64, attrs: &[KeyValue]) {
644        if let Some(i64_obs) = inst.as_any().downcast_ref::<Observable<i64>>() {
645            if self.i64s.contains(&i64_obs.id) {
646                i64_obs.observe(measurement, attrs)
647            } else {
648                global::handle_error(
649                    MetricsError::Other(format!("observable instrument not registered for callback, failed to record. name: {}, description: {}, unit: {:?}, number: f64",
650                    i64_obs.id.inner.name,
651                    i64_obs.id.inner.description,
652                    i64_obs.id.inner.unit,
653                )))
654            }
655        } else {
656            global::handle_error(MetricsError::Other(
657                "unknown observable instrument, failed to record.".into(),
658            ))
659        }
660    }
661}
662
663impl fmt::Debug for SdkMeter {
664    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
665        f.debug_struct("Meter").field("scope", &self.scope).finish()
666    }
667}
668
669/// Provides all OpenTelemetry instruments.
670struct InstrumentResolver<'a, T> {
671    meter: &'a SdkMeter,
672    resolve: &'a Resolver<T>,
673}
674
675impl<'a, T> InstrumentResolver<'a, T>
676where
677    T: Number<T>,
678{
679    fn new(meter: &'a SdkMeter, resolve: &'a Resolver<T>) -> Self {
680        InstrumentResolver { meter, resolve }
681    }
682
683    /// lookup returns the resolved measures.
684    fn lookup(
685        &self,
686        kind: InstrumentKind,
687        name: Cow<'static, str>,
688        description: Option<Cow<'static, str>>,
689        unit: Option<Cow<'static, str>>,
690    ) -> Result<ResolvedMeasures<T>> {
691        let aggregators = self.measures(kind, name, description, unit)?;
692        Ok(ResolvedMeasures {
693            measures: aggregators,
694        })
695    }
696
697    fn measures(
698        &self,
699        kind: InstrumentKind,
700        name: Cow<'static, str>,
701        description: Option<Cow<'static, str>>,
702        unit: Option<Cow<'static, str>>,
703    ) -> Result<Vec<Arc<dyn internal::Measure<T>>>> {
704        let inst = Instrument {
705            name,
706            description: description.unwrap_or_default(),
707            unit: unit.unwrap_or_default(),
708            kind: Some(kind),
709            scope: self.meter.scope.clone(),
710        };
711
712        self.resolve.measures(inst)
713    }
714}
715
716#[cfg(test)]
717mod tests {
718    use std::sync::Arc;
719
720    use opentelemetry::metrics::{InstrumentProvider, MeterProvider, MetricsError};
721
722    use super::{
723        InstrumentValidationPolicy, SdkMeter, INSTRUMENT_NAME_FIRST_ALPHABETIC,
724        INSTRUMENT_NAME_INVALID_CHAR, INSTRUMENT_NAME_LENGTH, INSTRUMENT_UNIT_INVALID_CHAR,
725        INSTRUMENT_UNIT_LENGTH,
726    };
727    use crate::{
728        metrics::{pipeline::Pipelines, SdkMeterProvider},
729        Resource, Scope,
730    };
731
732    #[test]
733    #[ignore = "See issue https://github.com/open-telemetry/opentelemetry-rust/issues/1699"]
734    fn test_instrument_creation() {
735        let provider = SdkMeterProvider::builder().build();
736        let meter = provider.meter("test");
737        assert!(meter.u64_counter("test").try_init().is_ok());
738        let result = meter.u64_counter("test with invalid name").try_init();
739        // this assert fails, as result is always ok variant.
740        assert!(result.is_err());
741    }
742
743    #[test]
744    fn test_instrument_config_validation() {
745        // scope and pipelines are not related to test
746        let meter = SdkMeter::new(
747            Scope::default(),
748            Arc::new(Pipelines::new(Resource::default(), Vec::new(), Vec::new())),
749        )
750        .with_validation_policy(InstrumentValidationPolicy::Strict);
751        // (name, expected error)
752        let instrument_name_test_cases = vec![
753            ("validateName", ""),
754            ("_startWithNoneAlphabet", INSTRUMENT_NAME_FIRST_ALPHABETIC),
755            ("utf8char锈", INSTRUMENT_NAME_INVALID_CHAR),
756            ("a".repeat(255).leak(), ""),
757            ("a".repeat(256).leak(), INSTRUMENT_NAME_LENGTH),
758            ("invalid name", INSTRUMENT_NAME_INVALID_CHAR),
759            ("allow/slash", ""),
760            ("allow_under_score", ""),
761            ("allow.dots.ok", ""),
762        ];
763        for (name, expected_error) in instrument_name_test_cases {
764            let assert = |result: Result<_, MetricsError>| {
765                if expected_error.is_empty() {
766                    assert!(result.is_ok());
767                } else {
768                    assert!(matches!(
769                        result.unwrap_err(),
770                        MetricsError::InvalidInstrumentConfiguration(msg) if msg == expected_error
771                    ));
772                }
773            };
774
775            assert(meter.u64_counter(name.into(), None, None).map(|_| ()));
776            assert(meter.f64_counter(name.into(), None, None).map(|_| ()));
777            assert(
778                meter
779                    .u64_observable_counter(name.into(), None, None, Vec::new())
780                    .map(|_| ()),
781            );
782            assert(
783                meter
784                    .f64_observable_counter(name.into(), None, None, Vec::new())
785                    .map(|_| ()),
786            );
787            assert(
788                meter
789                    .i64_up_down_counter(name.into(), None, None)
790                    .map(|_| ()),
791            );
792            assert(
793                meter
794                    .f64_up_down_counter(name.into(), None, None)
795                    .map(|_| ()),
796            );
797            assert(
798                meter
799                    .i64_observable_up_down_counter(name.into(), None, None, Vec::new())
800                    .map(|_| ()),
801            );
802            assert(
803                meter
804                    .f64_observable_up_down_counter(name.into(), None, None, Vec::new())
805                    .map(|_| ()),
806            );
807            assert(meter.u64_gauge(name.into(), None, None).map(|_| ()));
808            assert(meter.f64_gauge(name.into(), None, None).map(|_| ()));
809            assert(meter.i64_gauge(name.into(), None, None).map(|_| ()));
810            assert(
811                meter
812                    .u64_observable_gauge(name.into(), None, None, Vec::new())
813                    .map(|_| ()),
814            );
815            assert(
816                meter
817                    .i64_observable_gauge(name.into(), None, None, Vec::new())
818                    .map(|_| ()),
819            );
820            assert(
821                meter
822                    .f64_observable_gauge(name.into(), None, None, Vec::new())
823                    .map(|_| ()),
824            );
825            assert(meter.f64_histogram(name.into(), None, None).map(|_| ()));
826            assert(meter.u64_histogram(name.into(), None, None).map(|_| ()));
827        }
828
829        // (unit, expected error)
830        let instrument_unit_test_cases = vec![
831            (
832                "0123456789012345678901234567890123456789012345678901234567890123",
833                INSTRUMENT_UNIT_LENGTH,
834            ),
835            ("utf8char锈", INSTRUMENT_UNIT_INVALID_CHAR),
836            ("kb", ""),
837            ("Kb/sec", ""),
838            ("%", ""),
839            ("", ""),
840        ];
841
842        for (unit, expected_error) in instrument_unit_test_cases {
843            let assert = |result: Result<_, MetricsError>| {
844                if expected_error.is_empty() {
845                    assert!(result.is_ok());
846                } else {
847                    assert!(matches!(
848                        result.unwrap_err(),
849                        MetricsError::InvalidInstrumentConfiguration(msg) if msg == expected_error
850                    ));
851                }
852            };
853            let unit = Some(unit.into());
854            assert(
855                meter
856                    .u64_counter("test".into(), None, unit.clone())
857                    .map(|_| ()),
858            );
859            assert(
860                meter
861                    .f64_counter("test".into(), None, unit.clone())
862                    .map(|_| ()),
863            );
864            assert(
865                meter
866                    .u64_observable_counter("test".into(), None, unit.clone(), Vec::new())
867                    .map(|_| ()),
868            );
869            assert(
870                meter
871                    .f64_observable_counter("test".into(), None, unit.clone(), Vec::new())
872                    .map(|_| ()),
873            );
874            assert(
875                meter
876                    .i64_up_down_counter("test".into(), None, unit.clone())
877                    .map(|_| ()),
878            );
879            assert(
880                meter
881                    .f64_up_down_counter("test".into(), None, unit.clone())
882                    .map(|_| ()),
883            );
884            assert(
885                meter
886                    .i64_observable_up_down_counter("test".into(), None, unit.clone(), Vec::new())
887                    .map(|_| ()),
888            );
889            assert(
890                meter
891                    .f64_observable_up_down_counter("test".into(), None, unit.clone(), Vec::new())
892                    .map(|_| ()),
893            );
894            assert(
895                meter
896                    .u64_observable_gauge("test".into(), None, unit.clone(), Vec::new())
897                    .map(|_| ()),
898            );
899            assert(
900                meter
901                    .i64_observable_gauge("test".into(), None, unit.clone(), Vec::new())
902                    .map(|_| ()),
903            );
904            assert(
905                meter
906                    .f64_observable_gauge("test".into(), None, unit.clone(), Vec::new())
907                    .map(|_| ()),
908            );
909            assert(
910                meter
911                    .f64_histogram("test".into(), None, unit.clone())
912                    .map(|_| ()),
913            );
914            assert(
915                meter
916                    .u64_histogram("test".into(), None, unit.clone())
917                    .map(|_| ()),
918            );
919        }
920    }
921}