Skip to content

Commit 3a96380

Browse files
Add low memory temporality for metrics (#6648)
Co-authored-by: Piotr Kiełkowicz <[email protected]>
1 parent 79a11f8 commit 3a96380

File tree

6 files changed

+133
-0
lines changed

6 files changed

+133
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
OpenTelemetry.Metrics.MetricReaderTemporalityPreference.LowMemory = 3 -> OpenTelemetry.Metrics.MetricReaderTemporalityPreference

src/OpenTelemetry/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ Notes](../../RELEASENOTES.md).
66

77
## Unreleased
88

9+
* Added `LowMemory` temporality as an option in the OTLP metrics exporter.
10+
([#6648](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6648))
11+
912
## 1.14.0
1013

1114
Released 2025-Nov-12

src/OpenTelemetry/Metrics/Reader/MetricReader.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,21 @@ public abstract partial class MetricReader : IDisposable
3737
};
3838
};
3939

40+
private static readonly Func<Type, AggregationTemporality> LowMemoryTemporalityPreferenceFunc = (instrumentType) =>
41+
{
42+
return instrumentType.GetGenericTypeDefinition() switch
43+
{
44+
var type when type == typeof(Counter<>) => AggregationTemporality.Delta,
45+
var type when type == typeof(Histogram<>) => AggregationTemporality.Delta,
46+
47+
var type when type == typeof(UpDownCounter<>) => AggregationTemporality.Cumulative,
48+
var type when type == typeof(ObservableCounter<>) => AggregationTemporality.Cumulative,
49+
var type when type == typeof(ObservableUpDownCounter<>) => AggregationTemporality.Cumulative,
50+
51+
_ => AggregationTemporality.Cumulative,
52+
};
53+
};
54+
4055
private readonly Lock newTaskLock = new();
4156
private readonly Lock onCollectLock = new();
4257
private readonly TaskCompletionSource<bool> shutdownTcs = new();
@@ -72,6 +87,7 @@ public MetricReaderTemporalityPreference TemporalityPreference
7287
this.temporalityFunc = value switch
7388
{
7489
MetricReaderTemporalityPreference.Delta => MonotonicDeltaTemporalityPreferenceFunc,
90+
MetricReaderTemporalityPreference.LowMemory => LowMemoryTemporalityPreferenceFunc,
7591
_ => CumulativeTemporalityPreferenceFunc,
7692
};
7793
}

src/OpenTelemetry/Metrics/Reader/MetricReaderTemporalityPreference.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,12 @@ public enum MetricReaderTemporalityPreference
2121
/// Aggregations of non-monotonic measurements use cumulative temporality.
2222
/// </summary>
2323
Delta = 2,
24+
25+
/// <summary>
26+
/// Uses delta temporality for synchronous Counter and Histogram instruments and
27+
/// cumulative temporality for synchronous UpDownCounter, ObservableCounter and
28+
/// ObservableUpDownCounter instruments. This mode reduces SDK memory usage by avoiding
29+
/// the need to store both cumulative and delta states for temporality conversion.
30+
/// </summary>
31+
LowMemory = 3,
2432
}

