From 849ecf3247622ef556e00144783316a46de10005 Mon Sep 17 00:00:00 2001 From: Davide Melfi Date: Sun, 3 May 2026 12:03:42 +0100 Subject: [PATCH 1/4] feat: add public constructor to metric data types --- opentelemetry-sdk/src/metrics/data/mod.rs | 560 ++++++++++++++++++++++ 1 file changed, 560 insertions(+) diff --git a/opentelemetry-sdk/src/metrics/data/mod.rs b/opentelemetry-sdk/src/metrics/data/mod.rs index 9c63c9ada8..e39523749c 100644 --- a/opentelemetry-sdk/src/metrics/data/mod.rs +++ b/opentelemetry-sdk/src/metrics/data/mod.rs @@ -27,6 +27,14 @@ impl Default for ResourceMetrics { } impl ResourceMetrics { + /// Create a new builder to create an [ResourceMetrics] + pub fn builder() -> ResourceMetricsBuilder { + ResourceMetricsBuilder { + resource: Resource::empty(), + scope_metrics: Vec::new(), + } + } + /// Returns a reference to the [Resource] in [ResourceMetrics]. pub fn resource(&self) -> &Resource { &self.resource @@ -38,6 +46,34 @@ impl ResourceMetrics { } } +/// Configuration option for [ResourceMetrics] +#[derive(Debug)] +pub struct ResourceMetricsBuilder { + pub(crate) resource: Resource, + pub(crate) scope_metrics: Vec, +} + +impl ResourceMetricsBuilder { + /// Sets the [Resource] for this [ResourceMetrics] + pub fn with_resource(mut self, resource: Resource) -> Self { + self.resource = resource; + self + } + + /// Sets a [Vec] of [ScopeMetrics] for this [ResourceMetrics] + pub fn with_scope_metrics(mut self, scope_metrics: Vec) -> Self { + self.scope_metrics = scope_metrics; + self + } + /// Create a new [ResourceMetrics] from this configuration + pub fn build(self) -> ResourceMetrics { + ResourceMetrics { + resource: self.resource, + scope_metrics: self.scope_metrics, + } + } +} + /// A collection of metrics produced by a meter. #[derive(Default, Debug)] pub struct ScopeMetrics { @@ -48,6 +84,14 @@ pub struct ScopeMetrics { } impl ScopeMetrics { + /// Create a new builder to create a [ScopeMetrics] + pub fn builder() -> ScopeMetricsBuilder { + ScopeMetricsBuilder { + scope: InstrumentationScope::default(), + metrics: Vec::new(), + } + } + /// Returns a reference to the [InstrumentationScope] in [ScopeMetrics]. pub fn scope(&self) -> &InstrumentationScope { &self.scope @@ -59,6 +103,35 @@ impl ScopeMetrics { } } +/// Configuration option for [ScopeMetrics] +#[derive(Debug)] +pub struct ScopeMetricsBuilder { + scope: InstrumentationScope, + metrics: Vec, +} + +impl ScopeMetricsBuilder { + /// Sets the [InstrumentationScope] for this [ScopeMetrics] + pub fn with_scope(mut self, scope: InstrumentationScope) -> Self { + self.scope = scope; + self + } + + /// Sets a [Vec] of [Metric] for this [ScopeMetrics] + pub fn with_metrics(mut self, metrics: Vec) -> Self { + self.metrics = metrics; + self + } + + /// Create a new [ScopeMetrics] from this configuration + pub fn build(self) -> ScopeMetrics { + ScopeMetrics { + scope: self.scope, + metrics: self.metrics, + } + } +} + /// A collection of one or more aggregated time series from an [Instrument]. /// /// [Instrument]: crate::metrics::Instrument @@ -75,6 +148,16 @@ pub struct Metric { } impl Metric { + /// Create a new builder to create a [Metric] + pub fn builder(name: impl Into>, data: AggregatedMetrics) -> MetricBuilder { + MetricBuilder { + name: name.into(), + description: Cow::Borrowed(""), + unit: Cow::Borrowed(""), + data, + } + } + /// Returns the name of the instrument that created this data. pub fn name(&self) -> &str { &self.name @@ -96,6 +179,39 @@ impl Metric { } } +/// Configuration option for [Metric] +#[derive(Debug)] +pub struct MetricBuilder { + name: Cow<'static, str>, + description: Cow<'static, str>, + unit: Cow<'static, str>, + data: AggregatedMetrics, +} + +impl MetricBuilder { + /// Sets the description for this [Metric] + pub fn with_description(mut self, description: impl Into>) -> Self { + self.description = description.into(); + self + } + + /// Sets the unit for this [Metric] + pub fn with_unit(mut self, unit: impl Into>) -> Self { + self.unit = unit.into(); + self + } + + /// Create a new [Metric] from this configuration + pub fn build(self) -> Metric { + Metric { + name: self.name, + description: self.description, + unit: self.unit, + data: self.data, + } + } +} + /// Aggregated metrics data from an instrument #[derive(Debug)] pub enum AggregatedMetrics { @@ -175,6 +291,15 @@ pub struct GaugeDataPoint { } impl GaugeDataPoint { + /// Create a new builder to create a [GaugeDataPoint] + pub fn builder(value: T) -> GaugeDataPointBuilder { + GaugeDataPointBuilder { + attributes: Vec::new(), + value, + exemplars: Vec::new(), + } + } + /// Returns an iterator over the attributes in [GaugeDataPoint]. pub fn attributes(&self) -> impl Iterator { self.attributes.iter() @@ -186,6 +311,37 @@ impl GaugeDataPoint { } } +/// Configuration option for [GaugeDataPoint] +#[derive(Debug)] +pub struct GaugeDataPointBuilder { + attributes: Vec, + value: T, + exemplars: Vec>, +} + +impl GaugeDataPointBuilder { + /// Sets the attributes for this [GaugeDataPoint] + pub fn with_attributes(mut self, attributes: Vec) -> Self { + self.attributes = attributes; + self + } + + /// Sets the exemplars for this [GaugeDataPoint] + pub fn with_exemplars(mut self, exemplars: Vec>) -> Self { + self.exemplars = exemplars; + self + } + + /// Create a new [GaugeDataPoint] from this configuration + pub fn build(self) -> GaugeDataPoint { + GaugeDataPoint { + attributes: self.attributes, + value: self.value, + exemplars: self.exemplars, + } + } +} + impl GaugeDataPoint { /// Returns the value of this data point. pub fn value(&self) -> T { @@ -205,6 +361,15 @@ pub struct Gauge { } impl Gauge { + /// Create a new builder to create a [Gauge] + pub fn builder(data_points: Vec>, time: SystemTime) -> GaugeBuilder { + GaugeBuilder { + data_points, + start_time: None, + time, + } + } + /// Returns an iterator over the [GaugeDataPoint]s in [Gauge]. pub fn data_points(&self) -> impl Iterator> { self.data_points.iter() @@ -221,6 +386,31 @@ impl Gauge { } } +/// Configuration option for [Gauge] +#[derive(Debug)] +pub struct GaugeBuilder { + data_points: Vec>, + start_time: Option, + time: SystemTime, +} + +impl GaugeBuilder { + /// Sets the start time for this [Gauge] + pub fn with_start_time(mut self, start_time: SystemTime) -> Self { + self.start_time = Some(start_time); + self + } + + /// Create a new [Gauge] from this configuration + pub fn build(self) -> Gauge { + Gauge { + data_points: self.data_points, + start_time: self.start_time, + time: self.time, + } + } +} + /// DataPoint is a single data point in a time series. #[derive(Debug, Clone, PartialEq)] pub struct SumDataPoint { @@ -234,6 +424,15 @@ pub struct SumDataPoint { } impl SumDataPoint { + /// Create a new builder to create a [SumDataPoint] + pub fn builder(value: T) -> SumDataPointBuilder { + SumDataPointBuilder { + attributes: Vec::new(), + value, + exemplars: Vec::new(), + } + } + /// Returns an iterator over the attributes in [SumDataPoint]. pub fn attributes(&self) -> impl Iterator { self.attributes.iter() @@ -245,6 +444,37 @@ impl SumDataPoint { } } +/// Configuration option for [SumDataPoint] +#[derive(Debug)] +pub struct SumDataPointBuilder { + attributes: Vec, + value: T, + exemplars: Vec>, +} + +impl SumDataPointBuilder { + /// Sets the attributes for this [SumDataPoint] + pub fn with_attributes(mut self, attributes: Vec) -> Self { + self.attributes = attributes; + self + } + + /// Sets the exemplars for this [SumDataPoint] + pub fn with_exemplars(mut self, exemplars: Vec>) -> Self { + self.exemplars = exemplars; + self + } + + /// Create a new [SumDataPoint] from this configuration + pub fn build(self) -> SumDataPoint { + SumDataPoint { + attributes: self.attributes, + value: self.value, + exemplars: self.exemplars, + } + } +} + impl SumDataPoint { /// Returns the value of this data point. pub fn value(&self) -> T { @@ -269,6 +499,23 @@ pub struct Sum { } impl Sum { + /// Create a new builder to create a [Sum] + pub fn builder( + data_points: Vec>, + temporality: Temporality, + is_monotonic: bool, + start_time: SystemTime, + time: SystemTime, + ) -> SumBuilder { + SumBuilder { + data_points, + start_time, + time, + temporality, + is_monotonic, + } + } + /// Returns an iterator over the [SumDataPoint]s in [Sum]. pub fn data_points(&self) -> impl Iterator> { self.data_points.iter() @@ -296,6 +543,29 @@ impl Sum { } } +/// Configuration option for [Sum] +#[derive(Debug)] +pub struct SumBuilder { + data_points: Vec>, + start_time: SystemTime, + time: SystemTime, + temporality: Temporality, + is_monotonic: bool, +} + +impl SumBuilder { + /// Create a new [Sum] from this configuration + pub fn build(self) -> Sum { + Sum { + data_points: self.data_points, + start_time: self.start_time, + time: self.time, + temporality: self.temporality, + is_monotonic: self.is_monotonic, + } + } +} + /// Represents the histogram of all measurements of values from an instrument. #[derive(Debug, Clone)] pub struct Histogram { @@ -311,6 +581,21 @@ pub struct Histogram { } impl Histogram { + /// Create a new builder to create a [Histogram] + pub fn builder( + data_points: Vec>, + temporality: Temporality, + start_time: SystemTime, + time: SystemTime, + ) -> HistogramBuilder { + HistogramBuilder { + data_points, + start_time, + time, + temporality, + } + } + /// Returns an iterator over the [HistogramDataPoint]s in [Histogram]. pub fn data_points(&self) -> impl Iterator> { self.data_points.iter() @@ -333,6 +618,27 @@ impl Histogram { } } +/// Configuration option for [Histogram] +#[derive(Debug)] +pub struct HistogramBuilder { + data_points: Vec>, + start_time: SystemTime, + time: SystemTime, + temporality: Temporality, +} + +impl HistogramBuilder { + /// Create a new [Histogram] from this configuration + pub fn build(self) -> Histogram { + Histogram { + data_points: self.data_points, + start_time: self.start_time, + time: self.time, + temporality: self.temporality, + } + } +} + /// A single histogram data point in a time series. #[derive(Debug, Clone, PartialEq)] pub struct HistogramDataPoint { @@ -359,6 +665,25 @@ pub struct HistogramDataPoint { } impl HistogramDataPoint { + /// Create a new builder to create a [HistogramDataPoint] + pub fn builder( + count: u64, + sum: T, + bounds: Vec, + bucket_counts: Vec, + ) -> HistogramDataPointBuilder { + HistogramDataPointBuilder { + attributes: Vec::new(), + count, + bounds, + bucket_counts, + min: None, + max: None, + sum, + exemplars: Vec::new(), + } + } + /// Returns an iterator over the attributes in [HistogramDataPoint]. pub fn attributes(&self) -> impl Iterator { self.attributes.iter() @@ -385,6 +710,59 @@ impl HistogramDataPoint { } } +/// Configuration option for [HistogramDataPoint] +#[derive(Debug)] +pub struct HistogramDataPointBuilder { + attributes: Vec, + count: u64, + bounds: Vec, + bucket_counts: Vec, + min: Option, + max: Option, + sum: T, + exemplars: Vec>, +} + +impl HistogramDataPointBuilder { + /// Sets the attributes for this [HistogramDataPoint] + pub fn with_attributes(mut self, attributes: Vec) -> Self { + self.attributes = attributes; + self + } + + /// Sets the minimum value for this [HistogramDataPoint] + pub fn with_min(mut self, min: T) -> Self { + self.min = Some(min); + self + } + + /// Sets the maximum value for this [HistogramDataPoint] + pub fn with_max(mut self, max: T) -> Self { + self.max = Some(max); + self + } + + /// Sets the exemplars for this [HistogramDataPoint] + pub fn with_exemplars(mut self, exemplars: Vec>) -> Self { + self.exemplars = exemplars; + self + } + + /// Create a new [HistogramDataPoint] from this configuration + pub fn build(self) -> HistogramDataPoint { + HistogramDataPoint { + attributes: self.attributes, + count: self.count, + bounds: self.bounds, + bucket_counts: self.bucket_counts, + min: self.min, + max: self.max, + sum: self.sum, + exemplars: self.exemplars, + } + } +} + impl HistogramDataPoint { /// Returns the minimum value recorded. pub fn min(&self) -> Option { @@ -417,6 +795,21 @@ pub struct ExponentialHistogram { } impl ExponentialHistogram { + /// Create a new builder to create an [ExponentialHistogram] + pub fn builder( + data_points: Vec>, + temporality: Temporality, + start_time: SystemTime, + time: SystemTime, + ) -> ExponentialHistogramBuilder { + ExponentialHistogramBuilder { + data_points, + start_time, + time, + temporality, + } + } + /// Returns an iterator over the [ExponentialHistogramDataPoint]s in [ExponentialHistogram]. pub fn data_points(&self) -> impl Iterator> { self.data_points.iter() @@ -439,6 +832,27 @@ impl ExponentialHistogram { } } +/// Configuration option for [ExponentialHistogram] +#[derive(Debug)] +pub struct ExponentialHistogramBuilder { + data_points: Vec>, + start_time: SystemTime, + time: SystemTime, + temporality: Temporality, +} + +impl ExponentialHistogramBuilder { + /// Create a new [ExponentialHistogram] from this configuration + pub fn build(self) -> ExponentialHistogram { + ExponentialHistogram { + data_points: self.data_points, + start_time: self.start_time, + time: self.time, + temporality: self.temporality, + } + } +} + /// A single exponential histogram data point in a time series. #[derive(Debug, Clone, PartialEq)] pub struct ExponentialHistogramDataPoint { @@ -485,6 +899,30 @@ pub struct ExponentialHistogramDataPoint { } impl ExponentialHistogramDataPoint { + /// Create a new builder to create an [ExponentialHistogramDataPoint] + pub fn builder( + count: usize, + sum: T, + scale: i8, + zero_count: u64, + positive_bucket: ExponentialBucket, + negative_bucket: ExponentialBucket, + ) -> ExponentialHistogramDataPointBuilder { + ExponentialHistogramDataPointBuilder { + attributes: Vec::new(), + count, + min: None, + max: None, + sum, + scale, + zero_count, + positive_bucket, + negative_bucket, + zero_threshold: 0.0, + exemplars: Vec::new(), + } + } + /// Returns an iterator over the attributes in [ExponentialHistogramDataPoint]. pub fn attributes(&self) -> impl Iterator { self.attributes.iter() @@ -526,6 +964,71 @@ impl ExponentialHistogramDataPoint { } } +/// Configuration option for [ExponentialHistogramDataPoint] +#[derive(Debug)] +pub struct ExponentialHistogramDataPointBuilder { + attributes: Vec, + count: usize, + min: Option, + max: Option, + sum: T, + scale: i8, + zero_count: u64, + positive_bucket: ExponentialBucket, + negative_bucket: ExponentialBucket, + zero_threshold: f64, + exemplars: Vec>, +} + +impl ExponentialHistogramDataPointBuilder { + /// Sets the attributes for this [ExponentialHistogramDataPoint] + pub fn with_attributes(mut self, attributes: Vec) -> Self { + self.attributes = attributes; + self + } + + /// Sets the minimum value for this [ExponentialHistogramDataPoint] + pub fn with_min(mut self, min: T) -> Self { + self.min = Some(min); + self + } + + /// Sets the maximum value for this [ExponentialHistogramDataPoint] + pub fn with_max(mut self, max: T) -> Self { + self.max = Some(max); + self + } + + /// Sets the zero threshold for this [ExponentialHistogramDataPoint] + pub fn with_zero_threshold(mut self, zero_threshold: f64) -> Self { + self.zero_threshold = zero_threshold; + self + } + + /// Sets the exemplars for this [ExponentialHistogramDataPoint] + pub fn with_exemplars(mut self, exemplars: Vec>) -> Self { + self.exemplars = exemplars; + self + } + + /// Create a new [ExponentialHistogramDataPoint] from this configuration + pub fn build(self) -> ExponentialHistogramDataPoint { + ExponentialHistogramDataPoint { + attributes: self.attributes, + count: self.count, + min: self.min, + max: self.max, + sum: self.sum, + scale: self.scale, + zero_count: self.zero_count, + positive_bucket: self.positive_bucket, + negative_bucket: self.negative_bucket, + zero_threshold: self.zero_threshold, + exemplars: self.exemplars, + } + } +} + impl ExponentialHistogramDataPoint { /// Returns the minimum value recorded. pub fn min(&self) -> Option { @@ -557,6 +1060,11 @@ pub struct ExponentialBucket { } impl ExponentialBucket { + /// Create a new [ExponentialBucket] + pub fn new(offset: i32, counts: Vec) -> Self { + Self { offset, counts } + } + /// Returns the bucket index of the first entry in the counts vec. pub fn offset(&self) -> i32 { self.offset @@ -589,6 +1097,17 @@ pub struct Exemplar { } impl Exemplar { + /// Create a new builder to create an [Exemplar] + pub fn builder(value: T, time: SystemTime) -> ExemplarBuilder { + ExemplarBuilder { + filtered_attributes: Vec::new(), + time, + value, + span_id: [0; 8], + trace_id: [0; 16], + } + } + /// Returns an iterator over the filtered attributes in [Exemplar]. pub fn filtered_attributes(&self) -> impl Iterator { self.filtered_attributes.iter() @@ -610,6 +1129,47 @@ impl Exemplar { } } +/// Configuration option for [Exemplar] +#[derive(Debug)] +pub struct ExemplarBuilder { + filtered_attributes: Vec, + time: SystemTime, + value: T, + span_id: [u8; 8], + trace_id: [u8; 16], +} + +impl ExemplarBuilder { + /// Sets the filtered attributes for this [Exemplar] + pub fn with_filtered_attributes(mut self, filtered_attributes: Vec) -> Self { + self.filtered_attributes = filtered_attributes; + self + } + + /// Sets the span ID for this [Exemplar] + pub fn with_span_id(mut self, span_id: [u8; 8]) -> Self { + self.span_id = span_id; + self + } + + /// Sets the trace ID for this [Exemplar] + pub fn with_trace_id(mut self, trace_id: [u8; 16]) -> Self { + self.trace_id = trace_id; + self + } + + /// Create a new [Exemplar] from this configuration + pub fn build(self) -> Exemplar { + Exemplar { + filtered_attributes: self.filtered_attributes, + time: self.time, + value: self.value, + span_id: self.span_id, + trace_id: self.trace_id, + } + } +} + #[cfg(test)] mod tests { From d3e1646f433c3865313c7cd238ebc6e25c89aa2d Mon Sep 17 00:00:00 2001 From: Davide Melfi Date: Sun, 3 May 2026 12:29:17 +0100 Subject: [PATCH 2/4] test: add coverage for the new builders --- opentelemetry-sdk/src/metrics/data/mod.rs | 146 +++++++++++++++++++++- 1 file changed, 145 insertions(+), 1 deletion(-) diff --git a/opentelemetry-sdk/src/metrics/data/mod.rs b/opentelemetry-sdk/src/metrics/data/mod.rs index e39523749c..8b1c0b1e36 100644 --- a/opentelemetry-sdk/src/metrics/data/mod.rs +++ b/opentelemetry-sdk/src/metrics/data/mod.rs @@ -1173,11 +1173,155 @@ impl ExemplarBuilder { #[cfg(test)] mod tests { - use super::{Exemplar, ExponentialHistogramDataPoint, HistogramDataPoint, SumDataPoint}; + use super::*; use opentelemetry::time::now; use opentelemetry::KeyValue; + #[test] + fn build_resource_metrics_full_tree() { + let time = now(); + let exemplar = Exemplar::builder(1.0_f64, time) + .with_filtered_attributes(vec![KeyValue::new("filtered", "attr")]) + .with_span_id([1; 8]) + .with_trace_id([2; 16]) + .build(); + + let gauge_dp = GaugeDataPoint::builder(42.0_f64) + .with_attributes(vec![KeyValue::new("host", "localhost")]) + .with_exemplars(vec![exemplar]) + .build(); + + let gauge = Gauge::builder(vec![gauge_dp], time) + .with_start_time(time) + .build(); + + let metric = Metric::builder("my_gauge", AggregatedMetrics::F64(MetricData::Gauge(gauge))) + .with_description("a test gauge") + .with_unit("ms") + .build(); + + let scope_metrics = ScopeMetrics::builder().with_metrics(vec![metric]).build(); + + let rm = ResourceMetrics::builder() + .with_resource(Resource::builder().build()) + .with_scope_metrics(vec![scope_metrics]) + .build(); + + assert_eq!(rm.scope_metrics().count(), 1); + let sm = rm.scope_metrics().next().unwrap(); + assert_eq!(sm.metrics().count(), 1); + let m = sm.metrics().next().unwrap(); + assert_eq!(m.name(), "my_gauge"); + assert_eq!(m.description(), "a test gauge"); + assert_eq!(m.unit(), "ms"); + } + + #[test] + fn build_sum_with_data_points() { + let time = now(); + let dp = SumDataPoint::builder(100_i64) + .with_attributes(vec![KeyValue::new("key", "value")]) + .build(); + + let sum = Sum::builder(vec![dp], Temporality::Cumulative, true, time, time).build(); + + assert_eq!(sum.data_points().count(), 1); + assert!(sum.is_monotonic()); + assert_eq!(sum.temporality(), Temporality::Cumulative); + let dp = sum.data_points().next().unwrap(); + assert_eq!(dp.value(), 100); + assert_eq!(dp.attributes().count(), 1); + } + + #[test] + fn build_histogram_with_optional_fields() { + let time = now(); + let dp = HistogramDataPoint::builder(10, 42.0_f64, vec![5.0, 10.0], vec![3, 5, 2]) + .with_attributes(vec![KeyValue::new("key", "val")]) + .with_min(1.0) + .with_max(20.0) + .build(); + + let histogram = Histogram::builder(vec![dp], Temporality::Delta, time, time).build(); + + assert_eq!(histogram.data_points().count(), 1); + assert_eq!(histogram.temporality(), Temporality::Delta); + let dp = histogram.data_points().next().unwrap(); + assert_eq!(dp.count(), 10); + assert_eq!(dp.sum(), 42.0); + assert_eq!(dp.min(), Some(1.0)); + assert_eq!(dp.max(), Some(20.0)); + assert_eq!(dp.bounds().collect::>(), vec![5.0, 10.0]); + assert_eq!(dp.bucket_counts().collect::>(), vec![3, 5, 2]); + } + + #[test] + fn build_exponential_histogram() { + let time = now(); + let dp = ExponentialHistogramDataPoint::builder( + 5, + 100.0_f64, + 3, + 1, + ExponentialBucket::new(0, vec![1, 2, 3]), + ExponentialBucket::new(0, vec![4, 5]), + ) + .with_attributes(vec![KeyValue::new("key", "val")]) + .with_min(10.0) + .with_max(50.0) + .with_zero_threshold(0.001) + .build(); + + let eh = + ExponentialHistogram::builder(vec![dp], Temporality::Cumulative, time, time).build(); + + assert_eq!(eh.data_points().count(), 1); + let dp = eh.data_points().next().unwrap(); + assert_eq!(dp.count(), 5); + assert_eq!(dp.scale(), 3); + assert_eq!(dp.zero_count(), 1); + assert_eq!(dp.sum(), 100.0); + assert_eq!(dp.min(), Some(10.0)); + assert_eq!(dp.max(), Some(50.0)); + assert!((dp.zero_threshold() - 0.001).abs() < f64::EPSILON); + assert_eq!(dp.positive_bucket().offset(), 0); + assert_eq!( + dp.positive_bucket().counts().collect::>(), + vec![1, 2, 3] + ); + assert_eq!( + dp.negative_bucket().counts().collect::>(), + vec![4, 5] + ); + } + + #[test] + fn build_exemplar_with_defaults() { + let time = now(); + let exemplar = Exemplar::builder(42.0_f64, time).build(); + + assert_eq!(exemplar.value, 42.0); + assert_eq!(exemplar.time(), time); + assert_eq!(exemplar.span_id(), &[0; 8]); + assert_eq!(exemplar.trace_id(), &[0; 16]); + assert_eq!(exemplar.filtered_attributes().count(), 0); + } + + #[test] + fn build_defaults_without_optional_fields() { + let rm = ResourceMetrics::builder().build(); + assert_eq!(rm.scope_metrics().count(), 0); + + let sm = ScopeMetrics::builder().build(); + assert_eq!(sm.metrics().count(), 0); + + let dp = GaugeDataPoint::builder(0.0_f64).build(); + assert_eq!(dp.value(), 0.0); + assert_eq!(dp.attributes().count(), 0); + assert_eq!(dp.exemplars().count(), 0); + } + #[test] fn validate_cloning_data_points() { let data_type = SumDataPoint { From f4c0e4f9c2b7ac0be6413bdf3b7e8edfb775d7cd Mon Sep 17 00:00:00 2001 From: Davide Melfi Date: Sun, 3 May 2026 15:31:49 +0100 Subject: [PATCH 3/4] chore: added some change --- opentelemetry-sdk/CHANGELOG.md | 1 + opentelemetry-sdk/src/metrics/data/mod.rs | 122 +++++++--------------- 2 files changed, 40 insertions(+), 83 deletions(-) diff --git a/opentelemetry-sdk/CHANGELOG.md b/opentelemetry-sdk/CHANGELOG.md index 749faf6cc2..fc39b0bcee 100644 --- a/opentelemetry-sdk/CHANGELOG.md +++ b/opentelemetry-sdk/CHANGELOG.md @@ -2,6 +2,7 @@ ## vNext +- Added public constructors for metric data types, to allow users to use public `PushMetricExporter` [#3489][3489] - Removed `SimpleConcurrentLogProcessor` and the `experimental_logs_concurrent_log_processor` feature flag. The use cases it was designed for (ETW/user_events exporters) are better served by modeling those exporters as processors directly. diff --git a/opentelemetry-sdk/src/metrics/data/mod.rs b/opentelemetry-sdk/src/metrics/data/mod.rs index 8b1c0b1e36..a3ff31f487 100644 --- a/opentelemetry-sdk/src/metrics/data/mod.rs +++ b/opentelemetry-sdk/src/metrics/data/mod.rs @@ -499,15 +499,15 @@ pub struct Sum { } impl Sum { - /// Create a new builder to create a [Sum] - pub fn builder( + /// Create a new [Sum] + pub fn new( data_points: Vec>, temporality: Temporality, is_monotonic: bool, start_time: SystemTime, time: SystemTime, - ) -> SumBuilder { - SumBuilder { + ) -> Self { + Sum { data_points, start_time, time, @@ -543,29 +543,6 @@ impl Sum { } } -/// Configuration option for [Sum] -#[derive(Debug)] -pub struct SumBuilder { - data_points: Vec>, - start_time: SystemTime, - time: SystemTime, - temporality: Temporality, - is_monotonic: bool, -} - -impl SumBuilder { - /// Create a new [Sum] from this configuration - pub fn build(self) -> Sum { - Sum { - data_points: self.data_points, - start_time: self.start_time, - time: self.time, - temporality: self.temporality, - is_monotonic: self.is_monotonic, - } - } -} - /// Represents the histogram of all measurements of values from an instrument. #[derive(Debug, Clone)] pub struct Histogram { @@ -581,14 +558,14 @@ pub struct Histogram { } impl Histogram { - /// Create a new builder to create a [Histogram] - pub fn builder( + /// Create a new [Histogram] + pub fn new( data_points: Vec>, temporality: Temporality, start_time: SystemTime, time: SystemTime, - ) -> HistogramBuilder { - HistogramBuilder { + ) -> Self { + Histogram { data_points, start_time, time, @@ -618,27 +595,6 @@ impl Histogram { } } -/// Configuration option for [Histogram] -#[derive(Debug)] -pub struct HistogramBuilder { - data_points: Vec>, - start_time: SystemTime, - time: SystemTime, - temporality: Temporality, -} - -impl HistogramBuilder { - /// Create a new [Histogram] from this configuration - pub fn build(self) -> Histogram { - Histogram { - data_points: self.data_points, - start_time: self.start_time, - time: self.time, - temporality: self.temporality, - } - } -} - /// A single histogram data point in a time series. #[derive(Debug, Clone, PartialEq)] pub struct HistogramDataPoint { @@ -795,14 +751,14 @@ pub struct ExponentialHistogram { } impl ExponentialHistogram { - /// Create a new builder to create an [ExponentialHistogram] - pub fn builder( + /// Create a new [ExponentialHistogram] + pub fn new( data_points: Vec>, temporality: Temporality, start_time: SystemTime, time: SystemTime, - ) -> ExponentialHistogramBuilder { - ExponentialHistogramBuilder { + ) -> Self { + ExponentialHistogram { data_points, start_time, time, @@ -832,27 +788,6 @@ impl ExponentialHistogram { } } -/// Configuration option for [ExponentialHistogram] -#[derive(Debug)] -pub struct ExponentialHistogramBuilder { - data_points: Vec>, - start_time: SystemTime, - time: SystemTime, - temporality: Temporality, -} - -impl ExponentialHistogramBuilder { - /// Create a new [ExponentialHistogram] from this configuration - pub fn build(self) -> ExponentialHistogram { - ExponentialHistogram { - data_points: self.data_points, - start_time: self.start_time, - time: self.time, - temporality: self.temporality, - } - } -} - /// A single exponential histogram data point in a time series. #[derive(Debug, Clone, PartialEq)] pub struct ExponentialHistogramDataPoint { @@ -1196,12 +1131,21 @@ mod tests { .with_start_time(time) .build(); - let metric = Metric::builder("my_gauge", AggregatedMetrics::F64(MetricData::Gauge(gauge))) + let data = AggregatedMetrics::F64(MetricData::Gauge(gauge)); + + let metric = Metric::builder("my_gauge", data) .with_description("a test gauge") .with_unit("ms") .build(); - let scope_metrics = ScopeMetrics::builder().with_metrics(vec![metric]).build(); + let scope = InstrumentationScope::builder("my_lib") + .with_version("0.1.0") + .build(); + + let scope_metrics = ScopeMetrics::builder() + .with_metrics(vec![metric]) + .with_scope(scope.clone()) + .build(); let rm = ResourceMetrics::builder() .with_resource(Resource::builder().build()) @@ -1211,10 +1155,15 @@ mod tests { assert_eq!(rm.scope_metrics().count(), 1); let sm = rm.scope_metrics().next().unwrap(); assert_eq!(sm.metrics().count(), 1); + assert_eq!(sm.scope(), &scope); let m = sm.metrics().next().unwrap(); assert_eq!(m.name(), "my_gauge"); assert_eq!(m.description(), "a test gauge"); assert_eq!(m.unit(), "ms"); + assert!(matches!( + m.data(), + AggregatedMetrics::F64(MetricData::Gauge(_)) + )); } #[test] @@ -1224,7 +1173,7 @@ mod tests { .with_attributes(vec![KeyValue::new("key", "value")]) .build(); - let sum = Sum::builder(vec![dp], Temporality::Cumulative, true, time, time).build(); + let sum = Sum::new(vec![dp], Temporality::Cumulative, true, time, time); assert_eq!(sum.data_points().count(), 1); assert!(sum.is_monotonic()); @@ -1243,7 +1192,7 @@ mod tests { .with_max(20.0) .build(); - let histogram = Histogram::builder(vec![dp], Temporality::Delta, time, time).build(); + let histogram = Histogram::new(vec![dp], Temporality::Delta, time, time); assert_eq!(histogram.data_points().count(), 1); assert_eq!(histogram.temporality(), Temporality::Delta); @@ -1259,6 +1208,8 @@ mod tests { #[test] fn build_exponential_histogram() { let time = now(); + let exemplar = Exemplar::builder(42.0_f64, time).build(); + let dp = ExponentialHistogramDataPoint::builder( 5, 100.0_f64, @@ -1271,12 +1222,15 @@ mod tests { .with_min(10.0) .with_max(50.0) .with_zero_threshold(0.001) + .with_exemplars(vec![exemplar]) .build(); - let eh = - ExponentialHistogram::builder(vec![dp], Temporality::Cumulative, time, time).build(); + let eh = ExponentialHistogram::new(vec![dp], Temporality::Cumulative, time, time); assert_eq!(eh.data_points().count(), 1); + assert_eq!(eh.temporality(), Temporality::Cumulative); + assert_eq!(eh.start_time(), time); + assert_eq!(eh.time(), time); let dp = eh.data_points().next().unwrap(); assert_eq!(dp.count(), 5); assert_eq!(dp.scale(), 3); @@ -1294,6 +1248,8 @@ mod tests { dp.negative_bucket().counts().collect::>(), vec![4, 5] ); + + assert_eq!(dp.exemplars().count(), 1); } #[test] From e42bbdc4239fbd7321cff2e0f28f5680f25e2d28 Mon Sep 17 00:00:00 2001 From: Davide Melfi Date: Sun, 3 May 2026 15:47:17 +0100 Subject: [PATCH 4/4] chore: add some other coverage --- opentelemetry-sdk/src/metrics/data/mod.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/opentelemetry-sdk/src/metrics/data/mod.rs b/opentelemetry-sdk/src/metrics/data/mod.rs index a3ff31f487..cee3626115 100644 --- a/opentelemetry-sdk/src/metrics/data/mod.rs +++ b/opentelemetry-sdk/src/metrics/data/mod.rs @@ -1131,6 +1131,10 @@ mod tests { .with_start_time(time) .build(); + assert_eq!(gauge.data_points().count(), 1); + assert_eq!(gauge.start_time(), Some(time)); + assert_eq!(gauge.time(), time); + let data = AggregatedMetrics::F64(MetricData::Gauge(gauge)); let metric = Metric::builder("my_gauge", data) @@ -1178,9 +1182,12 @@ mod tests { assert_eq!(sum.data_points().count(), 1); assert!(sum.is_monotonic()); assert_eq!(sum.temporality(), Temporality::Cumulative); + assert_eq!(sum.start_time(), time); + assert_eq!(sum.time(), time); let dp = sum.data_points().next().unwrap(); assert_eq!(dp.value(), 100); assert_eq!(dp.attributes().count(), 1); + assert_eq!(dp.exemplars().count(), 0); } #[test] @@ -1190,12 +1197,15 @@ mod tests { .with_attributes(vec![KeyValue::new("key", "val")]) .with_min(1.0) .with_max(20.0) + .with_exemplars(vec![Exemplar::builder(5.0_f64, time).build()]) .build(); let histogram = Histogram::new(vec![dp], Temporality::Delta, time, time); assert_eq!(histogram.data_points().count(), 1); assert_eq!(histogram.temporality(), Temporality::Delta); + assert_eq!(histogram.start_time(), time); + assert_eq!(histogram.time(), time); let dp = histogram.data_points().next().unwrap(); assert_eq!(dp.count(), 10); assert_eq!(dp.sum(), 42.0); @@ -1203,6 +1213,8 @@ mod tests { assert_eq!(dp.max(), Some(20.0)); assert_eq!(dp.bounds().collect::>(), vec![5.0, 10.0]); assert_eq!(dp.bucket_counts().collect::>(), vec![3, 5, 2]); + assert_eq!(dp.attributes().count(), 1); + assert_eq!(dp.exemplars().count(), 1); } #[test] @@ -1250,6 +1262,7 @@ mod tests { ); assert_eq!(dp.exemplars().count(), 1); + assert_eq!(dp.attributes().count(), 1); } #[test]