Skip to content

Commit 8ff986b

Browse files
Yun-TingCodeBlanch
andauthored
[sdk] Added support for setting CardinalityLimit allowed for the metric managed by the view. (#5312)
Co-authored-by: Mikel Blanchard <[email protected]>
1 parent c057e6a commit 8ff986b

File tree

11 files changed

+147
-11
lines changed

11 files changed

+147
-11
lines changed

OpenTelemetry.sln

+1
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "experimental-apis", "experi
329329
docs\diagnostics\experimental-apis\OTEL1000.md = docs\diagnostics\experimental-apis\OTEL1000.md
330330
docs\diagnostics\experimental-apis\OTEL1001.md = docs\diagnostics\experimental-apis\OTEL1001.md
331331
docs\diagnostics\experimental-apis\OTEL1002.md = docs\diagnostics\experimental-apis\OTEL1002.md
332+
docs\diagnostics\experimental-apis\OTEL1003.md = docs\diagnostics\experimental-apis\OTEL1003.md
332333
docs\diagnostics\experimental-apis\README.md = docs\diagnostics\experimental-apis\README.md
333334
EndProjectSection
334335
EndProject

build/Common.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<Nullable>enable</Nullable>
1111
<ImplicitUsings>enable</ImplicitUsings>
1212
<!-- Suppress warnings for repo code using experimental features -->
13-
<NoWarn>$(NoWarn);OTEL1000;OTEL1001;OTEL1002</NoWarn>
13+
<NoWarn>$(NoWarn);OTEL1000;OTEL1001;OTEL1002;OTEL1003</NoWarn>
1414
<!--temporarily disable. See 3958-->
1515
<!--<AnalysisLevel>latest-All</AnalysisLevel>-->
1616
</PropertyGroup>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# OpenTelemetry .NET Diagnostic: OTEL1003
2+
3+
## Overview
4+
5+
This is an Experimental API diagnostic covering the following API:
6+
7+
* `MetricStreamConfiguration.CardinalityLimit.get`
8+
* `MetricStreamConfiguration.CardinalityLimit.set`
9+
10+
Experimental APIs may be changed or removed in the future.
11+
12+
## Details
13+
14+
The OpenTelemetry Specification defines the
15+
[cardinality limit](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#cardinality-limits)
16+
of a metric can be set by the matching view.
17+
18+
From the specification:
19+
20+
> The cardinality limit for an aggregation is defined in one of three ways:
21+
> A view with criteria matching the instrument an aggregation is created for has
22+
> an aggregation_cardinality_limit value defined for the stream, that value
23+
> SHOULD be used. If there is no matching view, but the MetricReader defines a
24+
> default cardinality limit value based on the instrument an aggregation is
25+
> created for, that value SHOULD be used. If none of the previous values are
26+
> defined, the default value of 2000 SHOULD be used.
27+
28+
We are exposing these APIs experimentally until the specification declares them
29+
stable.

docs/diagnostics/experimental-apis/README.md

+6
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ Description: Metrics Exemplar Support
3333

3434
Details: [OTEL1002](./OTEL1002.md)
3535

36+
### OTEL1003
37+
38+
Description: MetricStreamConfiguration CardinalityLimit Support
39+
40+
Details: [OTEL1003](./OTEL1003.md)
41+
3642
## Inactive
3743

3844
Experimental APIs which have been released stable or removed:

src/OpenTelemetry/.publicApi/Experimental/PublicAPI.Unshipped.txt

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ OpenTelemetry.Metrics.Exemplar.TraceId.get -> System.Diagnostics.ActivityTraceId
1818
OpenTelemetry.Metrics.ExemplarFilter
1919
OpenTelemetry.Metrics.ExemplarFilter.ExemplarFilter() -> void
2020
OpenTelemetry.Metrics.MetricPoint.GetExemplars() -> OpenTelemetry.Metrics.Exemplar[]!
21+
OpenTelemetry.Metrics.MetricStreamConfiguration.CardinalityLimit.get -> int?
22+
OpenTelemetry.Metrics.MetricStreamConfiguration.CardinalityLimit.set -> void
2123
OpenTelemetry.Metrics.TraceBasedExemplarFilter
2224
OpenTelemetry.Metrics.TraceBasedExemplarFilter.TraceBasedExemplarFilter() -> void
2325
static OpenTelemetry.Logs.LoggerProviderBuilderExtensions.AddProcessor(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder, OpenTelemetry.BaseProcessor<OpenTelemetry.Logs.LogRecord!>! processor) -> OpenTelemetry.Logs.LoggerProviderBuilder!

src/OpenTelemetry/CHANGELOG.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,18 @@
1111

1212
* Fixed an issue where `SimpleExemplarReservoir` was not resetting internal
1313
state for cumulative temporality.
14-
[#5230](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5230)
14+
([#5230](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5230))
1515

1616
* Fixed an issue causing `LogRecord`s to be incorrectly reused when wrapping an
1717
instance of `BatchLogRecordExportProcessor` inside another
1818
`BaseProcessor<LogRecord>` which leads to missing or incorrect data during
1919
export.
20-
[#5255](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5255)
20+
([#5255](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5255))
21+
22+
* **Experimental (pre-release builds only):** Added support for setting
23+
`CardinalityLimit` (the maximum number of data points allowed for a metric)
24+
when configuring a view.
25+
([#5312](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5312))
2126

2227
## 1.7.0
2328

src/OpenTelemetry/Metrics/Builder/MeterProviderBuilderExtensions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ public static MeterProviderBuilder SetMaxMetricStreams(this MeterProviderBuilder
236236
/// This may change in the future. See: https://github.com/open-telemetry/opentelemetry-dotnet/issues/2360.
237237
/// </remarks>
238238
/// <param name="meterProviderBuilder"><see cref="MeterProviderBuilder"/>.</param>
239-
/// <param name="maxMetricPointsPerMetricStream">Maximum maximum number of metric points allowed per metric stream.</param>
239+
/// <param name="maxMetricPointsPerMetricStream">Maximum number of metric points allowed per metric stream.</param>
240240
/// <returns>The supplied <see cref="MeterProviderBuilder"/> for chaining.</returns>
241241
public static MeterProviderBuilder SetMaxMetricPointsPerMetricStream(this MeterProviderBuilder meterProviderBuilder, int maxMetricPointsPerMetricStream)
242242
{

src/OpenTelemetry/Metrics/MetricReaderExt.cs

+6
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ internal List<Metric> AddMetricsListWithViews(Instrument instrument, List<Metric
136136
else
137137
{
138138
bool shouldReclaimUnusedMetricPoints = this.parentProvider is MeterProviderSdk meterProviderSdk && meterProviderSdk.ShouldReclaimUnusedMetricPoints;
139+
140+
if (metricStreamConfig != null && metricStreamConfig.CardinalityLimit != null)
141+
{
142+
this.maxMetricPointsPerMetricStream = metricStreamConfig.CardinalityLimit.Value;
143+
}
144+
139145
Metric metric = new(metricStreamIdentity, this.GetAggregationTemporality(metricStreamIdentity.InstrumentType), this.maxMetricPointsPerMetricStream, this.emitOverflowAttribute, shouldReclaimUnusedMetricPoints, this.exemplarFilter);
140146

141147
this.instrumentIdentityToMetric[metricStreamIdentity] = metric;

src/OpenTelemetry/Metrics/MetricStreamConfiguration.cs

+44-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
// Copyright The OpenTelemetry Authors
22
// SPDX-License-Identifier: Apache-2.0
33

4+
#if EXPOSE_EXPERIMENTAL_FEATURES && NET8_0_OR_GREATER
5+
using System.Diagnostics.CodeAnalysis;
6+
#endif
7+
using OpenTelemetry.Internal;
8+
49
namespace OpenTelemetry.Metrics;
510

611
/// <summary>
@@ -10,6 +15,8 @@ public class MetricStreamConfiguration
1015
{
1116
private string? name;
1217

18+
private int? cardinalityLimit = null;
19+
1320
/// <summary>
1421
/// Gets the drop configuration.
1522
/// </summary>
@@ -91,11 +98,44 @@ public string[]? TagKeys
9198
}
9299
}
93100

101+
#if EXPOSE_EXPERIMENTAL_FEATURES
102+
/// <summary>
103+
/// Gets or sets a positive integer value defining the maximum number of
104+
/// data points allowed for the metric managed by the view.
105+
/// </summary>
106+
/// <remarks>
107+
/// <para><b>WARNING</b>: This is an experimental API which might change or
108+
/// be removed in the future. Use at your own risk.</para>
109+
/// <para>Spec reference: <see
110+
/// href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#cardinality-limits">Cardinality
111+
/// limits</see>.</para>
112+
/// Note: If not set, the MeterProvider cardinality limit value will be
113+
/// used, which defaults to 2000. Call <see
114+
/// cref="MeterProviderBuilderExtensions.SetMaxMetricPointsPerMetricStream"/>
115+
/// to configure the MeterProvider default.
116+
/// </remarks>
117+
#if NET8_0_OR_GREATER
118+
[Experimental(DiagnosticDefinitions.CardinalityLimitExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)]
119+
#endif
120+
public
121+
#else
122+
internal
123+
#endif
124+
int? CardinalityLimit
125+
{
126+
get => this.cardinalityLimit;
127+
set
128+
{
129+
if (value != null)
130+
{
131+
Guard.ThrowIfOutOfRange(value.Value, min: 1, max: int.MaxValue);
132+
}
133+
134+
this.cardinalityLimit = value;
135+
}
136+
}
137+
94138
internal string[]? CopiedTagKeys { get; private set; }
95139

96140
internal int? ViewId { get; set; }
97-
98-
// TODO: MetricPoints caps can be configured here on
99-
// a per stream basis, when we add such a capability
100-
// in the future.
101141
}

src/Shared/DiagnosticDefinitions.cs

+1
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ internal static class DiagnosticDefinitions
1212
public const string LoggerProviderExperimentalApi = "OTEL1000";
1313
public const string LogsBridgeExperimentalApi = "OTEL1001";
1414
public const string ExemplarExperimentalApi = "OTEL1002";
15+
public const string CardinalityLimitExperimentalApi = "OTEL1003";
1516
}

test/OpenTelemetry.Tests/Metrics/MetricViewTests.cs

+49-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
using System.Diagnostics.Metrics;
5+
using System.Reflection;
56
using OpenTelemetry.Internal;
67
using OpenTelemetry.Tests;
78
using Xunit;
@@ -919,6 +920,34 @@ public void ViewConflict_OneInstrument_DifferentDescription()
919920
Assert.Equal(10, metricPoint2.GetSumLong());
920921
}
921922

923+
[Fact]
924+
public void CardinalityLimitofMatchingViewTakesPrecedenceOverMetricProviderWhenBothWereSet()
925+
{
926+
using var meter = new Meter(Utils.GetCurrentMethodName());
927+
var exportedItems = new List<Metric>();
928+
929+
using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
930+
.AddMeter(meter.Name)
931+
.SetMaxMetricPointsPerMetricStream(3)
932+
.AddView((instrument) =>
933+
{
934+
return new MetricStreamConfiguration() { Name = "MetricStreamA", CardinalityLimit = 10000 };
935+
})
936+
.AddInMemoryExporter(exportedItems));
937+
938+
var counter = meter.CreateCounter<long>("counter");
939+
counter.Add(100);
940+
941+
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
942+
943+
var metric = exportedItems[0];
944+
945+
var aggregatorStore = typeof(Metric).GetField("aggStore", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(metric) as AggregatorStore;
946+
var maxMetricPointsAttribute = (int)typeof(AggregatorStore).GetField("maxMetricPoints", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(aggregatorStore);
947+
948+
Assert.Equal(10000, maxMetricPointsAttribute);
949+
}
950+
922951
[Fact]
923952
public void ViewConflict_TwoDistinctInstruments_ThreeStreams()
924953
{
@@ -930,13 +959,18 @@ public void ViewConflict_TwoDistinctInstruments_ThreeStreams()
930959
.AddMeter(meter.Name)
931960
.AddView((instrument) =>
932961
{
933-
return new MetricStreamConfiguration() { Name = "MetricStreamA", Description = "description" };
962+
return new MetricStreamConfiguration() { Name = "MetricStreamA", Description = "description", CardinalityLimit = 256 };
934963
})
935964
.AddView((instrument) =>
936965
{
937966
return instrument.Description == "description1"
938-
? new MetricStreamConfiguration() { Name = "MetricStreamB" }
939-
: new MetricStreamConfiguration() { Name = "MetricStreamC" };
967+
? new MetricStreamConfiguration() { Name = "MetricStreamB", CardinalityLimit = 3 }
968+
: new MetricStreamConfiguration() { Name = "MetricStreamC", CardinalityLimit = 200000 };
969+
})
970+
.AddView((instrument) =>
971+
{
972+
// This view is ignored as the passed in CardinalityLimit is out of range.
973+
return new MetricStreamConfiguration() { Name = "MetricStreamD", CardinalityLimit = -1 };
940974
})
941975
.AddInMemoryExporter(exportedItems));
942976

@@ -953,12 +987,24 @@ public void ViewConflict_TwoDistinctInstruments_ThreeStreams()
953987
var metricB = exportedItems[1];
954988
var metricC = exportedItems[2];
955989

990+
var aggregatorStoreA = typeof(Metric).GetField("aggStore", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(metricA) as AggregatorStore;
991+
var maxMetricPointsAttributeA = (int)typeof(AggregatorStore).GetField("maxMetricPoints", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(aggregatorStoreA);
992+
993+
Assert.Equal(256, maxMetricPointsAttributeA);
956994
Assert.Equal("MetricStreamA", metricA.Name);
957995
Assert.Equal(20, GetAggregatedValue(metricA));
958996

997+
var aggregatorStoreB = typeof(Metric).GetField("aggStore", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(metricB) as AggregatorStore;
998+
var maxMetricPointsAttributeB = (int)typeof(AggregatorStore).GetField("maxMetricPoints", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(aggregatorStoreB);
999+
1000+
Assert.Equal(3, maxMetricPointsAttributeB);
9591001
Assert.Equal("MetricStreamB", metricB.Name);
9601002
Assert.Equal(10, GetAggregatedValue(metricB));
9611003

1004+
var aggregatorStoreC = typeof(Metric).GetField("aggStore", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(metricC) as AggregatorStore;
1005+
var maxMetricPointsAttributeC = (int)typeof(AggregatorStore).GetField("maxMetricPoints", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(aggregatorStoreC);
1006+
1007+
Assert.Equal(200000, maxMetricPointsAttributeC);
9621008
Assert.Equal("MetricStreamC", metricC.Name);
9631009
Assert.Equal(10, GetAggregatedValue(metricC));
9641010

0 commit comments

Comments
 (0)