opentelemetry_sdk/metrics/
periodic_reader.rs1use std::{
2 env, fmt,
3 sync::{
4 mpsc::{self, Receiver, Sender},
5 Arc, Mutex, Weak,
6 },
7 thread,
8 time::{Duration, Instant},
9};
10
11use opentelemetry::{otel_debug, otel_error, otel_info, otel_warn, Context};
12
13use crate::{
14 error::{OTelSdkError, OTelSdkResult},
15 metrics::{exporter::PushMetricExporter, reader::SdkProducer},
16 Resource,
17};
18
19use super::{
20 data::ResourceMetrics, instrument::InstrumentKind, pipeline::Pipeline, reader::MetricReader,
21 Temporality,
22};
23
24const DEFAULT_INTERVAL: Duration = Duration::from_secs(60);
25
26const METRIC_EXPORT_INTERVAL_NAME: &str = "OTEL_METRIC_EXPORT_INTERVAL";
27
28#[derive(Debug)]
30pub struct PeriodicReaderBuilder<E> {
31 interval: Duration,
32 exporter: E,
33}
34
35impl<E> PeriodicReaderBuilder<E>
36where
37 E: PushMetricExporter,
38{
39 fn new(exporter: E) -> Self {
40 let interval = env::var(METRIC_EXPORT_INTERVAL_NAME)
41 .ok()
42 .and_then(|v| v.parse().map(Duration::from_millis).ok())
43 .unwrap_or(DEFAULT_INTERVAL);
44
45 PeriodicReaderBuilder { interval, exporter }
46 }
47
48 pub fn with_interval(mut self, interval: Duration) -> Self {
56 if !interval.is_zero() {
57 self.interval = interval;
58 }
59 self
60 }
61
62 pub fn build(self) -> PeriodicReader<E> {
64 PeriodicReader::new(self.exporter, self.interval)
65 }
66}
67
68pub struct PeriodicReader<E: PushMetricExporter> {
136 inner: Arc<PeriodicReaderInner<E>>,
137}
138
139impl<E: PushMetricExporter> Clone for PeriodicReader<E> {
140 fn clone(&self) -> Self {
141 Self {
142 inner: Arc::clone(&self.inner),
143 }
144 }
145}
146
147impl<E: PushMetricExporter> PeriodicReader<E> {
148 pub fn builder(exporter: E) -> PeriodicReaderBuilder<E> {
150 PeriodicReaderBuilder::new(exporter)
151 }
152
153 fn new(exporter: E, interval: Duration) -> Self {
154 let (message_sender, message_receiver): (Sender<Message>, Receiver<Message>) =
155 mpsc::channel();
156 let exporter_arc = Arc::new(exporter);
157 let reader = PeriodicReader {
158 inner: Arc::new(PeriodicReaderInner {
159 message_sender,
160 producer: Mutex::new(None),
161 exporter: exporter_arc.clone(),
162 }),
163 };
164 let cloned_reader = reader.clone();
165
166 let mut rm = ResourceMetrics {
167 resource: Resource::empty(),
168 scope_metrics: Vec::new(),
169 };
170
171 let result_thread_creation = thread::Builder::new()
172 .name("OpenTelemetry.Metrics.PeriodicReader".to_string())
173 .spawn(move || {
174 let _suppress_guard = Context::enter_telemetry_suppressed_scope();
175 let mut interval_start = Instant::now();
176 let mut remaining_interval = interval;
177 otel_debug!(
178 name: "PeriodReaderThreadStarted",
179 interval_in_millisecs = interval.as_millis(),
180 );
181 loop {
182 otel_debug!(
183 name: "PeriodReaderThreadLoopAlive", message = "Next export will happen after interval, unless flush or shutdown is triggered.", interval_in_millisecs = remaining_interval.as_millis()
184 );
185 match message_receiver.recv_timeout(remaining_interval) {
186 Ok(Message::Flush(response_sender)) => {
187 otel_debug!(
188 name: "PeriodReaderThreadExportingDueToFlush"
189 );
190 let export_result = cloned_reader.collect_and_export(&mut rm);
191 otel_debug!(
192 name: "PeriodReaderInvokedExport",
193 export_result = format!("{:?}", export_result)
194 );
195
196 if export_result.is_err() {
206 if response_sender.send(false).is_err() {
207 otel_debug!(
208 name: "PeriodReader.Flush.ResponseSendError",
209 message = "PeriodicReader's flush has failed, but unable to send this info back to caller.
210 This occurs when the caller has timed out waiting for the response. If you see this occuring frequently, consider increasing the flush timeout."
211 );
212 }
213 } else if response_sender.send(true).is_err() {
214 otel_debug!(
215 name: "PeriodReader.Flush.ResponseSendError",
216 message = "PeriodicReader's flush has completed successfully, but unable to send this info back to caller.
217 This occurs when the caller has timed out waiting for the response. If you see this occuring frequently, consider increasing the flush timeout."
218 );
219 }
220
221 let elapsed = interval_start.elapsed();
223 if elapsed < interval {
224 remaining_interval = interval - elapsed;
225 otel_debug!(
226 name: "PeriodReaderThreadAdjustingRemainingIntervalAfterFlush",
227 remaining_interval = remaining_interval.as_secs()
228 );
229 } else {
230 otel_debug!(
231 name: "PeriodReaderThreadAdjustingExportAfterFlush",
232 );
233 interval_start = Instant::now();
240 remaining_interval = Duration::ZERO;
241 }
242 }
243 Ok(Message::Shutdown(response_sender)) => {
244 otel_debug!(name: "PeriodReaderThreadExportingDueToShutdown");
246 let export_result = cloned_reader.collect_and_export(&mut rm);
247 otel_debug!(
248 name: "PeriodReaderInvokedExport",
249 export_result = format!("{:?}", export_result)
250 );
251 let shutdown_result = exporter_arc.shutdown();
252 otel_debug!(
253 name: "PeriodReaderInvokedExporterShutdown",
254 shutdown_result = format!("{:?}", shutdown_result)
255 );
256
257 if export_result.is_err() || shutdown_result.is_err() {
267 if response_sender.send(false).is_err() {
268 otel_info!(
269 name: "PeriodReaderThreadShutdown.ResponseSendError",
270 message = "PeriodicReader's shutdown has failed, but unable to send this info back to caller.
271 This occurs when the caller has timed out waiting for the response. If you see this occuring frequently, consider increasing the shutdown timeout."
272 );
273 }
274 } else if response_sender.send(true).is_err() {
275 otel_debug!(
276 name: "PeriodReaderThreadShutdown.ResponseSendError",
277 message = "PeriodicReader completed its shutdown, but unable to send this info back to caller.
278 This occurs when the caller has timed out waiting for the response. If you see this occuring frequently, consider increasing the shutdown timeout."
279 );
280 }
281
282 otel_debug!(
283 name: "PeriodReaderThreadExiting",
284 reason = "ShutdownRequested"
285 );
286 break;
287 }
288 Err(mpsc::RecvTimeoutError::Timeout) => {
289 let export_start = Instant::now();
290 otel_debug!(
291 name: "PeriodReaderThreadExportingDueToTimer"
292 );
293
294 let export_result = cloned_reader.collect_and_export(&mut rm);
295 otel_debug!(
296 name: "PeriodReaderInvokedExport",
297 export_result = format!("{:?}", export_result)
298 );
299
300 let time_taken_for_export = export_start.elapsed();
301 if time_taken_for_export > interval {
302 otel_debug!(
303 name: "PeriodReaderThreadExportTookLongerThanInterval"
304 );
305 interval_start = Instant::now();
312 remaining_interval = Duration::ZERO;
313 } else {
314 remaining_interval = interval - time_taken_for_export;
315 interval_start = Instant::now();
316 }
317 }
318 Err(mpsc::RecvTimeoutError::Disconnected) => {
319 otel_debug!(
322 name: "PeriodReaderThreadExiting",
323 reason = "MessageSenderDisconnected"
324 );
325 break;
326 }
327 }
328 }
329 otel_debug!(
330 name: "PeriodReaderThreadStopped"
331 );
332 });
333
334 #[allow(unused_variables)]
336 if let Err(e) = result_thread_creation {
337 otel_error!(
338 name: "PeriodReaderThreadStartError",
339 message = "Failed to start PeriodicReader thread. Metrics will not be exported.",
340 error = format!("{:?}", e)
341 );
342 }
343 reader
344 }
345
346 fn collect_and_export(&self, rm: &mut ResourceMetrics) -> OTelSdkResult {
347 self.inner.collect_and_export(rm)
348 }
349}
350
351impl<E: PushMetricExporter> fmt::Debug for PeriodicReader<E> {
352 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
353 f.debug_struct("PeriodicReader").finish()
354 }
355}
356
357struct PeriodicReaderInner<E: PushMetricExporter> {
358 exporter: Arc<E>,
359 message_sender: mpsc::Sender<Message>,
360 producer: Mutex<Option<Weak<dyn SdkProducer>>>,
361}
362
363impl<E: PushMetricExporter> PeriodicReaderInner<E> {
364 fn register_pipeline(&self, producer: Weak<dyn SdkProducer>) {
365 let mut inner = self.producer.lock().expect("lock poisoned");
366 *inner = Some(producer);
367 }
368
369 fn temporality(&self, _kind: InstrumentKind) -> Temporality {
370 self.exporter.temporality()
371 }
372
373 fn collect(&self, rm: &mut ResourceMetrics) -> OTelSdkResult {
374 let producer = self.producer.lock().expect("lock poisoned");
375 if let Some(p) = producer.as_ref() {
376 p.upgrade()
377 .ok_or(OTelSdkError::AlreadyShutdown)?
378 .produce(rm)?;
379 Ok(())
380 } else {
381 otel_warn!(
382 name: "PeriodReader.MeterProviderNotRegistered",
383 message = "PeriodicReader is not registered with MeterProvider. Metrics will not be collected. \
384 This occurs when a periodic reader is created but not associated with a MeterProvider \
385 by calling `.with_reader(reader)` on MeterProviderBuilder."
386 );
387 Err(OTelSdkError::InternalFailure(
388 "MeterProvider is not registered".into(),
389 ))
390 }
391 }
392
393 fn collect_and_export(&self, rm: &mut ResourceMetrics) -> OTelSdkResult {
394 let current_time = Instant::now();
395 let collect_result = self.collect(rm);
396 let time_taken_for_collect = current_time.elapsed();
397
398 #[allow(clippy::question_mark)]
399 if let Err(e) = collect_result {
400 otel_warn!(
401 name: "PeriodReaderCollectError",
402 error = format!("{:?}", e)
403 );
404 return Err(OTelSdkError::InternalFailure(e.to_string()));
405 }
406
407 if rm.scope_metrics.is_empty() {
408 otel_debug!(name: "NoMetricsCollected");
409 return Ok(());
410 }
411
412 let metrics_count = rm.scope_metrics.iter().fold(0, |count, scope_metrics| {
413 count + scope_metrics.metrics.len()
414 });
415 otel_debug!(name: "PeriodicReaderMetricsCollected", count = metrics_count, time_taken_in_millis = time_taken_for_collect.as_millis());
416
417 futures_executor::block_on(self.exporter.export(rm))
420 }
421
422 fn force_flush(&self) -> OTelSdkResult {
423 let (response_tx, response_rx) = mpsc::channel();
437 self.message_sender
438 .send(Message::Flush(response_tx))
439 .map_err(|e| OTelSdkError::InternalFailure(e.to_string()))?;
440
441 if let Ok(response) = response_rx.recv() {
442 if response {
444 Ok(())
445 } else {
446 Err(OTelSdkError::InternalFailure("Failed to flush".into()))
447 }
448 } else {
449 Err(OTelSdkError::InternalFailure("Failed to flush".into()))
450 }
451 }
452
453 fn shutdown(&self) -> OTelSdkResult {
454 let (response_tx, response_rx) = mpsc::channel();
456 self.message_sender
457 .send(Message::Shutdown(response_tx))
458 .map_err(|e| OTelSdkError::InternalFailure(e.to_string()))?;
459
460 match response_rx.recv_timeout(Duration::from_secs(5)) {
462 Ok(response) => {
463 if response {
464 Ok(())
465 } else {
466 Err(OTelSdkError::InternalFailure("Failed to shutdown".into()))
467 }
468 }
469 Err(mpsc::RecvTimeoutError::Timeout) => {
470 Err(OTelSdkError::Timeout(Duration::from_secs(5)))
471 }
472 Err(mpsc::RecvTimeoutError::Disconnected) => {
473 Err(OTelSdkError::InternalFailure("Failed to shutdown".into()))
474 }
475 }
476 }
477}
478
479#[derive(Debug)]
480enum Message {
481 Flush(Sender<bool>),
482 Shutdown(Sender<bool>),
483}
484
485impl<E: PushMetricExporter> MetricReader for PeriodicReader<E> {
486 fn register_pipeline(&self, pipeline: Weak<Pipeline>) {
487 self.inner.register_pipeline(pipeline);
488 }
489
490 fn collect(&self, rm: &mut ResourceMetrics) -> OTelSdkResult {
491 self.inner.collect(rm)
492 }
493
494 fn force_flush(&self) -> OTelSdkResult {
495 self.inner.force_flush()
496 }
497
498 fn shutdown_with_timeout(&self, _timeout: Duration) -> OTelSdkResult {
503 self.inner.shutdown()
504 }
505
506 fn temporality(&self, kind: InstrumentKind) -> Temporality {
514 kind.temporality_preference(self.inner.temporality(kind))
515 }
516}
517
518#[cfg(all(test, feature = "testing"))]
519mod tests {
520 use super::PeriodicReader;
521 use crate::{
522 error::{OTelSdkError, OTelSdkResult},
523 metrics::{
524 data::ResourceMetrics, exporter::PushMetricExporter, reader::MetricReader,
525 InMemoryMetricExporter, SdkMeterProvider, Temporality,
526 },
527 Resource,
528 };
529 use opentelemetry::metrics::MeterProvider;
530 use std::{
531 sync::{
532 atomic::{AtomicBool, AtomicUsize, Ordering},
533 mpsc, Arc,
534 },
535 time::Duration,
536 };
537
538 #[derive(Debug, Clone)]
542 struct MetricExporterThatFailsOnlyOnFirst {
543 count: Arc<AtomicUsize>,
544 }
545
546 impl Default for MetricExporterThatFailsOnlyOnFirst {
547 fn default() -> Self {
548 MetricExporterThatFailsOnlyOnFirst {
549 count: Arc::new(AtomicUsize::new(0)),
550 }
551 }
552 }
553
554 impl MetricExporterThatFailsOnlyOnFirst {
555 fn get_count(&self) -> usize {
556 self.count.load(Ordering::Relaxed)
557 }
558 }
559
560 impl PushMetricExporter for MetricExporterThatFailsOnlyOnFirst {
561 async fn export(&self, _metrics: &ResourceMetrics) -> OTelSdkResult {
562 if self.count.fetch_add(1, Ordering::Relaxed) == 0 {
563 Err(OTelSdkError::InternalFailure("export failed".into()))
564 } else {
565 Ok(())
566 }
567 }
568
569 fn force_flush(&self) -> OTelSdkResult {
570 Ok(())
571 }
572
573 fn shutdown(&self) -> OTelSdkResult {
574 Ok(())
575 }
576
577 fn shutdown_with_timeout(&self, _timeout: Duration) -> OTelSdkResult {
578 Ok(())
579 }
580
581 fn temporality(&self) -> Temporality {
582 Temporality::Cumulative
583 }
584 }
585
586 #[derive(Debug, Clone, Default)]
587 struct MockMetricExporter {
588 is_shutdown: Arc<AtomicBool>,
589 }
590
591 impl PushMetricExporter for MockMetricExporter {
592 async fn export(&self, _metrics: &ResourceMetrics) -> OTelSdkResult {
593 Ok(())
594 }
595
596 fn force_flush(&self) -> OTelSdkResult {
597 Ok(())
598 }
599
600 fn shutdown(&self) -> OTelSdkResult {
601 self.shutdown_with_timeout(Duration::from_secs(5))
602 }
603
604 fn shutdown_with_timeout(&self, _timeout: Duration) -> OTelSdkResult {
605 self.is_shutdown.store(true, Ordering::Relaxed);
606 Ok(())
607 }
608
609 fn temporality(&self) -> Temporality {
610 Temporality::Cumulative
611 }
612 }
613
614 #[test]
615 fn collection_triggered_by_interval_multiple() {
616 let interval = std::time::Duration::from_millis(1);
618 let exporter = InMemoryMetricExporter::default();
619 let reader = PeriodicReader::builder(exporter.clone())
620 .with_interval(interval)
621 .build();
622 let i = Arc::new(AtomicUsize::new(0));
623 let i_clone = i.clone();
624
625 let meter_provider = SdkMeterProvider::builder().with_reader(reader).build();
627 let meter = meter_provider.meter("test");
628 let _counter = meter
629 .u64_observable_counter("testcounter")
630 .with_callback(move |_| {
631 i_clone.fetch_add(1, Ordering::Relaxed);
632 })
633 .build();
634
635 std::thread::sleep(interval * 5 * 20);
641
642 assert!(i.load(Ordering::Relaxed) >= 5);
644 }
645
646 #[test]
647 fn shutdown_repeat() {
648 let exporter = InMemoryMetricExporter::default();
650 let reader = PeriodicReader::builder(exporter.clone()).build();
651
652 let meter_provider = SdkMeterProvider::builder().with_reader(reader).build();
653 let result = meter_provider.shutdown();
654 assert!(result.is_ok());
655
656 let result = meter_provider.shutdown();
658 assert!(result.is_err());
659 assert!(matches!(result, Err(OTelSdkError::AlreadyShutdown)));
660
661 let result = meter_provider.shutdown();
663 assert!(result.is_err());
664 assert!(matches!(result, Err(OTelSdkError::AlreadyShutdown)));
665 }
666
667 #[test]
668 fn flush_after_shutdown() {
669 let exporter = InMemoryMetricExporter::default();
671 let reader = PeriodicReader::builder(exporter.clone()).build();
672
673 let meter_provider = SdkMeterProvider::builder().with_reader(reader).build();
674 let result = meter_provider.force_flush();
675 assert!(result.is_ok());
676
677 let result = meter_provider.shutdown();
678 assert!(result.is_ok());
679
680 let result = meter_provider.force_flush();
682 assert!(result.is_err());
683 }
684
685 #[test]
686 fn flush_repeat() {
687 let exporter = InMemoryMetricExporter::default();
689 let reader = PeriodicReader::builder(exporter.clone()).build();
690
691 let meter_provider = SdkMeterProvider::builder().with_reader(reader).build();
692 let result = meter_provider.force_flush();
693 assert!(result.is_ok());
694
695 let result = meter_provider.force_flush();
697 assert!(result.is_ok());
698 }
699
700 #[test]
701 fn periodic_reader_without_pipeline() {
702 let exporter = InMemoryMetricExporter::default();
704 let reader = PeriodicReader::builder(exporter.clone()).build();
705
706 let rm = &mut ResourceMetrics {
707 resource: Resource::empty(),
708 scope_metrics: Vec::new(),
709 };
710 let result = reader.collect(rm);
712 assert!(result.is_err());
713
714 let result = reader.force_flush();
716 assert!(result.is_err());
717
718 let meter_provider = SdkMeterProvider::builder()
721 .with_reader(reader.clone())
722 .build();
723
724 let result = reader.collect(rm);
726 assert!(result.is_ok());
727
728 let result = meter_provider.force_flush();
729 assert!(result.is_ok());
730 }
731
732 #[test]
733 fn exporter_failures_are_handled() {
734 let interval = std::time::Duration::from_millis(10);
739 let exporter = MetricExporterThatFailsOnlyOnFirst::default();
740 let reader = PeriodicReader::builder(exporter.clone())
741 .with_interval(interval)
742 .build();
743
744 let meter_provider = SdkMeterProvider::builder().with_reader(reader).build();
745 let meter = meter_provider.meter("test");
746 let counter = meter.u64_counter("sync_counter").build();
747 counter.add(1, &[]);
748 let _obs_counter = meter
749 .u64_observable_counter("testcounter")
750 .with_callback(move |observer| {
751 observer.observe(1, &[]);
752 })
753 .build();
754
755 std::thread::sleep(Duration::from_millis(500));
761
762 assert!(exporter.get_count() >= 2);
764 }
765
766 #[test]
767 fn shutdown_passed_to_exporter() {
768 let exporter = MockMetricExporter::default();
770 let reader = PeriodicReader::builder(exporter.clone()).build();
771
772 let meter_provider = SdkMeterProvider::builder().with_reader(reader).build();
773 let meter = meter_provider.meter("test");
774 let counter = meter.u64_counter("sync_counter").build();
775 counter.add(1, &[]);
776
777 let result = meter_provider.shutdown();
780 assert!(result.is_ok());
781 assert!(exporter.is_shutdown.load(Ordering::Relaxed));
782 }
783
784 #[test]
785 fn collection() {
786 collection_triggered_by_interval_helper();
787 collection_triggered_by_flush_helper();
788 collection_triggered_by_shutdown_helper();
789 collection_triggered_by_drop_helper();
790 }
791
792 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
793 async fn collection_from_tokio_multi_with_one_worker() {
794 collection_triggered_by_interval_helper();
795 collection_triggered_by_flush_helper();
796 collection_triggered_by_shutdown_helper();
797 collection_triggered_by_drop_helper();
798 }
799
800 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
801 async fn collection_from_tokio_with_two_worker() {
802 collection_triggered_by_interval_helper();
803 collection_triggered_by_flush_helper();
804 collection_triggered_by_shutdown_helper();
805 collection_triggered_by_drop_helper();
806 }
807
808 #[tokio::test(flavor = "current_thread")]
809 async fn collection_from_tokio_current() {
810 collection_triggered_by_interval_helper();
811 collection_triggered_by_flush_helper();
812 collection_triggered_by_shutdown_helper();
813 collection_triggered_by_drop_helper();
814 }
815
816 fn collection_triggered_by_interval_helper() {
817 collection_helper(|_| {
818 std::thread::sleep(Duration::from_millis(500));
823 });
824 }
825
826 fn collection_triggered_by_flush_helper() {
827 collection_helper(|meter_provider| {
828 meter_provider.force_flush().expect("flush should succeed");
829 });
830 }
831
832 fn collection_triggered_by_shutdown_helper() {
833 collection_helper(|meter_provider| {
834 meter_provider.shutdown().expect("shutdown should succeed");
835 });
836 }
837
838 fn collection_triggered_by_drop_helper() {
839 collection_helper(|meter_provider| {
840 drop(meter_provider);
841 });
842 }
843
844 fn collection_helper(trigger: fn(SdkMeterProvider)) {
845 let exporter = InMemoryMetricExporter::default();
847 let reader = PeriodicReader::builder(exporter.clone()).build();
848 let (sender, receiver) = mpsc::channel();
849
850 let meter_provider = SdkMeterProvider::builder().with_reader(reader).build();
851 let meter = meter_provider.meter("test");
852 let _counter = meter
853 .u64_observable_counter("testcounter")
854 .with_callback(move |observer| {
855 observer.observe(1, &[]);
856 sender.send(()).expect("channel should still be open");
857 })
858 .build();
859
860 trigger(meter_provider);
862
863 receiver
865 .recv_timeout(Duration::ZERO)
866 .expect("message should be available in channel, indicating a collection occurred, which should trigger observable callback");
867
868 let exported_metrics = exporter
869 .get_finished_metrics()
870 .expect("this should not fail");
871 assert!(
872 !exported_metrics.is_empty(),
873 "Metrics should be available in exporter."
874 );
875 }
876
877 async fn some_async_function() -> u64 {
878 std::thread::sleep(std::time::Duration::from_millis(1));
880 1
881 }
882
883 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
884 async fn async_inside_observable_callback_from_tokio_multi_with_one_worker() {
885 async_inside_observable_callback_helper();
886 }
887
888 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
889 async fn async_inside_observable_callback_from_tokio_multi_with_two_worker() {
890 async_inside_observable_callback_helper();
891 }
892
893 #[tokio::test(flavor = "current_thread")]
894 async fn async_inside_observable_callback_from_tokio_current_thread() {
895 async_inside_observable_callback_helper();
896 }
897
898 #[test]
899 fn async_inside_observable_callback_from_regular_main() {
900 async_inside_observable_callback_helper();
901 }
902
903 fn async_inside_observable_callback_helper() {
904 let interval = std::time::Duration::from_millis(10);
905 let exporter = InMemoryMetricExporter::default();
906 let reader = PeriodicReader::builder(exporter.clone())
907 .with_interval(interval)
908 .build();
909
910 let meter_provider = SdkMeterProvider::builder().with_reader(reader).build();
911 let meter = meter_provider.meter("test");
912 let _gauge = meter
913 .u64_observable_gauge("my_observable_gauge")
914 .with_callback(|observer| {
915 let value = futures_executor::block_on(some_async_function());
918 observer.observe(value, &[]);
919 })
920 .build();
921
922 meter_provider.force_flush().expect("flush should succeed");
923 let exported_metrics = exporter
924 .get_finished_metrics()
925 .expect("this should not fail");
926 assert!(
927 !exported_metrics.is_empty(),
928 "Metrics should be available in exporter."
929 );
930 }
931
932 async fn some_tokio_async_function() -> u64 {
933 tokio::time::sleep(Duration::from_millis(1)).await;
935 1
936 }
937
938 #[tokio::test(flavor = "multi_thread", worker_threads = 1)]
939
940 async fn tokio_async_inside_observable_callback_from_tokio_multi_with_one_worker() {
941 tokio_async_inside_observable_callback_helper(true);
942 }
943
944 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
945 async fn tokio_async_inside_observable_callback_from_tokio_multi_with_two_worker() {
946 tokio_async_inside_observable_callback_helper(true);
947 }
948
949 #[tokio::test(flavor = "current_thread")]
950 #[ignore] async fn tokio_async_inside_observable_callback_from_tokio_current_thread() {
952 tokio_async_inside_observable_callback_helper(true);
953 }
954
955 #[test]
956 fn tokio_async_inside_observable_callback_from_regular_main() {
957 tokio_async_inside_observable_callback_helper(false);
958 }
959
960 fn tokio_async_inside_observable_callback_helper(use_current_tokio_runtime: bool) {
961 let exporter = InMemoryMetricExporter::default();
962 let reader = PeriodicReader::builder(exporter.clone()).build();
963
964 let meter_provider = SdkMeterProvider::builder().with_reader(reader).build();
965 let meter = meter_provider.meter("test");
966
967 if use_current_tokio_runtime {
968 let rt = tokio::runtime::Handle::current().clone();
969 let _gauge = meter
970 .u64_observable_gauge("my_observable_gauge")
971 .with_callback(move |observer| {
972 let value = rt.block_on(some_tokio_async_function());
974 observer.observe(value, &[]);
975 })
976 .build();
977 } else {
980 let rt = tokio::runtime::Runtime::new().unwrap();
981 let _gauge = meter
982 .u64_observable_gauge("my_observable_gauge")
983 .with_callback(move |observer| {
984 let value = rt.block_on(some_tokio_async_function());
986 observer.observe(value, &[]);
987 })
988 .build();
989 };
993
994 meter_provider.force_flush().expect("flush should succeed");
995 let exported_metrics = exporter
996 .get_finished_metrics()
997 .expect("this should not fail");
998 assert!(
999 !exported_metrics.is_empty(),
1000 "Metrics should be available in exporter."
1001 );
1002 }
1003}