From e4554f2cd364a7c7e0de31601a6b63e57edbaec9 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Thu, 9 Mar 2023 10:34:33 -0800 Subject: [PATCH 1/4] Implement Update and Snapshot for exponential histogram buckets (#4267) Co-authored-by: Cijo Thomas --- .../Base2ExponentialBucketHistogram.cs | 25 +++++++ .../Metrics/CircularBufferBuckets.cs | 31 ++++++++ .../Metrics/ExponentialBucketSnapshot.cs | 42 +++++++++++ .../Metrics/ExponentialBuckets.cs | 72 +++++++++++++++++++ src/OpenTelemetry/Metrics/MetricPoint.cs | 27 +++++++ .../Metrics/AggregatorTest.cs | 41 +++++++++-- 6 files changed, 231 insertions(+), 7 deletions(-) create mode 100644 src/OpenTelemetry/Metrics/ExponentialBucketSnapshot.cs create mode 100644 src/OpenTelemetry/Metrics/ExponentialBuckets.cs diff --git a/src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogram.cs b/src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogram.cs index 1d721864c21..abc00f1cd69 100644 --- a/src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogram.cs +++ b/src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogram.cs @@ -38,6 +38,11 @@ internal sealed class Base2ExponentialBucketHistogram internal int IsCriticalSectionOccupied = 0; + internal int SnapshotScale; + internal long SnapshotZeroCount; + internal CircularBufferBuckets SnapshotPositiveBuckets; + internal CircularBufferBuckets SnapshotNegativeBuckets; + private int scale; private double scalingFactor; // 2 ^ scale / log(2) @@ -130,6 +135,9 @@ internal int Scale internal CircularBufferBuckets NegativeBuckets { get; } + internal ExponentialBucketSnapshot ExponentialBucketSnapshot => + new ExponentialBucketSnapshot(this.SnapshotScale, this.SnapshotZeroCount, this.SnapshotPositiveBuckets, this.SnapshotNegativeBuckets); + /// /// Maps a finite positive IEEE 754 double-precision floating-point /// number to Bucket[index] = ( base ^ index, base ^ (index + 1) ], @@ -211,4 +219,21 @@ public void Record(double value) n = buckets.TryIncrement(index >> n); Debug.Assert(n == 0, "Increment should always succeed after scale down."); } + + internal void Reset() + { + // TODO: Determine if this is sufficient for delta temporality. + // I'm not sure we should be resetting the scale. + this.ZeroCount = 0; + this.PositiveBuckets.Reset(); + this.NegativeBuckets.Reset(); + } + + internal void Snapshot() + { + this.SnapshotScale = this.Scale; + this.SnapshotZeroCount = this.ZeroCount; + this.SnapshotPositiveBuckets = this.PositiveBuckets.Copy(); + this.SnapshotNegativeBuckets = this.NegativeBuckets.Copy(); + } } diff --git a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs index 04a0e40fa0f..e8c0ef02ea1 100644 --- a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs +++ b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs @@ -267,6 +267,37 @@ static void Move(long[] array, uint src, uint dst) } } + internal void Reset() + { + if (this.trait != null) + { + for (var i = 0; i < this.trait.Length; ++i) + { + this.trait[i] = 0; + } + } + } + + internal CircularBufferBuckets Copy() + { + // TODO: Consider avoiding allocating a new one every time. + // Maybe we can have two instances that we swap between snapshots. + var copy = new CircularBufferBuckets(this.Capacity); + copy.begin = this.begin; + copy.end = this.end; + + if (this.trait != null) + { + copy.trait = new long[this.trait.Length]; + for (var i = 0; i < copy.trait.Length; ++i) + { + copy.trait[i] = this.trait[i]; + } + } + + return copy; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private int ModuloIndex(int value) { diff --git a/src/OpenTelemetry/Metrics/ExponentialBucketSnapshot.cs b/src/OpenTelemetry/Metrics/ExponentialBucketSnapshot.cs new file mode 100644 index 00000000000..f780100aade --- /dev/null +++ b/src/OpenTelemetry/Metrics/ExponentialBucketSnapshot.cs @@ -0,0 +1,42 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace OpenTelemetry.Metrics; + +internal class ExponentialBucketSnapshot +{ + internal ExponentialBucketSnapshot(int scale, long snapshotZeroCount, CircularBufferBuckets positiveBuckets, CircularBufferBuckets negativeBuckets) + { + this.Scale = scale; + this.ZeroCount = snapshotZeroCount; + this.PositiveOffset = positiveBuckets.Offset; + this.NegativeOffset = negativeBuckets.Offset; + this.PositiveBuckets = new(positiveBuckets); + this.NegativeBuckets = new(negativeBuckets); + } + + public int Scale { get; } + + public long ZeroCount { get; } + + public int PositiveOffset { get; } + + public int NegativeOffset { get; } + + public ExponentialBuckets PositiveBuckets { get; } + + public ExponentialBuckets NegativeBuckets { get; } +} diff --git a/src/OpenTelemetry/Metrics/ExponentialBuckets.cs b/src/OpenTelemetry/Metrics/ExponentialBuckets.cs new file mode 100644 index 00000000000..c32a270bf2d --- /dev/null +++ b/src/OpenTelemetry/Metrics/ExponentialBuckets.cs @@ -0,0 +1,72 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +namespace OpenTelemetry.Metrics; + +internal class ExponentialBuckets +{ + private readonly CircularBufferBuckets buckets; + + internal ExponentialBuckets(CircularBufferBuckets buckets) + { + this.buckets = buckets; + } + + public Enumerator GetEnumerator() => new(this.buckets); + + /// + /// Enumerates the bucket counts of an exponential histogram. + /// + // Note: Does not implement IEnumerator<> to prevent accidental boxing. + public struct Enumerator + { + private readonly int lastIndex; + private readonly CircularBufferBuckets buckets; + private int index; + + internal Enumerator(CircularBufferBuckets buckets) + { + this.buckets = buckets; + this.index = buckets.Offset; + this.Current = default; + this.lastIndex = buckets.Size - 1; + } + + /// + /// Gets the bucket count at the current position of the enumerator. + /// + public long Current { get; private set; } + + /// + /// Advances the enumerator to the next element of the . + /// + /// if the enumerator was + /// successfully advanced to the next element; if the enumerator has passed the end of the + /// collection. + public bool MoveNext() + { + if (this.index < this.buckets.Size) + { + this.Current = this.buckets[this.index++]; + return true; + } + + return false; + } + } +} diff --git a/src/OpenTelemetry/Metrics/MetricPoint.cs b/src/OpenTelemetry/Metrics/MetricPoint.cs index e4ce42c0799..eabc5c1d13f 100644 --- a/src/OpenTelemetry/Metrics/MetricPoint.cs +++ b/src/OpenTelemetry/Metrics/MetricPoint.cs @@ -276,6 +276,26 @@ public readonly HistogramBuckets GetHistogramBuckets() return this.mpComponents.HistogramBuckets; } + /// + /// Gets the buckets of the histogram associated with the metric point. + /// + /// + /// Applies to metric type. + /// + /// . + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#pragma warning disable SA1202 // Elements should be ordered by access. GetExponentialBucketSnapshot will be public soon. + internal readonly ExponentialBucketSnapshot GetExponentialBucketSnapshot() + { + if (this.aggType != AggregationType.Base2ExponentialHistogram && + this.aggType != AggregationType.Base2ExponentialHistogramWithMinMax) + { + this.ThrowNotSupportedMetricTypeException(nameof(this.GetExponentialBucketSnapshot)); + } + + return this.mpComponents.Base2ExponentialBucketHistogram.ExponentialBucketSnapshot; + } + /// /// Gets the Histogram Min and Max values. /// @@ -284,6 +304,7 @@ public readonly HistogramBuckets GetHistogramBuckets() /// True if minimum and maximum value exist, false otherwise. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetHistogramMinMaxValues(out double min, out double max) +#pragma warning restore SA1202 // Elements should be ordered by access { if (this.aggType == AggregationType.HistogramWithMinMax || this.aggType == AggregationType.HistogramWithMinMaxBuckets) @@ -1018,11 +1039,13 @@ internal void TakeSnapshot(bool outputDelta) // Lock acquired this.snapshotValue.AsLong = this.runningValue.AsLong; histogram.SnapshotSum = histogram.RunningSum; + histogram.Snapshot(); if (outputDelta) { this.runningValue.AsLong = 0; histogram.RunningSum = 0; + histogram.Reset(); } this.MetricPointStatus = MetricPointStatus.NoCollectPending; @@ -1049,6 +1072,7 @@ internal void TakeSnapshot(bool outputDelta) // Lock acquired this.snapshotValue.AsLong = this.runningValue.AsLong; histogram.SnapshotSum = histogram.RunningSum; + histogram.Snapshot(); histogram.SnapshotMin = histogram.RunningMin; histogram.SnapshotMax = histogram.RunningMax; @@ -1056,6 +1080,7 @@ internal void TakeSnapshot(bool outputDelta) { this.runningValue.AsLong = 0; histogram.RunningSum = 0; + histogram.Reset(); histogram.RunningMin = double.PositiveInfinity; histogram.RunningMax = double.NegativeInfinity; } @@ -1495,6 +1520,7 @@ private void UpdateBase2ExponentialHistogram(double number, ReadOnlySpan +using System.Diagnostics.Metrics; using Xunit; namespace OpenTelemetry.Metrics.Tests @@ -190,6 +191,8 @@ public void HistogramWithOnlySumCount() [InlineData(AggregationType.Base2ExponentialHistogramWithMinMax, AggregationTemporality.Delta)] internal void ExponentialHistogramTests(AggregationType aggregationType, AggregationTemporality aggregationTemporality) { + var valuesToRecord = new[] { -10, 0, 1, 9, 10, 11, 19 }; + var aggregatorStore = new AggregatorStore( $"{nameof(this.ExponentialHistogramTests)}", aggregationType, @@ -205,13 +208,13 @@ internal void ExponentialHistogramTests(AggregationType aggregationType, Aggrega Metric.DefaultHistogramBounds, Metric.DefaultExponentialHistogramMaxBuckets); - metricPoint.Update(-10); - metricPoint.Update(0); - metricPoint.Update(1); - metricPoint.Update(9); - metricPoint.Update(10); - metricPoint.Update(11); - metricPoint.Update(19); + var expectedHistogram = new Base2ExponentialBucketHistogram(); + + foreach (var value in valuesToRecord) + { + metricPoint.Update(value); + expectedHistogram.Record(value); + } metricPoint.TakeSnapshot(aggregationTemporality == AggregationTemporality.Delta); // TODO: Why outputDelta param? The aggregation temporality was declared when instantiateing the AggregatorStore. @@ -219,6 +222,7 @@ internal void ExponentialHistogramTests(AggregationType aggregationType, Aggrega var sum = metricPoint.GetHistogramSum(); var hasMinMax = metricPoint.TryGetHistogramMinMaxValues(out var min, out var max); + AssertExponentialBucketsAreCorrect(expectedHistogram, metricPoint.GetExponentialBucketSnapshot()); Assert.Equal(40, sum); Assert.Equal(7, count); @@ -241,6 +245,7 @@ internal void ExponentialHistogramTests(AggregationType aggregationType, Aggrega if (aggregationTemporality == AggregationTemporality.Cumulative) { + AssertExponentialBucketsAreCorrect(expectedHistogram, metricPoint.GetExponentialBucketSnapshot()); Assert.Equal(40, sum); Assert.Equal(7, count); @@ -257,6 +262,8 @@ internal void ExponentialHistogramTests(AggregationType aggregationType, Aggrega } else { + expectedHistogram.Reset(); + AssertExponentialBucketsAreCorrect(expectedHistogram, metricPoint.GetExponentialBucketSnapshot()); Assert.Equal(0, sum); Assert.Equal(0, count); @@ -272,5 +279,25 @@ internal void ExponentialHistogramTests(AggregationType aggregationType, Aggrega } } } + + private static void AssertExponentialBucketsAreCorrect(Base2ExponentialBucketHistogram expectedHistogram, ExponentialBucketSnapshot buckets) + { + Assert.Equal(expectedHistogram.Scale, buckets.Scale); + Assert.Equal(expectedHistogram.ZeroCount, buckets.ZeroCount); + Assert.Equal(expectedHistogram.PositiveBuckets.Offset, buckets.PositiveOffset); + Assert.Equal(expectedHistogram.NegativeBuckets.Offset, buckets.NegativeOffset); + + var index = expectedHistogram.PositiveBuckets.Offset; + foreach (var bucketCount in buckets.PositiveBuckets) + { + Assert.Equal(expectedHistogram.PositiveBuckets[index++], bucketCount); + } + + index = expectedHistogram.NegativeBuckets.Offset; + foreach (var bucketCount in buckets.NegativeBuckets) + { + Assert.Equal(expectedHistogram.PositiveBuckets[index++], bucketCount); + } + } } } From a1ea6d6aef7ecc87491af3ea8ed119451eaa8dd2 Mon Sep 17 00:00:00 2001 From: "J. Kalyana Sundaram" Date: Thu, 9 Mar 2023 14:40:19 -0800 Subject: [PATCH 2/4] Fix for #4264: Consider removing the "advanced" folder (#4286) --- OpenTelemetry.sln | 2 +- .../trace/{advanced => }/stratified-sampling-example/Program.cs | 0 docs/trace/{advanced => }/stratified-sampling-example/README.md | 0 .../stratified-sampling-example/StratifiedSampler.cs | 0 .../stratified-sampling-example.csproj | 0 5 files changed, 1 insertion(+), 1 deletion(-) rename docs/trace/{advanced => }/stratified-sampling-example/Program.cs (100%) rename docs/trace/{advanced => }/stratified-sampling-example/README.md (100%) rename docs/trace/{advanced => }/stratified-sampling-example/StratifiedSampler.cs (100%) rename docs/trace/{advanced => }/stratified-sampling-example/stratified-sampling-example.csproj (100%) diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index 1880cbb93c5..bd7a7fbe8e1 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -249,7 +249,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "getting-started-console", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "getting-started-jaeger", "docs\trace\getting-started-jaeger\getting-started-jaeger.csproj", "{A0C0B77C-6C7B-4EC2-AC61-EA1F489811B9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "stratified-sampling-example", "docs\trace\advanced\stratified-sampling-example\stratified-sampling-example.csproj", "{9C99621C-343E-479C-A943-332DB6129B71}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "stratified-sampling-example", "docs\trace\stratified-sampling-example\stratified-sampling-example.csproj", "{9C99621C-343E-479C-A943-332DB6129B71}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/docs/trace/advanced/stratified-sampling-example/Program.cs b/docs/trace/stratified-sampling-example/Program.cs similarity index 100% rename from docs/trace/advanced/stratified-sampling-example/Program.cs rename to docs/trace/stratified-sampling-example/Program.cs diff --git a/docs/trace/advanced/stratified-sampling-example/README.md b/docs/trace/stratified-sampling-example/README.md similarity index 100% rename from docs/trace/advanced/stratified-sampling-example/README.md rename to docs/trace/stratified-sampling-example/README.md diff --git a/docs/trace/advanced/stratified-sampling-example/StratifiedSampler.cs b/docs/trace/stratified-sampling-example/StratifiedSampler.cs similarity index 100% rename from docs/trace/advanced/stratified-sampling-example/StratifiedSampler.cs rename to docs/trace/stratified-sampling-example/StratifiedSampler.cs diff --git a/docs/trace/advanced/stratified-sampling-example/stratified-sampling-example.csproj b/docs/trace/stratified-sampling-example/stratified-sampling-example.csproj similarity index 100% rename from docs/trace/advanced/stratified-sampling-example/stratified-sampling-example.csproj rename to docs/trace/stratified-sampling-example/stratified-sampling-example.csproj From 3b1ceba8e6ddc43c7af4e85b5594f8f30c182236 Mon Sep 17 00:00:00 2001 From: "J. Kalyana Sundaram" Date: Fri, 10 Mar 2023 13:52:58 -0800 Subject: [PATCH 3/4] Example / proof of concept to achieve a combination of head-based sampling + a basic form of tail-based sampling at a span level. (#4206) Co-authored-by: Timothy Mothra Co-authored-by: Cijo Thomas --- OpenTelemetry.sln | 7 ++ .../ParentBasedElseAlwaysRecordSampler.cs | 61 ++++++++++ .../tail-based-sampling-span-level/Program.cs | 59 ++++++++++ .../tail-based-sampling-span-level/README.md | 108 ++++++++++++++++++ .../TailSamplingProcessor.cs | 73 ++++++++++++ .../tail-based-sampling-example.csproj | 5 + 6 files changed, 313 insertions(+) create mode 100644 docs/trace/tail-based-sampling-span-level/ParentBasedElseAlwaysRecordSampler.cs create mode 100644 docs/trace/tail-based-sampling-span-level/Program.cs create mode 100644 docs/trace/tail-based-sampling-span-level/README.md create mode 100644 docs/trace/tail-based-sampling-span-level/TailSamplingProcessor.cs create mode 100644 docs/trace/tail-based-sampling-span-level/tail-based-sampling-example.csproj diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln index bd7a7fbe8e1..19b4f346c93 100644 --- a/OpenTelemetry.sln +++ b/OpenTelemetry.sln @@ -249,6 +249,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "getting-started-console", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "getting-started-jaeger", "docs\trace\getting-started-jaeger\getting-started-jaeger.csproj", "{A0C0B77C-6C7B-4EC2-AC61-EA1F489811B9}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "tail-based-sampling-example", "docs\trace\tail-based-sampling-span-level\tail-based-sampling-example.csproj", "{800DB925-6014-4136-AC01-3356CF7CADD3}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "stratified-sampling-example", "docs\trace\stratified-sampling-example\stratified-sampling-example.csproj", "{9C99621C-343E-479C-A943-332DB6129B71}" EndProject Global @@ -525,6 +527,10 @@ Global {A0C0B77C-6C7B-4EC2-AC61-EA1F489811B9}.Debug|Any CPU.Build.0 = Debug|Any CPU {A0C0B77C-6C7B-4EC2-AC61-EA1F489811B9}.Release|Any CPU.ActiveCfg = Release|Any CPU {A0C0B77C-6C7B-4EC2-AC61-EA1F489811B9}.Release|Any CPU.Build.0 = Release|Any CPU + {800DB925-6014-4136-AC01-3356CF7CADD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {800DB925-6014-4136-AC01-3356CF7CADD3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {800DB925-6014-4136-AC01-3356CF7CADD3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {800DB925-6014-4136-AC01-3356CF7CADD3}.Release|Any CPU.Build.0 = Release|Any CPU {9C99621C-343E-479C-A943-332DB6129B71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9C99621C-343E-479C-A943-332DB6129B71}.Debug|Any CPU.Build.0 = Debug|Any CPU {9C99621C-343E-479C-A943-332DB6129B71}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -568,6 +574,7 @@ Global {DEDE8442-03CA-48CF-99B9-EA224D89D148} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818} {EF4F6280-14D1-49D4-8095-1AC36E169AA8} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818} {A0C0B77C-6C7B-4EC2-AC61-EA1F489811B9} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818} + {800DB925-6014-4136-AC01-3356CF7CADD3} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818} {9C99621C-343E-479C-A943-332DB6129B71} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/docs/trace/tail-based-sampling-span-level/ParentBasedElseAlwaysRecordSampler.cs b/docs/trace/tail-based-sampling-span-level/ParentBasedElseAlwaysRecordSampler.cs new file mode 100644 index 00000000000..ae15a40d370 --- /dev/null +++ b/docs/trace/tail-based-sampling-span-level/ParentBasedElseAlwaysRecordSampler.cs @@ -0,0 +1,61 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using OpenTelemetry.Trace; + +namespace SDKBasedSpanLevelTailSamplingSample; + +/// +/// Note: This is a proof-of-concept and is not meant to be used directly in production. +/// This is a composite sampler used to achieve a combination of parent-based sampling +/// and SDK-side "span-level" tail-based sampling. +/// It first invokes a head-sampling mechanism using the parent based sampling approach. +/// If the parent based sampler's decision is to sample it (i.e., record and export the span), +/// it retains that decision. If not, it returns a "record-only" sampling result that can be +/// changed later by a span processor based on span attributes (e.g., failure) that become +/// available only by the end of the span. +/// +internal class ParentBasedElseAlwaysRecordSampler : Sampler +{ + private const double DefaultSamplingProbabilityForRootSpan = 0.1; + private readonly ParentBasedSampler parentBasedSampler; + + public ParentBasedElseAlwaysRecordSampler(double samplingProbabilityForRootSpan = DefaultSamplingProbabilityForRootSpan) + { + this.parentBasedSampler = new ParentBasedSampler(new TraceIdRatioBasedSampler(samplingProbabilityForRootSpan)); + } + + public override SamplingResult ShouldSample(in SamplingParameters samplingParameters) + { + // First, let's sample using the parentbased sampler. + var samplingResult = this.parentBasedSampler.ShouldSample(samplingParameters); + + if (samplingResult.Decision != SamplingDecision.Drop) + { + // Parentbased sampler decided not to drop it, so we will sample this. + return samplingResult; + } + + // Parentbased sampler decided to drop it. We will return a RecordOnly + // decision so that the span filtering processors later in the pipeline + // can apply tailbased sampling rules (e.g., to sample all failed spans). + // Returning a RecordOnly decision is relevant because: + // 1. It causes the Processor pipeline to be invoked. + // 2. It causes activity.IsAllDataRequested to return true, so most + // instrumentations end up populating the required attributes. + return new SamplingResult(SamplingDecision.RecordOnly); + } +} diff --git a/docs/trace/tail-based-sampling-span-level/Program.cs b/docs/trace/tail-based-sampling-span-level/Program.cs new file mode 100644 index 00000000000..3dfc4cf4f08 --- /dev/null +++ b/docs/trace/tail-based-sampling-span-level/Program.cs @@ -0,0 +1,59 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Diagnostics; +using OpenTelemetry; +using OpenTelemetry.Trace; + +namespace SDKBasedSpanLevelTailSamplingSample; + +internal class Program +{ + private static readonly ActivitySource MyActivitySource = new("SDK.TailSampling.POC"); + + public static void Main(string[] args) + { + using var tracerProvider = Sdk.CreateTracerProviderBuilder() + .SetSampler(new ParentBasedElseAlwaysRecordSampler()) + .AddSource("SDK.TailSampling.POC") + .AddProcessor(new TailSamplingProcessor()) + .AddConsoleExporter() + .Build(); + + var random = new Random(2357); + + // Generate some spans + for (var i = 0; i < 50; i++) + { + using (var activity = MyActivitySource.StartActivity("SayHello")) + { + activity?.SetTag("foo", "bar"); + + // Simulate a mix of failed and successful spans + var randomValue = random.Next(5); + switch (randomValue) + { + case 0: + activity?.SetStatus(ActivityStatusCode.Error); + break; + default: + activity?.SetStatus(ActivityStatusCode.Ok); + break; + } + } + } + } +} diff --git a/docs/trace/tail-based-sampling-span-level/README.md b/docs/trace/tail-based-sampling-span-level/README.md new file mode 100644 index 00000000000..a6d2930203b --- /dev/null +++ b/docs/trace/tail-based-sampling-span-level/README.md @@ -0,0 +1,108 @@ +# Tail Based Sampling at an activity (span) level: An Example + +This document describes one possible way to achieve a form of tail-based +sampling to include all failed activities in addition to head-based sampling. + +It does this by leveraging the extensibility mechanisms in the OpenTelemetry +SDK. It uses a combination of a custom sampler and an ActivityProcessor +(span processor). + +This is a way to achieve a combination of: + +- Head-based sampling (probabilistic/unbiased sampling), and +- Tail-based sampling (a non-probabilistic/biased sampling). + +## How does this sampling example work? + +We use a hybrid approach: we do head based sampling to get a +probabilistic subset of all activities which includes both successful activities +and failure activities. In addition, we want to capture all failure activities. +To do this, if the parent based sampler's decision is to drop it, we return +a "Record-Only" sampling result. This ensures that the activity processor +receives that activity. In the activity processor, at the end of an activity, +we check if it is a failure activity. If so, we change the decision from +"Record-Only" to set the sampled flag so that the exporter receives the +activity. In this example, each activity is filtered individually without +consideration to any other activities. + +This is a basic form of tail-based sampling at an activity level. If an +activity failed, we always sample it in addition to all head-sampled +activities. + +## When should you consider such an option? + +This is a good option if you want to get all failure activities in addition to +head based sampling. With this, you get basic activity level tail-based sampling +at a SDK level without having to install any additional components. + +## Tradeoffs + +Tail-sampling this way involves many tradeoffs such as: + +1. Additional performance cost: Unlike head-based sampling where the sampling +decision is made at activity creation time, in tail sampling the decision is made +only at the end, so there is additional memory/processing cost. + +2. Partial traces: Since this sampling is at a activity level, the generated trace +will be partial. For example, if another part of the call tree is successful, +those activities may not be exported leading to an incomplete trace. + +3. If multiple exporters are used, this decision will impact all of them: +[Issue 3861](https://github.com/open-telemetry/opentelemetry-dotnet/issues/3861). + +## Sample Output + +You should see output such as the below when you run this example. + +```text +Including error activity with id +00-404ddff248b8f9a9b21e347d68d2640e-035858bc3c168885-01 and status Error +Activity.TraceId: 404ddff248b8f9a9b21e347d68d2640e +Activity.SpanId: 035858bc3c168885 +Activity.TraceFlags: Recorded +Activity.ActivitySourceName: SDK.TailSampling.POC +Activity.DisplayName: SayHello +Activity.Kind: Internal +Activity.StartTime: 2023-02-09T19:05:32.5563112Z +Activity.Duration: 00:00:00.0028144 +Activity.Tags: + foo: bar +StatusCode: Error +Resource associated with Activity: + service.name: unknown_service:Examples.TailBasedSamplingAtSpanLevel + +Dropping activity with id 00-ea861bda268c58d328ab7cbe49851499-daba29055de80a53-00 +and status Ok + +Including error activity with id +00-802dea991247e2d699d943167eb546de-cc120b0bd1741b52-01 and status Error +Activity.TraceId: 802dea991247e2d699d943167eb546de +Activity.SpanId: cc120b0bd1741b52 +Activity.TraceFlags: Recorded +Activity.ActivitySourceName: SDK.TailSampling.POC +Activity.DisplayName: SayHello +Activity.Kind: Internal +Activity.StartTime: 2023-02-09T19:05:32.7021138Z +Activity.Duration: 00:00:00.0000012 +Activity.Tags: + foo: bar +StatusCode: Error +Resource associated with Activity: + service.name: unknown_service:Examples.TailBasedSamplingAtSpanLevel + +Including head-sampled activity with id +00-f3c88010615e285c8f3cb3e2bcd70c7f-f9316215f12437c3-01 and status Ok +Activity.TraceId: f3c88010615e285c8f3cb3e2bcd70c7f +Activity.SpanId: f9316215f12437c3 +Activity.TraceFlags: Recorded +Activity.ActivitySourceName: SDK.TailSampling.POC +Activity.DisplayName: SayHello +Activity.Kind: Internal +Activity.StartTime: 2023-02-09T19:05:32.8519346Z +Activity.Duration: 00:00:00.0000034 +Activity.Tags: + foo: bar +StatusCode: Ok +Resource associated with Activity: + service.name: unknown_service:Examples.TailBasedSamplingAtSpanLevel +``` diff --git a/docs/trace/tail-based-sampling-span-level/TailSamplingProcessor.cs b/docs/trace/tail-based-sampling-span-level/TailSamplingProcessor.cs new file mode 100644 index 00000000000..19352cfa898 --- /dev/null +++ b/docs/trace/tail-based-sampling-span-level/TailSamplingProcessor.cs @@ -0,0 +1,73 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +using System.Diagnostics; +using OpenTelemetry; + +namespace SDKBasedSpanLevelTailSamplingSample; + +/// +/// A custom processor for filtering instances. +/// +internal sealed class TailSamplingProcessor : BaseProcessor +{ + public TailSamplingProcessor() + : base() + { + } + + public override void OnEnd(Activity activity) + { + if (activity.Recorded) + { + // This means that this activity was included based on head-based sampling, + // we continue with that decision and no further change is needed. + Console.WriteLine($"Including head-sampled activity with id {activity.Id} and status {activity.Status}"); + } + else + { + this.IncludeForExportIfFailedActivity(activity); + } + + base.OnEnd(activity); + } + + // Note: This is used to filter spans at the end of a span. + // This is a basic form of tail-based sampling at a span level. + // If a span failed, we always sample it in addition to all head-sampled spans. + // In this example, each span is filtered individually without consideration to any other spans. + // Tail-sampling this way involves many tradeoffs. A few examples of the tradeoffs: + // 1. Performance: Unlike head-based sampling where the sampling decision is made at span creation time, in + // tail sampling the decision is made only at the end, so there is additional memory cost. + // 2. Traces will not be complete: Since this sampling is at a span level, the generated trace will be partial and won't be complete. + // For example, if another part of the call tree is successful, those spans may not be sampled in leading to a partial trace. + // 3. If multiple exporters are used, this decision will impact all of them: https://github.com/open-telemetry/opentelemetry-dotnet/issues/3861. + private void IncludeForExportIfFailedActivity(Activity activity) + { + if (activity.Status == ActivityStatusCode.Error) + { + // We decide to always include all the failure spans + // Set the recorded flag so that this will be exported. + activity.ActivityTraceFlags |= ActivityTraceFlags.Recorded; + Console.WriteLine($"Including error activity with id {activity.Id} and status {activity.Status}"); + } + else + { + // This span is not sampled and exporters won't see this span. + Console.WriteLine($"Dropping activity with id {activity.Id} and status {activity.Status}"); + } + } +} diff --git a/docs/trace/tail-based-sampling-span-level/tail-based-sampling-example.csproj b/docs/trace/tail-based-sampling-span-level/tail-based-sampling-example.csproj new file mode 100644 index 00000000000..19aa9791432 --- /dev/null +++ b/docs/trace/tail-based-sampling-span-level/tail-based-sampling-example.csproj @@ -0,0 +1,5 @@ + + + + + From 9395b9e944cdae19a9823c0bc3dacb257c9c4863 Mon Sep 17 00:00:00 2001 From: Alan West <3676547+alanwest@users.noreply.github.com> Date: Fri, 10 Mar 2023 15:16:33 -0800 Subject: [PATCH 4/4] Expose exponential histogram data (#4290) --- .../.publicApi/net462/PublicAPI.Shipped.txt | 2 +- .../.publicApi/net462/PublicAPI.Unshipped.txt | 15 ++- .../.publicApi/net6.0/PublicAPI.Shipped.txt | 2 +- .../.publicApi/net6.0/PublicAPI.Unshipped.txt | 15 ++- .../netstandard2.0/PublicAPI.Shipped.txt | 2 +- .../netstandard2.0/PublicAPI.Unshipped.txt | 15 ++- .../netstandard2.1/PublicAPI.Shipped.txt | 2 +- .../netstandard2.1/PublicAPI.Unshipped.txt | 15 ++- .../Base2ExponentialBucketHistogram.cs | 21 ++--- .../Metrics/CircularBufferBuckets.cs | 16 +--- .../Metrics/ExponentialBucketSnapshot.cs | 42 --------- ...kets.cs => ExponentialHistogramBuckets.cs} | 40 +++++--- .../Metrics/ExponentialHistogramData.cs | 36 ++++++++ src/OpenTelemetry/Metrics/MetricPoint.cs | 92 +++++++++---------- .../Metrics/AggregatorTest.cs | 21 ++--- 15 files changed, 193 insertions(+), 143 deletions(-) delete mode 100644 src/OpenTelemetry/Metrics/ExponentialBucketSnapshot.cs rename src/OpenTelemetry/Metrics/{ExponentialBuckets.cs => ExponentialHistogramBuckets.cs} (65%) create mode 100644 src/OpenTelemetry/Metrics/ExponentialHistogramData.cs diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt index 66ffcd00e8a..c57d2df8e3c 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Shipped.txt @@ -10,7 +10,7 @@ ~OpenTelemetry.Metrics.Metric.MeterVersion.get -> string ~OpenTelemetry.Metrics.Metric.Name.get -> string ~OpenTelemetry.Metrics.Metric.Unit.get -> string -~OpenTelemetry.Metrics.MetricPoint.GetHistogramBuckets() -> OpenTelemetry.Metrics.HistogramBuckets +OpenTelemetry.Metrics.MetricPoint.GetHistogramBuckets() -> OpenTelemetry.Metrics.HistogramBuckets! ~OpenTelemetry.Metrics.MetricStreamConfiguration.Description.get -> string ~OpenTelemetry.Metrics.MetricStreamConfiguration.Description.set -> void ~OpenTelemetry.Metrics.MetricStreamConfiguration.Name.get -> string diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt index 955d808767a..fd540c1b338 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt @@ -10,13 +10,26 @@ 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.ExponentialHistogramBuckets +OpenTelemetry.Metrics.ExponentialHistogramBuckets.Enumerator +OpenTelemetry.Metrics.ExponentialHistogramBuckets.Enumerator.Current.get -> long +OpenTelemetry.Metrics.ExponentialHistogramBuckets.Enumerator.Enumerator() -> void +OpenTelemetry.Metrics.ExponentialHistogramBuckets.Enumerator.MoveNext() -> bool +OpenTelemetry.Metrics.ExponentialHistogramBuckets.GetEnumerator() -> OpenTelemetry.Metrics.ExponentialHistogramBuckets.Enumerator +OpenTelemetry.Metrics.ExponentialHistogramBuckets.Offset.get -> int +OpenTelemetry.Metrics.ExponentialHistogramData +OpenTelemetry.Metrics.ExponentialHistogramData.NegativeBuckets.get -> OpenTelemetry.Metrics.ExponentialHistogramBuckets! +OpenTelemetry.Metrics.ExponentialHistogramData.PositiveBuckets.get -> OpenTelemetry.Metrics.ExponentialHistogramBuckets! +OpenTelemetry.Metrics.ExponentialHistogramData.Scale.get -> int +OpenTelemetry.Metrics.ExponentialHistogramData.ZeroCount.get -> long OpenTelemetry.Metrics.TraceBasedExemplarFilter OpenTelemetry.Metrics.TraceBasedExemplarFilter.TraceBasedExemplarFilter() -> void static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.SetExemplarFilter(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder, OpenTelemetry.Metrics.ExemplarFilter! exemplarFilter) -> OpenTelemetry.Metrics.MeterProviderBuilder! ~abstract OpenTelemetry.Metrics.ExemplarFilter.ShouldSample(double value, System.ReadOnlySpan> tags) -> bool ~abstract OpenTelemetry.Metrics.ExemplarFilter.ShouldSample(long value, System.ReadOnlySpan> tags) -> bool ~OpenTelemetry.Metrics.Exemplar.FilteredTags.get -> System.Collections.Generic.List> -~OpenTelemetry.Metrics.MetricPoint.GetExemplars() -> OpenTelemetry.Metrics.Exemplar[] +OpenTelemetry.Metrics.MetricPoint.GetExemplars() -> OpenTelemetry.Metrics.Exemplar[]! +OpenTelemetry.Metrics.MetricPoint.GetExponentialHistogramData() -> OpenTelemetry.Metrics.ExponentialHistogramData! ~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 diff --git a/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Shipped.txt b/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Shipped.txt index 66ffcd00e8a..c57d2df8e3c 100644 --- a/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Shipped.txt @@ -10,7 +10,7 @@ ~OpenTelemetry.Metrics.Metric.MeterVersion.get -> string ~OpenTelemetry.Metrics.Metric.Name.get -> string ~OpenTelemetry.Metrics.Metric.Unit.get -> string -~OpenTelemetry.Metrics.MetricPoint.GetHistogramBuckets() -> OpenTelemetry.Metrics.HistogramBuckets +OpenTelemetry.Metrics.MetricPoint.GetHistogramBuckets() -> OpenTelemetry.Metrics.HistogramBuckets! ~OpenTelemetry.Metrics.MetricStreamConfiguration.Description.get -> string ~OpenTelemetry.Metrics.MetricStreamConfiguration.Description.set -> void ~OpenTelemetry.Metrics.MetricStreamConfiguration.Name.get -> string diff --git a/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt index b334cfe1664..dc2f9fb5133 100644 --- a/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt @@ -10,13 +10,26 @@ 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.ExponentialHistogramBuckets +OpenTelemetry.Metrics.ExponentialHistogramBuckets.Enumerator +OpenTelemetry.Metrics.ExponentialHistogramBuckets.Enumerator.Current.get -> long +OpenTelemetry.Metrics.ExponentialHistogramBuckets.Enumerator.Enumerator() -> void +OpenTelemetry.Metrics.ExponentialHistogramBuckets.Enumerator.MoveNext() -> bool +OpenTelemetry.Metrics.ExponentialHistogramBuckets.GetEnumerator() -> OpenTelemetry.Metrics.ExponentialHistogramBuckets.Enumerator +OpenTelemetry.Metrics.ExponentialHistogramBuckets.Offset.get -> int +OpenTelemetry.Metrics.ExponentialHistogramData +OpenTelemetry.Metrics.ExponentialHistogramData.NegativeBuckets.get -> OpenTelemetry.Metrics.ExponentialHistogramBuckets! +OpenTelemetry.Metrics.ExponentialHistogramData.PositiveBuckets.get -> OpenTelemetry.Metrics.ExponentialHistogramBuckets! +OpenTelemetry.Metrics.ExponentialHistogramData.Scale.get -> int +OpenTelemetry.Metrics.ExponentialHistogramData.ZeroCount.get -> long OpenTelemetry.Metrics.TraceBasedExemplarFilter OpenTelemetry.Metrics.TraceBasedExemplarFilter.TraceBasedExemplarFilter() -> void static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.SetExemplarFilter(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder, OpenTelemetry.Metrics.ExemplarFilter! exemplarFilter) -> OpenTelemetry.Metrics.MeterProviderBuilder! ~abstract OpenTelemetry.Metrics.ExemplarFilter.ShouldSample(double value, System.ReadOnlySpan> tags) -> bool ~abstract OpenTelemetry.Metrics.ExemplarFilter.ShouldSample(long value, System.ReadOnlySpan> tags) -> bool ~OpenTelemetry.Metrics.Exemplar.FilteredTags.get -> System.Collections.Generic.List> -~OpenTelemetry.Metrics.MetricPoint.GetExemplars() -> OpenTelemetry.Metrics.Exemplar[] +OpenTelemetry.Metrics.MetricPoint.GetExemplars() -> OpenTelemetry.Metrics.Exemplar[]! +OpenTelemetry.Metrics.MetricPoint.GetExponentialHistogramData() -> OpenTelemetry.Metrics.ExponentialHistogramData! ~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 diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt index 66ffcd00e8a..c57d2df8e3c 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Shipped.txt @@ -10,7 +10,7 @@ ~OpenTelemetry.Metrics.Metric.MeterVersion.get -> string ~OpenTelemetry.Metrics.Metric.Name.get -> string ~OpenTelemetry.Metrics.Metric.Unit.get -> string -~OpenTelemetry.Metrics.MetricPoint.GetHistogramBuckets() -> OpenTelemetry.Metrics.HistogramBuckets +OpenTelemetry.Metrics.MetricPoint.GetHistogramBuckets() -> OpenTelemetry.Metrics.HistogramBuckets! ~OpenTelemetry.Metrics.MetricStreamConfiguration.Description.get -> string ~OpenTelemetry.Metrics.MetricStreamConfiguration.Description.set -> void ~OpenTelemetry.Metrics.MetricStreamConfiguration.Name.get -> string diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index b334cfe1664..dc2f9fb5133 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -10,13 +10,26 @@ 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.ExponentialHistogramBuckets +OpenTelemetry.Metrics.ExponentialHistogramBuckets.Enumerator +OpenTelemetry.Metrics.ExponentialHistogramBuckets.Enumerator.Current.get -> long +OpenTelemetry.Metrics.ExponentialHistogramBuckets.Enumerator.Enumerator() -> void +OpenTelemetry.Metrics.ExponentialHistogramBuckets.Enumerator.MoveNext() -> bool +OpenTelemetry.Metrics.ExponentialHistogramBuckets.GetEnumerator() -> OpenTelemetry.Metrics.ExponentialHistogramBuckets.Enumerator +OpenTelemetry.Metrics.ExponentialHistogramBuckets.Offset.get -> int +OpenTelemetry.Metrics.ExponentialHistogramData +OpenTelemetry.Metrics.ExponentialHistogramData.NegativeBuckets.get -> OpenTelemetry.Metrics.ExponentialHistogramBuckets! +OpenTelemetry.Metrics.ExponentialHistogramData.PositiveBuckets.get -> OpenTelemetry.Metrics.ExponentialHistogramBuckets! +OpenTelemetry.Metrics.ExponentialHistogramData.Scale.get -> int +OpenTelemetry.Metrics.ExponentialHistogramData.ZeroCount.get -> long OpenTelemetry.Metrics.TraceBasedExemplarFilter OpenTelemetry.Metrics.TraceBasedExemplarFilter.TraceBasedExemplarFilter() -> void static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.SetExemplarFilter(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder, OpenTelemetry.Metrics.ExemplarFilter! exemplarFilter) -> OpenTelemetry.Metrics.MeterProviderBuilder! ~abstract OpenTelemetry.Metrics.ExemplarFilter.ShouldSample(double value, System.ReadOnlySpan> tags) -> bool ~abstract OpenTelemetry.Metrics.ExemplarFilter.ShouldSample(long value, System.ReadOnlySpan> tags) -> bool ~OpenTelemetry.Metrics.Exemplar.FilteredTags.get -> System.Collections.Generic.List> -~OpenTelemetry.Metrics.MetricPoint.GetExemplars() -> OpenTelemetry.Metrics.Exemplar[] +OpenTelemetry.Metrics.MetricPoint.GetExemplars() -> OpenTelemetry.Metrics.Exemplar[]! +OpenTelemetry.Metrics.MetricPoint.GetExponentialHistogramData() -> OpenTelemetry.Metrics.ExponentialHistogramData! ~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 diff --git a/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Shipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Shipped.txt index 66ffcd00e8a..c57d2df8e3c 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Shipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Shipped.txt @@ -10,7 +10,7 @@ ~OpenTelemetry.Metrics.Metric.MeterVersion.get -> string ~OpenTelemetry.Metrics.Metric.Name.get -> string ~OpenTelemetry.Metrics.Metric.Unit.get -> string -~OpenTelemetry.Metrics.MetricPoint.GetHistogramBuckets() -> OpenTelemetry.Metrics.HistogramBuckets +OpenTelemetry.Metrics.MetricPoint.GetHistogramBuckets() -> OpenTelemetry.Metrics.HistogramBuckets! ~OpenTelemetry.Metrics.MetricStreamConfiguration.Description.get -> string ~OpenTelemetry.Metrics.MetricStreamConfiguration.Description.set -> void ~OpenTelemetry.Metrics.MetricStreamConfiguration.Name.get -> string diff --git a/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt index b334cfe1664..dc2f9fb5133 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt @@ -10,13 +10,26 @@ 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.ExponentialHistogramBuckets +OpenTelemetry.Metrics.ExponentialHistogramBuckets.Enumerator +OpenTelemetry.Metrics.ExponentialHistogramBuckets.Enumerator.Current.get -> long +OpenTelemetry.Metrics.ExponentialHistogramBuckets.Enumerator.Enumerator() -> void +OpenTelemetry.Metrics.ExponentialHistogramBuckets.Enumerator.MoveNext() -> bool +OpenTelemetry.Metrics.ExponentialHistogramBuckets.GetEnumerator() -> OpenTelemetry.Metrics.ExponentialHistogramBuckets.Enumerator +OpenTelemetry.Metrics.ExponentialHistogramBuckets.Offset.get -> int +OpenTelemetry.Metrics.ExponentialHistogramData +OpenTelemetry.Metrics.ExponentialHistogramData.NegativeBuckets.get -> OpenTelemetry.Metrics.ExponentialHistogramBuckets! +OpenTelemetry.Metrics.ExponentialHistogramData.PositiveBuckets.get -> OpenTelemetry.Metrics.ExponentialHistogramBuckets! +OpenTelemetry.Metrics.ExponentialHistogramData.Scale.get -> int +OpenTelemetry.Metrics.ExponentialHistogramData.ZeroCount.get -> long OpenTelemetry.Metrics.TraceBasedExemplarFilter OpenTelemetry.Metrics.TraceBasedExemplarFilter.TraceBasedExemplarFilter() -> void static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.SetExemplarFilter(this OpenTelemetry.Metrics.MeterProviderBuilder! meterProviderBuilder, OpenTelemetry.Metrics.ExemplarFilter! exemplarFilter) -> OpenTelemetry.Metrics.MeterProviderBuilder! ~abstract OpenTelemetry.Metrics.ExemplarFilter.ShouldSample(double value, System.ReadOnlySpan> tags) -> bool ~abstract OpenTelemetry.Metrics.ExemplarFilter.ShouldSample(long value, System.ReadOnlySpan> tags) -> bool ~OpenTelemetry.Metrics.Exemplar.FilteredTags.get -> System.Collections.Generic.List> -~OpenTelemetry.Metrics.MetricPoint.GetExemplars() -> OpenTelemetry.Metrics.Exemplar[] +OpenTelemetry.Metrics.MetricPoint.GetExemplars() -> OpenTelemetry.Metrics.Exemplar[]! +OpenTelemetry.Metrics.MetricPoint.GetExponentialHistogramData() -> OpenTelemetry.Metrics.ExponentialHistogramData! ~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 diff --git a/src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogram.cs b/src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogram.cs index abc00f1cd69..2f90e9b460e 100644 --- a/src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogram.cs +++ b/src/OpenTelemetry/Metrics/Base2ExponentialBucketHistogram.cs @@ -38,10 +38,7 @@ internal sealed class Base2ExponentialBucketHistogram internal int IsCriticalSectionOccupied = 0; - internal int SnapshotScale; - internal long SnapshotZeroCount; - internal CircularBufferBuckets SnapshotPositiveBuckets; - internal CircularBufferBuckets SnapshotNegativeBuckets; + internal ExponentialHistogramData SnapshotExponentialHistogramData = new ExponentialHistogramData(); private int scale; private double scalingFactor; // 2 ^ scale / log(2) @@ -135,9 +132,6 @@ internal int Scale internal CircularBufferBuckets NegativeBuckets { get; } - internal ExponentialBucketSnapshot ExponentialBucketSnapshot => - new ExponentialBucketSnapshot(this.SnapshotScale, this.SnapshotZeroCount, this.SnapshotPositiveBuckets, this.SnapshotNegativeBuckets); - /// /// Maps a finite positive IEEE 754 double-precision floating-point /// number to Bucket[index] = ( base ^ index, base ^ (index + 1) ], @@ -231,9 +225,14 @@ internal void Reset() internal void Snapshot() { - this.SnapshotScale = this.Scale; - this.SnapshotZeroCount = this.ZeroCount; - this.SnapshotPositiveBuckets = this.PositiveBuckets.Copy(); - this.SnapshotNegativeBuckets = this.NegativeBuckets.Copy(); + this.SnapshotExponentialHistogramData.Scale = this.Scale; + this.SnapshotExponentialHistogramData.ZeroCount = this.ZeroCount; + this.SnapshotExponentialHistogramData.PositiveBuckets.SnapshotBuckets(this.PositiveBuckets); + this.SnapshotExponentialHistogramData.NegativeBuckets.SnapshotBuckets(this.NegativeBuckets); + } + + internal ExponentialHistogramData GetExponentialHistogramData() + { + return this.SnapshotExponentialHistogramData; } } diff --git a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs index e8c0ef02ea1..b7511e8a7d7 100644 --- a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs +++ b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs @@ -278,24 +278,14 @@ internal void Reset() } } - internal CircularBufferBuckets Copy() + internal void Copy(long[] dst) { - // TODO: Consider avoiding allocating a new one every time. - // Maybe we can have two instances that we swap between snapshots. - var copy = new CircularBufferBuckets(this.Capacity); - copy.begin = this.begin; - copy.end = this.end; + Debug.Assert(dst.Length == this.Capacity, "The length of the destination array must equal the capacity."); if (this.trait != null) { - copy.trait = new long[this.trait.Length]; - for (var i = 0; i < copy.trait.Length; ++i) - { - copy.trait[i] = this.trait[i]; - } + Array.Copy(this.trait, dst, this.Capacity); } - - return copy; } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/OpenTelemetry/Metrics/ExponentialBucketSnapshot.cs b/src/OpenTelemetry/Metrics/ExponentialBucketSnapshot.cs deleted file mode 100644 index f780100aade..00000000000 --- a/src/OpenTelemetry/Metrics/ExponentialBucketSnapshot.cs +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright The OpenTelemetry Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -namespace OpenTelemetry.Metrics; - -internal class ExponentialBucketSnapshot -{ - internal ExponentialBucketSnapshot(int scale, long snapshotZeroCount, CircularBufferBuckets positiveBuckets, CircularBufferBuckets negativeBuckets) - { - this.Scale = scale; - this.ZeroCount = snapshotZeroCount; - this.PositiveOffset = positiveBuckets.Offset; - this.NegativeOffset = negativeBuckets.Offset; - this.PositiveBuckets = new(positiveBuckets); - this.NegativeBuckets = new(negativeBuckets); - } - - public int Scale { get; } - - public long ZeroCount { get; } - - public int PositiveOffset { get; } - - public int NegativeOffset { get; } - - public ExponentialBuckets PositiveBuckets { get; } - - public ExponentialBuckets NegativeBuckets { get; } -} diff --git a/src/OpenTelemetry/Metrics/ExponentialBuckets.cs b/src/OpenTelemetry/Metrics/ExponentialHistogramBuckets.cs similarity index 65% rename from src/OpenTelemetry/Metrics/ExponentialBuckets.cs rename to src/OpenTelemetry/Metrics/ExponentialHistogramBuckets.cs index c32a270bf2d..83e0fe605c8 100644 --- a/src/OpenTelemetry/Metrics/ExponentialBuckets.cs +++ b/src/OpenTelemetry/Metrics/ExponentialHistogramBuckets.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,18 +14,34 @@ // limitations under the License. // +#nullable enable + namespace OpenTelemetry.Metrics; -internal class ExponentialBuckets +public sealed class ExponentialHistogramBuckets { - private readonly CircularBufferBuckets buckets; + private int size; + private long[] buckets = Array.Empty(); - internal ExponentialBuckets(CircularBufferBuckets buckets) + internal ExponentialHistogramBuckets() { - this.buckets = buckets; } - public Enumerator GetEnumerator() => new(this.buckets); + public int Offset { get; private set; } + + public Enumerator GetEnumerator() => new(this.size, this.buckets); + + internal void SnapshotBuckets(CircularBufferBuckets buckets) + { + if (this.buckets.Length != buckets.Capacity) + { + this.buckets = new long[buckets.Capacity]; + } + + this.Offset = buckets.Offset; + this.size = buckets.Size; + buckets.Copy(this.buckets); + } /// /// Enumerates the bucket counts of an exponential histogram. @@ -33,16 +49,16 @@ internal ExponentialBuckets(CircularBufferBuckets buckets) // Note: Does not implement IEnumerator<> to prevent accidental boxing. public struct Enumerator { - private readonly int lastIndex; - private readonly CircularBufferBuckets buckets; + private readonly int size; + private readonly long[] buckets; private int index; - internal Enumerator(CircularBufferBuckets buckets) + internal Enumerator(int size, long[] buckets) { + this.index = size; + this.size = size; this.buckets = buckets; - this.index = buckets.Offset; this.Current = default; - this.lastIndex = buckets.Size - 1; } /// @@ -60,7 +76,7 @@ internal Enumerator(CircularBufferBuckets buckets) /// collection. public bool MoveNext() { - if (this.index < this.buckets.Size) + if (this.index < this.size) { this.Current = this.buckets[this.index++]; return true; diff --git a/src/OpenTelemetry/Metrics/ExponentialHistogramData.cs b/src/OpenTelemetry/Metrics/ExponentialHistogramData.cs new file mode 100644 index 00000000000..d24402d2079 --- /dev/null +++ b/src/OpenTelemetry/Metrics/ExponentialHistogramData.cs @@ -0,0 +1,36 @@ +// +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#nullable enable + +namespace OpenTelemetry.Metrics; + +public sealed class ExponentialHistogramData +{ + internal ExponentialHistogramData() + { + this.PositiveBuckets = new(); + this.NegativeBuckets = new(); + } + + public int Scale { get; internal set; } + + public long ZeroCount { get; internal set; } + + public ExponentialHistogramBuckets PositiveBuckets { get; } + + public ExponentialHistogramBuckets NegativeBuckets { get; } +} diff --git a/src/OpenTelemetry/Metrics/MetricPoint.cs b/src/OpenTelemetry/Metrics/MetricPoint.cs index eabc5c1d13f..b411e937c98 100644 --- a/src/OpenTelemetry/Metrics/MetricPoint.cs +++ b/src/OpenTelemetry/Metrics/MetricPoint.cs @@ -14,6 +14,8 @@ // limitations under the License. // +#nullable enable + using System.Diagnostics; using System.Runtime.CompilerServices; @@ -31,7 +33,7 @@ public struct MetricPoint private readonly AggregationType aggType; - private MetricPointOptionalComponents mpComponents; + private MetricPointOptionalComponents? mpComponents; // Represents temporality adjusted "value" for double/long metric types or "count" when histogram private MetricPointValueStorage runningValue; @@ -58,15 +60,15 @@ internal MetricPoint( this.deltaLastValue = default; this.MetricPointStatus = MetricPointStatus.NoCollectPending; - ExemplarReservoir reservoir = null; + ExemplarReservoir? reservoir = null; if (this.aggType == AggregationType.HistogramWithBuckets || this.aggType == AggregationType.HistogramWithMinMaxBuckets) { this.mpComponents = new MetricPointOptionalComponents(); this.mpComponents.HistogramBuckets = new HistogramBuckets(histogramExplicitBounds); - if (aggregatorStore.IsExemplarEnabled()) + if (aggregatorStore!.IsExemplarEnabled()) { - reservoir = new AlignedHistogramBucketExemplarReservoir(histogramExplicitBounds.Length); + reservoir = new AlignedHistogramBucketExemplarReservoir(histogramExplicitBounds!.Length); } } else if (this.aggType == AggregationType.Histogram || @@ -86,7 +88,7 @@ internal MetricPoint( this.mpComponents = null; } - if (aggregatorStore.IsExemplarEnabled() && reservoir == null) + if (aggregatorStore!.IsExemplarEnabled() && reservoir == null) { reservoir = new SimpleExemplarReservoir(DefaultSimpleReservoirPoolSize); } @@ -250,7 +252,7 @@ public readonly double GetHistogramSum() this.ThrowNotSupportedMetricTypeException(nameof(this.GetHistogramSum)); } - return this.mpComponents.HistogramBuckets != null + return this.mpComponents!.HistogramBuckets != null ? this.mpComponents.HistogramBuckets.SnapshotSum : this.mpComponents.Base2ExponentialBucketHistogram.SnapshotSum; } @@ -273,27 +275,26 @@ public readonly HistogramBuckets GetHistogramBuckets() this.ThrowNotSupportedMetricTypeException(nameof(this.GetHistogramBuckets)); } - return this.mpComponents.HistogramBuckets; + return this.mpComponents!.HistogramBuckets; } /// - /// Gets the buckets of the histogram associated with the metric point. + /// Gets the exponential histogram data associated with the metric point. /// /// /// Applies to metric type. /// - /// . + /// . [MethodImpl(MethodImplOptions.AggressiveInlining)] -#pragma warning disable SA1202 // Elements should be ordered by access. GetExponentialBucketSnapshot will be public soon. - internal readonly ExponentialBucketSnapshot GetExponentialBucketSnapshot() + public ExponentialHistogramData GetExponentialHistogramData() { if (this.aggType != AggregationType.Base2ExponentialHistogram && this.aggType != AggregationType.Base2ExponentialHistogramWithMinMax) { - this.ThrowNotSupportedMetricTypeException(nameof(this.GetExponentialBucketSnapshot)); + this.ThrowNotSupportedMetricTypeException(nameof(this.GetExponentialHistogramData)); } - return this.mpComponents.Base2ExponentialBucketHistogram.ExponentialBucketSnapshot; + return this.mpComponents!.Base2ExponentialBucketHistogram.GetExponentialHistogramData(); } /// @@ -304,24 +305,23 @@ internal readonly ExponentialBucketSnapshot GetExponentialBucketSnapshot() /// True if minimum and maximum value exist, false otherwise. [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryGetHistogramMinMaxValues(out double min, out double max) -#pragma warning restore SA1202 // Elements should be ordered by access { if (this.aggType == AggregationType.HistogramWithMinMax || this.aggType == AggregationType.HistogramWithMinMaxBuckets) { - Debug.Assert(this.mpComponents.HistogramBuckets != null, "histogramBuckets was null"); + Debug.Assert(this.mpComponents!.HistogramBuckets != null, "histogramBuckets was null"); - min = this.mpComponents.HistogramBuckets.SnapshotMin; - max = this.mpComponents.HistogramBuckets.SnapshotMax; + min = this.mpComponents!.HistogramBuckets!.SnapshotMin; + max = this.mpComponents!.HistogramBuckets!.SnapshotMax; return true; } if (this.aggType == AggregationType.Base2ExponentialHistogramWithMinMax) { - Debug.Assert(this.mpComponents.Base2ExponentialBucketHistogram != null, "base2ExponentialBucketHistogram was null"); + Debug.Assert(this.mpComponents!.Base2ExponentialBucketHistogram != null, "base2ExponentialBucketHistogram was null"); - min = this.mpComponents.Base2ExponentialBucketHistogram.SnapshotMin; - max = this.mpComponents.Base2ExponentialBucketHistogram.SnapshotMax; + min = this.mpComponents!.Base2ExponentialBucketHistogram!.SnapshotMin; + max = this.mpComponents!.Base2ExponentialBucketHistogram!.SnapshotMax; return true; } @@ -430,7 +430,7 @@ internal void UpdateWithExemplar(long number, ReadOnlySpan> tags = default, bool reportExemplar = false) { - var histogramBuckets = this.mpComponents.HistogramBuckets; + var histogramBuckets = this.mpComponents!.HistogramBuckets; var sw = default(SpinWait); while (true) { @@ -1410,7 +1410,7 @@ private void UpdateHistogram(double number, ReadOnlySpan> tags = default, bool reportExemplar = false) { - var histogramBuckets = this.mpComponents.HistogramBuckets; + var histogramBuckets = this.mpComponents!.HistogramBuckets; var sw = default(SpinWait); while (true) { @@ -1441,7 +1441,7 @@ private void UpdateHistogramWithMinMax(double number, ReadOnlySpan> tags = default, bool reportExemplar = false) { - var histogramBuckets = this.mpComponents.HistogramBuckets; + var histogramBuckets = this.mpComponents!.HistogramBuckets; int i = histogramBuckets.FindBucketIndex(number); var sw = default(SpinWait); @@ -1472,7 +1472,7 @@ private void UpdateHistogramWithBuckets(double number, ReadOnlySpan> tags = default, bool reportExemplar = false) { - var histogramBuckets = this.mpComponents.HistogramBuckets; + var histogramBuckets = this.mpComponents!.HistogramBuckets; int i = histogramBuckets.FindBucketIndex(number); var sw = default(SpinWait); @@ -1508,7 +1508,7 @@ private void UpdateHistogramWithBucketsAndMinMax(double number, ReadOnlySpan> tags = default, bool reportExemplar = false) #pragma warning restore IDE0060 // Remove unused parameter { - var histogram = this.mpComponents.Base2ExponentialBucketHistogram; + var histogram = this.mpComponents!.Base2ExponentialBucketHistogram; var sw = default(SpinWait); while (true) @@ -1536,7 +1536,7 @@ private void UpdateBase2ExponentialHistogram(double number, ReadOnlySpan> tags = default, bool reportExemplar = false) #pragma warning restore IDE0060 // Remove unused parameter { - var histogram = this.mpComponents.Base2ExponentialBucketHistogram; + var histogram = this.mpComponents!.Base2ExponentialBucketHistogram; var sw = default(SpinWait); while (true) diff --git a/test/OpenTelemetry.Tests/Metrics/AggregatorTest.cs b/test/OpenTelemetry.Tests/Metrics/AggregatorTest.cs index 951f659611a..8561b59cf05 100644 --- a/test/OpenTelemetry.Tests/Metrics/AggregatorTest.cs +++ b/test/OpenTelemetry.Tests/Metrics/AggregatorTest.cs @@ -14,7 +14,6 @@ // limitations under the License. // -using System.Diagnostics.Metrics; using Xunit; namespace OpenTelemetry.Metrics.Tests @@ -222,7 +221,7 @@ internal void ExponentialHistogramTests(AggregationType aggregationType, Aggrega var sum = metricPoint.GetHistogramSum(); var hasMinMax = metricPoint.TryGetHistogramMinMaxValues(out var min, out var max); - AssertExponentialBucketsAreCorrect(expectedHistogram, metricPoint.GetExponentialBucketSnapshot()); + AssertExponentialBucketsAreCorrect(expectedHistogram, metricPoint.GetExponentialHistogramData()); Assert.Equal(40, sum); Assert.Equal(7, count); @@ -245,7 +244,7 @@ internal void ExponentialHistogramTests(AggregationType aggregationType, Aggrega if (aggregationTemporality == AggregationTemporality.Cumulative) { - AssertExponentialBucketsAreCorrect(expectedHistogram, metricPoint.GetExponentialBucketSnapshot()); + AssertExponentialBucketsAreCorrect(expectedHistogram, metricPoint.GetExponentialHistogramData()); Assert.Equal(40, sum); Assert.Equal(7, count); @@ -263,7 +262,7 @@ internal void ExponentialHistogramTests(AggregationType aggregationType, Aggrega else { expectedHistogram.Reset(); - AssertExponentialBucketsAreCorrect(expectedHistogram, metricPoint.GetExponentialBucketSnapshot()); + AssertExponentialBucketsAreCorrect(expectedHistogram, metricPoint.GetExponentialHistogramData()); Assert.Equal(0, sum); Assert.Equal(0, count); @@ -280,21 +279,21 @@ internal void ExponentialHistogramTests(AggregationType aggregationType, Aggrega } } - private static void AssertExponentialBucketsAreCorrect(Base2ExponentialBucketHistogram expectedHistogram, ExponentialBucketSnapshot buckets) + private static void AssertExponentialBucketsAreCorrect(Base2ExponentialBucketHistogram expectedHistogram, ExponentialHistogramData data) { - Assert.Equal(expectedHistogram.Scale, buckets.Scale); - Assert.Equal(expectedHistogram.ZeroCount, buckets.ZeroCount); - Assert.Equal(expectedHistogram.PositiveBuckets.Offset, buckets.PositiveOffset); - Assert.Equal(expectedHistogram.NegativeBuckets.Offset, buckets.NegativeOffset); + Assert.Equal(expectedHistogram.Scale, data.Scale); + Assert.Equal(expectedHistogram.ZeroCount, data.ZeroCount); + Assert.Equal(expectedHistogram.PositiveBuckets.Offset, data.PositiveBuckets.Offset); + Assert.Equal(expectedHistogram.NegativeBuckets.Offset, data.NegativeBuckets.Offset); var index = expectedHistogram.PositiveBuckets.Offset; - foreach (var bucketCount in buckets.PositiveBuckets) + foreach (var bucketCount in data.PositiveBuckets) { Assert.Equal(expectedHistogram.PositiveBuckets[index++], bucketCount); } index = expectedHistogram.NegativeBuckets.Offset; - foreach (var bucketCount in buckets.NegativeBuckets) + foreach (var bucketCount in data.NegativeBuckets) { Assert.Equal(expectedHistogram.PositiveBuckets[index++], bucketCount); }