diff --git a/src/OpenTelemetry/.publicApi/Experimental/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/Experimental/PublicAPI.Unshipped.txt index b530cdf98f6..c174b5b3d63 100644 --- a/src/OpenTelemetry/.publicApi/Experimental/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/Experimental/PublicAPI.Unshipped.txt @@ -23,7 +23,7 @@ OpenTelemetry.Metrics.ExemplarMeasurement OpenTelemetry.Metrics.ExemplarMeasurement.ExemplarMeasurement() -> void OpenTelemetry.Metrics.ExemplarMeasurement.Tags.get -> System.ReadOnlySpan> OpenTelemetry.Metrics.ExemplarMeasurement.Value.get -> T -OpenTelemetry.Metrics.MetricPoint.TryGetExemplars(out OpenTelemetry.Metrics.ReadOnlyExemplarCollection? exemplars) -> bool +OpenTelemetry.Metrics.MetricPoint.TryGetExemplars(out OpenTelemetry.Metrics.ReadOnlyExemplarCollection exemplars) -> bool OpenTelemetry.Metrics.MetricStreamConfiguration.CardinalityLimit.get -> int? OpenTelemetry.Metrics.MetricStreamConfiguration.CardinalityLimit.set -> void OpenTelemetry.Metrics.ReadOnlyExemplarCollection diff --git a/src/OpenTelemetry/Metrics/Exemplar/ReadOnlyExemplarCollection.cs b/src/OpenTelemetry/Metrics/Exemplar/ReadOnlyExemplarCollection.cs index 5f5fdae68ad..4a43bf3ebc6 100644 --- a/src/OpenTelemetry/Metrics/Exemplar/ReadOnlyExemplarCollection.cs +++ b/src/OpenTelemetry/Metrics/Exemplar/ReadOnlyExemplarCollection.cs @@ -23,6 +23,7 @@ namespace OpenTelemetry.Metrics; #endif readonly struct ReadOnlyExemplarCollection { + internal static readonly ReadOnlyExemplarCollection Empty = new(Array.Empty()); private readonly Exemplar[] exemplars; internal ReadOnlyExemplarCollection(Exemplar[] exemplars) @@ -50,24 +51,37 @@ public Enumerator GetEnumerator() internal ReadOnlyExemplarCollection Copy() { - var exemplarCopies = new Exemplar[this.exemplars.Length]; + var maximumCount = this.MaximumCount; - int i = 0; - foreach (ref readonly var exemplar in this) + if (maximumCount > 0) { - exemplar.Copy(ref exemplarCopies[i++]); + var exemplarCopies = new Exemplar[maximumCount]; + + int i = 0; + foreach (ref readonly var exemplar in this) + { + if (exemplar.IsUpdated()) + { + exemplar.Copy(ref exemplarCopies[i++]); + } + } + + return new ReadOnlyExemplarCollection(exemplarCopies); } - return new ReadOnlyExemplarCollection(exemplarCopies); + return Empty; } internal IReadOnlyList ToReadOnlyList() { var list = new List(this.MaximumCount); - foreach (var item in this) + foreach (var exemplar in this) { - list.Add(item); + // Note: If ToReadOnlyList is ever made public it should make sure + // to take copies of exemplars or make sure the instance was first + // copied using the Copy method above. + list.Add(exemplar); } return list; diff --git a/src/OpenTelemetry/Metrics/MetricPoint.cs b/src/OpenTelemetry/Metrics/MetricPoint.cs index 5be79f13942..2ccba6a307a 100644 --- a/src/OpenTelemetry/Metrics/MetricPoint.cs +++ b/src/OpenTelemetry/Metrics/MetricPoint.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using OpenTelemetry.Internal; @@ -373,10 +372,10 @@ public readonly bool TryGetHistogramMinMaxValues(out double min, out double max) [MethodImpl(MethodImplOptions.AggressiveInlining)] internal #endif - readonly bool TryGetExemplars([NotNullWhen(true)] out ReadOnlyExemplarCollection? exemplars) + readonly bool TryGetExemplars(out ReadOnlyExemplarCollection exemplars) { - exemplars = this.mpComponents?.Exemplars; - return exemplars.HasValue; + exemplars = this.mpComponents?.Exemplars ?? ReadOnlyExemplarCollection.Empty; + return exemplars.MaximumCount > 0; } internal readonly MetricPoint Copy() @@ -945,13 +944,14 @@ internal void TakeSnapshot(bool outputDelta) internal void TakeSnapshotWithExemplar(bool outputDelta) { Debug.Assert(this.mpComponents != null, "this.mpComponents was null"); + Debug.Assert(this.mpComponents!.ExemplarReservoir != null, "this.mpComponents.ExemplarReservoir was null"); switch (this.aggType) { case AggregationType.LongSumIncomingDelta: case AggregationType.LongSumIncomingCumulative: { - this.mpComponents!.AcquireLock(); + this.mpComponents.AcquireLock(); if (outputDelta) { @@ -965,7 +965,7 @@ internal void TakeSnapshotWithExemplar(bool outputDelta) this.snapshotValue.AsLong = this.runningValue.AsLong; } - this.mpComponents.Exemplars = this.mpComponents.ExemplarReservoir?.Collect(); + this.mpComponents.Exemplars = this.mpComponents.ExemplarReservoir!.Collect(); this.mpComponents.ReleaseLock(); @@ -989,7 +989,7 @@ internal void TakeSnapshotWithExemplar(bool outputDelta) this.snapshotValue.AsDouble = this.runningValue.AsDouble; } - this.mpComponents.Exemplars = this.mpComponents.ExemplarReservoir?.Collect(); + this.mpComponents.Exemplars = this.mpComponents.ExemplarReservoir!.Collect(); this.mpComponents.ReleaseLock(); @@ -998,11 +998,11 @@ internal void TakeSnapshotWithExemplar(bool outputDelta) case AggregationType.LongGauge: { - this.mpComponents!.AcquireLock(); + this.mpComponents.AcquireLock(); this.snapshotValue.AsLong = this.runningValue.AsLong; this.MetricPointStatus = MetricPointStatus.NoCollectPending; - this.mpComponents.Exemplars = this.mpComponents.ExemplarReservoir?.Collect(); + this.mpComponents.Exemplars = this.mpComponents.ExemplarReservoir!.Collect(); this.mpComponents.ReleaseLock(); @@ -1011,11 +1011,11 @@ internal void TakeSnapshotWithExemplar(bool outputDelta) case AggregationType.DoubleGauge: { - this.mpComponents!.AcquireLock(); + this.mpComponents.AcquireLock(); this.snapshotValue.AsDouble = this.runningValue.AsDouble; this.MetricPointStatus = MetricPointStatus.NoCollectPending; - this.mpComponents.Exemplars = this.mpComponents.ExemplarReservoir?.Collect(); + this.mpComponents.Exemplars = this.mpComponents.ExemplarReservoir!.Collect(); this.mpComponents.ReleaseLock(); @@ -1024,9 +1024,9 @@ internal void TakeSnapshotWithExemplar(bool outputDelta) case AggregationType.HistogramWithBuckets: { - Debug.Assert(this.mpComponents!.HistogramBuckets != null, "HistogramBuckets was null"); + Debug.Assert(this.mpComponents.HistogramBuckets != null, "HistogramBuckets was null"); - var histogramBuckets = this.mpComponents!.HistogramBuckets!; + var histogramBuckets = this.mpComponents.HistogramBuckets!; this.mpComponents.AcquireLock(); @@ -1041,7 +1041,7 @@ internal void TakeSnapshotWithExemplar(bool outputDelta) histogramBuckets.Snapshot(outputDelta); - this.mpComponents.Exemplars = this.mpComponents.ExemplarReservoir?.Collect(); + this.mpComponents.Exemplars = this.mpComponents.ExemplarReservoir!.Collect(); this.MetricPointStatus = MetricPointStatus.NoCollectPending; @@ -1052,9 +1052,9 @@ internal void TakeSnapshotWithExemplar(bool outputDelta) case AggregationType.Histogram: { - Debug.Assert(this.mpComponents!.HistogramBuckets != null, "HistogramBuckets was null"); + Debug.Assert(this.mpComponents.HistogramBuckets != null, "HistogramBuckets was null"); - var histogramBuckets = this.mpComponents!.HistogramBuckets!; + var histogramBuckets = this.mpComponents.HistogramBuckets!; this.mpComponents.AcquireLock(); @@ -1067,7 +1067,7 @@ internal void TakeSnapshotWithExemplar(bool outputDelta) histogramBuckets.RunningSum = 0; } - this.mpComponents.Exemplars = this.mpComponents.ExemplarReservoir?.Collect(); + this.mpComponents.Exemplars = this.mpComponents.ExemplarReservoir!.Collect(); this.MetricPointStatus = MetricPointStatus.NoCollectPending; this.mpComponents.ReleaseLock(); @@ -1077,9 +1077,9 @@ internal void TakeSnapshotWithExemplar(bool outputDelta) case AggregationType.HistogramWithMinMaxBuckets: { - Debug.Assert(this.mpComponents!.HistogramBuckets != null, "HistogramBuckets was null"); + Debug.Assert(this.mpComponents.HistogramBuckets != null, "HistogramBuckets was null"); - var histogramBuckets = this.mpComponents!.HistogramBuckets!; + var histogramBuckets = this.mpComponents.HistogramBuckets!; this.mpComponents.AcquireLock(); @@ -1098,7 +1098,7 @@ internal void TakeSnapshotWithExemplar(bool outputDelta) histogramBuckets.Snapshot(outputDelta); - this.mpComponents.Exemplars = this.mpComponents.ExemplarReservoir?.Collect(); + this.mpComponents.Exemplars = this.mpComponents.ExemplarReservoir!.Collect(); this.MetricPointStatus = MetricPointStatus.NoCollectPending; this.mpComponents.ReleaseLock(); @@ -1108,9 +1108,9 @@ internal void TakeSnapshotWithExemplar(bool outputDelta) case AggregationType.HistogramWithMinMax: { - Debug.Assert(this.mpComponents!.HistogramBuckets != null, "HistogramBuckets was null"); + Debug.Assert(this.mpComponents.HistogramBuckets != null, "HistogramBuckets was null"); - var histogramBuckets = this.mpComponents!.HistogramBuckets!; + var histogramBuckets = this.mpComponents.HistogramBuckets!; this.mpComponents.AcquireLock(); @@ -1127,7 +1127,7 @@ internal void TakeSnapshotWithExemplar(bool outputDelta) histogramBuckets.RunningMax = double.NegativeInfinity; } - this.mpComponents.Exemplars = this.mpComponents.ExemplarReservoir?.Collect(); + this.mpComponents.Exemplars = this.mpComponents.ExemplarReservoir!.Collect(); this.MetricPointStatus = MetricPointStatus.NoCollectPending; this.mpComponents.ReleaseLock(); @@ -1137,9 +1137,9 @@ internal void TakeSnapshotWithExemplar(bool outputDelta) case AggregationType.Base2ExponentialHistogram: { - Debug.Assert(this.mpComponents!.Base2ExponentialBucketHistogram != null, "Base2ExponentialBucketHistogram was null"); + Debug.Assert(this.mpComponents.Base2ExponentialBucketHistogram != null, "Base2ExponentialBucketHistogram was null"); - var histogram = this.mpComponents!.Base2ExponentialBucketHistogram!; + var histogram = this.mpComponents.Base2ExponentialBucketHistogram!; this.mpComponents.AcquireLock(); @@ -1154,7 +1154,7 @@ internal void TakeSnapshotWithExemplar(bool outputDelta) histogram.Reset(); } - this.mpComponents.Exemplars = this.mpComponents.ExemplarReservoir?.Collect(); + this.mpComponents.Exemplars = this.mpComponents.ExemplarReservoir!.Collect(); this.MetricPointStatus = MetricPointStatus.NoCollectPending; this.mpComponents.ReleaseLock(); @@ -1164,9 +1164,9 @@ internal void TakeSnapshotWithExemplar(bool outputDelta) case AggregationType.Base2ExponentialHistogramWithMinMax: { - Debug.Assert(this.mpComponents!.Base2ExponentialBucketHistogram != null, "Base2ExponentialBucketHistogram was null"); + Debug.Assert(this.mpComponents.Base2ExponentialBucketHistogram != null, "Base2ExponentialBucketHistogram was null"); - var histogram = this.mpComponents!.Base2ExponentialBucketHistogram!; + var histogram = this.mpComponents.Base2ExponentialBucketHistogram!; this.mpComponents.AcquireLock(); @@ -1185,7 +1185,7 @@ internal void TakeSnapshotWithExemplar(bool outputDelta) histogram.RunningMax = double.NegativeInfinity; } - this.mpComponents.Exemplars = this.mpComponents.ExemplarReservoir?.Collect(); + this.mpComponents.Exemplars = this.mpComponents.ExemplarReservoir!.Collect(); this.MetricPointStatus = MetricPointStatus.NoCollectPending; this.mpComponents.ReleaseLock(); diff --git a/src/OpenTelemetry/Metrics/MetricPointOptionalComponents.cs b/src/OpenTelemetry/Metrics/MetricPointOptionalComponents.cs index 84511b1b549..440a9cc36b6 100644 --- a/src/OpenTelemetry/Metrics/MetricPointOptionalComponents.cs +++ b/src/OpenTelemetry/Metrics/MetricPointOptionalComponents.cs @@ -20,7 +20,7 @@ internal sealed class MetricPointOptionalComponents public ExemplarReservoir? ExemplarReservoir; - public ReadOnlyExemplarCollection? Exemplars; + public ReadOnlyExemplarCollection Exemplars = ReadOnlyExemplarCollection.Empty; private int isCriticalSectionOccupied = 0; @@ -30,7 +30,7 @@ public MetricPointOptionalComponents Copy() { HistogramBuckets = this.HistogramBuckets?.Copy(), Base2ExponentialBucketHistogram = this.Base2ExponentialBucketHistogram?.Copy(), - Exemplars = this.Exemplars?.Copy(), + Exemplars = this.Exemplars.Copy(), }; return copy; diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs index 45479f92ae9..1cf3d1c1778 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs @@ -849,7 +849,7 @@ void AssertExemplars(T value, Metric metric) var result = metricPoint.TryGetExemplars(out var exemplars); Assert.True(result); - var exemplarEnumerator = exemplars.Value.GetEnumerator(); + var exemplarEnumerator = exemplars.GetEnumerator(); Assert.True(exemplarEnumerator.MoveNext()); ref readonly var exemplar = ref exemplarEnumerator.Current; diff --git a/test/OpenTelemetry.Tests/Metrics/MetricTestsBase.cs b/test/OpenTelemetry.Tests/Metrics/MetricTestsBase.cs index 11c90c8d899..7d72b773ea6 100644 --- a/test/OpenTelemetry.Tests/Metrics/MetricTestsBase.cs +++ b/test/OpenTelemetry.Tests/Metrics/MetricTestsBase.cs @@ -237,7 +237,7 @@ internal static IReadOnlyList GetExemplars(MetricPoint mp) { if (mp.TryGetExemplars(out var exemplars)) { - return exemplars.Value.ToReadOnlyList(); + return exemplars.ToReadOnlyList(); } return Array.Empty();