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] Added support for setting CardinalityLimit allowed for the metric managed by the view. #5312

Merged
merged 14 commits into from
Feb 6, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ OpenTelemetry.Metrics.Exemplar.TraceId.get -> System.Diagnostics.ActivityTraceId
OpenTelemetry.Metrics.ExemplarFilter
OpenTelemetry.Metrics.ExemplarFilter.ExemplarFilter() -> void
OpenTelemetry.Metrics.MetricPoint.GetExemplars() -> OpenTelemetry.Metrics.Exemplar[]!
OpenTelemetry.Metrics.MetricStreamConfiguration.MaxMetricPointsPerMetricStream.get -> int?
OpenTelemetry.Metrics.MetricStreamConfiguration.MaxMetricPointsPerMetricStream.set -> void
OpenTelemetry.Metrics.TraceBasedExemplarFilter
OpenTelemetry.Metrics.TraceBasedExemplarFilter.TraceBasedExemplarFilter() -> void
static OpenTelemetry.Logs.LoggerProviderBuilderExtensions.AddProcessor(this OpenTelemetry.Logs.LoggerProviderBuilder! loggerProviderBuilder, OpenTelemetry.BaseProcessor<OpenTelemetry.Logs.LogRecord!>! processor) -> OpenTelemetry.Logs.LoggerProviderBuilder!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ public static MeterProviderBuilder SetMaxMetricStreams(this MeterProviderBuilder
/// This may change in the future. See: https://github.com/open-telemetry/opentelemetry-dotnet/issues/2360.
/// </remarks>
/// <param name="meterProviderBuilder"><see cref="MeterProviderBuilder"/>.</param>
/// <param name="maxMetricPointsPerMetricStream">Maximum maximum number of metric points allowed per metric stream.</param>
/// <param name="maxMetricPointsPerMetricStream">Maximum number of metric points allowed per metric stream.</param>
/// <returns>The supplied <see cref="MeterProviderBuilder"/> for chaining.</returns>
public static MeterProviderBuilder SetMaxMetricPointsPerMetricStream(this MeterProviderBuilder meterProviderBuilder, int maxMetricPointsPerMetricStream)
{
Expand Down
6 changes: 6 additions & 0 deletions src/OpenTelemetry/Metrics/MetricReaderExt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ internal List<Metric> AddMetricsListWithViews(Instrument instrument, List<Metric
else
{
bool shouldReclaimUnusedMetricPoints = this.parentProvider is MeterProviderSdk meterProviderSdk && meterProviderSdk.ShouldReclaimUnusedMetricPoints;

if (metricStreamConfig != null && metricStreamConfig.MaxMetricPointsPerMetricStream != null)
{
this.maxMetricPointsPerMetricStream = metricStreamConfig.MaxMetricPointsPerMetricStream.Value;
}

Metric metric = new(metricStreamIdentity, this.GetAggregationTemporality(metricStreamIdentity.InstrumentType), this.maxMetricPointsPerMetricStream, this.emitOverflowAttribute, shouldReclaimUnusedMetricPoints, this.exemplarFilter);

this.instrumentIdentityToMetric[metricStreamIdentity] = metric;
Expand Down
40 changes: 36 additions & 4 deletions src/OpenTelemetry/Metrics/MetricStreamConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using OpenTelemetry.Internal;

namespace OpenTelemetry.Metrics;

/// <summary>
Expand All @@ -10,6 +12,8 @@ public class MetricStreamConfiguration
{
private string? name;

private int? maxMetricPointsPerMetricStream = 2000;
Yun-Ting marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Gets the drop configuration.
/// </summary>
Expand Down Expand Up @@ -91,11 +95,39 @@ public string[]? TagKeys
}
}

/// <summary>
/// Gets or sets a positive integer value
/// defining the maximum number of data points allowed to
/// per view.
/// </summary>
Yun-Ting marked this conversation as resolved.
Show resolved Hide resolved
/// <remarks>
/// Note: If there is no matching view, but the MetricReader
Yun-Ting marked this conversation as resolved.
Show resolved Hide resolved
/// defines a default cardinality limit value based on the
/// instrument an aggregation is created for, that value
/// will be used. The default value of 2000 will be used
/// if neither the view nor the MetricReader configures
/// MaxMetricPointsPerMetricStream.
/// </remarks>
Yun-Ting marked this conversation as resolved.
Show resolved Hide resolved
#if EXPOSE_EXPERIMENTAL_FEATURES
public
CodeBlanch marked this conversation as resolved.
Show resolved Hide resolved
#else
internal
#endif
int? MaxMetricPointsPerMetricStream
Yun-Ting marked this conversation as resolved.
Show resolved Hide resolved
{
get => this.maxMetricPointsPerMetricStream;
set
{
if (value != null)
{
Guard.ThrowIfOutOfRange(value.Value, min: 1, max: int.MaxValue);
cijothomas marked this conversation as resolved.
Show resolved Hide resolved
}

this.maxMetricPointsPerMetricStream = value;
}
}

internal string[]? CopiedTagKeys { get; private set; }

internal int? ViewId { get; set; }

// TODO: MetricPoints caps can be configured here on
// a per stream basis, when we add such a capability
// in the future.
}
52 changes: 49 additions & 3 deletions test/OpenTelemetry.Tests/Metrics/MetricViewTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

using System.Diagnostics.Metrics;
using System.Reflection;
using OpenTelemetry.Internal;
using OpenTelemetry.Tests;
using Xunit;
Expand Down Expand Up @@ -919,6 +920,34 @@ public void ViewConflict_OneInstrument_DifferentDescription()
Assert.Equal(10, metricPoint2.GetSumLong());
}

[Fact]
public void MaxMetricPointsPerMetricStreamofMatchingViewTakesPrecedenceOverTheMetricReaderWhenBothWereSet()
{
using var meter = new Meter(Utils.GetCurrentMethodName());
var exportedItems = new List<Metric>();

using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
.SetMaxMetricPointsPerMetricStream(3)
.AddView((instrument) =>
{
return new MetricStreamConfiguration() { Name = "MetricStreamA", MaxMetricPointsPerMetricStream = 10000 };
})
.AddInMemoryExporter(exportedItems));

var counter = meter.CreateCounter<long>("counter");
counter.Add(100);

meterProvider.ForceFlush(MaxTimeToAllowForFlush);

var metric = exportedItems[0];

var aggregatorStore = typeof(Metric).GetField("aggStore", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(metric) as AggregatorStore;
var maxMetricPointsAttribute = (int)typeof(AggregatorStore).GetField("maxMetricPoints", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(aggregatorStore);

Assert.Equal(10000, maxMetricPointsAttribute);
}

[Fact]
public void ViewConflict_TwoDistinctInstruments_ThreeStreams()
{
Expand All @@ -930,13 +959,18 @@ public void ViewConflict_TwoDistinctInstruments_ThreeStreams()
.AddMeter(meter.Name)
.AddView((instrument) =>
{
return new MetricStreamConfiguration() { Name = "MetricStreamA", Description = "description" };
return new MetricStreamConfiguration() { Name = "MetricStreamA", Description = "description", MaxMetricPointsPerMetricStream = 256 };
})
.AddView((instrument) =>
{
return instrument.Description == "description1"
? new MetricStreamConfiguration() { Name = "MetricStreamB" }
: new MetricStreamConfiguration() { Name = "MetricStreamC" };
? new MetricStreamConfiguration() { Name = "MetricStreamB", MaxMetricPointsPerMetricStream = 3 }
: new MetricStreamConfiguration() { Name = "MetricStreamC", MaxMetricPointsPerMetricStream = 200000 };
})
.AddView((instrument) =>
{
// This view is ignored as the passed in MaxMetricPointsPerMetricStream is out of range.
Yun-Ting marked this conversation as resolved.
Show resolved Hide resolved
return new MetricStreamConfiguration() { Name = "MetricStreamD", MaxMetricPointsPerMetricStream = -1 };
})
.AddInMemoryExporter(exportedItems));

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

var aggregatorStoreA = typeof(Metric).GetField("aggStore", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(metricA) as AggregatorStore;
var maxMetricPointsAttributeA = (int)typeof(AggregatorStore).GetField("maxMetricPoints", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(aggregatorStoreA);

Assert.Equal(256, maxMetricPointsAttributeA);
Assert.Equal("MetricStreamA", metricA.Name);
Assert.Equal(20, GetAggregatedValue(metricA));

var aggregatorStoreB = typeof(Metric).GetField("aggStore", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(metricB) as AggregatorStore;
var maxMetricPointsAttributeB = (int)typeof(AggregatorStore).GetField("maxMetricPoints", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(aggregatorStoreB);

Assert.Equal(3, maxMetricPointsAttributeB);
Assert.Equal("MetricStreamB", metricB.Name);
Assert.Equal(10, GetAggregatedValue(metricB));

var aggregatorStoreC = typeof(Metric).GetField("aggStore", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(metricC) as AggregatorStore;
var maxMetricPointsAttributeC = (int)typeof(AggregatorStore).GetField("maxMetricPoints", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(aggregatorStoreC);

Assert.Equal(200000, maxMetricPointsAttributeC);
Assert.Equal("MetricStreamC", metricC.Name);
Assert.Equal(10, GetAggregatedValue(metricC));

Expand Down
Loading