Skip to content

Commit f4e3923

Browse files
[sdk-metrics] Support .NET 9 Synchronous Gauge (open-telemetry#5867)
Co-authored-by: Mikel Blanchard <[email protected]>
1 parent cc0b9e9 commit f4e3923

File tree

5 files changed

+108
-1
lines changed

5 files changed

+108
-1
lines changed

docs/metrics/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ static readonly Meter MyMeter = new("MyCompany.MyProduct.MyLibrary", "1.0");
8686
| [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) |
8787
| [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) |
8888
| [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) |
89-
| [Gauge](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#gauge) (experimental) | N/A |
89+
| [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) |
9090
| [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) |
9191
| [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) |
9292

src/OpenTelemetry/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ Notes](../../RELEASENOTES.md).
3131
See [#5854](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5854)
3232
for details.
3333

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

3641
Released 2024-Jun-14

src/OpenTelemetry/Metrics/Metric.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ internal Metric(
117117
aggType = AggregationType.DoubleGauge;
118118
this.MetricType = MetricType.DoubleGauge;
119119
}
120+
else if (instrumentIdentity.InstrumentType == typeof(Gauge<double>)
121+
|| instrumentIdentity.InstrumentType == typeof(Gauge<float>))
122+
{
123+
aggType = AggregationType.DoubleGauge;
124+
this.MetricType = MetricType.DoubleGauge;
125+
}
120126
else if (instrumentIdentity.InstrumentType == typeof(ObservableGauge<long>)
121127
|| instrumentIdentity.InstrumentType == typeof(ObservableGauge<int>)
122128
|| instrumentIdentity.InstrumentType == typeof(ObservableGauge<short>)
@@ -125,6 +131,14 @@ internal Metric(
125131
aggType = AggregationType.LongGauge;
126132
this.MetricType = MetricType.LongGauge;
127133
}
134+
else if (instrumentIdentity.InstrumentType == typeof(Gauge<long>)
135+
|| instrumentIdentity.InstrumentType == typeof(Gauge<int>)
136+
|| instrumentIdentity.InstrumentType == typeof(Gauge<short>)
137+
|| instrumentIdentity.InstrumentType == typeof(Gauge<byte>))
138+
{
139+
aggType = AggregationType.LongGauge;
140+
this.MetricType = MetricType.LongGauge;
141+
}
128142
else if (instrumentIdentity.IsHistogram)
129143
{
130144
var explicitBucketBounds = instrumentIdentity.HistogramBucketBounds;

src/OpenTelemetry/Metrics/Reader/MetricReader.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public abstract partial class MetricReader : IDisposable
2727

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

3132
var type when type == typeof(UpDownCounter<>) => AggregationTemporality.Cumulative,
3233
var type when type == typeof(ObservableUpDownCounter<>) => AggregationTemporality.Cumulative,

test/OpenTelemetry.Tests/Metrics/MetricApiTestsBase.cs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1613,6 +1613,93 @@ public void UnsupportedMetricInstrument()
16131613
Assert.Empty(exportedItems);
16141614
}
16151615

1616+
[Fact]
1617+
public void GaugeIsExportedCorrectly()
1618+
{
1619+
var exportedItems = new List<Metric>();
1620+
1621+
using var meter = new Meter($"{Utils.GetCurrentMethodName()}");
1622+
1623+
using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
1624+
.AddMeter(meter.Name)
1625+
.AddInMemoryExporter(exportedItems));
1626+
1627+
var gauge = meter.CreateGauge<long>(name: "NoiseLevel", unit: "dB", description: "Background Noise Level");
1628+
gauge.Record(10);
1629+
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
1630+
Assert.Single(exportedItems);
1631+
var metric = exportedItems[0];
1632+
Assert.Equal("Background Noise Level", metric.Description);
1633+
List<MetricPoint> metricPoints = new List<MetricPoint>();
1634+
foreach (ref readonly var mp in metric.GetMetricPoints())
1635+
{
1636+
metricPoints.Add(mp);
1637+
}
1638+
1639+
var lastValue = metricPoints[0].GetGaugeLastValueLong();
1640+
Assert.Equal(10, lastValue);
1641+
}
1642+
1643+
[Theory]
1644+
[InlineData(MetricReaderTemporalityPreference.Cumulative)]
1645+
[InlineData(MetricReaderTemporalityPreference.Delta)]
1646+
public void GaugeHandlesNoNewMeasurementsCorrectlyWithTemporality(MetricReaderTemporalityPreference temporalityPreference)
1647+
{
1648+
var exportedMetrics = new List<Metric>();
1649+
1650+
using var meter = new Meter($"{Utils.GetCurrentMethodName()}");
1651+
using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
1652+
.AddMeter(meter.Name)
1653+
.AddInMemoryExporter(exportedMetrics, metricReaderOptions =>
1654+
{
1655+
metricReaderOptions.TemporalityPreference = temporalityPreference;
1656+
}));
1657+
1658+
var noiseLevelGauge = meter.CreateGauge<long>(name: "NoiseLevel", unit: "dB", description: "Background Noise Level");
1659+
noiseLevelGauge.Record(10);
1660+
1661+
// Force a flush to export the recorded data
1662+
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
1663+
1664+
// Validate first export / flush
1665+
var firstMetric = exportedMetrics[0];
1666+
var firstMetricPoints = new List<MetricPoint>();
1667+
foreach (ref readonly var metricPoint in firstMetric.GetMetricPoints())
1668+
{
1669+
firstMetricPoints.Add(metricPoint);
1670+
}
1671+
1672+
Assert.Single(firstMetricPoints);
1673+
var firstMetricPoint = firstMetricPoints[0];
1674+
Assert.Equal(10, firstMetricPoint.GetGaugeLastValueLong());
1675+
1676+
// Flush the metrics again without recording any new measurements
1677+
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
1678+
1679+
// Validate second export / flush
1680+
if (temporalityPreference == MetricReaderTemporalityPreference.Cumulative)
1681+
{
1682+
// For cumulative temporality, data points should still be collected
1683+
// without any new measurements
1684+
Assert.Equal(2, exportedMetrics.Count);
1685+
var secondMetric = exportedMetrics[1];
1686+
var secondMetricPoints = new List<MetricPoint>();
1687+
foreach (ref readonly var metricPoint in secondMetric.GetMetricPoints())
1688+
{
1689+
secondMetricPoints.Add(metricPoint);
1690+
}
1691+
1692+
Assert.Single(secondMetricPoints);
1693+
var secondMetricPoint = secondMetricPoints[0];
1694+
Assert.Equal(10, secondMetricPoint.GetGaugeLastValueLong());
1695+
}
1696+
else if (temporalityPreference == MetricReaderTemporalityPreference.Delta)
1697+
{
1698+
// For delta temporality, no new metric should be collected
1699+
Assert.Single(exportedMetrics);
1700+
}
1701+
}
1702+
16161703
internal static IConfiguration BuildConfiguration(bool emitOverflowAttribute, bool shouldReclaimUnusedMetricPoints)
16171704
{
16181705
var configurationData = new Dictionary<string, string>();

0 commit comments

Comments
 (0)