Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[sdk-metrics] Support .NET 9 Synchronous Gauge #5867

2 changes: 1 addition & 1 deletion docs/metrics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ static readonly Meter MyMeter = new("MyCompany.MyProduct.MyLibrary", "1.0");
| [Asynchronous Gauge](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#asynchronous-gauge) | [`ObservableGauge<T>`](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.observablegauge-1) |
| [Asynchronous UpDownCounter](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#asynchronous-updowncounter) | [`ObservableUpDownCounter<T>`](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.observableupdowncounter-1) |
| [Counter](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#counter) | [`Counter<T>`](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.counter-1) |
| [Gauge](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#gauge) (experimental) | N/A |
| [Gauge](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#gauge) | [`Gauge<T>`](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.gauge-1) |
| [Histogram](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#histogram) | [`Histogram<T>`](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.histogram-1) |
| [UpDownCounter](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#updowncounter) | [`UpDownCounter<T>`](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.updowncounter-1) |

Expand Down
5 changes: 5 additions & 0 deletions src/OpenTelemetry/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ Notes](../../RELEASENOTES.md).
See [#5854](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5854)
for details.

* Added support for collecting metrics emitted via the .NET 9
[Gauge&lt;T&gt;](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.gauge-1)
API.
([#5867](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5867))

## 1.9.0

Released 2024-Jun-14
Expand Down
14 changes: 14 additions & 0 deletions src/OpenTelemetry/Metrics/Metric.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ internal Metric(
aggType = AggregationType.DoubleGauge;
this.MetricType = MetricType.DoubleGauge;
}
else if (instrumentIdentity.InstrumentType == typeof(Gauge<double>)
|| instrumentIdentity.InstrumentType == typeof(Gauge<float>))
{
aggType = AggregationType.DoubleGauge;
this.MetricType = MetricType.DoubleGauge;
}
else if (instrumentIdentity.InstrumentType == typeof(ObservableGauge<long>)
|| instrumentIdentity.InstrumentType == typeof(ObservableGauge<int>)
|| instrumentIdentity.InstrumentType == typeof(ObservableGauge<short>)
Expand All @@ -125,6 +131,14 @@ internal Metric(
aggType = AggregationType.LongGauge;
this.MetricType = MetricType.LongGauge;
}
else if (instrumentIdentity.InstrumentType == typeof(Gauge<long>)
|| instrumentIdentity.InstrumentType == typeof(Gauge<int>)
|| instrumentIdentity.InstrumentType == typeof(Gauge<short>)
|| instrumentIdentity.InstrumentType == typeof(Gauge<byte>))
{
aggType = AggregationType.LongGauge;
this.MetricType = MetricType.LongGauge;
}
else if (instrumentIdentity.IsHistogram)
{
var explicitBucketBounds = instrumentIdentity.HistogramBucketBounds;
Expand Down
1 change: 1 addition & 0 deletions src/OpenTelemetry/Metrics/Reader/MetricReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public abstract partial class MetricReader : IDisposable

// Temporality is not defined for gauges, so this does not really affect anything.
var type when type == typeof(ObservableGauge<>) => AggregationTemporality.Delta,
var type when type == typeof(Gauge<>) => AggregationTemporality.Delta,

var type when type == typeof(UpDownCounter<>) => AggregationTemporality.Cumulative,
var type when type == typeof(ObservableUpDownCounter<>) => AggregationTemporality.Cumulative,
Expand Down
87 changes: 87 additions & 0 deletions test/OpenTelemetry.Tests/Metrics/MetricApiTestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1613,6 +1613,93 @@ public void UnsupportedMetricInstrument()
Assert.Empty(exportedItems);
}

[Fact]
public void GaugeIsExportedCorrectly()
cijothomas marked this conversation as resolved.
Show resolved Hide resolved
{
var exportedItems = new List<Metric>();

using var meter = new Meter($"{Utils.GetCurrentMethodName()}");

using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
.AddInMemoryExporter(exportedItems));

var gauge = meter.CreateGauge<long>(name: "NoiseLevel", unit: "dB", description: "Background Noise Level");
gauge.Record(10);
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
Assert.Single(exportedItems);
var metric = exportedItems[0];
Assert.Equal("Background Noise Level", metric.Description);
List<MetricPoint> metricPoints = new List<MetricPoint>();
foreach (ref readonly var mp in metric.GetMetricPoints())
{
metricPoints.Add(mp);
}

var lastValue = metricPoints[0].GetGaugeLastValueLong();
Assert.Equal(10, lastValue);
}

[Theory]
[InlineData(MetricReaderTemporalityPreference.Cumulative)]
[InlineData(MetricReaderTemporalityPreference.Delta)]
public void GaugeHandlesNoNewMeasurementsCorrectlyWithTemporality(MetricReaderTemporalityPreference temporalityPreference)
{
var exportedMetrics = new List<Metric>();

using var meter = new Meter($"{Utils.GetCurrentMethodName()}");
using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
.AddInMemoryExporter(exportedMetrics, metricReaderOptions =>
{
metricReaderOptions.TemporalityPreference = temporalityPreference;
}));

var noiseLevelGauge = meter.CreateGauge<long>(name: "NoiseLevel", unit: "dB", description: "Background Noise Level");
noiseLevelGauge.Record(10);

// Force a flush to export the recorded data
meterProvider.ForceFlush(MaxTimeToAllowForFlush);

// Validate first export / flush
var firstMetric = exportedMetrics[0];
var firstMetricPoints = new List<MetricPoint>();
foreach (ref readonly var metricPoint in firstMetric.GetMetricPoints())
{
firstMetricPoints.Add(metricPoint);
}

Assert.Single(firstMetricPoints);
var firstMetricPoint = firstMetricPoints[0];
Assert.Equal(10, firstMetricPoint.GetGaugeLastValueLong());

// Flush the metrics again without recording any new measurements
meterProvider.ForceFlush(MaxTimeToAllowForFlush);

// Validate second export / flush
if (temporalityPreference == MetricReaderTemporalityPreference.Cumulative)
{
// For cumulative temporality, data points should still be collected
// without any new measurements
Assert.Equal(2, exportedMetrics.Count);
var secondMetric = exportedMetrics[1];
var secondMetricPoints = new List<MetricPoint>();
foreach (ref readonly var metricPoint in secondMetric.GetMetricPoints())
{
secondMetricPoints.Add(metricPoint);
}

Assert.Single(secondMetricPoints);
var secondMetricPoint = secondMetricPoints[0];
Assert.Equal(10, secondMetricPoint.GetGaugeLastValueLong());
}
else if (temporalityPreference == MetricReaderTemporalityPreference.Delta)
{
// For delta temporality, no new metric should be collected
rajkumar-rangaraj marked this conversation as resolved.
Show resolved Hide resolved
Assert.Single(exportedMetrics);
}
}

internal static IConfiguration BuildConfiguration(bool emitOverflowAttribute, bool shouldReclaimUnusedMetricPoints)
{
var configurationData = new Dictionary<string, string>();
Expand Down
Loading