From af6df1fb97affaca676409915d2c26a0aa42d011 Mon Sep 17 00:00:00 2001 From: unknown <3676547+alanwest@users.noreply.github.com> Date: Tue, 21 Mar 2023 18:50:39 -0700 Subject: [PATCH 01/17] Make exponential histogram config public --- src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt | 4 ++++ src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt | 4 ++++ .../.publicApi/netstandard2.0/PublicAPI.Unshipped.txt | 4 ++++ .../.publicApi/netstandard2.1/PublicAPI.Unshipped.txt | 4 ++++ .../Metrics/Base2ExponentialBucketHistogramConfiguration.cs | 2 +- 5 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt index ac2084882d9..63f21abfa02 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt @@ -2,6 +2,10 @@ OpenTelemetry.Metrics.AlwaysOffExemplarFilter OpenTelemetry.Metrics.AlwaysOffExemplarFilter.AlwaysOffExemplarFilter() -> void OpenTelemetry.Metrics.AlwaysOnExemplarFilter OpenTelemetry.Metrics.AlwaysOnExemplarFilter.AlwaysOnExemplarFilter() -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.Base2ExponentialBucketHistogramConfiguration() -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.get -> int +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.set -> void OpenTelemetry.Metrics.Exemplar OpenTelemetry.Metrics.Exemplar.DoubleValue.get -> double OpenTelemetry.Metrics.Exemplar.Exemplar() -> void diff --git a/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt index c79ebddeb5e..5f327677c2f 100644 --- a/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt @@ -2,6 +2,10 @@ OpenTelemetry.Metrics.AlwaysOffExemplarFilter OpenTelemetry.Metrics.AlwaysOffExemplarFilter.AlwaysOffExemplarFilter() -> void OpenTelemetry.Metrics.AlwaysOnExemplarFilter OpenTelemetry.Metrics.AlwaysOnExemplarFilter.AlwaysOnExemplarFilter() -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.Base2ExponentialBucketHistogramConfiguration() -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.get -> int +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.set -> void OpenTelemetry.Metrics.Exemplar OpenTelemetry.Metrics.Exemplar.DoubleValue.get -> double OpenTelemetry.Metrics.Exemplar.Exemplar() -> void diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index c79ebddeb5e..5f327677c2f 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -2,6 +2,10 @@ OpenTelemetry.Metrics.AlwaysOffExemplarFilter OpenTelemetry.Metrics.AlwaysOffExemplarFilter.AlwaysOffExemplarFilter() -> void OpenTelemetry.Metrics.AlwaysOnExemplarFilter OpenTelemetry.Metrics.AlwaysOnExemplarFilter.AlwaysOnExemplarFilter() -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.Base2ExponentialBucketHistogramConfiguration() -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.get -> int +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.set -> void OpenTelemetry.Metrics.Exemplar OpenTelemetry.Metrics.Exemplar.DoubleValue.get -> double OpenTelemetry.Metrics.Exemplar.Exemplar() -> void diff --git a/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt index c79ebddeb5e..5f327677c2f 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt @@ -2,6 +2,10 @@ OpenTelemetry.Metrics.AlwaysOffExemplarFilter OpenTelemetry.Metrics.AlwaysOffExemplarFilter.AlwaysOffExemplarFilter() -> void OpenTelemetry.Metrics.AlwaysOnExemplarFilter OpenTelemetry.Metrics.AlwaysOnExemplarFilter.AlwaysOnExemplarFilter() -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.Base2ExponentialBucketHistogramConfiguration() -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.get -> int +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.set -> void OpenTelemetry.Metrics.Exemplar OpenTelemetry.Metrics.Exemplar.DoubleValue.get -> double OpenTelemetry.Metrics.Exemplar.Exemplar() -> void diff --git a/src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogramConfiguration.cs b/src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogramConfiguration.cs index f6f7db9df19..34176b944d4 100644 --- a/src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogramConfiguration.cs +++ b/src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogramConfiguration.cs @@ -19,7 +19,7 @@ namespace OpenTelemetry.Metrics; /// /// Stores configuration for a histogram metric stream with base-2 exponential bucket boundaries. /// -internal sealed class Base2ExponentialBucketHistogramConfiguration : HistogramConfiguration +public sealed class Base2ExponentialBucketHistogramConfiguration : HistogramConfiguration { private int maxSize = 160; From 7ffdbd336461b449cc2c4a77e5b95f671dad0dfc Mon Sep 17 00:00:00 2001 From: unknown <3676547+alanwest@users.noreply.github.com> Date: Tue, 21 Mar 2023 18:51:36 -0700 Subject: [PATCH 02/17] Stub out console exporter support --- .../ConsoleMetricExporter.cs | 61 +++++++++++-------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs b/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs index f66a34ba756..0c2b01d8236 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs @@ -92,7 +92,7 @@ public override ExportResult Export(in Batch batch) var metricType = metric.MetricType; - if (metricType.IsHistogram()) + if (metricType == MetricType.Histogram || metricType == MetricType.ExponentialHistogram) { var bucketsBuilder = new StringBuilder(); var sum = metricPoint.GetHistogramSum(); @@ -105,44 +105,53 @@ public override ExportResult Export(in Batch batch) bucketsBuilder.AppendLine(); - bool isFirstIteration = true; - double previousExplicitBound = default; - foreach (var histogramMeasurement in metricPoint.GetHistogramBuckets()) + if (metricType == MetricType.Histogram) { - if (isFirstIteration) + bool isFirstIteration = true; + double previousExplicitBound = default; + foreach (var histogramMeasurement in metricPoint.GetHistogramBuckets()) { - bucketsBuilder.Append("(-Infinity,"); - bucketsBuilder.Append(histogramMeasurement.ExplicitBound); - bucketsBuilder.Append(']'); - bucketsBuilder.Append(':'); - bucketsBuilder.Append(histogramMeasurement.BucketCount); - previousExplicitBound = histogramMeasurement.ExplicitBound; - isFirstIteration = false; - } - else - { - bucketsBuilder.Append('('); - bucketsBuilder.Append(previousExplicitBound); - bucketsBuilder.Append(','); - if (histogramMeasurement.ExplicitBound != double.PositiveInfinity) + if (isFirstIteration) { + bucketsBuilder.Append("(-Infinity,"); bucketsBuilder.Append(histogramMeasurement.ExplicitBound); + bucketsBuilder.Append(']'); + bucketsBuilder.Append(':'); + bucketsBuilder.Append(histogramMeasurement.BucketCount); previousExplicitBound = histogramMeasurement.ExplicitBound; + isFirstIteration = false; } else { - bucketsBuilder.Append("+Infinity"); + bucketsBuilder.Append('('); + bucketsBuilder.Append(previousExplicitBound); + bucketsBuilder.Append(','); + if (histogramMeasurement.ExplicitBound != double.PositiveInfinity) + { + bucketsBuilder.Append(histogramMeasurement.ExplicitBound); + previousExplicitBound = histogramMeasurement.ExplicitBound; + } + else + { + bucketsBuilder.Append("+Infinity"); + } + + bucketsBuilder.Append(']'); + bucketsBuilder.Append(':'); + bucketsBuilder.Append(histogramMeasurement.BucketCount); } - bucketsBuilder.Append(']'); - bucketsBuilder.Append(':'); - bucketsBuilder.Append(histogramMeasurement.BucketCount); + bucketsBuilder.AppendLine(); } - bucketsBuilder.AppendLine(); + valueDisplay = bucketsBuilder.ToString(); + } + else + { + // TODO: Consider how/if to display buckets for exponential histograms. + // If I recall, the collector's console exporter does not currently display buckets in any way. + // Java I think does display buckets, but also Java writes to the console in OTLP/JSON format. } - - valueDisplay = bucketsBuilder.ToString(); } else if (metricType.IsDouble()) { From 0fb670d00be53d9452d0e3e9c756502f9a65c72b Mon Sep 17 00:00:00 2001 From: unknown <3676547+alanwest@users.noreply.github.com> Date: Tue, 21 Mar 2023 18:52:12 -0700 Subject: [PATCH 03/17] Add TODO in prometheus exporter --- .../Internal/PrometheusSerializerExt.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs index 445e0410c81..5b77ccbe2ad 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs @@ -99,7 +99,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric) buffer[cursor++] = ASCII_LINEFEED; } } - else + else if (metric.MetricType.IsHistogram()) { foreach (ref readonly var metricPoint in metric.GetMetricPoints()) { @@ -194,6 +194,10 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric) buffer[cursor++] = ASCII_LINEFEED; } } + else + { + // TODO: Support exponential histogram. + } buffer[cursor++] = ASCII_LINEFEED; From b7b1dad0d59e4dc3f48b555b4ac004983bfcf111 Mon Sep 17 00:00:00 2001 From: unknown <3676547+alanwest@users.noreply.github.com> Date: Tue, 21 Mar 2023 18:52:55 -0700 Subject: [PATCH 04/17] Add OTLP exporter support for exponential histograms --- .../Implementation/MetricItemExtensions.cs | 51 ++++++++++ .../OtlpMetricsExporterTests.cs | 97 +++++++++++++++++++ 2 files changed, 148 insertions(+) diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs index 9208eaa3e99..9042c52c35c 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs @@ -307,6 +307,57 @@ internal static OtlpMetrics.Metric ToOtlpMetric(this Metric metric) otlpMetric.Histogram = histogram; break; } + + case MetricType.ExponentialHistogram: + { + var histogram = new OtlpMetrics.ExponentialHistogram + { + AggregationTemporality = temporality, + }; + + foreach (ref readonly var metricPoint in metric.GetMetricPoints()) + { + var dataPoint = new OtlpMetrics.ExponentialHistogramDataPoint + { + StartTimeUnixNano = (ulong)metricPoint.StartTime.ToUnixTimeNanoseconds(), + TimeUnixNano = (ulong)metricPoint.EndTime.ToUnixTimeNanoseconds(), + }; + + AddAttributes(metricPoint.Tags, dataPoint.Attributes); + dataPoint.Count = (ulong)metricPoint.GetHistogramCount(); + dataPoint.Sum = metricPoint.GetHistogramSum(); + + if (metricPoint.TryGetHistogramMinMaxValues(out double min, out double max)) + { + dataPoint.Min = min; + dataPoint.Max = max; + } + + var buckets = metricPoint.GetExponentialHistogramData(); + dataPoint.Scale = buckets.Scale; + + dataPoint.Positive = new OtlpMetrics.ExponentialHistogramDataPoint.Types.Buckets(); + dataPoint.Positive.Offset = buckets.PositiveBuckets.Offset; + foreach (var bucketCount in buckets.PositiveBuckets) + { + dataPoint.Positive.BucketCounts.Add((ulong)bucketCount); + } + + dataPoint.Negative = new OtlpMetrics.ExponentialHistogramDataPoint.Types.Buckets(); + dataPoint.Negative.Offset = buckets.NegativeBuckets.Offset; + foreach (var bucketCount in buckets.NegativeBuckets) + { + dataPoint.Negative.BucketCounts.Add((ulong)bucketCount); + } + + // TODO: exemplars. + + histogram.DataPoints.Add(dataPoint); + } + + otlpMetric.ExponentialHistogram = histogram; + break; + } } return otlpMetric; diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs index 48e41e3fb86..49b13041bd1 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs @@ -469,6 +469,103 @@ public void TestUpDownCounterToOtlpMetric(string name, string description, strin Assert.Empty(dataPoint.Exemplars); } + [Theory] + [InlineData("test_histogram", null, null, 123L, null, MetricReaderTemporalityPreference.Cumulative)] + [InlineData("test_histogram", null, null, null, 123.45, MetricReaderTemporalityPreference.Cumulative)] + [InlineData("test_histogram", null, null, 123L, null, MetricReaderTemporalityPreference.Delta)] + [InlineData("test_histogram", "description", "unit", 123L, null, MetricReaderTemporalityPreference.Cumulative)] + [InlineData("test_histogram", null, null, 123L, null, MetricReaderTemporalityPreference.Delta, "key1", "value1", "key2", 123)] + public void TestExponentialHistogramToOtlpMetric(string name, string description, string unit, long? longValue, double? doubleValue, MetricReaderTemporalityPreference aggregationTemporality, params object[] keysValues) + { + var metrics = new List(); + + using var meter = new Meter(Utils.GetCurrentMethodName()); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddInMemoryExporter(metrics, metricReaderOptions => + { + metricReaderOptions.TemporalityPreference = aggregationTemporality; + }) + .AddView(instrument => + { + return new Base2ExponentialBucketHistogramConfiguration(); + }) + .Build(); + + var attributes = ToAttributes(keysValues).ToArray(); + if (longValue.HasValue) + { + var histogram = meter.CreateHistogram(name, unit, description); + histogram.Record(longValue.Value, attributes); + } + else + { + var histogram = meter.CreateHistogram(name, unit, description); + histogram.Record(doubleValue.Value, attributes); + } + + provider.ForceFlush(); + + var batch = new Batch(metrics.ToArray(), metrics.Count); + + var request = new OtlpCollector.ExportMetricsServiceRequest(); + request.AddMetrics(ResourceBuilder.CreateEmpty().Build().ToOtlpResource(), batch); + + var resourceMetric = request.ResourceMetrics.Single(); + var scopeMetrics = resourceMetric.ScopeMetrics.Single(); + var actual = scopeMetrics.Metrics.Single(); + + Assert.Equal(name, actual.Name); + Assert.Equal(description ?? string.Empty, actual.Description); + Assert.Equal(unit ?? string.Empty, actual.Unit); + + Assert.Equal(OtlpMetrics.Metric.DataOneofCase.ExponentialHistogram, actual.DataCase); + + Assert.Null(actual.Gauge); + Assert.Null(actual.Sum); + Assert.Null(actual.Histogram); + Assert.NotNull(actual.ExponentialHistogram); + Assert.Null(actual.Summary); + + var otlpAggregationTemporality = aggregationTemporality == MetricReaderTemporalityPreference.Cumulative + ? OtlpMetrics.AggregationTemporality.Cumulative + : OtlpMetrics.AggregationTemporality.Delta; + Assert.Equal(otlpAggregationTemporality, actual.ExponentialHistogram.AggregationTemporality); + + Assert.Single(actual.ExponentialHistogram.DataPoints); + var dataPoint = actual.ExponentialHistogram.DataPoints.First(); + Assert.True(dataPoint.StartTimeUnixNano > 0); + Assert.True(dataPoint.TimeUnixNano > 0); + + Assert.Equal(1UL, dataPoint.Count); + + if (longValue.HasValue) + { + Assert.Equal((double)longValue, dataPoint.Sum); + } + else + { + Assert.Equal(doubleValue, dataPoint.Sum); + } + + Assert.Equal(0UL, dataPoint.ZeroCount); + Assert.Equal(20, dataPoint.Scale); + Assert.True(dataPoint.Positive.Offset > 0); + Assert.Equal(1UL, dataPoint.Positive.BucketCounts[0]); + Assert.True(dataPoint.Negative.Offset <= 0); + + if (attributes.Length > 0) + { + OtlpTestHelpers.AssertOtlpAttributes(attributes, dataPoint.Attributes); + } + else + { + Assert.Empty(dataPoint.Attributes); + } + + Assert.Empty(dataPoint.Exemplars); + } + [Theory] [InlineData("test_histogram", null, null, 123L, null, MetricReaderTemporalityPreference.Cumulative)] [InlineData("test_histogram", null, null, null, 123.45, MetricReaderTemporalityPreference.Cumulative)] From 763ed8d1f5f3b1e1954a06f1106101eb34bd08a0 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Wed, 22 Mar 2023 16:01:04 -0700 Subject: [PATCH 05/17] Fix console exporter --- src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs b/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs index 0c2b01d8236..aaaec95ab45 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs @@ -143,8 +143,6 @@ public override ExportResult Export(in Batch batch) bucketsBuilder.AppendLine(); } - - valueDisplay = bucketsBuilder.ToString(); } else { @@ -152,6 +150,8 @@ public override ExportResult Export(in Batch batch) // If I recall, the collector's console exporter does not currently display buckets in any way. // Java I think does display buckets, but also Java writes to the console in OTLP/JSON format. } + + valueDisplay = bucketsBuilder.ToString(); } else if (metricType.IsDouble()) { From c05bea82c2bf68c845b645ccd4bb3dfc2881edce Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Wed, 22 Mar 2023 17:48:48 -0700 Subject: [PATCH 06/17] Add exponential histogram config option to example ASP.NET Core app --- examples/AspNetCore/Program.cs | 20 ++++++++++++++++++++ examples/AspNetCore/appsettings.json | 1 + 2 files changed, 21 insertions(+) diff --git a/examples/AspNetCore/Program.cs b/examples/AspNetCore/Program.cs index 8835f260fba..bc4e0602cda 100644 --- a/examples/AspNetCore/Program.cs +++ b/examples/AspNetCore/Program.cs @@ -14,6 +14,7 @@ // limitations under the License. // +using System.Diagnostics.Metrics; using Examples.AspNetCore; using OpenTelemetry.Exporter; using OpenTelemetry.Instrumentation.AspNetCore; @@ -33,6 +34,9 @@ // Note: Switch between Console/OTLP by setting UseLogExporter in appsettings.json. var logExporter = appBuilder.Configuration.GetValue("UseLogExporter").ToLowerInvariant(); +// Note: Switch between Explicit/Exponential by setting HistogramAggregation in appsettings.json +var histogramAggregation = appBuilder.Configuration.GetValue("HistogramAggregation").ToLowerInvariant(); + // Build a resource configuration action to set service information. Action configureResource = r => r.AddService( serviceName: appBuilder.Configuration.GetValue("ServiceName"), @@ -111,6 +115,22 @@ .AddHttpClientInstrumentation() .AddAspNetCoreInstrumentation(); + switch (histogramAggregation) + { + case "exponential": + builder.AddView(instrument => + { + return instrument.GetType().GetGenericTypeDefinition() == typeof(Histogram<>) + ? new Base2ExponentialBucketHistogramConfiguration() + : null; + }); + break; + default: + // Explicit bounds histogram is the default. + // No additional configuration necessary. + break; + } + switch (metricsExporter) { case "prometheus": diff --git a/examples/AspNetCore/appsettings.json b/examples/AspNetCore/appsettings.json index 19f0513276b..1f2d3368dc9 100644 --- a/examples/AspNetCore/appsettings.json +++ b/examples/AspNetCore/appsettings.json @@ -14,6 +14,7 @@ "UseTracingExporter": "console", "UseMetricsExporter": "console", "UseLogExporter": "console", + "HistogramAggregation": "explicit", "Jaeger": { "AgentHost": "localhost", "AgentPort": 6831, From 330f1819f8ca1208cd7a33290bef15567d30c91c Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Wed, 22 Mar 2023 17:49:18 -0700 Subject: [PATCH 07/17] Update TODO in console exporter --- src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs b/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs index aaaec95ab45..2c1da352ad1 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs @@ -147,8 +147,6 @@ public override ExportResult Export(in Batch batch) else { // TODO: Consider how/if to display buckets for exponential histograms. - // If I recall, the collector's console exporter does not currently display buckets in any way. - // Java I think does display buckets, but also Java writes to the console in OTLP/JSON format. } valueDisplay = bucketsBuilder.ToString(); From a76536a7a0ea2ccf6798f346feb0b1d61bfe3974 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Wed, 22 Mar 2023 17:51:16 -0700 Subject: [PATCH 08/17] Fix whitespace --- .../OtlpMetricsExporterTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs index 49b13041bd1..9b6e8f5c87f 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs @@ -486,10 +486,10 @@ public void TestExponentialHistogramToOtlpMetric(string name, string description { metricReaderOptions.TemporalityPreference = aggregationTemporality; }) - .AddView(instrument => - { - return new Base2ExponentialBucketHistogramConfiguration(); - }) + .AddView(instrument => + { + return new Base2ExponentialBucketHistogramConfiguration(); + }) .Build(); var attributes = ToAttributes(keysValues).ToArray(); From 39f6f08e174219d133c45891d4f8e535d8db600f Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Mon, 27 Mar 2023 11:34:29 -0700 Subject: [PATCH 09/17] Update changelogs --- .../CHANGELOG.md | 4 ++++ src/OpenTelemetry/CHANGELOG.md | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md index 7fad8554f47..f48c3f782d6 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +* Add support for exporting histograms aggregated using the + [Base2 Exponential Bucket Histogram Aggregation](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#base2-exponential-bucket-histogram-aggregation). + ([#TBD](https://github.com/open-telemetry/opentelemetry-dotnet/pull/TBD)) + ## 1.5.0-alpha.1 Released 2023-Mar-07 diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md index a7b9a3e133a..faf62d493fa 100644 --- a/src/OpenTelemetry/CHANGELOG.md +++ b/src/OpenTelemetry/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +* Add support for configuring the + [Base2 Exponential Bucket Histogram Aggregation](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#base2-exponential-bucket-histogram-aggregation). + This aggregation is supported by OTLP but not yet by Prometheus. + ([#TBD](https://github.com/open-telemetry/opentelemetry-dotnet/pull/TBD)) + * Implementation of `SuppressInstrumentationScope` changed to improve performance. ([#4304](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4304)) From 97e3fbcf8599f8364ce5f2e06ca75cc300adedd6 Mon Sep 17 00:00:00 2001 From: unknown <3676547+alanwest@users.noreply.github.com> Date: Mon, 27 Mar 2023 11:47:04 -0700 Subject: [PATCH 10/17] Update public api --- src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt | 2 ++ src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt | 2 ++ .../.publicApi/netstandard2.0/PublicAPI.Unshipped.txt | 2 ++ .../.publicApi/netstandard2.1/PublicAPI.Unshipped.txt | 2 ++ 4 files changed, 8 insertions(+) diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt index 63f21abfa02..d0ed4d4dec8 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt @@ -4,6 +4,8 @@ OpenTelemetry.Metrics.AlwaysOnExemplarFilter OpenTelemetry.Metrics.AlwaysOnExemplarFilter.AlwaysOnExemplarFilter() -> void OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.Base2ExponentialBucketHistogramConfiguration() -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.get -> int +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.set -> void OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.get -> int OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.set -> void OpenTelemetry.Metrics.Exemplar diff --git a/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt index 5f327677c2f..a06e307d7c4 100644 --- a/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt @@ -4,6 +4,8 @@ OpenTelemetry.Metrics.AlwaysOnExemplarFilter OpenTelemetry.Metrics.AlwaysOnExemplarFilter.AlwaysOnExemplarFilter() -> void OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.Base2ExponentialBucketHistogramConfiguration() -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.get -> int +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.set -> void OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.get -> int OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.set -> void OpenTelemetry.Metrics.Exemplar diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index 5f327677c2f..a06e307d7c4 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -4,6 +4,8 @@ OpenTelemetry.Metrics.AlwaysOnExemplarFilter OpenTelemetry.Metrics.AlwaysOnExemplarFilter.AlwaysOnExemplarFilter() -> void OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.Base2ExponentialBucketHistogramConfiguration() -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.get -> int +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.set -> void OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.get -> int OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.set -> void OpenTelemetry.Metrics.Exemplar diff --git a/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt index 5f327677c2f..a06e307d7c4 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt @@ -4,6 +4,8 @@ OpenTelemetry.Metrics.AlwaysOnExemplarFilter OpenTelemetry.Metrics.AlwaysOnExemplarFilter.AlwaysOnExemplarFilter() -> void OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.Base2ExponentialBucketHistogramConfiguration() -> void +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.get -> int +OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxScale.set -> void OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.get -> int OpenTelemetry.Metrics.Base2ExponentialBucketHistogramConfiguration.MaxSize.set -> void OpenTelemetry.Metrics.Exemplar From 035663cd2e8e9b4017bd3d3e191e36a60425a4d1 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Mon, 27 Mar 2023 12:28:57 -0700 Subject: [PATCH 11/17] Update PR number --- src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md | 2 +- src/OpenTelemetry/CHANGELOG.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md index f48c3f782d6..4aa495e2ccc 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md @@ -4,7 +4,7 @@ * Add support for exporting histograms aggregated using the [Base2 Exponential Bucket Histogram Aggregation](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#base2-exponential-bucket-histogram-aggregation). - ([#TBD](https://github.com/open-telemetry/opentelemetry-dotnet/pull/TBD)) + ([#4337](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4337)) ## 1.5.0-alpha.1 diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md index faf62d493fa..57f7fdb1ebb 100644 --- a/src/OpenTelemetry/CHANGELOG.md +++ b/src/OpenTelemetry/CHANGELOG.md @@ -5,7 +5,7 @@ * Add support for configuring the [Base2 Exponential Bucket Histogram Aggregation](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#base2-exponential-bucket-histogram-aggregation). This aggregation is supported by OTLP but not yet by Prometheus. - ([#TBD](https://github.com/open-telemetry/opentelemetry-dotnet/pull/TBD)) + ([#4337](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4337)) * Implementation of `SuppressInstrumentationScope` changed to improve performance. From 7806a75be5a4baa5d83789da673f478b368e7a0d Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Mon, 27 Mar 2023 14:01:12 -0700 Subject: [PATCH 12/17] Do not use IsHistogram() --- .../Internal/PrometheusSerializerExt.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs index 5b77ccbe2ad..0de749716a7 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs @@ -99,7 +99,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric) buffer[cursor++] = ASCII_LINEFEED; } } - else if (metric.MetricType.IsHistogram()) + else if (metric.MetricType == MetricType.Histogram) { foreach (ref readonly var metricPoint in metric.GetMetricPoints()) { From 7a1dcbdbe58af27de4763a0c4e185dd305e07d56 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Mon, 27 Mar 2023 14:06:52 -0700 Subject: [PATCH 13/17] Do not use IsHistogram() --- .../Internal/PrometheusSerializerExt.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs index 0de749716a7..a09b4fbf3f4 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs @@ -41,7 +41,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric) cursor = WriteUnitMetadata(buffer, cursor, metric.Name, metric.Unit); cursor = WriteHelpMetadata(buffer, cursor, metric.Name, metric.Unit, metric.Description); - if (!metric.MetricType.IsHistogram()) + if (metric.MetricType != MetricType.Histogram && metric.MetricType != MetricType.ExponentialHistogram) { foreach (ref readonly var metricPoint in metric.GetMetricPoints()) { From 20cda0d017de887c37ee97e2407ddcef5b034b5a Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Mon, 27 Mar 2023 14:09:56 -0700 Subject: [PATCH 14/17] Update changelog --- src/OpenTelemetry/CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md index 57f7fdb1ebb..33ac5c0f5f0 100644 --- a/src/OpenTelemetry/CHANGELOG.md +++ b/src/OpenTelemetry/CHANGELOG.md @@ -3,8 +3,9 @@ ## Unreleased * Add support for configuring the - [Base2 Exponential Bucket Histogram Aggregation](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#base2-exponential-bucket-histogram-aggregation). - This aggregation is supported by OTLP but not yet by Prometheus. + [Base2 Exponential Bucket Histogram Aggregation](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#base2-exponential-bucket-histogram-aggregation) + using the `AddView` API. This aggregation is supported by OTLP but not yet by + Prometheus. ([#4337](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4337)) * Implementation of `SuppressInstrumentationScope` changed to improve From 8d017ba98651b013deee23f13ee781b471d45a76 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Mon, 27 Mar 2023 14:19:34 -0700 Subject: [PATCH 15/17] 3rd times the charm. Really have Prometheus ignore exponential histograms. --- .../Internal/PrometheusSerializerExt.cs | 15 ++++++----- .../PrometheusSerializerTests.cs | 25 +++++++++++++++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs index a09b4fbf3f4..3094129cc7e 100644 --- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs +++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs @@ -36,12 +36,19 @@ UpDownCounter becomes gauge public static int WriteMetric(byte[] buffer, int cursor, Metric metric) { + if (metric.MetricType == MetricType.ExponentialHistogram) + { + // Exponential histograms are not yet support by Prometheus. + // They are ignored for now. + return cursor; + } + int metricType = (int)metric.MetricType >> 4; cursor = WriteTypeMetadata(buffer, cursor, metric.Name, metric.Unit, MetricTypes[metricType]); cursor = WriteUnitMetadata(buffer, cursor, metric.Name, metric.Unit); cursor = WriteHelpMetadata(buffer, cursor, metric.Name, metric.Unit, metric.Description); - if (metric.MetricType != MetricType.Histogram && metric.MetricType != MetricType.ExponentialHistogram) + if (!metric.MetricType.IsHistogram()) { foreach (ref readonly var metricPoint in metric.GetMetricPoints()) { @@ -99,7 +106,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric) buffer[cursor++] = ASCII_LINEFEED; } } - else if (metric.MetricType == MetricType.Histogram) + else { foreach (ref readonly var metricPoint in metric.GetMetricPoints()) { @@ -194,10 +201,6 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric) buffer[cursor++] = ASCII_LINEFEED; } } - else - { - // TODO: Support exponential histogram. - } buffer[cursor++] = ASCII_LINEFEED; diff --git a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs index 9970be5c83d..4819867c527 100644 --- a/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs +++ b/test/OpenTelemetry.Exporter.Prometheus.HttpListener.Tests/PrometheusSerializerTests.cs @@ -462,5 +462,30 @@ public void HistogramNaN() + "$").Replace('\'', '"'), Encoding.UTF8.GetString(buffer, 0, cursor)); } + + [Fact] + public void ExponentialHistogramIsIgnoredForNow() + { + var buffer = new byte[85000]; + var metrics = new List(); + + using var meter = new Meter(Utils.GetCurrentMethodName()); + using var provider = Sdk.CreateMeterProviderBuilder() + .AddMeter(meter.Name) + .AddView(instrument => new Base2ExponentialBucketHistogramConfiguration()) + .AddInMemoryExporter(metrics) + .Build(); + + var histogram = meter.CreateHistogram("test_histogram"); + histogram.Record(18); + histogram.Record(100); + + provider.ForceFlush(); + + var cursor = PrometheusSerializer.WriteMetric(buffer, 0, metrics[0]); + Assert.Matches( + "^$", + Encoding.UTF8.GetString(buffer, 0, cursor)); + } } } From 5f69b7bab8eb273fe64c902a5eb3553a59447687 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Mon, 27 Mar 2023 14:32:11 -0700 Subject: [PATCH 16/17] Add message about not displaying buckets --- src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs b/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs index 2c1da352ad1..fc9d13de06b 100644 --- a/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs +++ b/src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs @@ -147,6 +147,7 @@ public override ExportResult Export(in Batch batch) else { // TODO: Consider how/if to display buckets for exponential histograms. + bucketsBuilder.AppendLine("Buckets are not displayed for exponential histograms."); } valueDisplay = bucketsBuilder.ToString(); From 75ad3492207cd098ad5c6571c7bb40a71893aad9 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Mon, 27 Mar 2023 21:02:26 -0700 Subject: [PATCH 17/17] Rename buckets to exponentialHistogramData --- .../Implementation/MetricItemExtensions.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs index 9042c52c35c..f63d95f3647 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/MetricItemExtensions.cs @@ -333,19 +333,19 @@ internal static OtlpMetrics.Metric ToOtlpMetric(this Metric metric) dataPoint.Max = max; } - var buckets = metricPoint.GetExponentialHistogramData(); - dataPoint.Scale = buckets.Scale; + var exponentialHistogramData = metricPoint.GetExponentialHistogramData(); + dataPoint.Scale = exponentialHistogramData.Scale; dataPoint.Positive = new OtlpMetrics.ExponentialHistogramDataPoint.Types.Buckets(); - dataPoint.Positive.Offset = buckets.PositiveBuckets.Offset; - foreach (var bucketCount in buckets.PositiveBuckets) + dataPoint.Positive.Offset = exponentialHistogramData.PositiveBuckets.Offset; + foreach (var bucketCount in exponentialHistogramData.PositiveBuckets) { dataPoint.Positive.BucketCounts.Add((ulong)bucketCount); } dataPoint.Negative = new OtlpMetrics.ExponentialHistogramDataPoint.Types.Buckets(); - dataPoint.Negative.Offset = buckets.NegativeBuckets.Offset; - foreach (var bucketCount in buckets.NegativeBuckets) + dataPoint.Negative.Offset = exponentialHistogramData.NegativeBuckets.Offset; + foreach (var bucketCount in exponentialHistogramData.NegativeBuckets) { dataPoint.Negative.BucketCounts.Add((ulong)bucketCount); }