test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpMetricsExporterTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,7 @@ public void TestHistogramToOtlpMetric(string name, string? description, string?
727727
[InlineData("cuMulative", MetricReaderTemporalityPreference.Cumulative)]
728728
[InlineData("DeltA", MetricReaderTemporalityPreference.Delta)]
729729
[InlineData("invalid", MetricReaderTemporalityPreference.Cumulative)]
730+
[InlineData("lowmemory", MetricReaderTemporalityPreference.LowMemory)]
730731
public void TestTemporalityPreferenceUsingConfiguration(string configValue, MetricReaderTemporalityPreference expectedTemporality)
731732
{
732733
var testExecuted = false;
@@ -757,6 +758,7 @@ public void TestTemporalityPreferenceUsingConfiguration(string configValue, Metr
757758
[InlineData("cuMulative", MetricReaderTemporalityPreference.Cumulative)]
758759
[InlineData("DeltA", MetricReaderTemporalityPreference.Delta)]
759760
[InlineData("invalid", MetricReaderTemporalityPreference.Cumulative)]
761+
[InlineData("lowmemory", MetricReaderTemporalityPreference.LowMemory)]
760762
public void TestTemporalityPreferenceUsingEnvVar(string configValue, MetricReaderTemporalityPreference expectedTemporality)
761763
{
762764
Environment.SetEnvironmentVariable(OtlpSpecConfigDefinitionTests.MetricsData.TemporalityKeyName, configValue);
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
using System.Diagnostics.Metrics;
5+
using OpenTelemetry.Tests;
6+
using Xunit;
7+
8+
namespace OpenTelemetry.Metrics.Tests;
9+
10+
public class MetricReaderTests
11+
{
12+
[Theory]
13+
[InlineData("counter", typeof(long), AggregationTemporality.Delta)]
14+
[InlineData("counter", typeof(double), AggregationTemporality.Delta)]
15+
[InlineData("histogram", typeof(long), AggregationTemporality.Delta)]
16+
[InlineData("histogram", typeof(double), AggregationTemporality.Delta)]
17+
[InlineData("updowncounter", typeof(long), AggregationTemporality.Cumulative)]
18+
[InlineData("updowncounter", typeof(double), AggregationTemporality.Cumulative)]
19+
[InlineData("observablecounter", typeof(long), AggregationTemporality.Cumulative)]
20+
[InlineData("observablecounter", typeof(double), AggregationTemporality.Cumulative)]
21+
[InlineData("observableupdowncounter", typeof(long), AggregationTemporality.Cumulative)]
22+
[InlineData("observableupdowncounter", typeof(double), AggregationTemporality.Cumulative)]
23+
public void LowMemoryTemporality_UsesCorrectAggregationTemporality(string instrumentName, Type valueType, AggregationTemporality expectedTemporality)
24+
{
25+
var metrics = new List<Metric>();
26+
using var meter = new Meter(Utils.GetCurrentMethodName());
27+
using var provider = Sdk.CreateMeterProviderBuilder()
28+
.AddMeter(meter.Name)
29+
.AddInMemoryExporter(metrics, metricReaderOptions =>
30+
{
31+
metricReaderOptions.TemporalityPreference = MetricReaderTemporalityPreference.LowMemory;
32+
})
33+
.Build();
34+
35+
switch (instrumentName)
36+
{
37+
case "counter":
38+
if (valueType == typeof(long))
39+
{
40+
meter.CreateCounter<long>("test_counter").Add(1);
41+
}
42+
else
43+
{
44+
meter.CreateCounter<double>("test_counter").Add(1);
45+
}
46+
47+
break;
48+
49+
case "histogram":
50+
if (valueType == typeof(long))
51+
{
52+
meter.CreateHistogram<long>("test_histogram").Record(1);
53+
}
54+
else
55+
{
56+
meter.CreateHistogram<double>("test_histogram").Record(1);
57+
}
58+
59+
break;
60+
61+
case "updowncounter":
62+
if (valueType == typeof(long))
63+
{
64+
meter.CreateUpDownCounter<long>("test_updown").Add(1);
65+
}
66+
else
67+
{
68+
meter.CreateUpDownCounter<double>("test_updown").Add(1);
69+
}
70+
71+
break;
72+
73+
case "observablecounter":
74+
if (valueType == typeof(long))
75+
{
76+
meter.CreateObservableCounter("test_observable_counter", () => new Measurement<long>(1));
77+
}
78+
else
79+
{
80+
meter.CreateObservableCounter("test_observable_counter", () => new Measurement<double>(1));
81+
}
82+
83+
break;
84+
85+
case "observableupdowncounter":
86+
if (valueType == typeof(long))
87+
{
88+
meter.CreateObservableUpDownCounter("test_observable_updown", () => new Measurement<long>(1));
89+
}
90+
else
91+
{
92+
meter.CreateObservableUpDownCounter("test_observable_updown", () => new Measurement<double>(1));
93+
}
94+
95+
break;
96+
}
97+
98+
provider.ForceFlush();
99+
100+
var metric = Assert.Single(metrics);
101+
Assert.Equal(expectedTemporality, metric.Temporality);
102+
}
103+
}

0 commit comments

Comments
 (0)