From 2acba3afe504edd8181f119ea1425ca9ab64e4cc Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Thu, 29 Feb 2024 21:36:21 -0800 Subject: [PATCH 1/8] ExemplarFilter updates to match specification. --- .../Experimental/PublicAPI.Unshipped.txt | 22 ++---- .../Builder/MeterProviderBuilderExtensions.cs | 52 ++++++++----- .../Exemplar/AlwaysOffExemplarFilter.cs | 16 +--- .../Exemplar/AlwaysOnExemplarFilter.cs | 19 +---- .../Metrics/Exemplar/ExemplarFilter.cs | 16 +--- .../Metrics/Exemplar/ExemplarFilterType.cs | 48 ++++++++++++ .../Exemplar/TraceBasedExemplarFilter.cs | 16 +--- test/Benchmarks/Metrics/ExemplarBenchmarks.cs | 73 +++++++++++++------ .../OtlpMetricsExporterTests.cs | 12 +-- .../Program.cs | 2 +- .../Metrics/MetricExemplarTests.cs | 12 +-- 11 files changed, 157 insertions(+), 131 deletions(-) create mode 100644 src/OpenTelemetry/Metrics/Exemplar/ExemplarFilterType.cs diff --git a/src/OpenTelemetry/.publicApi/Experimental/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/Experimental/PublicAPI.Unshipped.txt index c174b5b3d63..8c1a746f041 100644 --- a/src/OpenTelemetry/.publicApi/Experimental/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/Experimental/PublicAPI.Unshipped.txt @@ -5,10 +5,6 @@ OpenTelemetry.Logs.LogRecord.Severity.get -> OpenTelemetry.Logs.LogRecordSeverit OpenTelemetry.Logs.LogRecord.Severity.set -> void OpenTelemetry.Logs.LogRecord.SeverityText.get -> string? OpenTelemetry.Logs.LogRecord.SeverityText.set -> void -OpenTelemetry.Metrics.AlwaysOffExemplarFilter -OpenTelemetry.Metrics.AlwaysOffExemplarFilter.AlwaysOffExemplarFilter() -> void -OpenTelemetry.Metrics.AlwaysOnExemplarFilter -OpenTelemetry.Metrics.AlwaysOnExemplarFilter.AlwaysOnExemplarFilter() -> void OpenTelemetry.Metrics.Exemplar OpenTelemetry.Metrics.Exemplar.DoubleValue.get -> double OpenTelemetry.Metrics.Exemplar.Exemplar() -> void @@ -17,8 +13,10 @@ OpenTelemetry.Metrics.Exemplar.LongValue.get -> long OpenTelemetry.Metrics.Exemplar.SpanId.get -> System.Diagnostics.ActivitySpanId OpenTelemetry.Metrics.Exemplar.Timestamp.get -> System.DateTimeOffset OpenTelemetry.Metrics.Exemplar.TraceId.get -> System.Diagnostics.ActivityTraceId -OpenTelemetry.Metrics.ExemplarFilter -OpenTelemetry.Metrics.ExemplarFilter.ExemplarFilter() -> void +OpenTelemetry.Metrics.ExemplarFilterType +OpenTelemetry.Metrics.ExemplarFilterType.AlwaysOff = 0 -> OpenTelemetry.Metrics.ExemplarFilterType +OpenTelemetry.Metrics.ExemplarFilterType.AlwaysOn = 1 -> OpenTelemetry.Metrics.ExemplarFilterType +OpenTelemetry.Metrics.ExemplarFilterType.TraceBased = 2 -> OpenTelemetry.Metrics.ExemplarFilterType OpenTelemetry.Metrics.ExemplarMeasurement OpenTelemetry.Metrics.ExemplarMeasurement.ExemplarMeasurement() -> void OpenTelemetry.Metrics.ExemplarMeasurement.Tags.get -> System.ReadOnlySpan> @@ -33,8 +31,6 @@ OpenTelemetry.Metrics.ReadOnlyExemplarCollection.Enumerator.Enumerator() -> void OpenTelemetry.Metrics.ReadOnlyExemplarCollection.Enumerator.MoveNext() -> bool OpenTelemetry.Metrics.ReadOnlyExemplarCollection.GetEnumerator() -> OpenTelemetry.Metrics.ReadOnlyExemplarCollection.Enumerator OpenTelemetry.Metrics.ReadOnlyExemplarCollection.ReadOnlyExemplarCollection() -> void -OpenTelemetry.Metrics.TraceBasedExemplarFilter -OpenTelemetry.Metrics.TraceBasedExemplarFilter.TraceBasedExemplarFilter() -> void OpenTelemetry.ReadOnlyFilteredTagCollection OpenTelemetry.ReadOnlyFilteredTagCollection.Enumerator OpenTelemetry.ReadOnlyFilteredTagCollection.Enumerator.Current.get -> System.Collections.Generic.KeyValuePair @@ -51,19 +47,11 @@ static OpenTelemetry.Logs.LoggerProviderBuilderExtensions.SetResourceBuilder(thi static OpenTelemetry.Logs.LoggerProviderExtensions.AddProcessor(this OpenTelemetry.Logs.LoggerProvider! provider, OpenTelemetry.BaseProcessor! processor) -> OpenTelemetry.Logs.LoggerProvider! static OpenTelemetry.Logs.LoggerProviderExtensions.ForceFlush(this OpenTelemetry.Logs.LoggerProvider! provider, int timeoutMilliseconds = -1) -> bool static OpenTelemetry.Logs.LoggerProviderExtensions.Shutdown(this OpenTelemetry.Logs.LoggerProvider! provider, int timeoutMilliseconds = -1) -> bool -static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.SetExemplarFilter(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder, OpenTelemetry.Metrics.ExemplarFilter! exemplarFilter) -> OpenTelemetry.Metrics.MeterProviderBuilder! +static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.SetExemplarFilter(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder, OpenTelemetry.Metrics.ExemplarFilterType exemplarFilter = OpenTelemetry.Metrics.ExemplarFilterType.TraceBased) -> OpenTelemetry.Metrics.MeterProviderBuilder! static OpenTelemetry.OpenTelemetryBuilderSdkExtensions.WithLogging(this OpenTelemetry.IOpenTelemetryBuilder! builder) -> OpenTelemetry.IOpenTelemetryBuilder! static OpenTelemetry.OpenTelemetryBuilderSdkExtensions.WithLogging(this OpenTelemetry.IOpenTelemetryBuilder! builder, System.Action! configure) -> OpenTelemetry.IOpenTelemetryBuilder! static OpenTelemetry.OpenTelemetryBuilderSdkExtensions.WithLogging(this OpenTelemetry.IOpenTelemetryBuilder! builder, System.Action? configureBuilder, System.Action? configureOptions) -> OpenTelemetry.IOpenTelemetryBuilder! static OpenTelemetry.Sdk.CreateLoggerProviderBuilder() -> OpenTelemetry.Logs.LoggerProviderBuilder! -abstract OpenTelemetry.Metrics.ExemplarFilter.ShouldSample(double value, System.ReadOnlySpan> tags) -> bool -abstract OpenTelemetry.Metrics.ExemplarFilter.ShouldSample(long value, System.ReadOnlySpan> tags) -> bool -override OpenTelemetry.Metrics.AlwaysOffExemplarFilter.ShouldSample(double value, System.ReadOnlySpan> tags) -> bool -override OpenTelemetry.Metrics.AlwaysOffExemplarFilter.ShouldSample(long value, System.ReadOnlySpan> tags) -> bool -override OpenTelemetry.Metrics.AlwaysOnExemplarFilter.ShouldSample(double value, System.ReadOnlySpan> tags) -> bool -override OpenTelemetry.Metrics.AlwaysOnExemplarFilter.ShouldSample(long value, System.ReadOnlySpan> tags) -> bool -override OpenTelemetry.Metrics.TraceBasedExemplarFilter.ShouldSample(double value, System.ReadOnlySpan> tags) -> bool -override OpenTelemetry.Metrics.TraceBasedExemplarFilter.ShouldSample(long value, System.ReadOnlySpan> tags) -> bool static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.UseOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder) -> Microsoft.Extensions.Logging.ILoggingBuilder! static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.UseOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action! configure) -> Microsoft.Extensions.Logging.ILoggingBuilder! static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.UseOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action? configureBuilder, System.Action? configureOptions) -> Microsoft.Extensions.Logging.ILoggingBuilder! diff --git a/src/OpenTelemetry/Metrics/Builder/MeterProviderBuilderExtensions.cs b/src/OpenTelemetry/Metrics/Builder/MeterProviderBuilderExtensions.cs index ee1de49604f..dbe132ae14b 100644 --- a/src/OpenTelemetry/Metrics/Builder/MeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry/Metrics/Builder/MeterProviderBuilderExtensions.cs @@ -319,36 +319,54 @@ public static MeterProvider Build(this MeterProviderBuilder meterProviderBuilder #if EXPOSE_EXPERIMENTAL_FEATURES /// - /// Sets the to be used for this provider. - /// This is applied to all the metrics from this provider. + /// Sets the to be used for this provider + /// which controls how measurements will be offered to exemplar reservoirs. + /// Default provider configuration: . /// - /// - /// . - /// ExemplarFilter to use. - /// The supplied for chaining. + /// + /// + /// Note: Use or to enable exemplars. + /// Specification: . + /// + /// . + /// to + /// use. + /// The supplied for + /// chaining. #if NET8_0_OR_GREATER [Experimental(DiagnosticDefinitions.ExemplarExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] #endif public #else - /// - /// Sets the to be used for this provider. - /// This is applied to all the metrics from this provider. - /// - /// . - /// ExemplarFilter to use. - /// The supplied for chaining. internal #endif - static MeterProviderBuilder SetExemplarFilter(this MeterProviderBuilder meterProviderBuilder, ExemplarFilter exemplarFilter) + static MeterProviderBuilder SetExemplarFilter( + this MeterProviderBuilder meterProviderBuilder, + ExemplarFilterType exemplarFilter = ExemplarFilterType.TraceBased) { - Guard.ThrowIfNull(exemplarFilter); - meterProviderBuilder.ConfigureBuilder((sp, builder) => { if (builder is MeterProviderBuilderSdk meterProviderBuilderSdk) { - meterProviderBuilderSdk.SetExemplarFilter(exemplarFilter); + switch (exemplarFilter) + { + case ExemplarFilterType.AlwaysOn: + meterProviderBuilderSdk.SetExemplarFilter(new AlwaysOnExemplarFilter()); + break; + case ExemplarFilterType.AlwaysOff: + meterProviderBuilderSdk.SetExemplarFilter(new AlwaysOffExemplarFilter()); + break; + case ExemplarFilterType.TraceBased: + meterProviderBuilderSdk.SetExemplarFilter(new TraceBasedExemplarFilter()); + break; + default: + throw new NotSupportedException($"SdkExemplarFilter '{exemplarFilter}' is not supported."); + } } }); diff --git a/src/OpenTelemetry/Metrics/Exemplar/AlwaysOffExemplarFilter.cs b/src/OpenTelemetry/Metrics/Exemplar/AlwaysOffExemplarFilter.cs index 6939e0b96c9..5f40db2de36 100644 --- a/src/OpenTelemetry/Metrics/Exemplar/AlwaysOffExemplarFilter.cs +++ b/src/OpenTelemetry/Metrics/Exemplar/AlwaysOffExemplarFilter.cs @@ -1,31 +1,17 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if EXPOSE_EXPERIMENTAL_FEATURES && NET8_0_OR_GREATER -using System.Diagnostics.CodeAnalysis; -using OpenTelemetry.Internal; -#endif - namespace OpenTelemetry.Metrics; -#if EXPOSE_EXPERIMENTAL_FEATURES /// /// An implementation which makes no measurements /// eligible for becoming an . /// /// -/// /// Specification: . /// -#if NET8_0_OR_GREATER -[Experimental(DiagnosticDefinitions.ExemplarExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif -public -#else -internal -#endif -sealed class AlwaysOffExemplarFilter : ExemplarFilter +internal sealed class AlwaysOffExemplarFilter : ExemplarFilter { /// public override bool ShouldSample(long value, ReadOnlySpan> tags) diff --git a/src/OpenTelemetry/Metrics/Exemplar/AlwaysOnExemplarFilter.cs b/src/OpenTelemetry/Metrics/Exemplar/AlwaysOnExemplarFilter.cs index b81a144df8b..67f2e4ced5a 100644 --- a/src/OpenTelemetry/Metrics/Exemplar/AlwaysOnExemplarFilter.cs +++ b/src/OpenTelemetry/Metrics/Exemplar/AlwaysOnExemplarFilter.cs @@ -1,34 +1,17 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if EXPOSE_EXPERIMENTAL_FEATURES && NET8_0_OR_GREATER -using System.Diagnostics.CodeAnalysis; -using OpenTelemetry.Internal; -#endif - namespace OpenTelemetry.Metrics; -#if EXPOSE_EXPERIMENTAL_FEATURES /// /// An implementation which makes all measurements /// eligible for becoming an . /// /// -/// /// Specification: . /// -#if NET8_0_OR_GREATER -[Experimental(DiagnosticDefinitions.ExemplarExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif -public -#else -/// -/// An ExemplarFilter which makes all measurements eligible for being an Exemplar. -/// -internal -#endif - sealed class AlwaysOnExemplarFilter : ExemplarFilter +internal sealed class AlwaysOnExemplarFilter : ExemplarFilter { /// public override bool ShouldSample(long value, ReadOnlySpan> tags) diff --git a/src/OpenTelemetry/Metrics/Exemplar/ExemplarFilter.cs b/src/OpenTelemetry/Metrics/Exemplar/ExemplarFilter.cs index b0895a52b11..20296b5540d 100644 --- a/src/OpenTelemetry/Metrics/Exemplar/ExemplarFilter.cs +++ b/src/OpenTelemetry/Metrics/Exemplar/ExemplarFilter.cs @@ -1,30 +1,16 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if EXPOSE_EXPERIMENTAL_FEATURES && NET8_0_OR_GREATER -using System.Diagnostics.CodeAnalysis; -using OpenTelemetry.Internal; -#endif - namespace OpenTelemetry.Metrics; -#if EXPOSE_EXPERIMENTAL_FEATURES /// /// ExemplarFilter base implementation and contract. /// /// -/// /// Specification: . /// -#if NET8_0_OR_GREATER -[Experimental(DiagnosticDefinitions.ExemplarExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif -public -#else -internal -#endif - abstract class ExemplarFilter +internal abstract class ExemplarFilter { /// /// Determines if a given measurement is eligible for being diff --git a/src/OpenTelemetry/Metrics/Exemplar/ExemplarFilterType.cs b/src/OpenTelemetry/Metrics/Exemplar/ExemplarFilterType.cs new file mode 100644 index 00000000000..d74fc58b820 --- /dev/null +++ b/src/OpenTelemetry/Metrics/Exemplar/ExemplarFilterType.cs @@ -0,0 +1,48 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +using System.Diagnostics; + +namespace OpenTelemetry.Metrics; + +/// +/// Defines the exemplar filters supported by the OpenTelemetry .NET Metrics +/// SDK. +/// +/// +/// Specification: . +/// +public enum ExemplarFilterType +{ + /// + /// An exemplar filter which makes no measurements eligible for becoming an + /// . + /// + /// + /// Specification: . + /// + AlwaysOff, + + /// + /// An exemplar filter which makes all measurements eligible for becoming an + /// . + /// + /// + /// Specification: . + /// + AlwaysOn, + + /// + /// An exemplar filter which makes measurements recorded in the context of a + /// sampled (span) eligible for becoming an . + /// + /// + /// Specification: . + /// + TraceBased, +} diff --git a/src/OpenTelemetry/Metrics/Exemplar/TraceBasedExemplarFilter.cs b/src/OpenTelemetry/Metrics/Exemplar/TraceBasedExemplarFilter.cs index 915a050dc98..db1b16a0b15 100644 --- a/src/OpenTelemetry/Metrics/Exemplar/TraceBasedExemplarFilter.cs +++ b/src/OpenTelemetry/Metrics/Exemplar/TraceBasedExemplarFilter.cs @@ -1,34 +1,20 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 -#if EXPOSE_EXPERIMENTAL_FEATURES && NET8_0_OR_GREATER -using System.Diagnostics.CodeAnalysis; -using OpenTelemetry.Internal; -#endif - using System.Diagnostics; namespace OpenTelemetry.Metrics; -#if EXPOSE_EXPERIMENTAL_FEATURES /// /// An implementation which makes measurements /// recorded in the context of a sampled (span) eligible /// for becoming an . /// /// -/// /// Specification: . /// -#if NET8_0_OR_GREATER -[Experimental(DiagnosticDefinitions.ExemplarExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] -#endif -public -#else -internal -#endif - sealed class TraceBasedExemplarFilter : ExemplarFilter +internal sealed class TraceBasedExemplarFilter : ExemplarFilter { /// public override bool ShouldSample(long value, ReadOnlySpan> tags) diff --git a/test/Benchmarks/Metrics/ExemplarBenchmarks.cs b/test/Benchmarks/Metrics/ExemplarBenchmarks.cs index 81d285ea8b4..c52d31a934a 100644 --- a/test/Benchmarks/Metrics/ExemplarBenchmarks.cs +++ b/test/Benchmarks/Metrics/ExemplarBenchmarks.cs @@ -33,22 +33,20 @@ public class ExemplarBenchmarks private static readonly ThreadLocal ThreadLocalRandom = new(() => new Random()); private readonly string[] dimensionValues = new string[] { "DimVal1", "DimVal2", "DimVal3", "DimVal4", "DimVal5", "DimVal6", "DimVal7", "DimVal8", "DimVal9", "DimVal10" }; private Histogram histogramWithoutTagReduction; - private Histogram histogramWithTagReduction; - private MeterProvider meterProvider; private Meter meter; [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1602:Enumeration items should be documented", Justification = "Test only.")] - public enum ExemplarFilterToUse + public enum ExemplarConfigurationType { AlwaysOff, AlwaysOn, - HighValueOnly, + AlwaysOnWithHighValueSampling, } - [Params(ExemplarFilterToUse.AlwaysOn, ExemplarFilterToUse.AlwaysOff, ExemplarFilterToUse.HighValueOnly)] - public ExemplarFilterToUse ExemplarFilter { get; set; } + [Params(ExemplarConfigurationType.AlwaysOn, ExemplarConfigurationType.AlwaysOff, ExemplarConfigurationType.AlwaysOnWithHighValueSampling)] + public ExemplarConfigurationType ExemplarConfiguration { get; set; } [GlobalSetup] public void Setup() @@ -58,25 +56,38 @@ public void Setup() this.histogramWithTagReduction = this.meter.CreateHistogram("HistogramWithTagReduction"); var exportedItems = new List(); - ExemplarFilter exemplarFilter = new AlwaysOffExemplarFilter(); - if (this.ExemplarFilter == ExemplarFilterToUse.AlwaysOn) - { - exemplarFilter = new AlwaysOnExemplarFilter(); - } - else if (this.ExemplarFilter == ExemplarFilterToUse.HighValueOnly) - { - exemplarFilter = new HighValueExemplarFilter(); - } + var exemplarFilter = this.ExemplarConfiguration != ExemplarConfigurationType.AlwaysOff + ? ExemplarFilterType.AlwaysOn + : ExemplarFilterType.AlwaysOff; this.meterProvider = Sdk.CreateMeterProviderBuilder() .AddMeter(this.meter.Name) .SetExemplarFilter(exemplarFilter) - .AddView("HistogramWithTagReduction", new MetricStreamConfiguration() { TagKeys = new string[] { "DimName1", "DimName2", "DimName3" } }) + .AddView( + "HistogramWithoutTagReduction", + new MetricStreamConfiguration() + { + ExemplarReservoirFactory = CreateExemplarReservoir, + }) + .AddView( + "HistogramWithTagReduction", + new MetricStreamConfiguration() + { + TagKeys = new string[] { "DimName1", "DimName2", "DimName3" }, + ExemplarReservoirFactory = CreateExemplarReservoir, + }) .AddInMemoryExporter(exportedItems, metricReaderOptions => { metricReaderOptions.PeriodicExportingMetricReaderOptions.ExportIntervalMilliseconds = 1000; }) .Build(); + + ExemplarReservoir CreateExemplarReservoir() + { + return this.ExemplarConfiguration == ExemplarConfigurationType.AlwaysOnWithHighValueSampling + ? new HighValueExemplarReservoir(800D) + : null; + } } [GlobalCleanup] @@ -118,16 +129,36 @@ public void HistogramWithTagReduction() this.histogramWithTagReduction.Record(random.Next(1000), tags); } - internal class HighValueExemplarFilter : ExemplarFilter + private sealed class HighValueExemplarReservoir : FixedSizeExemplarReservoir { - public override bool ShouldSample(long value, ReadOnlySpan> tags) + private readonly double threshold; + private int measurementCount; + + public HighValueExemplarReservoir(double threshold) + : base(10) { - return value > 800; + this.threshold = threshold; + } + + public override void Offer(in ExemplarMeasurement measurement) + { + if (measurement.Value >= this.threshold) + { + this.UpdateExemplar(this.measurementCount++ % this.Capacity, in measurement); + } + } + + public override void Offer(in ExemplarMeasurement measurement) + { + if (measurement.Value >= this.threshold) + { + this.UpdateExemplar(this.measurementCount++ % this.Capacity, in measurement); + } } - public override bool ShouldSample(double value, ReadOnlySpan> tags) + protected override void OnCollected() { - return value > 800; + this.measurementCount = 0; } } } diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs index 1cf3d1c1778..7322c4d90e5 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs @@ -234,7 +234,7 @@ public void TestGaugeToOtlpMetric(string name, string description, string unit, using var meter = new Meter(Utils.GetCurrentMethodName()); using var provider = Sdk.CreateMeterProviderBuilder() .AddMeter(meter.Name) - .SetExemplarFilter(enableExemplars ? new AlwaysOnExemplarFilter() : new AlwaysOffExemplarFilter()) + .SetExemplarFilter(enableExemplars ? ExemplarFilterType.AlwaysOn : ExemplarFilterType.AlwaysOff) .AddInMemoryExporter(metrics) .Build(); @@ -309,7 +309,7 @@ public void TestCounterToOtlpMetric(string name, string description, string unit using var meter = new Meter(Utils.GetCurrentMethodName()); using var provider = Sdk.CreateMeterProviderBuilder() .AddMeter(meter.Name) - .SetExemplarFilter(enableExemplars ? new AlwaysOnExemplarFilter() : new AlwaysOffExemplarFilter()) + .SetExemplarFilter(enableExemplars ? ExemplarFilterType.AlwaysOn : ExemplarFilterType.AlwaysOff) .AddInMemoryExporter(metrics, metricReaderOptions => { metricReaderOptions.TemporalityPreference = aggregationTemporality; @@ -406,7 +406,7 @@ public void TestUpDownCounterToOtlpMetric(string name, string description, strin using var meter = new Meter(Utils.GetCurrentMethodName()); using var provider = Sdk.CreateMeterProviderBuilder() .AddMeter(meter.Name) - .SetExemplarFilter(enableExemplars ? new AlwaysOnExemplarFilter() : new AlwaysOffExemplarFilter()) + .SetExemplarFilter(enableExemplars ? ExemplarFilterType.AlwaysOn : ExemplarFilterType.AlwaysOff) .AddInMemoryExporter(metrics, metricReaderOptions => { metricReaderOptions.TemporalityPreference = aggregationTemporality; @@ -503,7 +503,7 @@ public void TestExponentialHistogramToOtlpMetric(string name, string description using var meter = new Meter(Utils.GetCurrentMethodName()); using var provider = Sdk.CreateMeterProviderBuilder() .AddMeter(meter.Name) - .SetExemplarFilter(enableExemplars ? new AlwaysOnExemplarFilter() : new AlwaysOffExemplarFilter()) + .SetExemplarFilter(enableExemplars ? ExemplarFilterType.AlwaysOn : ExemplarFilterType.AlwaysOff) .AddInMemoryExporter(metrics, metricReaderOptions => { metricReaderOptions.TemporalityPreference = aggregationTemporality; @@ -643,7 +643,7 @@ public void TestHistogramToOtlpMetric(string name, string description, string un using var meter = new Meter(Utils.GetCurrentMethodName()); using var provider = Sdk.CreateMeterProviderBuilder() .AddMeter(meter.Name) - .SetExemplarFilter(enableExemplars ? new AlwaysOnExemplarFilter() : new AlwaysOffExemplarFilter()) + .SetExemplarFilter(enableExemplars ? ExemplarFilterType.AlwaysOn : ExemplarFilterType.AlwaysOff) .AddInMemoryExporter(metrics, metricReaderOptions => { metricReaderOptions.TemporalityPreference = aggregationTemporality; @@ -793,7 +793,7 @@ public void ToOtlpExemplarTests(bool enableTagFiltering, bool enableTracing) using var meterProvider = Sdk.CreateMeterProviderBuilder() .AddMeter(meter.Name) - .SetExemplarFilter(new AlwaysOnExemplarFilter()) + .SetExemplarFilter(ExemplarFilterType.AlwaysOn) .AddView(i => { return !enableTagFiltering diff --git a/test/OpenTelemetry.Tests.Stress.Metrics/Program.cs b/test/OpenTelemetry.Tests.Stress.Metrics/Program.cs index f43e4d12fe8..17360444c8e 100644 --- a/test/OpenTelemetry.Tests.Stress.Metrics/Program.cs +++ b/test/OpenTelemetry.Tests.Stress.Metrics/Program.cs @@ -56,7 +56,7 @@ public MetricsStressTest(MetricsStressTestOptions options) if (options.EnableExemplars) { - builder.SetExemplarFilter(new AlwaysOnExemplarFilter()); + builder.SetExemplarFilter(ExemplarFilterType.AlwaysOn); } if (options.AddViewToFilterTags) diff --git a/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs b/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs index e1dd5effe54..61bb3f42701 100644 --- a/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs +++ b/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs @@ -28,7 +28,7 @@ public void TestExemplarsCounter(MetricReaderTemporalityPreference temporality) using var container = this.BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) - .SetExemplarFilter(new AlwaysOnExemplarFilter()) + .SetExemplarFilter(ExemplarFilterType.AlwaysOn) .AddView(i => { if (i.Name.StartsWith("testCounter")) @@ -153,7 +153,7 @@ public void TestExemplarsObservable(MetricReaderTemporalityPreference temporalit using var container = this.BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) - .SetExemplarFilter(new AlwaysOnExemplarFilter()) + .SetExemplarFilter(ExemplarFilterType.AlwaysOn) .AddInMemoryExporter(exportedItems, metricReaderOptions => { metricReaderOptions.TemporalityPreference = temporality; @@ -237,7 +237,7 @@ public void TestExemplarsHistogramWithBuckets(MetricReaderTemporalityPreference using var container = this.BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) - .SetExemplarFilter(new AlwaysOnExemplarFilter()) + .SetExemplarFilter(ExemplarFilterType.AlwaysOn) .AddView(i => { if (i.Name.StartsWith("histogramWithBucketsAndMinMax")) @@ -367,7 +367,7 @@ public void TestExemplarsHistogramWithoutBuckets(MetricReaderTemporalityPreferen using var container = this.BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) - .SetExemplarFilter(new AlwaysOnExemplarFilter()) + .SetExemplarFilter(ExemplarFilterType.AlwaysOn) .AddView(i => { if (i.Name.StartsWith("histogramWithoutBucketsAndMinMax")) @@ -495,7 +495,7 @@ public void TestExemplarsExponentialHistogram(MetricReaderTemporalityPreference using var container = this.BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) - .SetExemplarFilter(new AlwaysOnExemplarFilter()) + .SetExemplarFilter(ExemplarFilterType.AlwaysOn) .AddView(i => { if (i.Name.StartsWith("exponentialHistogramWithMinMax")) @@ -612,7 +612,7 @@ public void TestExemplarsFilterTags() using var container = this.BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) - .SetExemplarFilter(new AlwaysOnExemplarFilter()) + .SetExemplarFilter(ExemplarFilterType.AlwaysOn) .AddView(histogram.Name, new MetricStreamConfiguration() { TagKeys = new string[] { "key1" } }) .AddInMemoryExporter(exportedItems, metricReaderOptions => { From 18cfb2de0702b791bac70278b546312b71dd1f62 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Thu, 29 Feb 2024 21:43:33 -0800 Subject: [PATCH 2/8] Make ExemplarFilterType an experimental API. --- .../Metrics/Exemplar/ExemplarFilterType.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/OpenTelemetry/Metrics/Exemplar/ExemplarFilterType.cs b/src/OpenTelemetry/Metrics/Exemplar/ExemplarFilterType.cs index d74fc58b820..1b703ba3e2f 100644 --- a/src/OpenTelemetry/Metrics/Exemplar/ExemplarFilterType.cs +++ b/src/OpenTelemetry/Metrics/Exemplar/ExemplarFilterType.cs @@ -2,18 +2,31 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics; +#if EXPOSE_EXPERIMENTAL_FEATURES && NET8_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; +using OpenTelemetry.Internal; +#endif namespace OpenTelemetry.Metrics; +#if EXPOSE_EXPERIMENTAL_FEATURES /// /// Defines the exemplar filters supported by the OpenTelemetry .NET Metrics /// SDK. /// /// +/// /// Specification: . /// -public enum ExemplarFilterType +#if NET8_0_OR_GREATER +[Experimental(DiagnosticDefinitions.ExemplarExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)] +#endif +public +#else +internal +#endif + enum ExemplarFilterType { /// /// An exemplar filter which makes no measurements eligible for becoming an From 1073a12b7e82ff0c136c5e7b05ea99b88f73dccc Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Thu, 29 Feb 2024 21:50:49 -0800 Subject: [PATCH 3/8] CHANGELOG patch. --- src/OpenTelemetry/CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md index 755d94e0b3b..9c8f3dc863f 100644 --- a/src/OpenTelemetry/CHANGELOG.md +++ b/src/OpenTelemetry/CHANGELOG.md @@ -55,6 +55,16 @@ API. ([#5396](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5396)) +* **Experimental (pre-release builds only):** Removed the `ExemplarFilter`, + `AlwaysOffExemplarFilter`, `AlwaysOnExemplarFilter`, and + `TraceBasedExemplarFilter` APIs. The `MeterProviderBuilder.SetExemplarFilter` + extension method now accepts an `ExemplarFilterType` enumeration (which + contains definitions for the supported filter types `AlwaysOff`, `AlwaysOn`, + and `TraceBased`) instead of an `ExemplarFilter` instance. This was done in + response to changes made to the [OpenTelemetry Metrics SDK + Specification](https://github.com/open-telemetry/opentelemetry-specification/pull/3820). + ([#5404](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5404)) + ## 1.7.0 Released 2023-Dec-08 From 0884011a546a1121885848ce5db97317accfb2a1 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Fri, 1 Mar 2024 09:47:55 -0800 Subject: [PATCH 4/8] Code review. --- src/OpenTelemetry/Metrics/Exemplar/ExemplarFilterType.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/OpenTelemetry/Metrics/Exemplar/ExemplarFilterType.cs b/src/OpenTelemetry/Metrics/Exemplar/ExemplarFilterType.cs index 1b703ba3e2f..959d1f8e42f 100644 --- a/src/OpenTelemetry/Metrics/Exemplar/ExemplarFilterType.cs +++ b/src/OpenTelemetry/Metrics/Exemplar/ExemplarFilterType.cs @@ -11,8 +11,7 @@ namespace OpenTelemetry.Metrics; #if EXPOSE_EXPERIMENTAL_FEATURES /// -/// Defines the exemplar filters supported by the OpenTelemetry .NET Metrics -/// SDK. +/// Defines the supported exemplar filters. /// /// /// @@ -33,8 +32,10 @@ enum ExemplarFilterType /// . /// /// - /// Specification: . + /// Note: Setting on a meter provider + /// effectively disables exemplars. + /// Specification: . /// AlwaysOff, From ce1e34ef8176336db072cc435e79cb0a936217d5 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Fri, 1 Mar 2024 10:04:24 -0800 Subject: [PATCH 5/8] Code review. --- examples/AspNetCore/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/AspNetCore/Program.cs b/examples/AspNetCore/Program.cs index e41e068b407..18279940976 100644 --- a/examples/AspNetCore/Program.cs +++ b/examples/AspNetCore/Program.cs @@ -85,7 +85,7 @@ builder .AddMeter(Instrumentation.MeterName) #if EXPOSE_EXPERIMENTAL_FEATURES - .SetExemplarFilter(new TraceBasedExemplarFilter()) + .SetExemplarFilter(ExemplarFilterType.TraceBased) #endif .AddRuntimeInstrumentation() .AddHttpClientInstrumentation() From af79723d2a2d6945cfc860cab93b47602eb15ce8 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Fri, 1 Mar 2024 10:06:41 -0800 Subject: [PATCH 6/8] Doc updates. --- docs/metrics/customizing-the-sdk/README.md | 54 +++++++--------------- docs/metrics/extending-the-sdk/README.md | 39 +--------------- 2 files changed, 18 insertions(+), 75 deletions(-) diff --git a/docs/metrics/customizing-the-sdk/README.md b/docs/metrics/customizing-the-sdk/README.md index 14a72aaf2ac..4cb57f11693 100644 --- a/docs/metrics/customizing-the-sdk/README.md +++ b/docs/metrics/customizing-the-sdk/README.md @@ -412,26 +412,23 @@ exemplars. #### ExemplarFilter -`ExemplarFilter` determines which measurements are eligible to become an -Exemplar. i.e. `ExemplarFilter` determines which measurements are offered to -`ExemplarReservoir`, which makes the final decision about whether the offered -measurement gets stored as an exemplar. They can be used to control the noise -and overhead associated with Exemplar collection. +`ExemplarFilter` determines which measurements are offered to the configured +`ExemplarReservoir`, which makes the final decision about whether or not the +offered measurement gets recorded as an `Exemplar`. Generally `ExemplarFilter` +is a mechanism to control the overhead associated with `Exemplar` offering. -OpenTelemetry SDK comes with the following Filters: +OpenTelemetry SDK comes with the following `ExemplarFilters` (defined on +`ExemplarFilterType`): -* `AlwaysOnExemplarFilter` - makes all measurements eligible for being an Exemplar. -* `AlwaysOffExemplarFilter` - makes no measurements eligible for being an - Exemplar. Using this is as good as turning off Exemplar feature, and is the current +* `AlwaysOff`: Makes no measurements eligible for becoming an `Exemplar`. Using + this is as good as turning off the `Exemplar` feature and is the current default. -* `TraceBasedExemplarFilter` - makes those measurements eligible for being an -Exemplar, which are recorded in the context of a sampled parent `Activity` -(span). +* `AlwaysOn`: Makes all measurements eligible for becoming an `Exemplar`. +* `TraceBased`: Makes those measurements eligible for becoming an `Exemplar` + which are recorded in the context of a sampled `Activity` (span). -`SetExemplarFilter` method on `MeterProviderBuilder` can be used to set the -desired `ExemplarFilter`. - -The snippet below shows how to set `ExemplarFilter`. +The `SetExemplarFilter` extension method on `MeterProviderBuilder` can be used +to set the desired `ExemplarFilterType` and enable `Exemplar` collection: ```csharp using OpenTelemetry; @@ -439,31 +436,14 @@ using OpenTelemetry.Metrics; using var meterProvider = Sdk.CreateMeterProviderBuilder() // rest of config not shown - .SetExemplarFilter(new TraceBasedExemplarFilter()) + .SetExemplarFilter(ExemplarFilterType.TraceBased) .Build(); ``` -> [!NOTE] -> As of today, there is no separate toggle for enable/disable Exemplar feature. -Exemplars can be disabled by setting filter as `AlwaysOffExemplarFilter`, which -is also the default (i.e Exemplar feature is disabled by default). Users can -enable the feature by setting filter to anything other than -`AlwaysOffExemplarFilter`. For example: `.SetExemplarFilter(new TraceBasedExemplarFilter())`. - -If the built-in `ExemplarFilter`s are not meeting the needs, one may author -custom `ExemplarFilter` as shown -[here](../extending-the-sdk/README.md#exemplarfilter). A custom filter, which -eliminates all un-interesting measurements from becoming Exemplar is a -recommended way to control performance overhead associated with collecting -Exemplars. See -[benchmark](../../../test/Benchmarks/Metrics/ExemplarBenchmarks.cs) to see how -much impact can `ExemplarFilter` have on performance. - #### ExemplarReservoir -`ExemplarReservoir` receives the measurements sampled in by the `ExemplarFilter` -and is responsible for storing Exemplars. `ExemplarReservoir` ultimately decides -which measurements get stored as exemplars. The following are the default +`ExemplarReservoir` receives the measurements sampled by the `ExemplarFilter` +and is responsible for recording `Exemplar`s. The following are the default reservoirs: * `AlignedHistogramBucketExemplarReservoir` is the default reservoir used for @@ -479,7 +459,7 @@ size (currently defaulting to 1) determines the maximum number of exemplars stored. > [!NOTE] -> Currently there is no ability to change or configure Reservoir. +> Currently there is no ability to change or configure `ExemplarReservoir`. ### Instrumentation diff --git a/docs/metrics/extending-the-sdk/README.md b/docs/metrics/extending-the-sdk/README.md index 28df4c467c5..c7293ac418a 100644 --- a/docs/metrics/extending-the-sdk/README.md +++ b/docs/metrics/extending-the-sdk/README.md @@ -74,44 +74,7 @@ Not supported. ## ExemplarFilter -OpenTelemetry .NET SDK has provided the following built-in `ExemplarFilter`s: - -* [AlwaysOnExemplarFilter](../../../src/OpenTelemetry/Metrics/Exemplar/AlwaysOnExemplarFilter.cs) -* [AlwaysOffExemplarFilter](../../../src/OpenTelemetry/Metrics/Exemplar/AlwaysOffExemplarFilter.cs) -* [TraceBasedExemplarFilter](../../../src/OpenTelemetry/Metrics/Exemplar/TraceBasedExemplarFilter.cs) - -Custom exemplar filters can be implemented to achieve filtering based on other criterion: - -* `ExemplarFilter` should derive from `OpenTelemetry.ExemplarFilter` (which - belongs to the [OpenTelemetry](../../../src/OpenTelemetry/README.md) package) - and implement the `ShouldSample` method. - -One example is a filter, which filters all measurements of value lower -than given threshold is given below. Such a filter prevents any measurements -below the given threshold from ever becoming a `Exemplar`. Such filters could -also incorporate the `TraceBasedExemplarFilter` condition as well, as storing -exemplars for non-sampled traces may be undesired. - -```csharp -public sealed class HighValueFilter : ExemplarFilter -{ - private readonly double maxValue; - - public HighValueFilter(double maxValue) - { - this.maxValue = maxValue; - } - public override bool ShouldSample(long value, ReadOnlySpan> tags) - { - return Activity.Current?.Recorded && value > this.maxValue; - } - - public override bool ShouldSample(double value, ReadOnlySpan> tags) - { - return Activity.Current?.Recorded && value > this.maxValue; - } -} -``` +Not supported. ## ExemplarReservoir From 6963b0c21caa179335165ef7de3d8bf86ac681a5 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Fri, 1 Mar 2024 10:29:35 -0800 Subject: [PATCH 7/8] Test case. --- .../Metrics/MetricExemplarTests.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs b/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs index 61bb3f42701..c0927683f86 100644 --- a/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs +++ b/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs @@ -601,6 +601,53 @@ static void ValidateSecondPhase( } } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void TestTraceBasedExemplarFilter(bool enableTracing) + { + var exportedItems = new List(); + + using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + + var counter = meter.CreateCounter("testCounter"); + + using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + .AddMeter(meter.Name) + .SetExemplarFilter(ExemplarFilterType.TraceBased) + .AddInMemoryExporter(exportedItems)); + + if (enableTracing) + { + using var act = new Activity("test").Start(); + act.ActivityTraceFlags = ActivityTraceFlags.Recorded; + counter.Add(18); + } + else + { + counter.Add(18); + } + + meterProvider.ForceFlush(); + + Assert.Single(exportedItems); + + var metricPoint = GetFirstMetricPoint(exportedItems); + + Assert.NotNull(metricPoint); + + var exemplars = GetExemplars(metricPoint.Value); + + if (enableTracing) + { + Assert.Single(exemplars); + } + else + { + Assert.Empty(exemplars); + } + } + [Fact] public void TestExemplarsFilterTags() { From 1c75977c82743837ac83b54ca1650b730af89a92 Mon Sep 17 00:00:00 2001 From: Mikel Blanchard Date: Fri, 1 Mar 2024 10:49:20 -0800 Subject: [PATCH 8/8] Benchmark improvements. --- test/Benchmarks/Metrics/ExemplarBenchmarks.cs | 126 +++++++++++++----- 1 file changed, 90 insertions(+), 36 deletions(-) diff --git a/test/Benchmarks/Metrics/ExemplarBenchmarks.cs b/test/Benchmarks/Metrics/ExemplarBenchmarks.cs index c52d31a934a..ac1d347fba7 100644 --- a/test/Benchmarks/Metrics/ExemplarBenchmarks.cs +++ b/test/Benchmarks/Metrics/ExemplarBenchmarks.cs @@ -9,21 +9,31 @@ using OpenTelemetry.Tests; /* -BenchmarkDotNet v0.13.10, Windows 11 (10.0.23424.1000) -Intel Core i7-9700 CPU 3.00GHz, 1 CPU, 8 logical and 8 physical cores -.NET SDK 8.0.100 - [Host] : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2 - DefaultJob : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX2 - - -| Method | ExemplarFilter | Mean | Error | StdDev | Allocated | -|-------------------------- |--------------- |---------:|--------:|--------:|----------:| -| HistogramNoTagReduction | AlwaysOff | 274.2 ns | 2.94 ns | 2.60 ns | - | -| HistogramWithTagReduction | AlwaysOff | 241.6 ns | 1.78 ns | 1.58 ns | - | -| HistogramNoTagReduction | AlwaysOn | 300.9 ns | 3.10 ns | 2.90 ns | - | -| HistogramWithTagReduction | AlwaysOn | 312.9 ns | 4.81 ns | 4.50 ns | - | -| HistogramNoTagReduction | HighValueOnly | 262.8 ns | 2.24 ns | 1.99 ns | - | -| HistogramWithTagReduction | HighValueOnly | 258.3 ns | 5.12 ns | 5.03 ns | - | +BenchmarkDotNet v0.13.10, Windows 11 (10.0.22631.3155/23H2/2023Update/SunValley3) +12th Gen Intel Core i9-12900HK, 1 CPU, 20 logical and 14 physical cores +.NET SDK 8.0.200 + [Host] : .NET 8.0.2 (8.0.224.6711), X64 RyuJIT AVX2 + DefaultJob : .NET 8.0.2 (8.0.224.6711), X64 RyuJIT AVX2 + + +| Method | ExemplarConfiguration | Mean | Error | StdDev | Allocated | +|-------------------------- |---------------------- |---------:|--------:|--------:|----------:| +| HistogramNoTagReduction | AlwaysOff | 174.6 ns | 1.32 ns | 1.24 ns | - | +| HistogramWithTagReduction | AlwaysOff | 161.8 ns | 2.63 ns | 2.46 ns | - | +| CounterNoTagReduction | AlwaysOff | 141.6 ns | 2.12 ns | 1.77 ns | - | +| CounterWithTagReduction | AlwaysOff | 141.7 ns | 2.11 ns | 1.87 ns | - | +| HistogramNoTagReduction | AlwaysOn | 201.1 ns | 3.05 ns | 2.86 ns | - | +| HistogramWithTagReduction | AlwaysOn | 196.5 ns | 1.91 ns | 1.78 ns | - | +| CounterNoTagReduction | AlwaysOn | 149.7 ns | 1.42 ns | 1.33 ns | - | +| CounterWithTagReduction | AlwaysOn | 143.5 ns | 2.09 ns | 1.95 ns | - | +| HistogramNoTagReduction | TraceBased | 171.9 ns | 2.33 ns | 2.18 ns | - | +| HistogramWithTagReduction | TraceBased | 164.9 ns | 2.70 ns | 2.52 ns | - | +| CounterNoTagReduction | TraceBased | 148.1 ns | 2.76 ns | 2.58 ns | - | +| CounterWithTagReduction | TraceBased | 141.2 ns | 1.43 ns | 1.34 ns | - | +| HistogramNoTagReduction | Alway(...)pling [29] | 183.9 ns | 1.49 ns | 1.39 ns | - | +| HistogramWithTagReduction | Alway(...)pling [29] | 176.1 ns | 3.35 ns | 3.29 ns | - | +| CounterNoTagReduction | Alway(...)pling [29] | 159.3 ns | 3.12 ns | 4.38 ns | - | +| CounterWithTagReduction | Alway(...)pling [29] | 158.7 ns | 3.06 ns | 3.65 ns | - | */ namespace Benchmarks.Metrics; @@ -32,8 +42,10 @@ public class ExemplarBenchmarks { private static readonly ThreadLocal ThreadLocalRandom = new(() => new Random()); private readonly string[] dimensionValues = new string[] { "DimVal1", "DimVal2", "DimVal3", "DimVal4", "DimVal5", "DimVal6", "DimVal7", "DimVal8", "DimVal9", "DimVal10" }; - private Histogram histogramWithoutTagReduction; - private Histogram histogramWithTagReduction; + private Histogram histogramWithoutTagReduction; + private Histogram histogramWithTagReduction; + private Counter counterWithoutTagReduction; + private Counter counterWithTagReduction; private MeterProvider meterProvider; private Meter meter; @@ -42,40 +54,50 @@ public enum ExemplarConfigurationType { AlwaysOff, AlwaysOn, + TraceBased, AlwaysOnWithHighValueSampling, } - [Params(ExemplarConfigurationType.AlwaysOn, ExemplarConfigurationType.AlwaysOff, ExemplarConfigurationType.AlwaysOnWithHighValueSampling)] + [Params(ExemplarConfigurationType.AlwaysOn, ExemplarConfigurationType.AlwaysOff, ExemplarConfigurationType.TraceBased, ExemplarConfigurationType.AlwaysOnWithHighValueSampling)] public ExemplarConfigurationType ExemplarConfiguration { get; set; } [GlobalSetup] public void Setup() { this.meter = new Meter(Utils.GetCurrentMethodName()); - this.histogramWithoutTagReduction = this.meter.CreateHistogram("HistogramWithoutTagReduction"); - this.histogramWithTagReduction = this.meter.CreateHistogram("HistogramWithTagReduction"); + this.histogramWithoutTagReduction = this.meter.CreateHistogram("HistogramWithoutTagReduction"); + this.histogramWithTagReduction = this.meter.CreateHistogram("HistogramWithTagReduction"); + this.counterWithoutTagReduction = this.meter.CreateCounter("CounterWithoutTagReduction"); + this.counterWithTagReduction = this.meter.CreateCounter("CounterWithTagReduction"); var exportedItems = new List(); - var exemplarFilter = this.ExemplarConfiguration != ExemplarConfigurationType.AlwaysOff - ? ExemplarFilterType.AlwaysOn - : ExemplarFilterType.AlwaysOff; + var exemplarFilter = this.ExemplarConfiguration == ExemplarConfigurationType.TraceBased + ? ExemplarFilterType.TraceBased + : this.ExemplarConfiguration != ExemplarConfigurationType.AlwaysOff + ? ExemplarFilterType.AlwaysOn + : ExemplarFilterType.AlwaysOff; this.meterProvider = Sdk.CreateMeterProviderBuilder() .AddMeter(this.meter.Name) .SetExemplarFilter(exemplarFilter) - .AddView( - "HistogramWithoutTagReduction", - new MetricStreamConfiguration() + .AddView(i => + { + if (i.Name.Contains("WithTagReduction")) { - ExemplarReservoirFactory = CreateExemplarReservoir, - }) - .AddView( - "HistogramWithTagReduction", - new MetricStreamConfiguration() + return new MetricStreamConfiguration() + { + TagKeys = new string[] { "DimName1", "DimName2", "DimName3" }, + ExemplarReservoirFactory = CreateExemplarReservoir, + }; + } + else { - TagKeys = new string[] { "DimName1", "DimName2", "DimName3" }, - ExemplarReservoirFactory = CreateExemplarReservoir, - }) + return new MetricStreamConfiguration() + { + ExemplarReservoirFactory = CreateExemplarReservoir, + }; + } + }) .AddInMemoryExporter(exportedItems, metricReaderOptions => { metricReaderOptions.PeriodicExportingMetricReaderOptions.ExportIntervalMilliseconds = 1000; @@ -110,7 +132,7 @@ public void HistogramNoTagReduction() { "DimName5", this.dimensionValues[random.Next(0, 10)] }, }; - this.histogramWithoutTagReduction.Record(random.Next(1000), tags); + this.histogramWithoutTagReduction.Record(random.NextDouble() * 1000D, tags); } [Benchmark] @@ -126,7 +148,39 @@ public void HistogramWithTagReduction() { "DimName5", this.dimensionValues[random.Next(0, 10)] }, }; - this.histogramWithTagReduction.Record(random.Next(1000), tags); + this.histogramWithTagReduction.Record(random.NextDouble() * 1000D, tags); + } + + [Benchmark] + public void CounterNoTagReduction() + { + var random = ThreadLocalRandom.Value; + var tags = new TagList + { + { "DimName1", this.dimensionValues[random.Next(0, 2)] }, + { "DimName2", this.dimensionValues[random.Next(0, 2)] }, + { "DimName3", this.dimensionValues[random.Next(0, 5)] }, + { "DimName4", this.dimensionValues[random.Next(0, 5)] }, + { "DimName5", this.dimensionValues[random.Next(0, 10)] }, + }; + + this.counterWithoutTagReduction.Add(random.Next(1000), tags); + } + + [Benchmark] + public void CounterWithTagReduction() + { + var random = ThreadLocalRandom.Value; + var tags = new TagList + { + { "DimName1", this.dimensionValues[random.Next(0, 2)] }, + { "DimName2", this.dimensionValues[random.Next(0, 2)] }, + { "DimName3", this.dimensionValues[random.Next(0, 5)] }, + { "DimName4", this.dimensionValues[random.Next(0, 5)] }, + { "DimName5", this.dimensionValues[random.Next(0, 10)] }, + }; + + this.counterWithTagReduction.Add(random.Next(1000), tags); } private sealed class HighValueExemplarReservoir : FixedSizeExemplarReservoir