diff --git a/opentelemetry-otlp/src/exporter/http/metrics.rs b/opentelemetry-otlp/src/exporter/http/metrics.rs index b7b7f0363a..454ea03f3c 100644 --- a/opentelemetry-otlp/src/exporter/http/metrics.rs +++ b/opentelemetry-otlp/src/exporter/http/metrics.rs @@ -4,12 +4,12 @@ use crate::metric::MetricsClient; use http::{header::CONTENT_TYPE, Method}; use opentelemetry::otel_debug; use opentelemetry_sdk::error::{OTelSdkError, OTelSdkResult}; -use opentelemetry_sdk::metrics::data::ResourceMetrics; +use opentelemetry_sdk::metrics::exporter::ResourceMetrics; use super::OtlpHttpClient; impl MetricsClient for OtlpHttpClient { - async fn export(&self, metrics: &ResourceMetrics) -> OTelSdkResult { + async fn export(&self, metrics: ResourceMetrics<'_>) -> OTelSdkResult { let client = self .client .lock() diff --git a/opentelemetry-otlp/src/exporter/http/mod.rs b/opentelemetry-otlp/src/exporter/http/mod.rs index 72defd081e..90481ed962 100644 --- a/opentelemetry-otlp/src/exporter/http/mod.rs +++ b/opentelemetry-otlp/src/exporter/http/mod.rs @@ -27,7 +27,7 @@ use std::time::Duration; mod metrics; #[cfg(feature = "metrics")] -use opentelemetry_sdk::metrics::data::ResourceMetrics; +use opentelemetry_sdk::metrics::exporter::ResourceMetrics; #[cfg(feature = "logs")] pub(crate) mod logs; @@ -326,7 +326,7 @@ impl OtlpHttpClient { #[cfg(feature = "metrics")] fn build_metrics_export_body( &self, - metrics: &ResourceMetrics, + metrics: ResourceMetrics<'_>, ) -> Option<(Vec, &'static str)> { use opentelemetry_proto::tonic::collector::metrics::v1::ExportMetricsServiceRequest; diff --git a/opentelemetry-otlp/src/exporter/tonic/metrics.rs b/opentelemetry-otlp/src/exporter/tonic/metrics.rs index 1d760bac76..48bfd8f2bd 100644 --- a/opentelemetry-otlp/src/exporter/tonic/metrics.rs +++ b/opentelemetry-otlp/src/exporter/tonic/metrics.rs @@ -6,7 +6,7 @@ use opentelemetry_proto::tonic::collector::metrics::v1::{ metrics_service_client::MetricsServiceClient, ExportMetricsServiceRequest, }; use opentelemetry_sdk::error::{OTelSdkError, OTelSdkResult}; -use opentelemetry_sdk::metrics::data::ResourceMetrics; +use opentelemetry_sdk::metrics::exporter::ResourceMetrics; use tonic::{codegen::CompressionEncoding, service::Interceptor, transport::Channel, Request}; use super::BoxInterceptor; @@ -52,7 +52,7 @@ impl TonicMetricsClient { } impl MetricsClient for TonicMetricsClient { - async fn export(&self, metrics: &ResourceMetrics) -> OTelSdkResult { + async fn export(&self, metrics: ResourceMetrics<'_>) -> OTelSdkResult { let (mut client, metadata, extensions) = self .inner .lock() diff --git a/opentelemetry-otlp/src/metric.rs b/opentelemetry-otlp/src/metric.rs index 9cfb6d3788..5a4c7398e0 100644 --- a/opentelemetry-otlp/src/metric.rs +++ b/opentelemetry-otlp/src/metric.rs @@ -17,9 +17,8 @@ use crate::{ExporterBuildError, NoExporterBuilderSet}; use core::fmt; use opentelemetry_sdk::error::OTelSdkResult; -use opentelemetry_sdk::metrics::{ - data::ResourceMetrics, exporter::PushMetricExporter, Temporality, -}; +use opentelemetry_sdk::metrics::exporter::ResourceMetrics; +use opentelemetry_sdk::metrics::{exporter::PushMetricExporter, Temporality}; use std::fmt::{Debug, Formatter}; use std::time::Duration; @@ -123,7 +122,7 @@ impl HasHttpConfig for MetricExporterBuilder { pub(crate) trait MetricsClient: fmt::Debug + Send + Sync + 'static { fn export( &self, - metrics: &ResourceMetrics, + metrics: ResourceMetrics<'_>, ) -> impl std::future::Future + Send; fn shutdown(&self) -> OTelSdkResult; } @@ -149,7 +148,7 @@ impl Debug for MetricExporter { } impl PushMetricExporter for MetricExporter { - async fn export(&self, metrics: &ResourceMetrics) -> OTelSdkResult { + async fn export(&self, metrics: ResourceMetrics<'_>) -> OTelSdkResult { match &self.client { #[cfg(feature = "grpc-tonic")] SupportedTransportClient::Tonic(client) => client.export(metrics).await, diff --git a/opentelemetry-proto/src/transform/metrics.rs b/opentelemetry-proto/src/transform/metrics.rs index c040898ec3..22f54fd691 100644 --- a/opentelemetry-proto/src/transform/metrics.rs +++ b/opentelemetry-proto/src/transform/metrics.rs @@ -11,9 +11,9 @@ pub mod tonic { use opentelemetry_sdk::metrics::data::{ AggregatedMetrics, Exemplar as SdkExemplar, ExponentialHistogram as SdkExponentialHistogram, Gauge as SdkGauge, - Histogram as SdkHistogram, Metric as SdkMetric, MetricData, ResourceMetrics, - ScopeMetrics as SdkScopeMetrics, Sum as SdkSum, + Histogram as SdkHistogram, MetricData, Sum as SdkSum, }; + use opentelemetry_sdk::metrics::exporter::{Metric, ResourceMetrics, ScopeMetrics}; use opentelemetry_sdk::metrics::Temporality; use opentelemetry_sdk::Resource as SdkResource; @@ -110,12 +110,18 @@ pub mod tonic { } } - impl From<&ResourceMetrics> for ExportMetricsServiceRequest { - fn from(rm: &ResourceMetrics) -> Self { + impl From> for ExportMetricsServiceRequest { + fn from(rm: ResourceMetrics<'_>) -> Self { + let mut scope_metrics = Vec::new(); + rm.scope_metrics.collect(|mut iter| { + while let Some(scope_metric) = iter.next_scope_metrics() { + scope_metrics.push(scope_metric.into()); + } + }); ExportMetricsServiceRequest { resource_metrics: vec![TonicResourceMetrics { - resource: Some((&rm.resource).into()), - scope_metrics: rm.scope_metrics.iter().map(Into::into).collect(), + resource: Some(rm.resource.into()), + scope_metrics, schema_url: rm.resource.schema_url().map(Into::into).unwrap_or_default(), }], } @@ -131,11 +137,15 @@ pub mod tonic { } } - impl From<&SdkScopeMetrics> for TonicScopeMetrics { - fn from(sm: &SdkScopeMetrics) -> Self { + impl From> for TonicScopeMetrics { + fn from(mut sm: ScopeMetrics<'_>) -> Self { + let mut metrics = Vec::new(); + while let Some(metric) = sm.metrics.next_metric() { + metrics.push(metric.into()); + } TonicScopeMetrics { - scope: Some((&sm.scope, None).into()), - metrics: sm.metrics.iter().map(Into::into).collect(), + scope: Some((sm.scope, None).into()), + metrics, schema_url: sm .scope .schema_url() @@ -145,12 +155,12 @@ pub mod tonic { } } - impl From<&SdkMetric> for TonicMetric { - fn from(metric: &SdkMetric) -> Self { + impl From> for TonicMetric { + fn from(metric: Metric<'_>) -> Self { TonicMetric { - name: metric.name.to_string(), - description: metric.description.to_string(), - unit: metric.unit.to_string(), + name: metric.instrument.name.to_string(), + description: metric.instrument.description.to_string(), + unit: metric.instrument.unit.to_string(), metadata: vec![], // internal and currently unused data: Some(match &metric.data { AggregatedMetrics::F64(data) => data.into(), diff --git a/opentelemetry-sdk/CHANGELOG.md b/opentelemetry-sdk/CHANGELOG.md index 9b1c8ecdf6..aa41c3ddbb 100644 --- a/opentelemetry-sdk/CHANGELOG.md +++ b/opentelemetry-sdk/CHANGELOG.md @@ -2,6 +2,15 @@ ## vNext +- *Breaking* change for `PushMetricExporter::export` from accepting + `metrics: &ResourceMetrics`, to accepting `metrics: ResourceMetrics<'_>`. + In addition, `ResourceMetrics` was also changed to allow improving underlying + metric collection without any allocations in the future. + [#2957](https://github.com/open-telemetry/opentelemetry-rust/pull/2957) +- *Breaking* change for `Metric::data` field: From dynamic `Box` + to new enum `AggregatedMetrics`. + [#2857](https://github.com/open-telemetry/opentelemetry-rust/pull/2857) + - **Feature**: Added context based telemetry suppression. [#2868](https://github.com/open-telemetry/opentelemetry-rust/pull/2868) - `SdkLogger`, `SdkTracer` modified to respect telemetry suppression based on `Context`. In other words, if the current context has telemetry suppression @@ -48,6 +57,7 @@ also modified to suppress telemetry before invoking exporters. - The `export` method on `PushMetricExporter` now accepts `&ResourceMetrics` instead of `&mut ResourceMetrics`. + ## 0.29.0 Released 2025-Mar-21 diff --git a/opentelemetry-sdk/benches/metric.rs b/opentelemetry-sdk/benches/metric.rs index 03208542b1..4d50cb0ab7 100644 --- a/opentelemetry-sdk/benches/metric.rs +++ b/opentelemetry-sdk/benches/metric.rs @@ -6,8 +6,10 @@ use opentelemetry::{ use opentelemetry_sdk::{ error::OTelSdkResult, metrics::{ - data::ResourceMetrics, new_view, reader::MetricReader, Aggregation, Instrument, - InstrumentKind, ManualReader, Pipeline, SdkMeterProvider, Stream, Temporality, View, + new_view, + reader::{MetricReader, ResourceMetricsData}, + Aggregation, Instrument, InstrumentKind, ManualReader, Pipeline, SdkMeterProvider, Stream, + Temporality, View, }, Resource, }; @@ -23,7 +25,7 @@ impl MetricReader for SharedReader { self.0.register_pipeline(pipeline) } - fn collect(&self, rm: &mut ResourceMetrics) -> OTelSdkResult { + fn collect(&self, rm: &mut ResourceMetricsData) -> OTelSdkResult { self.0.collect(rm) } @@ -240,7 +242,7 @@ fn counters(c: &mut Criterion) { }); let (rdr, cntr) = bench_counter(None, "cumulative"); - let mut rm = ResourceMetrics { + let mut rm = ResourceMetricsData { resource: Resource::builder_empty().build(), scope_metrics: Vec::new(), }; @@ -337,7 +339,7 @@ fn benchmark_collect_histogram(b: &mut Bencher, n: usize) { h.record(1, &[]); } - let mut rm = ResourceMetrics { + let mut rm = ResourceMetricsData { resource: Resource::builder_empty().build(), scope_metrics: Vec::new(), }; diff --git a/opentelemetry-sdk/src/metrics/data/mod.rs b/opentelemetry-sdk/src/metrics/data/mod.rs index 819d2ef5fe..f3dc09abc9 100644 --- a/opentelemetry-sdk/src/metrics/data/mod.rs +++ b/opentelemetry-sdk/src/metrics/data/mod.rs @@ -1,46 +1,11 @@ //! Types for delivery of pre-aggregated metric time series data. -use std::{borrow::Cow, time::SystemTime}; +use std::time::SystemTime; -use opentelemetry::{InstrumentationScope, KeyValue}; - -use crate::Resource; +use opentelemetry::KeyValue; use super::Temporality; -/// A collection of [ScopeMetrics] and the associated [Resource] that created them. -#[derive(Debug)] -pub struct ResourceMetrics { - /// The entity that collected the metrics. - pub resource: Resource, - /// The collection of metrics with unique [InstrumentationScope]s. - pub scope_metrics: Vec, -} - -/// A collection of metrics produced by a meter. -#[derive(Default, Debug)] -pub struct ScopeMetrics { - /// The [InstrumentationScope] that the meter was created with. - pub scope: InstrumentationScope, - /// The list of aggregations created by the meter. - pub metrics: Vec, -} - -/// A collection of one or more aggregated time series from an [Instrument]. -/// -/// [Instrument]: crate::metrics::Instrument -#[derive(Debug)] -pub struct Metric { - /// The name of the instrument that created this data. - pub name: Cow<'static, str>, - /// The description of the instrument, which can be used in documentation. - pub description: Cow<'static, str>, - /// The unit in which the instrument reports. - pub unit: Cow<'static, str>, - /// The aggregated data from an instrument. - pub data: AggregatedMetrics, -} - /// Aggregated metrics data from an instrument #[derive(Debug)] pub enum AggregatedMetrics { diff --git a/opentelemetry-sdk/src/metrics/exporter.rs b/opentelemetry-sdk/src/metrics/exporter.rs index c1280db7be..54a6e2bf6d 100644 --- a/opentelemetry-sdk/src/metrics/exporter.rs +++ b/opentelemetry-sdk/src/metrics/exporter.rs @@ -1,11 +1,116 @@ //! Interfaces for exporting metrics -use crate::error::OTelSdkResult; -use std::time::Duration; +use opentelemetry::InstrumentationScope; -use crate::metrics::data::ResourceMetrics; +use crate::{error::OTelSdkResult, Resource}; +use std::{fmt::Debug, slice::Iter, time::Duration}; -use super::Temporality; +use super::{ + data::AggregatedMetrics, + reader::{ResourceMetricsData, ScopeMetricsData}, + InstrumentInfo, Temporality, +}; + +/// Stores borrowed metrics and provide a way to collect them +#[derive(Debug)] +pub struct ScopeMetricsCollector<'a> { + iter: ScopeMetricsLendingIter<'a>, +} + +impl ScopeMetricsCollector<'_> { + /// Start collecting all metrics + pub fn collect(self, process: impl FnOnce(ScopeMetricsLendingIter<'_>)) { + process(self.iter) + } +} + +/// A collection of [`ScopeMetricsCollector`] and the associated [Resource] that created them. +#[derive(Debug)] +pub struct ResourceMetrics<'a> { + /// The entity that collected the metrics. + pub resource: &'a Resource, + /// The collection of metrics with unique [InstrumentationScope]s. + pub scope_metrics: ScopeMetricsCollector<'a>, +} + +/// Iterator over libraries instrumentation scopes ([`InstrumentationScope`]) together with metrics. +/// Doesn't implement standard [`Iterator`], because it returns borrowed items. AKA "LendingIterator". +pub struct ScopeMetricsLendingIter<'a> { + iter: Iter<'a, ScopeMetricsData>, +} + +/// A collection of metrics produced by a [`InstrumentationScope`] meter. +#[derive(Debug)] +pub struct ScopeMetrics<'a> { + /// The [InstrumentationScope] that the meter was created with. + pub scope: &'a InstrumentationScope, + /// The list of aggregations created by the meter. + pub metrics: MetricsLendingIter<'a>, +} + +/// Iterator over aggregations created by the meter. +/// Doesn't implement standard [`Iterator`], because it returns borrowed items. AKA "LendingIterator". +pub struct MetricsLendingIter<'a> { + iter: Iter<'a, super::reader::MetricsData>, +} + +/// A collection of one or more aggregated time series from an [Instrument]. +/// +/// [Instrument]: crate::metrics::Instrument +#[derive(Debug)] +pub struct Metric<'a> { + /// The name of the instrument that created this data. + pub instrument: &'a InstrumentInfo, + /// The aggregated data from an instrument. + pub data: &'a AggregatedMetrics, +} + +impl<'a> ResourceMetrics<'a> { + pub(crate) fn new(data: &'a ResourceMetricsData) -> Self { + Self { + resource: &data.resource, + scope_metrics: ScopeMetricsCollector { + iter: ScopeMetricsLendingIter { + iter: data.scope_metrics.iter(), + }, + }, + } + } +} + +impl Debug for ScopeMetricsLendingIter<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BatchScopeMetrics").finish() + } +} + +impl ScopeMetricsLendingIter<'_> { + /// Advances the iterator and returns the next value. + pub fn next_scope_metrics(&mut self) -> Option> { + self.iter.next().map(|item| ScopeMetrics { + scope: &item.scope, + metrics: MetricsLendingIter { + iter: item.metrics.iter(), + }, + }) + } +} + +impl MetricsLendingIter<'_> { + /// Advances the iterator and returns the next value. + pub fn next_metric(&mut self) -> Option> { + self.iter.next().map(|item| Metric { + instrument: &item.instrument, + data: &item.data, + }) + } +} + +impl Debug for MetricsLendingIter<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BatchMetrics").finish() + } +} /// Exporter handles the delivery of metric data to external receivers. /// @@ -18,7 +123,7 @@ pub trait PushMetricExporter: Send + Sync + 'static { /// considered unrecoverable and will be logged. fn export( &self, - metrics: &ResourceMetrics, + metrics: ResourceMetrics<'_>, ) -> impl std::future::Future + Send; /// Flushes any metric data held by an exporter. diff --git a/opentelemetry-sdk/src/metrics/in_memory_exporter.rs b/opentelemetry-sdk/src/metrics/in_memory_exporter.rs index 903c2a46ec..6908aa67fe 100644 --- a/opentelemetry-sdk/src/metrics/in_memory_exporter.rs +++ b/opentelemetry-sdk/src/metrics/in_memory_exporter.rs @@ -1,7 +1,5 @@ use crate::error::{OTelSdkError, OTelSdkResult}; -use crate::metrics::data::{ - ExponentialHistogram, Gauge, Histogram, MetricData, ResourceMetrics, Sum, -}; +use crate::metrics::data::{ExponentialHistogram, Gauge, Histogram, MetricData, Sum}; use crate::metrics::exporter::PushMetricExporter; use crate::metrics::Temporality; use crate::InMemoryExporterError; @@ -10,7 +8,9 @@ use std::fmt; use std::sync::{Arc, Mutex}; use std::time::Duration; -use super::data::{AggregatedMetrics, Metric, ScopeMetrics}; +use super::data::AggregatedMetrics; +use super::exporter::ResourceMetrics; +use super::reader::{MetricsData, ResourceMetricsData, ScopeMetricsData}; /// An in-memory metrics exporter that stores metrics data in memory. /// @@ -60,7 +60,7 @@ use super::data::{AggregatedMetrics, Metric, ScopeMetrics}; ///# } /// ``` pub struct InMemoryMetricExporter { - metrics: Arc>>, + metrics: Arc>>, temporality: Temporality, } @@ -146,11 +146,33 @@ impl InMemoryMetricExporter { /// let exporter = InMemoryMetricExporter::default(); /// let finished_metrics = exporter.get_finished_metrics().unwrap(); /// ``` - pub fn get_finished_metrics(&self) -> Result, InMemoryExporterError> { + pub fn get_finished_metrics(&self) -> Result, InMemoryExporterError> { let metrics = self .metrics .lock() - .map(|metrics_guard| metrics_guard.iter().map(Self::clone_metrics).collect()) + .map(|metrics_guard| { + metrics_guard + .iter() + .map(|data| ResourceMetricsData { + resource: data.resource.clone(), + scope_metrics: data + .scope_metrics + .iter() + .map(|data| ScopeMetricsData { + scope: data.scope.clone(), + metrics: data + .metrics + .iter() + .map(|data| MetricsData { + instrument: data.instrument.clone(), + data: Self::clone_data(&data.data), + }) + .collect(), + }) + .collect(), + }) + .collect() + }) .map_err(InMemoryExporterError::from)?; Ok(metrics) } @@ -172,26 +194,32 @@ impl InMemoryMetricExporter { .map(|mut metrics_guard| metrics_guard.clear()); } - fn clone_metrics(metric: &ResourceMetrics) -> ResourceMetrics { - ResourceMetrics { - resource: metric.resource.clone(), - scope_metrics: metric - .scope_metrics - .iter() - .map(|scope_metric| ScopeMetrics { - scope: scope_metric.scope.clone(), - metrics: scope_metric - .metrics - .iter() - .map(|metric| Metric { - name: metric.name.clone(), - description: metric.description.clone(), - unit: metric.unit.clone(), - data: Self::clone_data(&metric.data), - }) - .collect(), - }) - .collect(), + fn clone_metrics(rm: ResourceMetrics<'_>) -> Option { + let mut scope_metrics = Vec::new(); + rm.scope_metrics.collect(|mut iter| { + while let Some(mut scope_metric) = iter.next_scope_metrics() { + let mut metrics = Vec::new(); + while let Some(metric) = scope_metric.metrics.next_metric() { + metrics.push(MetricsData { + instrument: metric.instrument.clone(), + data: Self::clone_data(metric.data), + }); + } + if !metrics.is_empty() { + scope_metrics.push(ScopeMetricsData { + scope: scope_metric.scope.clone(), + metrics, + }); + } + } + }); + if !scope_metrics.is_empty() { + Some(ResourceMetricsData { + resource: rm.resource.clone(), + scope_metrics, + }) + } else { + None } } @@ -237,11 +265,11 @@ impl InMemoryMetricExporter { } impl PushMetricExporter for InMemoryMetricExporter { - async fn export(&self, metrics: &ResourceMetrics) -> OTelSdkResult { + async fn export(&self, metrics: ResourceMetrics<'_>) -> OTelSdkResult { self.metrics .lock() .map(|mut metrics_guard| { - metrics_guard.push_back(InMemoryMetricExporter::clone_metrics(metrics)) + metrics_guard.extend(InMemoryMetricExporter::clone_metrics(metrics)) }) .map_err(|_| OTelSdkError::InternalFailure("Failed to lock metrics".to_string())) } diff --git a/opentelemetry-sdk/src/metrics/instrument.rs b/opentelemetry-sdk/src/metrics/instrument.rs index 559e9c5328..6ca88ac072 100644 --- a/opentelemetry-sdk/src/metrics/instrument.rs +++ b/opentelemetry-sdk/src/metrics/instrument.rs @@ -65,6 +65,19 @@ impl InstrumentKind { } } +/// Describes properties an instrument is created with +#[derive(Debug, Clone)] +pub struct InstrumentInfo { + /// The human-readable identifier of the instrument. + pub name: Cow<'static, str>, + /// describes the purpose of the instrument. + pub description: Cow<'static, str>, + /// The functional group of the instrument. + pub kind: InstrumentKind, + /// Unit is the unit of measurement recorded by the instrument. + pub unit: Cow<'static, str>, +} + /// Describes properties an instrument is created with, also used for filtering /// in [View](crate::metrics::View)s. /// diff --git a/opentelemetry-sdk/src/metrics/manual_reader.rs b/opentelemetry-sdk/src/metrics/manual_reader.rs index 855a6a14f4..99df24a481 100644 --- a/opentelemetry-sdk/src/metrics/manual_reader.rs +++ b/opentelemetry-sdk/src/metrics/manual_reader.rs @@ -10,8 +10,8 @@ use crate::{ metrics::Temporality, }; +use super::reader::ResourceMetricsData; use super::{ - data::ResourceMetrics, pipeline::Pipeline, reader::{MetricReader, SdkProducer}, }; @@ -90,7 +90,7 @@ impl MetricReader for ManualReader { /// callbacks necessary and returning the results. /// /// Returns an error if called after shutdown. - fn collect(&self, rm: &mut ResourceMetrics) -> OTelSdkResult { + fn collect(&self, rm: &mut ResourceMetricsData) -> OTelSdkResult { let inner = self .inner .lock() diff --git a/opentelemetry-sdk/src/metrics/mod.rs b/opentelemetry-sdk/src/metrics/mod.rs index 285ef39a73..cb335b640c 100644 --- a/opentelemetry-sdk/src/metrics/mod.rs +++ b/opentelemetry-sdk/src/metrics/mod.rs @@ -82,8 +82,7 @@ pub use periodic_reader::*; #[cfg(feature = "experimental_metrics_custom_reader")] pub use pipeline::Pipeline; -#[cfg(feature = "experimental_metrics_custom_reader")] -pub use instrument::InstrumentKind; +pub use instrument::{InstrumentInfo, InstrumentKind}; #[cfg(feature = "spec_unstable_metrics_views")] pub use instrument::*; @@ -118,11 +117,12 @@ pub enum Temporality { #[cfg(all(test, feature = "testing"))] mod tests { - use self::data::{HistogramDataPoint, ScopeMetrics, SumDataPoint}; + use self::data::{HistogramDataPoint, SumDataPoint}; use super::data::MetricData; use super::internal::Number; + use super::reader::ResourceMetricsData; + use super::reader::ScopeMetricsData; use super::*; - use crate::metrics::data::ResourceMetrics; use crate::metrics::internal::AggregatedMetricsAccess; use crate::metrics::InMemoryMetricExporter; use crate::metrics::InMemoryMetricExporterBuilder; @@ -693,8 +693,8 @@ mod tests { "There should be single metric merging duplicate instruments" ); let metric = &resource_metrics[0].scope_metrics[0].metrics[0]; - assert_eq!(metric.name, "my_counter"); - assert_eq!(metric.unit, "my_unit"); + assert_eq!(metric.instrument.name, "my_counter"); + assert_eq!(metric.instrument.unit, "my_unit"); let MetricData::Sum(sum) = u64::extract_metrics_data_ref(&metric.data) .expect("Sum aggregation expected for Counter instruments by default") else { @@ -759,9 +759,9 @@ mod tests { if let Some(scope1) = scope1 { let metric1 = &scope1.metrics[0]; - assert_eq!(metric1.name, "my_counter"); - assert_eq!(metric1.unit, "my_unit"); - assert_eq!(metric1.description, "my_description"); + assert_eq!(metric1.instrument.name, "my_counter"); + assert_eq!(metric1.instrument.unit, "my_unit"); + assert_eq!(metric1.instrument.description, "my_description"); let MetricData::Sum(sum1) = u64::extract_metrics_data_ref(&metric1.data) .expect("Sum aggregation expected for Counter instruments by default") else { @@ -779,9 +779,9 @@ mod tests { if let Some(scope2) = scope2 { let metric2 = &scope2.metrics[0]; - assert_eq!(metric2.name, "my_counter"); - assert_eq!(metric2.unit, "my_unit"); - assert_eq!(metric2.description, "my_description"); + assert_eq!(metric2.instrument.name, "my_counter"); + assert_eq!(metric2.instrument.unit, "my_unit"); + assert_eq!(metric2.instrument.description, "my_description"); let MetricData::Sum(sum2) = u64::extract_metrics_data_ref(&metric2.data) .expect("Sum aggregation expected for Counter instruments by default") @@ -865,9 +865,9 @@ mod tests { assert!(scope.attributes().eq(&[KeyValue::new("key", "value1")])); let metric = &resource_metrics[0].scope_metrics[0].metrics[0]; - assert_eq!(metric.name, "my_counter"); - assert_eq!(metric.unit, "my_unit"); - assert_eq!(metric.description, "my_description"); + assert_eq!(metric.instrument.name, "my_counter"); + assert_eq!(metric.instrument.unit, "my_unit"); + assert_eq!(metric.instrument.description, "my_description"); let MetricData::Sum(sum) = u64::extract_metrics_data_ref(&metric.data) .expect("Sum aggregation expected for Counter instruments by default") @@ -922,11 +922,11 @@ mod tests { assert!(!resource_metrics.is_empty()); let metric = &resource_metrics[0].scope_metrics[0].metrics[0]; assert_eq!( - metric.name, "test_histogram", + metric.instrument.name, "test_histogram", "View rename should be ignored and original name retained." ); assert_eq!( - metric.unit, "test_unit", + metric.instrument.unit, "test_unit", "View rename of unit should be ignored and original unit retained." ); } @@ -988,7 +988,7 @@ mod tests { .expect("metrics are expected to be exported."); assert!(!resource_metrics.is_empty()); let metric = &resource_metrics[0].scope_metrics[0].metrics[0]; - assert_eq!(metric.name, "my_observable_counter",); + assert_eq!(metric.instrument.name, "my_observable_counter",); let MetricData::Sum(sum) = u64::extract_metrics_data_ref(&metric.data) .expect("Sum aggregation expected for ObservableCounter instruments by default") @@ -1064,7 +1064,7 @@ mod tests { .expect("metrics are expected to be exported."); assert!(!resource_metrics.is_empty()); let metric = &resource_metrics[0].scope_metrics[0].metrics[0]; - assert_eq!(metric.name, "my_counter",); + assert_eq!(metric.instrument.name, "my_counter",); let MetricData::Sum(sum) = u64::extract_metrics_data_ref(&metric.data) .expect("Sum aggregation expected for Counter instruments by default") @@ -2955,9 +2955,9 @@ mod tests { } fn find_scope_metric<'a>( - metrics: &'a [ScopeMetrics], + metrics: &'a [ScopeMetricsData], name: &'a str, - ) -> Option<&'a ScopeMetrics> { + ) -> Option<&'a ScopeMetricsData> { metrics .iter() .find(|&scope_metric| scope_metric.scope.name() == name) @@ -2968,7 +2968,7 @@ mod tests { meter_provider: SdkMeterProvider, // Saving this on the test context for lifetime simplicity - resource_metrics: Vec, + resource_metrics: Vec, } impl TestContext { @@ -3081,9 +3081,9 @@ mod tests { assert!(!resource_metric.scope_metrics[0].metrics.is_empty()); let metric = &resource_metric.scope_metrics[0].metrics[0]; - assert_eq!(metric.name, counter_name); + assert_eq!(metric.instrument.name, counter_name); if let Some(expected_unit) = unit_name { - assert_eq!(metric.unit, expected_unit); + assert_eq!(metric.instrument.unit, expected_unit); } T::extract_metrics_data_ref(&metric.data) @@ -3125,10 +3125,10 @@ mod tests { assert!(!resource_metric.scope_metrics[0].metrics.is_empty()); let metric = &resource_metric.scope_metrics[0].metrics[0]; - assert_eq!(metric.name, counter_name); + assert_eq!(metric.instrument.name, counter_name); if let Some(expected_unit) = unit_name { - assert_eq!(metric.unit, expected_unit); + assert_eq!(metric.instrument.unit, expected_unit); } let aggregation = T::extract_metrics_data_ref(&metric.data) diff --git a/opentelemetry-sdk/src/metrics/periodic_reader.rs b/opentelemetry-sdk/src/metrics/periodic_reader.rs index 28b391278e..be6a8a6951 100644 --- a/opentelemetry-sdk/src/metrics/periodic_reader.rs +++ b/opentelemetry-sdk/src/metrics/periodic_reader.rs @@ -12,12 +12,17 @@ use opentelemetry::{otel_debug, otel_error, otel_info, otel_warn, Context}; use crate::{ error::{OTelSdkError, OTelSdkResult}, - metrics::{exporter::PushMetricExporter, reader::SdkProducer}, + metrics::{ + exporter::{PushMetricExporter, ResourceMetrics}, + reader::SdkProducer, + }, Resource, }; use super::{ - data::ResourceMetrics, instrument::InstrumentKind, pipeline::Pipeline, reader::MetricReader, + instrument::InstrumentKind, + pipeline::Pipeline, + reader::{MetricReader, ResourceMetricsData}, Temporality, }; @@ -358,7 +363,7 @@ impl PeriodicReaderInner { self.exporter.temporality() } - fn collect(&self, rm: &mut ResourceMetrics) -> OTelSdkResult { + fn collect(&self, rm: &mut ResourceMetricsData) -> OTelSdkResult { let producer = self.producer.lock().expect("lock poisoned"); if let Some(p) = producer.as_ref() { p.upgrade() @@ -381,7 +386,7 @@ impl PeriodicReaderInner { fn collect_and_export(&self) -> OTelSdkResult { // TODO: Reuse the internal vectors. Or refactor to avoid needing any // owned data structures to be passed to exporters. - let mut rm = ResourceMetrics { + let mut rm = ResourceMetricsData { resource: Resource::empty(), scope_metrics: Vec::new(), }; @@ -411,7 +416,7 @@ impl PeriodicReaderInner { // Relying on futures executor to execute async call. // TODO: Pass timeout to exporter - futures_executor::block_on(self.exporter.export(&rm)) + futures_executor::block_on(self.exporter.export(ResourceMetrics::new(&rm))) } fn force_flush(&self) -> OTelSdkResult { @@ -482,7 +487,7 @@ impl MetricReader for PeriodicReader { self.inner.register_pipeline(pipeline); } - fn collect(&self, rm: &mut ResourceMetrics) -> OTelSdkResult { + fn collect(&self, rm: &mut ResourceMetricsData) -> OTelSdkResult { self.inner.collect(rm) } @@ -516,7 +521,8 @@ mod tests { use crate::{ error::{OTelSdkError, OTelSdkResult}, metrics::{ - data::ResourceMetrics, exporter::PushMetricExporter, reader::MetricReader, + exporter::{PushMetricExporter, ResourceMetrics}, + reader::{MetricReader, ResourceMetricsData}, InMemoryMetricExporter, SdkMeterProvider, Temporality, }, Resource, @@ -553,7 +559,7 @@ mod tests { } impl PushMetricExporter for MetricExporterThatFailsOnlyOnFirst { - async fn export(&self, _metrics: &ResourceMetrics) -> OTelSdkResult { + async fn export(&self, _metrics: ResourceMetrics<'_>) -> OTelSdkResult { if self.count.fetch_add(1, Ordering::Relaxed) == 0 { Err(OTelSdkError::InternalFailure("export failed".into())) } else { @@ -584,7 +590,7 @@ mod tests { } impl PushMetricExporter for MockMetricExporter { - async fn export(&self, _metrics: &ResourceMetrics) -> OTelSdkResult { + async fn export(&self, _metrics: ResourceMetrics<'_>) -> OTelSdkResult { Ok(()) } @@ -698,7 +704,7 @@ mod tests { let exporter = InMemoryMetricExporter::default(); let reader = PeriodicReader::builder(exporter.clone()).build(); - let rm = &mut ResourceMetrics { + let rm = &mut ResourceMetricsData { resource: Resource::empty(), scope_metrics: Vec::new(), }; diff --git a/opentelemetry-sdk/src/metrics/periodic_reader_with_async_runtime.rs b/opentelemetry-sdk/src/metrics/periodic_reader_with_async_runtime.rs index 066db5b340..f99047b7a9 100644 --- a/opentelemetry-sdk/src/metrics/periodic_reader_with_async_runtime.rs +++ b/opentelemetry-sdk/src/metrics/periodic_reader_with_async_runtime.rs @@ -13,17 +13,18 @@ use futures_util::{ }; use opentelemetry::{otel_debug, otel_error}; -use crate::runtime::{to_interval_stream, Runtime}; use crate::{ error::{OTelSdkError, OTelSdkResult}, metrics::{exporter::PushMetricExporter, reader::SdkProducer}, Resource, }; - -use super::{ - data::ResourceMetrics, instrument::InstrumentKind, pipeline::Pipeline, reader::MetricReader, +use crate::{ + metrics::{exporter::ResourceMetrics, reader::ResourceMetricsData}, + runtime::{to_interval_stream, Runtime}, }; +use super::{instrument::InstrumentKind, pipeline::Pipeline, reader::MetricReader}; + const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30); const DEFAULT_INTERVAL: Duration = Duration::from_secs(60); @@ -120,7 +121,7 @@ where reader, timeout: self.timeout, runtime, - rm: ResourceMetrics { + rm: ResourceMetricsData { resource: Resource::empty(), scope_metrics: Vec::new(), }, @@ -238,7 +239,7 @@ struct PeriodicReaderWorker { reader: PeriodicReader, timeout: Duration, runtime: RT, - rm: ResourceMetrics, + rm: ResourceMetricsData, } impl PeriodicReaderWorker { @@ -259,7 +260,7 @@ impl PeriodicReaderWorker { message = "Calling exporter's export method with collected metrics.", count = self.rm.scope_metrics.len(), ); - let export = self.reader.exporter.export(&self.rm); + let export = self.reader.exporter.export(ResourceMetrics::new(&self.rm)); let timeout = self.runtime.delay(self.timeout); pin_mut!(export); pin_mut!(timeout); @@ -353,7 +354,7 @@ impl MetricReader for PeriodicReader { worker(self); } - fn collect(&self, rm: &mut ResourceMetrics) -> OTelSdkResult { + fn collect(&self, rm: &mut ResourceMetricsData) -> OTelSdkResult { let inner = self .inner .lock() @@ -443,11 +444,8 @@ impl MetricReader for PeriodicReader { mod tests { use super::PeriodicReader; use crate::error::OTelSdkError; - use crate::metrics::reader::MetricReader; - use crate::{ - metrics::data::ResourceMetrics, metrics::InMemoryMetricExporter, metrics::SdkMeterProvider, - runtime, Resource, - }; + use crate::metrics::reader::{MetricReader, ResourceMetricsData}; + use crate::{metrics::InMemoryMetricExporter, metrics::SdkMeterProvider, runtime, Resource}; use opentelemetry::metrics::MeterProvider; use std::sync::mpsc; @@ -494,7 +492,7 @@ mod tests { // Arrange let exporter = InMemoryMetricExporter::default(); let reader = PeriodicReader::builder(exporter.clone(), runtime::Tokio).build(); - let mut rm = ResourceMetrics { + let mut rm = ResourceMetricsData { resource: Resource::empty(), scope_metrics: Vec::new(), }; diff --git a/opentelemetry-sdk/src/metrics/pipeline.rs b/opentelemetry-sdk/src/metrics/pipeline.rs index 05d2861807..7c9446b692 100644 --- a/opentelemetry-sdk/src/metrics/pipeline.rs +++ b/opentelemetry-sdk/src/metrics/pipeline.rs @@ -11,19 +11,19 @@ use crate::{ error::{OTelSdkError, OTelSdkResult}, metrics::{ aggregation, - data::{Metric, ResourceMetrics, ScopeMetrics}, error::{MetricError, MetricResult}, instrument::{Instrument, InstrumentId, InstrumentKind, Stream}, internal::{self, AggregateBuilder, Number}, - reader::{MetricReader, SdkProducer}, + reader::{MetricReader, MetricsData, ScopeMetricsData, SdkProducer}, view::View, + InstrumentInfo, }, Resource, }; use self::internal::AggregateFns; -use super::{aggregation::Aggregation, Temporality}; +use super::{aggregation::Aggregation, reader::ResourceMetricsData, Temporality}; /// Connects all of the instruments created by a meter provider to a [MetricReader]. /// @@ -38,7 +38,7 @@ pub struct Pipeline { pub(crate) resource: Resource, reader: Box, views: Vec>, - inner: Mutex, + pub(crate) inner: Mutex, } impl fmt::Debug for Pipeline { @@ -53,9 +53,9 @@ type GenericCallback = Arc; const DEFAULT_CARDINALITY_LIMIT: usize = 2000; #[derive(Default)] -struct PipelineInner { - aggregations: HashMap>, - callbacks: Vec, +pub(crate) struct PipelineInner { + pub(crate) aggregations: HashMap>, + pub(crate) callbacks: Vec, } impl fmt::Debug for PipelineInner { @@ -100,7 +100,7 @@ impl Pipeline { impl SdkProducer for Pipeline { /// Returns aggregated metrics from a single collection. - fn produce(&self, rm: &mut ResourceMetrics) -> OTelSdkResult { + fn produce(&self, rm: &mut ResourceMetricsData) -> OTelSdkResult { let inner = self .inner .lock() @@ -125,7 +125,7 @@ impl SdkProducer for Pipeline { let sm = match rm.scope_metrics.get_mut(i) { Some(sm) => sm, None => { - rm.scope_metrics.push(ScopeMetrics::default()); + rm.scope_metrics.push(ScopeMetricsData::default()); rm.scope_metrics.last_mut().unwrap() } }; @@ -138,10 +138,8 @@ impl SdkProducer for Pipeline { let mut m = sm.metrics.get_mut(j); match (inst.comp_agg.call(m.as_mut().map(|m| &mut m.data)), m) { // No metric to re-use, expect agg to create new metric data - ((len, Some(initial_agg)), None) if len > 0 => sm.metrics.push(Metric { - name: inst.name.clone(), - description: inst.description.clone(), - unit: inst.unit.clone(), + ((len, Some(initial_agg)), None) if len > 0 => sm.metrics.push(MetricsData { + instrument: inst.info.clone(), data: initial_agg, }), // Existing metric can be re-used, update its values @@ -150,9 +148,7 @@ impl SdkProducer for Pipeline { // previous aggregation was of a different type prev_agg.data = data; } - prev_agg.name.clone_from(&inst.name); - prev_agg.description.clone_from(&inst.description); - prev_agg.unit.clone_from(&inst.unit); + prev_agg.instrument.clone_from(&inst.info); } _ => continue, } @@ -174,19 +170,17 @@ impl SdkProducer for Pipeline { } /// A synchronization point between a [Pipeline] and an instrument's aggregate function. -struct InstrumentSync { - name: Cow<'static, str>, - description: Cow<'static, str>, - unit: Cow<'static, str>, - comp_agg: Arc, +pub(crate) struct InstrumentSync { + pub(crate) info: InstrumentInfo, + pub(crate) comp_agg: Arc, } impl fmt::Debug for InstrumentSync { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("InstrumentSync") - .field("name", &self.name) - .field("description", &self.description) - .field("unit", &self.unit) + .field("name", &self.info.name) + .field("description", &self.info.description) + .field("unit", &self.info.unit) .finish() } } @@ -410,9 +404,12 @@ where self.pipeline.add_sync( scope.clone(), InstrumentSync { - name: stream.name, - description: stream.description, - unit: stream.unit, + info: InstrumentInfo { + name: stream.name, + description: stream.description, + unit: stream.unit, + kind, + }, comp_agg: collect, }, ); diff --git a/opentelemetry-sdk/src/metrics/reader.rs b/opentelemetry-sdk/src/metrics/reader.rs index ae19841155..196c82429e 100644 --- a/opentelemetry-sdk/src/metrics/reader.rs +++ b/opentelemetry-sdk/src/metrics/reader.rs @@ -1,9 +1,43 @@ //! Interfaces for reading and producing metrics +use opentelemetry::InstrumentationScope; + use crate::error::OTelSdkResult; +use crate::Resource; use std::time::Duration; use std::{fmt, sync::Weak}; -use super::{data::ResourceMetrics, instrument::InstrumentKind, pipeline::Pipeline, Temporality}; +use super::data::AggregatedMetrics; +use super::InstrumentInfo; +use super::{pipeline::Pipeline, InstrumentKind, Temporality}; + +/// A collection of [ScopeMetricsData] and the associated [Resource] that created them. +#[derive(Debug)] +pub struct ResourceMetricsData { + /// The entity that collected the metrics. + pub resource: Resource, + /// The collection of metrics with unique [InstrumentationScope]s. + pub scope_metrics: Vec, +} + +/// A collection of metrics produced by a meter. +#[derive(Debug, Default)] +pub struct ScopeMetricsData { + /// The [InstrumentationScope] that the meter was created with. + pub scope: InstrumentationScope, + /// The list of aggregations created by the meter. + pub metrics: Vec, +} + +/// A collection of one or more aggregated time series from an [Instrument]. +/// +/// [Instrument]: crate::metrics::Instrument +#[derive(Debug)] +pub struct MetricsData { + /// The name of the instrument that created this data. + pub instrument: InstrumentInfo, + /// The aggregated data from an instrument. + pub data: AggregatedMetrics, +} /// The interface used between the SDK and an exporter. /// @@ -27,10 +61,10 @@ pub trait MetricReader: fmt::Debug + Send + Sync + 'static { fn register_pipeline(&self, pipeline: Weak); /// Gathers and returns all metric data related to the [MetricReader] from the - /// SDK and stores it in the provided [ResourceMetrics] reference. + /// SDK and stores it in the provided [ResourceMetricsData] reference. /// /// An error is returned if this is called after shutdown. - fn collect(&self, rm: &mut ResourceMetrics) -> OTelSdkResult; + fn collect(&self, rm: &mut ResourceMetricsData) -> OTelSdkResult; /// Flushes all metric measurements held in an export pipeline. /// @@ -63,5 +97,5 @@ pub trait MetricReader: fmt::Debug + Send + Sync + 'static { /// Produces metrics for a [MetricReader]. pub(crate) trait SdkProducer: fmt::Debug + Send + Sync { /// Returns aggregated metrics from a single collection. - fn produce(&self, rm: &mut ResourceMetrics) -> OTelSdkResult; + fn produce(&self, rm: &mut ResourceMetricsData) -> OTelSdkResult; } diff --git a/opentelemetry-sdk/src/testing/metrics/metric_reader.rs b/opentelemetry-sdk/src/testing/metrics/metric_reader.rs index 27f72321cc..217c1f5052 100644 --- a/opentelemetry-sdk/src/testing/metrics/metric_reader.rs +++ b/opentelemetry-sdk/src/testing/metrics/metric_reader.rs @@ -1,8 +1,7 @@ use crate::error::{OTelSdkError, OTelSdkResult}; +use crate::metrics::reader::ResourceMetricsData; use crate::metrics::Temporality; -use crate::metrics::{ - data::ResourceMetrics, instrument::InstrumentKind, pipeline::Pipeline, reader::MetricReader, -}; +use crate::metrics::{instrument::InstrumentKind, pipeline::Pipeline, reader::MetricReader}; use std::sync::{Arc, Mutex, Weak}; use std::time::Duration; @@ -34,7 +33,7 @@ impl Default for TestMetricReader { impl MetricReader for TestMetricReader { fn register_pipeline(&self, _pipeline: Weak) {} - fn collect(&self, _rm: &mut ResourceMetrics) -> OTelSdkResult { + fn collect(&self, _rm: &mut ResourceMetricsData) -> OTelSdkResult { Ok(()) } diff --git a/opentelemetry-stdout/src/metrics/exporter.rs b/opentelemetry-stdout/src/metrics/exporter.rs index 2ff7cfce5d..bbc5d0c16d 100644 --- a/opentelemetry-stdout/src/metrics/exporter.rs +++ b/opentelemetry-stdout/src/metrics/exporter.rs @@ -1,14 +1,14 @@ use chrono::{DateTime, Utc}; use core::{f64, fmt}; use opentelemetry_sdk::metrics::data::{AggregatedMetrics, MetricData}; +use opentelemetry_sdk::metrics::exporter::{ + MetricsLendingIter, ResourceMetrics, ScopeMetricsCollector, +}; use opentelemetry_sdk::metrics::Temporality; use opentelemetry_sdk::{ error::OTelSdkResult, metrics::{ - data::{ - Gauge, GaugeDataPoint, Histogram, HistogramDataPoint, ResourceMetrics, ScopeMetrics, - Sum, SumDataPoint, - }, + data::{Gauge, GaugeDataPoint, Histogram, HistogramDataPoint, Sum, SumDataPoint}, exporter::PushMetricExporter, }, }; @@ -42,7 +42,7 @@ impl fmt::Debug for MetricExporter { impl PushMetricExporter for MetricExporter { /// Write Metrics to stdout - async fn export(&self, metrics: &ResourceMetrics) -> OTelSdkResult { + async fn export(&self, metrics: ResourceMetrics<'_>) -> OTelSdkResult { if self.is_shutdown.load(atomic::Ordering::SeqCst) { Err(opentelemetry_sdk::error::OTelSdkError::AlreadyShutdown) } else { @@ -55,7 +55,7 @@ impl PushMetricExporter for MetricExporter { metrics.resource.iter().for_each(|(k, v)| { println!("\t -> {}={:?}", k, v); }); - print_metrics(&metrics.scope_metrics); + print_scope_metrics(metrics.scope_metrics); Ok(()) } } @@ -79,61 +79,71 @@ impl PushMetricExporter for MetricExporter { } } -fn print_metrics(metrics: &[ScopeMetrics]) { - for (i, metric) in metrics.iter().enumerate() { - println!("\tInstrumentation Scope #{}", i); - println!("\t\tName : {}", &metric.scope.name()); - if let Some(version) = &metric.scope.version() { - println!("\t\tVersion : {:?}", version); - } - if let Some(schema_url) = &metric.scope.schema_url() { - println!("\t\tSchemaUrl: {:?}", schema_url); +fn print_scope_metrics(scope_metrics: ScopeMetricsCollector<'_>) { + scope_metrics.collect(|mut metrics| { + let mut iter = 0; + while let Some(scope_metric) = metrics.next_scope_metrics() { + iter += 1; + println!("\tInstrumentation Scope #{}", iter); + println!("\t\tName : {}", &scope_metric.scope.name()); + if let Some(version) = &scope_metric.scope.version() { + println!("\t\tVersion : {:?}", version); + } + if let Some(schema_url) = &scope_metric.scope.schema_url() { + println!("\t\tSchemaUrl: {:?}", schema_url); + } + scope_metric + .scope + .attributes() + .enumerate() + .for_each(|(index, kv)| { + if index == 0 { + println!("\t\tScope Attributes:"); + } + println!("\t\t\t -> {}: {}", kv.key, kv.value); + }); + print_metrics(scope_metric.metrics); } - metric - .scope - .attributes() - .enumerate() - .for_each(|(index, kv)| { - if index == 0 { - println!("\t\tScope Attributes:"); - } - println!("\t\t\t -> {}: {}", kv.key, kv.value); - }); + }); +} - metric.metrics.iter().enumerate().for_each(|(i, metric)| { - println!("Metric #{}", i); - println!("\t\tName : {}", &metric.name); - println!("\t\tDescription : {}", &metric.description); - println!("\t\tUnit : {}", &metric.unit); +fn print_metrics(mut metrics: MetricsLendingIter<'_>) { + let mut iter = 0; + while let Some(metric) = metrics.next_metric() { + iter += 1; - fn print_info(data: &MetricData) - where - T: Debug, - { - match data { - MetricData::Gauge(gauge) => { - println!("\t\tType : Gauge"); - print_gauge(gauge); - } - MetricData::Sum(sum) => { - println!("\t\tType : Sum"); - print_sum(sum); - } - MetricData::Histogram(hist) => { - println!("\t\tType : Histogram"); - print_histogram(hist); - } - MetricData::ExponentialHistogram(_) => { - println!("\t\tType : Exponential Histogram"); - } + println!("Metric #{}", iter); + println!("\t\tName : {}", &metric.instrument.name); + println!("\t\tDescription : {}", &metric.instrument.description); + println!("\t\tUnit : {}", &metric.instrument.unit); + + fn print_info(data: &MetricData) + where + T: Debug, + { + match data { + MetricData::Gauge(gauge) => { + println!("\t\tType : Gauge"); + print_gauge(gauge); + } + MetricData::Sum(sum) => { + println!("\t\tType : Sum"); + print_sum(sum); + } + MetricData::Histogram(hist) => { + println!("\t\tType : Histogram"); + print_histogram(hist); + } + MetricData::ExponentialHistogram(_) => { + println!("\t\tType : Exponential Histogram"); } } - match &metric.data { - AggregatedMetrics::F64(data) => print_info(data), - AggregatedMetrics::U64(data) => print_info(data), - AggregatedMetrics::I64(data) => print_info(data), - } - }); + } + match &metric.data { + AggregatedMetrics::F64(data) => print_info(data), + AggregatedMetrics::U64(data) => print_info(data), + AggregatedMetrics::I64(data) => print_info(data), + } } }