1use core::fmt;
2use opentelemetry::{
3 metrics::{Meter, MeterProvider},
4 otel_debug, otel_error, otel_info, InstrumentationScope,
5};
6use std::time::Duration;
7use std::{
8 collections::HashMap,
9 sync::{
10 atomic::{AtomicBool, Ordering},
11 Arc, Mutex,
12 },
13};
14
15use crate::error::OTelSdkResult;
16use crate::Resource;
17
18use super::{
19 exporter::PushMetricExporter, meter::SdkMeter, noop::NoopMeter,
20 periodic_reader::PeriodicReader, pipeline::Pipelines, reader::MetricReader, view::View,
21 Instrument, Stream,
22};
23
24#[derive(Clone, Debug)]
35pub struct SdkMeterProvider {
36 inner: Arc<SdkMeterProviderInner>,
37}
38
39#[derive(Debug)]
40struct SdkMeterProviderInner {
41 pipes: Arc<Pipelines>,
42 meters: Mutex<HashMap<InstrumentationScope, Arc<SdkMeter>>>,
43 shutdown_invoked: AtomicBool,
44}
45
46impl Default for SdkMeterProvider {
47 fn default() -> Self {
48 SdkMeterProvider::builder().build()
49 }
50}
51
52impl SdkMeterProvider {
53 pub fn builder() -> MeterProviderBuilder {
55 MeterProviderBuilder::default()
56 }
57
58 pub fn force_flush(&self) -> OTelSdkResult {
98 self.inner.force_flush()
99 }
100
101 pub fn shutdown_with_timeout(&self, _timeout: Duration) -> OTelSdkResult {
114 otel_debug!(
115 name: "MeterProvider.Shutdown",
116 message = "User initiated shutdown of MeterProvider."
117 );
118 self.inner.shutdown()
119 }
120
121 pub fn shutdown(&self) -> OTelSdkResult {
123 self.shutdown_with_timeout(Duration::from_secs(5))
124 }
125}
126
127impl SdkMeterProviderInner {
128 fn force_flush(&self) -> OTelSdkResult {
129 if self
130 .shutdown_invoked
131 .load(std::sync::atomic::Ordering::Relaxed)
132 {
133 Err(crate::error::OTelSdkError::AlreadyShutdown)
134 } else {
135 self.pipes.force_flush()
136 }
137 }
138
139 fn shutdown_with_timeout(&self, _timeout: Duration) -> OTelSdkResult {
140 if self
141 .shutdown_invoked
142 .swap(true, std::sync::atomic::Ordering::SeqCst)
143 {
144 Err(crate::error::OTelSdkError::AlreadyShutdown)
146 } else {
147 self.pipes.shutdown()
148 }
149 }
150
151 fn shutdown(&self) -> OTelSdkResult {
152 self.shutdown_with_timeout(Duration::from_secs(5))
153 }
154}
155
156impl Drop for SdkMeterProviderInner {
157 fn drop(&mut self) {
158 if self.shutdown_invoked.load(Ordering::Relaxed) {
161 otel_debug!(
162 name: "MeterProvider.Drop.AlreadyShutdown",
163 message = "MeterProvider was already shut down; drop will not attempt shutdown again."
164 );
165 } else {
166 otel_info!(
167 name: "MeterProvider.Drop",
168 message = "Last reference of MeterProvider dropped, initiating shutdown."
169 );
170 if let Err(err) = self.shutdown() {
171 otel_error!(
172 name: "MeterProvider.Drop.ShutdownFailed",
173 message = "Shutdown attempt failed during drop of MeterProvider.",
174 reason = format!("{}", err)
175 );
176 } else {
177 otel_debug!(
178 name: "MeterProvider.Drop.ShutdownCompleted",
179 );
180 }
181 }
182 }
183}
184
185impl MeterProvider for SdkMeterProvider {
186 fn meter(&self, name: &'static str) -> Meter {
187 let scope = InstrumentationScope::builder(name).build();
188 self.meter_with_scope(scope)
189 }
190
191 fn meter_with_scope(&self, scope: InstrumentationScope) -> Meter {
192 if self.inner.shutdown_invoked.load(Ordering::Relaxed) {
193 otel_debug!(
194 name: "MeterProvider.NoOpMeterReturned",
195 meter_name = scope.name(),
196 );
197 return Meter::new(Arc::new(NoopMeter::new()));
198 }
199
200 if scope.name().is_empty() {
201 otel_info!(name: "MeterNameEmpty", message = "Meter name is empty; consider providing a meaningful name. Meter will function normally and the provided name will be used as-is.");
202 };
203
204 if let Ok(mut meters) = self.inner.meters.lock() {
205 if let Some(existing_meter) = meters.get(&scope) {
206 otel_debug!(
207 name: "MeterProvider.ExistingMeterReturned",
208 meter_name = scope.name(),
209 );
210 Meter::new(existing_meter.clone())
211 } else {
212 let new_meter = Arc::new(SdkMeter::new(scope.clone(), self.inner.pipes.clone()));
213 meters.insert(scope.clone(), new_meter.clone());
214 otel_debug!(
215 name: "MeterProvider.NewMeterCreated",
216 meter_name = scope.name(),
217 );
218 Meter::new(new_meter)
219 }
220 } else {
221 otel_debug!(
222 name: "MeterProvider.NoOpMeterReturned",
223 meter_name = scope.name(),
224 );
225 Meter::new(Arc::new(NoopMeter::new()))
226 }
227 }
228}
229
230#[derive(Default)]
232pub struct MeterProviderBuilder {
233 resource: Option<Resource>,
234 readers: Vec<Box<dyn MetricReader>>,
235 views: Vec<Arc<dyn View>>,
236}
237
238impl MeterProviderBuilder {
239 pub fn with_resource(mut self, resource: Resource) -> Self {
273 self.resource = match self.resource {
274 Some(existing) => Some(existing.merge(&resource)),
275 None => Some(resource),
276 };
277
278 self
279 }
280
281 pub fn with_reader<T: MetricReader>(mut self, reader: T) -> Self {
288 self.readers.push(Box::new(reader));
289 self
290 }
291
292 pub fn with_periodic_exporter<T>(mut self, exporter: T) -> Self
305 where
306 T: PushMetricExporter,
307 {
308 let reader = PeriodicReader::builder(exporter).build();
309 self.readers.push(Box::new(reader));
310 self
311 }
312
313 pub fn with_view<T>(mut self, view: T) -> Self
390 where
391 T: Fn(&Instrument) -> Option<Stream> + Send + Sync + 'static,
392 {
393 self.views.push(Arc::new(view));
394 self
395 }
396
397 pub fn build(self) -> SdkMeterProvider {
399 otel_debug!(
400 name: "MeterProvider.Building",
401 builder = format!("{:?}", &self),
402 );
403
404 let meter_provider = SdkMeterProvider {
405 inner: Arc::new(SdkMeterProviderInner {
406 pipes: Arc::new(Pipelines::new(
407 self.resource.unwrap_or(Resource::builder().build()),
408 self.readers,
409 self.views,
410 )),
411 meters: Default::default(),
412 shutdown_invoked: AtomicBool::new(false),
413 }),
414 };
415
416 otel_debug!(
417 name: "MeterProvider.Built",
418 );
419 meter_provider
420 }
421}
422
423impl fmt::Debug for MeterProviderBuilder {
424 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425 f.debug_struct("MeterProviderBuilder")
426 .field("resource", &self.resource)
427 .field("readers", &self.readers)
428 .field("views", &self.views.len())
429 .finish()
430 }
431}
432#[cfg(all(test, feature = "testing"))]
433mod tests {
434 use crate::error::OTelSdkError;
435 use crate::metrics::SdkMeterProvider;
436 use crate::resource::{
437 SERVICE_NAME, TELEMETRY_SDK_LANGUAGE, TELEMETRY_SDK_NAME, TELEMETRY_SDK_VERSION,
438 };
439 use crate::testing::metrics::metric_reader::TestMetricReader;
440 use crate::Resource;
441 use opentelemetry::metrics::MeterProvider;
442 use opentelemetry::{global, InstrumentationScope};
443 use opentelemetry::{Key, KeyValue, Value};
444 use std::env;
445
446 #[test]
447 fn test_meter_provider_resource() {
448 let assert_resource = |provider: &super::SdkMeterProvider,
449 resource_key: &'static str,
450 expect: Option<&'static str>| {
451 assert_eq!(
452 provider.inner.pipes.0[0]
453 .resource
454 .get(&Key::from_static_str(resource_key))
455 .map(|v| v.to_string()),
456 expect.map(|s| s.to_string())
457 );
458 };
459 let assert_telemetry_resource = |provider: &super::SdkMeterProvider| {
460 assert_eq!(
461 provider.inner.pipes.0[0]
462 .resource
463 .get(&TELEMETRY_SDK_LANGUAGE.into()),
464 Some(Value::from("rust"))
465 );
466 assert_eq!(
467 provider.inner.pipes.0[0]
468 .resource
469 .get(&TELEMETRY_SDK_NAME.into()),
470 Some(Value::from("opentelemetry"))
471 );
472 assert_eq!(
473 provider.inner.pipes.0[0]
474 .resource
475 .get(&TELEMETRY_SDK_VERSION.into()),
476 Some(Value::from(env!("CARGO_PKG_VERSION")))
477 );
478 };
479
480 temp_env::with_var_unset("OTEL_RESOURCE_ATTRIBUTES", || {
482 let reader = TestMetricReader::new();
483 let default_meter_provider = super::SdkMeterProvider::builder()
484 .with_reader(reader)
485 .build();
486 let service_name = default_meter_provider.inner.pipes.0[0]
487 .resource
488 .get(&Key::from_static_str(SERVICE_NAME))
489 .map(|v| v.to_string())
490 .unwrap();
491 assert!(
492 service_name.starts_with("unknown_service:opentelemetry_sdk-"),
493 "Expected service name to start with 'unknown_service:opentelemetry_sdk-', got: {}",
494 service_name
495 );
496 assert_telemetry_resource(&default_meter_provider);
497 });
498
499 let reader2 = TestMetricReader::new();
501 let custom_meter_provider = super::SdkMeterProvider::builder()
502 .with_reader(reader2)
503 .with_resource(
504 Resource::builder_empty()
505 .with_service_name("test_service")
506 .build(),
507 )
508 .build();
509 assert_resource(&custom_meter_provider, SERVICE_NAME, Some("test_service"));
510 assert_eq!(custom_meter_provider.inner.pipes.0[0].resource.len(), 1);
511
512 temp_env::with_var(
513 "OTEL_RESOURCE_ATTRIBUTES",
514 Some("key1=value1, k2, k3=value2"),
515 || {
516 let reader3 = TestMetricReader::new();
518 let env_resource_provider = super::SdkMeterProvider::builder()
519 .with_reader(reader3)
520 .build();
521 let service_name = env_resource_provider.inner.pipes.0[0]
522 .resource
523 .get(&Key::from_static_str(SERVICE_NAME))
524 .map(|v| v.to_string())
525 .unwrap();
526 assert!(
527 service_name.starts_with("unknown_service:opentelemetry_sdk-"),
528 "Expected service name to start with 'unknown_service:opentelemetry_sdk-', got: {}",
529 service_name
530 );
531 assert_resource(&env_resource_provider, "key1", Some("value1"));
532 assert_resource(&env_resource_provider, "k3", Some("value2"));
533 assert_telemetry_resource(&env_resource_provider);
534 assert_eq!(env_resource_provider.inner.pipes.0[0].resource.len(), 6);
535 },
536 );
537
538 temp_env::with_var(
540 "OTEL_RESOURCE_ATTRIBUTES",
541 Some("my-custom-key=env-val,k2=value2"),
542 || {
543 let reader4 = TestMetricReader::new();
544 let user_provided_resource_config_provider = super::SdkMeterProvider::builder()
545 .with_reader(reader4)
546 .with_resource(
547 Resource::builder()
548 .with_attributes([
549 KeyValue::new("my-custom-key", "my-custom-value"),
550 KeyValue::new("my-custom-key2", "my-custom-value2"),
551 ])
552 .build(),
553 )
554 .build();
555 let service_name = user_provided_resource_config_provider.inner.pipes.0[0]
556 .resource
557 .get(&Key::from_static_str(SERVICE_NAME))
558 .map(|v| v.to_string())
559 .unwrap();
560 assert!(
561 service_name.starts_with("unknown_service:opentelemetry_sdk-"),
562 "Expected service name to start with 'unknown_service:opentelemetry_sdk-', got: {}",
563 service_name
564 );
565 assert_resource(
566 &user_provided_resource_config_provider,
567 "my-custom-key",
568 Some("my-custom-value"),
569 );
570 assert_resource(
571 &user_provided_resource_config_provider,
572 "my-custom-key2",
573 Some("my-custom-value2"),
574 );
575 assert_resource(
576 &user_provided_resource_config_provider,
577 "k2",
578 Some("value2"),
579 );
580 assert_telemetry_resource(&user_provided_resource_config_provider);
581 assert_eq!(
582 user_provided_resource_config_provider.inner.pipes.0[0]
583 .resource
584 .len(),
585 7
586 );
587 },
588 );
589
590 let reader5 = TestMetricReader::new();
592 let no_service_name = super::SdkMeterProvider::builder()
593 .with_reader(reader5)
594 .with_resource(Resource::empty())
595 .build();
596
597 assert_eq!(no_service_name.inner.pipes.0[0].resource.len(), 0)
598 }
599
600 #[test]
601 fn test_meter_provider_shutdown() {
602 let reader = TestMetricReader::new();
603 let provider = super::SdkMeterProvider::builder()
604 .with_reader(reader.clone())
605 .build();
606 global::set_meter_provider(provider.clone());
607 assert!(!reader.is_shutdown());
608 let meter = global::meter("test");
610 let counter = meter.u64_counter("test_counter").build();
611 let shutdown_res = provider.shutdown();
613 assert!(shutdown_res.is_ok());
614
615 let shutdown_res = provider.shutdown();
617 assert!(matches!(shutdown_res, Err(OTelSdkError::AlreadyShutdown)));
618
619 assert!(shutdown_res.is_err());
620 assert!(reader.is_shutdown());
621 counter.add(1, &[]);
624 }
625 #[test]
626 fn test_shutdown_invoked_on_last_drop() {
627 let reader = TestMetricReader::new();
628 let provider = super::SdkMeterProvider::builder()
629 .with_reader(reader.clone())
630 .build();
631 let clone1 = provider.clone();
632 let clone2 = provider.clone();
633
634 assert!(!reader.is_shutdown());
636
637 drop(clone1);
639 assert!(!reader.is_shutdown());
640
641 drop(clone2);
643 assert!(!reader.is_shutdown());
644
645 drop(provider);
647 assert!(reader.is_shutdown());
649 }
650
651 #[test]
652 fn same_meter_reused_same_scope() {
653 let provider = super::SdkMeterProvider::builder().build();
654 let _meter1 = provider.meter("test");
655 let _meter2 = provider.meter("test");
656 assert_eq!(provider.inner.meters.lock().unwrap().len(), 1);
657
658 let scope = InstrumentationScope::builder("test")
659 .with_version("1.0.0")
660 .with_schema_url("http://example.com")
661 .build();
662
663 let _meter3 = provider.meter_with_scope(scope.clone());
664 let _meter4 = provider.meter_with_scope(scope.clone());
665 let _meter5 = provider.meter_with_scope(scope);
666 assert_eq!(provider.inner.meters.lock().unwrap().len(), 2);
667
668 let make_scope = |name| {
670 InstrumentationScope::builder(name)
671 .with_version("1.0.0")
672 .with_schema_url("http://example.com")
673 .build()
674 };
675
676 let _meter6 = provider.meter_with_scope(make_scope("ABC"));
677 let _meter7 = provider.meter_with_scope(make_scope("Abc"));
678 let _meter8 = provider.meter_with_scope(make_scope("abc"));
679
680 assert_eq!(provider.inner.meters.lock().unwrap().len(), 5);
681 }
682
683 #[test]
684 fn same_meter_reused_same_scope_attributes() {
685 let meter_provider = super::SdkMeterProvider::builder().build();
686 let make_scope = |attributes| {
687 InstrumentationScope::builder("test.meter")
688 .with_version("v0.1.0")
689 .with_schema_url("http://example.com")
690 .with_attributes(attributes)
691 .build()
692 };
693
694 let _meter1 =
695 meter_provider.meter_with_scope(make_scope(vec![KeyValue::new("key", "value1")]));
696 let _meter2 =
697 meter_provider.meter_with_scope(make_scope(vec![KeyValue::new("key", "value1")]));
698
699 assert_eq!(meter_provider.inner.meters.lock().unwrap().len(), 1);
700
701 let _meter3 = meter_provider.meter_with_scope(make_scope(vec![
703 KeyValue::new("key1", "value1"),
704 KeyValue::new("key2", "value2"),
705 ]));
706 let _meter4 = meter_provider.meter_with_scope(make_scope(vec![
707 KeyValue::new("key2", "value2"),
708 KeyValue::new("key1", "value1"),
709 ]));
710
711 assert_eq!(meter_provider.inner.meters.lock().unwrap().len(), 2);
712 }
713
714 #[test]
715 fn different_meter_different_attributes() {
716 let meter_provider = super::SdkMeterProvider::builder().build();
717 let make_scope = |attributes| {
718 InstrumentationScope::builder("test.meter")
719 .with_version("v0.1.0")
720 .with_schema_url("http://example.com")
721 .with_attributes(attributes)
722 .build()
723 };
724
725 let _meter1 = meter_provider.meter_with_scope(make_scope(vec![]));
726 let _meter2 =
728 meter_provider.meter_with_scope(make_scope(vec![KeyValue::new("key1", "value1")]));
729 let _meter3 =
730 meter_provider.meter_with_scope(make_scope(vec![KeyValue::new("Key1", "value1")]));
731 let _meter4 =
732 meter_provider.meter_with_scope(make_scope(vec![KeyValue::new("key1", "Value1")]));
733 let _meter5 = meter_provider.meter_with_scope(make_scope(vec![
734 KeyValue::new("key1", "value1"),
735 KeyValue::new("key2", "value2"),
736 ]));
737
738 assert_eq!(meter_provider.inner.meters.lock().unwrap().len(), 5);
739 }
740
741 #[test]
742 fn with_resource_multiple_calls_ensure_additive() {
743 let builder = SdkMeterProvider::builder()
744 .with_resource(
745 Resource::builder_empty()
746 .with_attributes(vec![KeyValue::new("key1", "value1")])
747 .build(),
748 )
749 .with_resource(
750 Resource::builder_empty()
751 .with_attributes(vec![KeyValue::new("key2", "value2")])
752 .build(),
753 )
754 .with_resource(
755 Resource::builder_empty()
756 .with_schema_url(vec![], "http://example.com")
757 .build(),
758 )
759 .with_resource(
760 Resource::builder_empty()
761 .with_attributes(vec![KeyValue::new("key3", "value3")])
762 .build(),
763 );
764
765 let resource = builder.resource.unwrap();
766
767 assert_eq!(
768 resource.get(&Key::from_static_str("key1")),
769 Some(Value::from("value1"))
770 );
771 assert_eq!(
772 resource.get(&Key::from_static_str("key2")),
773 Some(Value::from("value2"))
774 );
775 assert_eq!(
776 resource.get(&Key::from_static_str("key3")),
777 Some(Value::from("value3"))
778 );
779 assert_eq!(resource.schema_url(), Some("http://example.com"));
780 }
781}