diff --git a/src/OpenTelemetry/.publicApi/Stable/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/Stable/PublicAPI.Unshipped.txt index a9a6c031d7..d0a7c8e949 100644 --- a/src/OpenTelemetry/.publicApi/Stable/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/Stable/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ +abstract OpenTelemetry.Metrics.ExemplarReservoir.Offer(in OpenTelemetry.Metrics.ExemplarMeasurement measurement) -> void OpenTelemetry.Metrics.Exemplar OpenTelemetry.Metrics.Exemplar.DoubleValue.get -> double OpenTelemetry.Metrics.Exemplar.Exemplar() -> void @@ -10,7 +11,12 @@ 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.MetricPoint.GetGaugeLastValueDecimal() -> decimal +OpenTelemetry.Metrics.MetricPoint.GetSumDecimal() -> decimal OpenTelemetry.Metrics.MetricPoint.TryGetExemplars(out OpenTelemetry.Metrics.ReadOnlyExemplarCollection exemplars) -> bool +OpenTelemetry.Metrics.MetricType.DecimalGauge = 46 -> OpenTelemetry.Metrics.MetricType +OpenTelemetry.Metrics.MetricType.DecimalSum = 30 -> OpenTelemetry.Metrics.MetricType +OpenTelemetry.Metrics.MetricType.DecimalSumNonMonotonic = 142 -> OpenTelemetry.Metrics.MetricType OpenTelemetry.Metrics.ReadOnlyExemplarCollection OpenTelemetry.Metrics.ReadOnlyExemplarCollection.Enumerator OpenTelemetry.Metrics.ReadOnlyExemplarCollection.Enumerator.Current.get -> OpenTelemetry.Metrics.Exemplar diff --git a/src/OpenTelemetry/Metrics/AggregationType.cs b/src/OpenTelemetry/Metrics/AggregationType.cs index 13d3a03ad5..c08fe7e9d8 100644 --- a/src/OpenTelemetry/Metrics/AggregationType.cs +++ b/src/OpenTelemetry/Metrics/AggregationType.cs @@ -69,4 +69,19 @@ internal enum AggregationType /// Exponential Histogram with sum, count, min, max. /// Base2ExponentialHistogramWithMinMax = 11, + + /// + /// Calculate SUM from incoming delta measurements. + /// + DecimalSumIncomingDelta = 12, + + /// + /// Calculate SUM from incoming cumulative measurements. + /// + DecimalSumIncomingCumulative = 13, + + /// + /// Keep LastValue. + /// + DecimalGauge = 14, } diff --git a/src/OpenTelemetry/Metrics/AggregatorStore.cs b/src/OpenTelemetry/Metrics/AggregatorStore.cs index 34a4bb5f2c..9087d3f4ce 100644 --- a/src/OpenTelemetry/Metrics/AggregatorStore.cs +++ b/src/OpenTelemetry/Metrics/AggregatorStore.cs @@ -51,6 +51,7 @@ internal sealed class AggregatorStore private readonly int exponentialHistogramMaxScale; private readonly UpdateLongDelegate updateLongCallback; private readonly UpdateDoubleDelegate updateDoubleCallback; + private readonly UpdateDecimalDelegate updateDecimalCallback; private readonly ExemplarFilterType exemplarFilter; private readonly Func[], int, int> lookupAggregatorStore; @@ -91,11 +92,13 @@ internal AggregatorStore( { this.updateLongCallback = this.UpdateLong; this.updateDoubleCallback = this.UpdateDouble; + this.updateDecimalCallback = this.UpdateDecimal; } else { this.updateLongCallback = this.UpdateLongCustomTags; this.updateDoubleCallback = this.UpdateDoubleCustomTags; + this.updateDecimalCallback = this.UpdateDecimalCustomTags; #if NET8_0_OR_GREATER var hs = FrozenSet.ToFrozenSet(metricStreamIdentity.TagKeys, StringComparer.Ordinal); #else @@ -150,6 +153,8 @@ internal AggregatorStore( private delegate void UpdateDoubleDelegate(double value, ReadOnlySpan> tags); + private delegate void UpdateDecimalDelegate(decimal value, ReadOnlySpan> tags); + internal DateTimeOffset StartTimeExclusive { get; private set; } internal DateTimeOffset EndTimeInclusive { get; private set; } @@ -189,6 +194,19 @@ internal void Update(double value, ReadOnlySpan> t } } + internal void Update(decimal value, ReadOnlySpan> tags) + { + try + { + this.updateDecimalCallback(value, tags); + } + catch (Exception) + { + Interlocked.Increment(ref this.DroppedMeasurements); + OpenTelemetrySdkEventSource.Log.MeasurementDropped(this.name, "SDK internal error occurred.", "Contact SDK owners."); + } + } + internal int Snapshot() { this.batchSize = 0; @@ -1043,6 +1061,20 @@ private void UpdateDoubleCustomTags(double value, ReadOnlySpan> tags) + { + var index = this.FindMetricAggregatorsDefault(tags); + + this.UpdateDecimalMetricPoint(index, value, tags); + } + + private void UpdateDecimalCustomTags(decimal value, ReadOnlySpan> tags) + { + var index = this.FindMetricAggregatorsCustomTag(tags); + + this.UpdateDecimalMetricPoint(index, value, tags); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void UpdateDoubleMetricPoint(int metricPointIndex, double value, ReadOnlySpan> tags) { @@ -1084,6 +1116,47 @@ private void UpdateDoubleMetricPoint(int metricPointIndex, double value, ReadOnl } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void UpdateDecimalMetricPoint(int metricPointIndex, decimal value, ReadOnlySpan> tags) + { + if (metricPointIndex < 0) + { + Interlocked.Increment(ref this.DroppedMeasurements); + + if (this.EmitOverflowAttribute) + { + this.InitializeOverflowTagPointIfNotInitialized(); + this.metricPoints[1].Update(value); + } + else if (Interlocked.CompareExchange(ref this.metricCapHitMessageLogged, 1, 0) == 0) + { + OpenTelemetrySdkEventSource.Log.MeasurementDropped(this.name, this.metricPointCapHitMessage, MetricPointCapHitFixMessage); + } + + return; + } + + var exemplarFilterType = this.exemplarFilter; + if (exemplarFilterType == ExemplarFilterType.AlwaysOff) + { + this.metricPoints[metricPointIndex].Update(value); + } + else if (exemplarFilterType == ExemplarFilterType.AlwaysOn) + { + this.metricPoints[metricPointIndex].UpdateWithExemplar( + value, + tags, + offerExemplar: true); + } + else + { + this.metricPoints[metricPointIndex].UpdateWithExemplar( + value, + tags, + offerExemplar: Activity.Current?.Recorded ?? false); + } + } + private int FindMetricAggregatorsDefault(ReadOnlySpan> tags) { int tagLength = tags.Length; diff --git a/src/OpenTelemetry/Metrics/Exemplar/AlignedHistogramBucketExemplarReservoir.cs b/src/OpenTelemetry/Metrics/Exemplar/AlignedHistogramBucketExemplarReservoir.cs index ce99dd85f7..f5d02f5991 100644 --- a/src/OpenTelemetry/Metrics/Exemplar/AlignedHistogramBucketExemplarReservoir.cs +++ b/src/OpenTelemetry/Metrics/Exemplar/AlignedHistogramBucketExemplarReservoir.cs @@ -32,4 +32,13 @@ public override void Offer(in ExemplarMeasurement measurement) this.UpdateExemplar(measurement.ExplicitBucketHistogramBucketIndex, in measurement); } + + public override void Offer(in ExemplarMeasurement measurement) + { + Debug.Assert( + measurement.ExplicitBucketHistogramBucketIndex != -1, + "ExplicitBucketHistogramBucketIndex was -1"); + + this.UpdateExemplar(measurement.ExplicitBucketHistogramBucketIndex, in measurement); + } } diff --git a/src/OpenTelemetry/Metrics/Exemplar/ExemplarReservoir.cs b/src/OpenTelemetry/Metrics/Exemplar/ExemplarReservoir.cs index 4eb800921f..6a78fa3eaf 100644 --- a/src/OpenTelemetry/Metrics/Exemplar/ExemplarReservoir.cs +++ b/src/OpenTelemetry/Metrics/Exemplar/ExemplarReservoir.cs @@ -58,6 +58,12 @@ internal ExemplarReservoir() /// . public abstract void Offer(in ExemplarMeasurement measurement); + /// + /// Offers a measurement to the reservoir. + /// + /// . + public abstract void Offer(in ExemplarMeasurement measurement); + /// /// Collects all the exemplars accumulated by the Reservoir. /// diff --git a/src/OpenTelemetry/Metrics/Exemplar/SimpleFixedSizeExemplarReservoir.cs b/src/OpenTelemetry/Metrics/Exemplar/SimpleFixedSizeExemplarReservoir.cs index d7f53e934c..b03240fc7d 100644 --- a/src/OpenTelemetry/Metrics/Exemplar/SimpleFixedSizeExemplarReservoir.cs +++ b/src/OpenTelemetry/Metrics/Exemplar/SimpleFixedSizeExemplarReservoir.cs @@ -32,6 +32,11 @@ public override void Offer(in ExemplarMeasurement measurement) this.Offer(in measurement); } + public override void Offer(in ExemplarMeasurement measurement) + { + this.Offer(in measurement); + } + protected override void OnCollected() { // Reset internal state irrespective of temporality. diff --git a/src/OpenTelemetry/Metrics/MeterProviderSdk.cs b/src/OpenTelemetry/Metrics/MeterProviderSdk.cs index 595fd54e77..7d2d1234b9 100644 --- a/src/OpenTelemetry/Metrics/MeterProviderSdk.cs +++ b/src/OpenTelemetry/Metrics/MeterProviderSdk.cs @@ -180,6 +180,9 @@ internal MeterProviderSdk( this.listener.SetMeasurementEventCallback(static (instrument, value, tags, state) => MeasurementRecordedLong(instrument, value, tags, state)); this.listener.SetMeasurementEventCallback(static (instrument, value, tags, state) => MeasurementRecordedLong(instrument, value, tags, state)); + // decimal + this.listener.SetMeasurementEventCallback(MeasurementRecordedDecimal); + this.listener.MeasurementsCompleted = MeasurementsCompleted; this.listener.Start(); @@ -228,6 +231,17 @@ internal static void MeasurementRecordedDouble(Instrument instrument, double val metricState.RecordMeasurementDouble(value, tags); } + internal static void MeasurementRecordedDecimal(Instrument instrument, decimal value, ReadOnlySpan> tags, object? state) + { + if (state is not MetricState metricState) + { + OpenTelemetrySdkEventSource.Log.MeasurementDropped(instrument?.Name ?? "UnknownInstrument", "SDK internal error occurred.", "Contact SDK owners."); + return; + } + + metricState.RecordMeasurementDecimal(value, tags); + } + internal object? InstrumentPublished(Instrument instrument, bool listeningIsManagedExternally) { var listenToInstrumentUsingSdkConfiguration = this.shouldListenTo(instrument); diff --git a/src/OpenTelemetry/Metrics/Metric.cs b/src/OpenTelemetry/Metrics/Metric.cs index 32b107c5b2..3e660302a1 100644 --- a/src/OpenTelemetry/Metrics/Metric.cs +++ b/src/OpenTelemetry/Metrics/Metric.cs @@ -83,6 +83,16 @@ internal Metric( aggType = AggregationType.DoubleSumIncomingCumulative; this.MetricType = MetricType.DoubleSum; } + else if (instrumentIdentity.InstrumentType == typeof(Counter)) + { + aggType = AggregationType.DecimalSumIncomingDelta; + this.MetricType = MetricType.DecimalSum; + } + else if (instrumentIdentity.InstrumentType == typeof(ObservableCounter)) + { + aggType = AggregationType.DecimalSumIncomingCumulative; + this.MetricType = MetricType.DecimalSum; + } else if (instrumentIdentity.InstrumentType == typeof(ObservableUpDownCounter) || instrumentIdentity.InstrumentType == typeof(ObservableUpDownCounter) || instrumentIdentity.InstrumentType == typeof(ObservableUpDownCounter) @@ -111,6 +121,16 @@ internal Metric( aggType = AggregationType.DoubleSumIncomingCumulative; this.MetricType = MetricType.DoubleSumNonMonotonic; } + else if (instrumentIdentity.InstrumentType == typeof(UpDownCounter)) + { + aggType = AggregationType.DecimalSumIncomingDelta; + this.MetricType = MetricType.DecimalSumNonMonotonic; + } + else if (instrumentIdentity.InstrumentType == typeof(ObservableUpDownCounter)) + { + aggType = AggregationType.DecimalSumIncomingCumulative; + this.MetricType = MetricType.DecimalSumNonMonotonic; + } else if (instrumentIdentity.InstrumentType == typeof(ObservableGauge) || instrumentIdentity.InstrumentType == typeof(ObservableGauge)) { @@ -125,12 +145,18 @@ internal Metric( aggType = AggregationType.LongGauge; this.MetricType = MetricType.LongGauge; } + else if (instrumentIdentity.InstrumentType == typeof(ObservableGauge)) + { + aggType = AggregationType.DecimalGauge; + this.MetricType = MetricType.DecimalGauge; + } else if (instrumentIdentity.InstrumentType == typeof(Histogram) || instrumentIdentity.InstrumentType == typeof(Histogram) || instrumentIdentity.InstrumentType == typeof(Histogram) || instrumentIdentity.InstrumentType == typeof(Histogram) || instrumentIdentity.InstrumentType == typeof(Histogram) - || instrumentIdentity.InstrumentType == typeof(Histogram)) + || instrumentIdentity.InstrumentType == typeof(Histogram) + || instrumentIdentity.InstrumentType == typeof(Histogram)) { var explicitBucketBounds = instrumentIdentity.HistogramBucketBounds; var exponentialMaxSize = instrumentIdentity.ExponentialHistogramMaxSize; @@ -228,6 +254,9 @@ internal void UpdateLong(long value, ReadOnlySpan> internal void UpdateDouble(double value, ReadOnlySpan> tags) => this.AggregatorStore.Update(value, tags); + internal void UpdateDecimal(decimal value, ReadOnlySpan> tags) + => this.AggregatorStore.Update(value, tags); + internal int Snapshot() => this.AggregatorStore.Snapshot(); } diff --git a/src/OpenTelemetry/Metrics/MetricPoint.cs b/src/OpenTelemetry/Metrics/MetricPoint.cs index 2e3e400a9e..7c00c1e738 100644 --- a/src/OpenTelemetry/Metrics/MetricPoint.cs +++ b/src/OpenTelemetry/Metrics/MetricPoint.cs @@ -25,6 +25,8 @@ public struct MetricPoint private readonly AggregationType aggType; + private readonly SemaphoreSlim semaphore = new SemaphoreSlim(1); + private MetricPointOptionalComponents? mpComponents; // Represents temporality adjusted "value" for double/long metric types or "count" when histogram @@ -196,6 +198,24 @@ public readonly double GetSumDouble() return this.snapshotValue.AsDouble; } + /// + /// Gets the sum decimal value associated with the metric point. + /// + /// + /// Applies to metric type. + /// + /// Long sum value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly decimal GetSumDecimal() + { + if (this.aggType != AggregationType.DecimalSumIncomingDelta && this.aggType != AggregationType.DecimalSumIncomingCumulative) + { + this.ThrowNotSupportedMetricTypeException(nameof(this.GetSumLong)); + } + + return this.snapshotValue.AsDecimal; + } + /// /// Gets the last long value of the gauge associated with the metric point. /// @@ -232,6 +252,24 @@ public readonly double GetGaugeLastValueDouble() return this.snapshotValue.AsDouble; } + /// + /// Gets the last decimal value of the gauge associated with the metric point. + /// + /// + /// Applies to metric type. + /// + /// Decimal gauge value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly decimal GetGaugeLastValueDecimal() + { + if (this.aggType != AggregationType.DecimalGauge) + { + this.ThrowNotSupportedMetricTypeException(nameof(this.GetGaugeLastValueDecimal)); + } + + return this.snapshotValue.AsDecimal; + } + /// /// Gets the count value of the histogram associated with the metric point. /// @@ -613,6 +651,130 @@ internal void UpdateWithExemplar(double number, ReadOnlySpan> tags, bool offerExemplar) + { + switch (this.aggType) + { + case AggregationType.DecimalSumIncomingDelta: + { + this.semaphore.Wait(); + this.runningValue.AsDecimal += number; + this.semaphore.Release(); + break; + } + + case AggregationType.DecimalSumIncomingCumulative: + case AggregationType.DecimalGauge: + { + this.semaphore.Wait(); + this.runningValue.AsDecimal = number; + this.semaphore.Release(); + break; + } + + case AggregationType.Histogram: + { + this.UpdateHistogram((double)number, tags, offerExemplar); + return; + } + + case AggregationType.HistogramWithMinMax: + { + this.UpdateHistogramWithMinMax((double)number, tags, offerExemplar); + return; + } + + case AggregationType.HistogramWithBuckets: + { + this.UpdateHistogramWithBuckets((double)number, tags, offerExemplar); + return; + } + + case AggregationType.HistogramWithMinMaxBuckets: + { + this.UpdateHistogramWithBucketsAndMinMax((double)number, tags, offerExemplar); + return; + } + + case AggregationType.Base2ExponentialHistogram: + { + this.UpdateBase2ExponentialHistogram((double)number, tags, offerExemplar); + return; + } + + case AggregationType.Base2ExponentialHistogramWithMinMax: + { + this.UpdateBase2ExponentialHistogramWithMinMax((double)number, tags, offerExemplar); + return; + } + } + + this.CompleteUpdate(); + + this.UpdateExemplar(number, tags, offerExemplar); + } + internal void TakeSnapshot(bool outputDelta) { switch (this.aggType) @@ -667,6 +829,35 @@ internal void TakeSnapshot(bool outputDelta) break; } + case AggregationType.DecimalSumIncomingDelta: + case AggregationType.DecimalSumIncomingCumulative: + { + this.semaphore.Wait(); + + if (outputDelta) + { + decimal initValue = this.runningValue.AsDecimal; + this.snapshotValue.AsDecimal = initValue - this.deltaLastValue.AsDecimal; + this.deltaLastValue.AsDecimal = initValue; + this.MetricPointStatus = MetricPointStatus.NoCollectPending; + + // Check again if value got updated, if yes reset status. + // This ensures no Updates get Lost. + if (initValue != this.runningValue.AsDecimal) + { + this.MetricPointStatus = MetricPointStatus.CollectPending; + } + } + else + { + this.snapshotValue.AsDecimal = this.runningValue.AsDecimal; + } + + this.semaphore.Release(); + + break; + } + case AggregationType.LongGauge: { this.snapshotValue.AsLong = Interlocked.Read(ref this.runningValue.AsLong); @@ -697,6 +888,25 @@ internal void TakeSnapshot(bool outputDelta) break; } + case AggregationType.DecimalGauge: + { + this.semaphore.Wait(); + + this.snapshotValue.AsDecimal = this.runningValue.AsDecimal; + this.MetricPointStatus = MetricPointStatus.NoCollectPending; + + // Check again if value got updated, if yes reset status. + // This ensures no Updates get Lost. + if (this.snapshotValue.AsDecimal != this.runningValue.AsDecimal) + { + this.MetricPointStatus = MetricPointStatus.CollectPending; + } + + this.semaphore.Release(); + + break; + } + case AggregationType.HistogramWithBuckets: { Debug.Assert(this.mpComponents?.HistogramBuckets != null, "HistogramBuckets was null"); @@ -1064,6 +1274,19 @@ private readonly void UpdateExemplar(double number, ReadOnlySpan> tags, bool offerExemplar) + { + if (offerExemplar) + { + Debug.Assert(this.mpComponents?.ExemplarReservoir != null, "ExemplarReservoir was null"); + + // TODO: A custom implementation of `ExemplarReservoir.Offer` might throw an exception. + this.mpComponents!.ExemplarReservoir!.Offer( + new ExemplarMeasurement(number, tags)); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void CompleteUpdate() { diff --git a/src/OpenTelemetry/Metrics/MetricPointValueStorage.cs b/src/OpenTelemetry/Metrics/MetricPointValueStorage.cs index 2e9ef64d95..31d2e032d6 100644 --- a/src/OpenTelemetry/Metrics/MetricPointValueStorage.cs +++ b/src/OpenTelemetry/Metrics/MetricPointValueStorage.cs @@ -13,4 +13,7 @@ internal struct MetricPointValueStorage [FieldOffset(0)] public double AsDouble; + + [FieldOffset(0)] + public decimal AsDecimal; } diff --git a/src/OpenTelemetry/Metrics/MetricState.cs b/src/OpenTelemetry/Metrics/MetricState.cs index ae552861b6..5623f37cb8 100644 --- a/src/OpenTelemetry/Metrics/MetricState.cs +++ b/src/OpenTelemetry/Metrics/MetricState.cs @@ -11,15 +11,18 @@ internal sealed class MetricState public readonly RecordMeasurementAction RecordMeasurementLong; public readonly RecordMeasurementAction RecordMeasurementDouble; + public readonly RecordMeasurementAction RecordMeasurementDecimal; private MetricState( Action completeMeasurement, RecordMeasurementAction recordMeasurementLong, - RecordMeasurementAction recordMeasurementDouble) + RecordMeasurementAction recordMeasurementDouble, + RecordMeasurementAction recordMeasurementDecimal) { this.CompleteMeasurement = completeMeasurement; this.RecordMeasurementLong = recordMeasurementLong; this.RecordMeasurementDouble = recordMeasurementDouble; + this.RecordMeasurementDecimal = recordMeasurementDecimal; } internal delegate void RecordMeasurementAction(T value, ReadOnlySpan> tags); @@ -32,7 +35,8 @@ public static MetricState BuildForSingleMetric( return new( completeMeasurement: () => MetricReader.DeactivateMetric(metric!), recordMeasurementLong: metric!.UpdateLong, - recordMeasurementDouble: metric!.UpdateDouble); + recordMeasurementDouble: metric!.UpdateDouble, + recordMeasurementDecimal: metric!.UpdateDecimal); } public static MetricState BuildForMetricList( @@ -65,6 +69,13 @@ public static MetricState BuildForMetricList( { metricsArray[i].UpdateDouble(v, t); } + }, + recordMeasurementDecimal: (v, t) => + { + for (int i = 0; i < metricsArray.Length; i++) + { + metricsArray[i].UpdateDecimal(v, t); + } }); } } diff --git a/src/OpenTelemetry/Metrics/MetricType.cs b/src/OpenTelemetry/Metrics/MetricType.cs index 526bdbb466..7a89e2363b 100644 --- a/src/OpenTelemetry/Metrics/MetricType.cs +++ b/src/OpenTelemetry/Metrics/MetricType.cs @@ -72,4 +72,19 @@ public enum MetricType : byte /// Non-monotonic Sum of Double type. /// DoubleSumNonMonotonic = 0x8d, + + /// + /// Sum of Decimal type. + /// + DecimalSum = 0x1e, + + /// + /// Non-monotonic Sum of Decimal type. + /// + DecimalSumNonMonotonic = 0x8e, + + /// + /// Gauge of Decimal type. + /// + DecimalGauge = 0x2e, } diff --git a/test/Benchmarks/Metrics/ExemplarBenchmarks.cs b/test/Benchmarks/Metrics/ExemplarBenchmarks.cs index ac1d347fba..608128ef2c 100644 --- a/test/Benchmarks/Metrics/ExemplarBenchmarks.cs +++ b/test/Benchmarks/Metrics/ExemplarBenchmarks.cs @@ -210,6 +210,14 @@ public override void Offer(in ExemplarMeasurement measurement) } } + public override void Offer(in ExemplarMeasurement measurement) + { + if ((double)measurement.Value >= this.threshold) + { + this.UpdateExemplar(this.measurementCount++ % this.Capacity, in measurement); + } + } + protected override void OnCollected() { this.measurementCount = 0; diff --git a/test/OpenTelemetry.Tests/Metrics/MetricApiTestsBase.cs b/test/OpenTelemetry.Tests/Metrics/MetricApiTestsBase.cs index cc5a34d7fd..4db5df59fa 100644 --- a/test/OpenTelemetry.Tests/Metrics/MetricApiTestsBase.cs +++ b/test/OpenTelemetry.Tests/Metrics/MetricApiTestsBase.cs @@ -5,7 +5,6 @@ using System.Diagnostics.Metrics; using Microsoft.Extensions.Configuration; using OpenTelemetry.Exporter; -using OpenTelemetry.Internal; using OpenTelemetry.Tests; using Xunit; using Xunit.Abstractions; @@ -1581,36 +1580,173 @@ public void SetupSdkProviderWithNoReader(bool hasViews) } [Fact] - public void UnsupportedMetricInstrument() + public void DecimalCounterMeasurement() { - using var meter = new Meter($"{Utils.GetCurrentMethodName()}"); + using var meter = new Meter(Utils.GetCurrentMethodName()); var exportedItems = new List(); using var container = this.BuildMeterProvider(out var meterProvider, builder => builder .AddMeter(meter.Name) .AddInMemoryExporter(exportedItems)); - using (var inMemoryEventListener = new InMemoryEventListener(OpenTelemetrySdkEventSource.Log)) - { - var counter = meter.CreateCounter("counter"); - counter.Add(1); - - // This validates that we log InstrumentIgnored event - // and not something else. - var instrumentIgnoredEvents = inMemoryEventListener.Events.Where((e) => e.EventId == 33); -#if BUILDING_HOSTING_TESTS - // Note: When using IMetricsListener this event is fired twice. Once - // for the SDK listener ignoring it because it isn't listening to - // the meter and then once for IMetricsListener ignoring it because - // decimal is not supported. - Assert.Equal(2, instrumentIgnoredEvents.Count()); -#else - Assert.Single(instrumentIgnoredEvents); -#endif + var counter = meter.CreateCounter("myCounter"); + counter.Add(1); + counter.Add(1); + + meterProvider.ForceFlush(); + Assert.Single(exportedItems); + var metric = exportedItems[0]; + Assert.Equal("myCounter", metric.Name); + List metricPoints = new List(); + foreach (ref readonly var mp in metric.GetMetricPoints()) + { + metricPoints.Add(mp); } - meterProvider.ForceFlush(MaxTimeToAllowForFlush); - Assert.Empty(exportedItems); + Assert.Single(metricPoints); + var metricPoint = metricPoints[0]; + Assert.Equal(2, metricPoint.GetSumDecimal()); + } + + [Fact] + public void DecimalObservableCounterMeasurement() + { + using var meter = new Meter(Utils.GetCurrentMethodName()); + var exportedItems = new List(); + + using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + .AddMeter(meter.Name) + .AddInMemoryExporter(exportedItems)); + + var observableCounter = meter.CreateObservableCounter("myObservableCounter", () => 100.5m); + + meterProvider.ForceFlush(); + Assert.Single(exportedItems); + var metric = exportedItems[0]; + Assert.Equal("myObservableCounter", metric.Name); + List metricPoints = new List(); + foreach (ref readonly var mp in metric.GetMetricPoints()) + { + metricPoints.Add(mp); + } + + Assert.Single(metricPoints); + var metricPoint = metricPoints[0]; + Assert.Equal(100.5m, metricPoint.GetSumDecimal()); + } + + [Fact] + public void DecimalUpDownCounterMeasurement() + { + using var meter = new Meter(Utils.GetCurrentMethodName()); + var exportedItems = new List(); + + using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + .AddMeter(meter.Name) + .AddInMemoryExporter(exportedItems)); + + var upDownCounter = meter.CreateUpDownCounter("myUpDownCounter"); + upDownCounter.Add(2); + upDownCounter.Add(-1); + + meterProvider.ForceFlush(); + Assert.Single(exportedItems); + var metric = exportedItems[0]; + Assert.Equal("myUpDownCounter", metric.Name); + List metricPoints = new List(); + foreach (ref readonly var mp in metric.GetMetricPoints()) + { + metricPoints.Add(mp); + } + + Assert.Single(metricPoints); + var metricPoint = metricPoints[0]; + Assert.Equal(1, metricPoint.GetSumDecimal()); + } + + [Fact] + public void DecimalObservableUpDownCounterMeasurement() + { + using var meter = new Meter(Utils.GetCurrentMethodName()); + var exportedItems = new List(); + + using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + .AddMeter(meter.Name) + .AddInMemoryExporter(exportedItems)); + + var observableUpDownCounter = meter.CreateObservableUpDownCounter("myObservableUpDownCounter", () => 100.5m); + + meterProvider.ForceFlush(); + Assert.Single(exportedItems); + var metric = exportedItems[0]; + Assert.Equal("myObservableUpDownCounter", metric.Name); + List metricPoints = new List(); + foreach (ref readonly var mp in metric.GetMetricPoints()) + { + metricPoints.Add(mp); + } + + Assert.Single(metricPoints); + var metricPoint = metricPoints[0]; + Assert.Equal(100.5m, metricPoint.GetSumDecimal()); + } + + [Fact] + public void DecimalObservableGaugeMeasurement() + { + using var meter = new Meter(Utils.GetCurrentMethodName()); + var exportedItems = new List(); + + using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + .AddMeter(meter.Name) + .AddInMemoryExporter(exportedItems)); + + var gauge = meter.CreateObservableGauge("myObservableGauge", () => 100.5m); + + meterProvider.ForceFlush(); + Assert.Single(exportedItems); + var metric = exportedItems[0]; + Assert.Equal("myObservableGauge", metric.Name); + List metricPoints = new List(); + foreach (ref readonly var mp in metric.GetMetricPoints()) + { + metricPoints.Add(mp); + } + + Assert.Single(metricPoints); + var metricPoint = metricPoints[0]; + Assert.Equal(100.5m, metricPoint.GetGaugeLastValueDecimal()); + } + + [Fact] + public void DecimalHistogramMeasurement() + { + using var meter = new Meter(Utils.GetCurrentMethodName()); + var exportedItems = new List(); + + using var container = this.BuildMeterProvider(out var meterProvider, builder => builder + .AddMeter(meter.Name) + .AddInMemoryExporter(exportedItems)); + + var histogram = meter.CreateHistogram("myHistogram"); + histogram.Record(1); + histogram.Record(100.5m); + histogram.Record(300.5436543m); + + meterProvider.ForceFlush(); + Assert.Single(exportedItems); + var metric = exportedItems[0]; + Assert.Equal("myHistogram", metric.Name); + List metricPoints = new List(); + foreach (ref readonly var mp in metric.GetMetricPoints()) + { + metricPoints.Add(mp); + } + + Assert.Single(metricPoints); + var metricPoint = metricPoints[0]; + Assert.Equal(3, metricPoint.GetHistogramCount()); + Assert.Equal(402.0436543, metricPoint.GetHistogramSum()); } internal static IConfiguration BuildConfiguration(bool emitOverflowAttribute, bool shouldReclaimUnusedMetricPoints) diff --git a/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs b/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs index 0e5058ba1b..67865d69f0 100644 --- a/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs +++ b/test/OpenTelemetry.Tests/Metrics/MetricExemplarTests.cs @@ -841,5 +841,10 @@ public override void Offer(in ExemplarMeasurement measurement) { throw new NotSupportedException(); } + + public override void Offer(in ExemplarMeasurement measurement) + { + throw new NotSupportedException(); + } } }