[sdk-metrics] Obsolete SetMaxMetricPointsPerMetricStream + standarize on "Cardinality Limit" name (#5328)
Co-authored-by: Yun-Ting Lin <yunl@microsoft.com>
This commit is contained in:
parent
f214d27e93
commit
cf00e4254e
|
@ -11,19 +11,37 @@ Experimental APIs may be changed or removed in the future.
|
|||
|
||||
## Details
|
||||
|
||||
The OpenTelemetry Specification defines the
|
||||
[cardinality limit](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#cardinality-limits)
|
||||
of a metric can be set by the matching view.
|
||||
|
||||
From the specification:
|
||||
|
||||
> The cardinality limit for an aggregation is defined in one of three ways:
|
||||
> A view with criteria matching the instrument an aggregation is created for has
|
||||
> an aggregation_cardinality_limit value defined for the stream, that value
|
||||
> SHOULD be used. If there is no matching view, but the MetricReader defines a
|
||||
> default cardinality limit value based on the instrument an aggregation is
|
||||
> created for, that value SHOULD be used. If none of the previous values are
|
||||
> defined, the default value of 2000 SHOULD be used.
|
||||
>
|
||||
> 1. A view with criteria matching the instrument an aggregation is created for
|
||||
> has an `aggregation_cardinality_limit` value defined for the stream, that
|
||||
> value SHOULD be used.
|
||||
> 2. If there is no matching view, but the `MetricReader` defines a default
|
||||
> cardinality limit value based on the instrument an aggregation is created
|
||||
> for, that value SHOULD be used.
|
||||
> 3. If none of the previous values are defined, the default value of 2000
|
||||
> SHOULD be used.
|
||||
|
||||
We are exposing these APIs experimentally until the specification declares them
|
||||
stable.
|
||||
|
||||
### Setting cardinality limit for a specific Metric via the View API
|
||||
|
||||
The OpenTelemetry Specification defines the [cardinality
|
||||
limit](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#cardinality-limits)
|
||||
of a metric can be set by the matching view.
|
||||
|
||||
```csharp
|
||||
using var meterProvider = Sdk.CreateMeterProviderBuilder()
|
||||
.AddView(
|
||||
instrumentName: "MyFruitCounter",
|
||||
new MetricStreamConfiguration { CardinalityLimit = 10 })
|
||||
.Build();
|
||||
```
|
||||
|
||||
### Setting cardinality limit for a specific MetricReader
|
||||
|
||||
[This is not currently supported by OpenTelemetry
|
||||
.NET.](https://github.com/open-telemetry/opentelemetry-dotnet/issues/5331)
|
||||
|
|
|
@ -379,17 +379,19 @@ predictable and reliable behavior when excessive cardinality happens, whether it
|
|||
was due to a malicious attack or developer making mistakes while writing code.
|
||||
|
||||
OpenTelemetry has a default cardinality limit of `2000` per metric. This limit
|
||||
can be configured at `MeterProvider` level using the
|
||||
`SetMaxMetricPointsPerMetricStream` method, or at individual
|
||||
[view](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#view)
|
||||
level using `MetricStreamConfiguration.CardinalityLimit`. Refer to this
|
||||
[doc](../../docs/metrics/customizing-the-sdk/README.md#changing-maximum-metricpoints-per-metricstream)
|
||||
can be configured at the individual metric level using the [View
|
||||
API](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#view)
|
||||
and the `MetricStreamConfiguration.CardinalityLimit` setting. Refer to this
|
||||
[doc](../../docs/metrics/customizing-the-sdk/README.md#changing-the-cardinality-limit-for-a-metric)
|
||||
for more information.
|
||||
|
||||
Given a metric, once the cardinality limit is reached, any new measurement which
|
||||
cannot be independently aggregated because of the limit will be aggregated using
|
||||
the [overflow
|
||||
attribute](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#overflow-attribute).
|
||||
cannot be independently aggregated because of the limit will be dropped or
|
||||
aggregated using the [overflow
|
||||
attribute](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#overflow-attribute)
|
||||
(if enabled). When NOT using the overflow attribute feature a warning is written
|
||||
to the [self-diagnostic log](../../src/OpenTelemetry/README.md#self-diagnostics)
|
||||
the first time an overflow is detected for a given metric.
|
||||
|
||||
> [!NOTE]
|
||||
> Overflow attribute was introduced in OpenTelemetry .NET
|
||||
|
|
|
@ -367,90 +367,24 @@ MyFruitCounter.Add(1, new("name", "apple"), new("color", "red"));
|
|||
AnotherFruitCounter.Add(1, new("name", "apple"), new("color", "red"));
|
||||
```
|
||||
|
||||
### Changing maximum MetricPoints per MetricStream
|
||||
### Changing the cardinality limit for a Metric
|
||||
|
||||
A Metric stream can contain as many Metric points as the number of unique
|
||||
combination of keys and values. To protect the SDK from unbounded memory usage,
|
||||
SDK limits the maximum number of metric points per metric stream, to a default
|
||||
of 2000. Once the limit is hit, any new key/value combination for that metric is
|
||||
ignored. The SDK chooses the key/value combinations in the order in which they
|
||||
are emitted. `SetMaxMetricPointsPerMetricStream` can be used to override the
|
||||
default.
|
||||
To set the [cardinality limit](../README.md#cardinality-limits) for an
|
||||
individual metric, use `MetricStreamConfiguration.CardinalityLimit` setting on
|
||||
the View API:
|
||||
|
||||
> [!NOTE]
|
||||
> One `MetricPoint` is reserved for every `MetricStream` for the
|
||||
special case where there is no key/value pair associated with the metric. The
|
||||
maximum number of `MetricPoint`s has to accommodate for this special case.
|
||||
|
||||
Consider the below example. Here we set the maximum number of `MetricPoint`s
|
||||
allowed to be `3`. This means that for every `MetricStream`, the SDK will export
|
||||
measurements for up to `3` distinct key/value combinations of the metric. There
|
||||
are two instruments published here: `MyFruitCounter` and `AnotherFruitCounter`.
|
||||
There are two total `MetricStream`s created one for each of these instruments.
|
||||
SDK will limit the maximum number of distinct key/value combinations for each of
|
||||
these `MetricStream`s to `3`.
|
||||
|
||||
```csharp
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Metrics;
|
||||
using OpenTelemetry;
|
||||
using OpenTelemetry.Metrics;
|
||||
|
||||
Counter<long> MyFruitCounter = MyMeter.CreateCounter<long>("MyFruitCounter");
|
||||
Counter<long> AnotherFruitCounter = MyMeter.CreateCounter<long>("AnotherFruitCounter");
|
||||
|
||||
using var meterProvider = Sdk.CreateMeterProviderBuilder()
|
||||
.AddMeter("*")
|
||||
.AddConsoleExporter()
|
||||
.SetMaxMetricPointsPerMetricStream(3) // The default value is 2000
|
||||
.Build();
|
||||
|
||||
// There are four distinct key/value combinations emitted for `MyFruitCounter`:
|
||||
// 1. No key/value pair
|
||||
// 2. (name:apple, color:red)
|
||||
// 3. (name:lemon, color:yellow)
|
||||
// 4. (name:apple, color:green)
|
||||
|
||||
// Since the maximum number of `MetricPoint`s allowed is `3`, the SDK will only export measurements for the following three combinations:
|
||||
// 1. No key/value pair
|
||||
// 2. (name:apple, color:red)
|
||||
// 3. (name:lemon, color:yellow)
|
||||
|
||||
MyFruitCounter.Add(1); // Exported (No key/value pair)
|
||||
MyFruitCounter.Add(1, new("name", "apple"), new("color", "red")); // Exported
|
||||
MyFruitCounter.Add(2, new("name", "lemon"), new("color", "yellow")); // Exported
|
||||
MyFruitCounter.Add(1, new("name", "lemon"), new("color", "yellow")); // Exported
|
||||
MyFruitCounter.Add(2, new("name", "apple"), new("color", "green")); // Not exported
|
||||
MyFruitCounter.Add(5, new("name", "apple"), new("color", "red")); // Exported
|
||||
MyFruitCounter.Add(4, new("name", "lemon"), new("color", "yellow")); // Exported
|
||||
|
||||
// There are four distinct key/value combinations emitted for `AnotherFruitCounter`:
|
||||
// 1. (name:kiwi)
|
||||
// 2. (name:banana, color:yellow)
|
||||
// 3. (name:mango, color:yellow)
|
||||
// 4. (name:banana, color:green)
|
||||
|
||||
// Since the maximum number of `MetricPoint`s allowed is `3`, the SDK will only export measurements for the following three combinations:
|
||||
// 1. No key/value pair (This is a special case. The SDK reserves a `MetricPoint` for it even if it's not explicitly emitted.)
|
||||
// 2. (name:kiwi)
|
||||
// 3. (name:banana, color:yellow)
|
||||
|
||||
AnotherFruitCounter.Add(4, new KeyValuePair<string, object>("name", "kiwi")); // Exported
|
||||
AnotherFruitCounter.Add(1, new("name", "banana"), new("color", "yellow")); // Exported
|
||||
AnotherFruitCounter.Add(2, new("name", "mango"), new("color", "yellow")); // Not exported
|
||||
AnotherFruitCounter.Add(1, new("name", "mango"), new("color", "yellow")); // Not exported
|
||||
AnotherFruitCounter.Add(2, new("name", "banana"), new("color", "green")); // Not exported
|
||||
AnotherFruitCounter.Add(5, new("name", "banana"), new("color", "yellow")); // Exported
|
||||
AnotherFruitCounter.Add(4, new("name", "mango"), new("color", "yellow")); // Not exported
|
||||
```
|
||||
|
||||
To set the [cardinality limit](../README.md#cardinality-limits) at individual
|
||||
metric level, use `MetricStreamConfiguration.CardinalityLimit`:
|
||||
> `MetricStreamConfiguration.CardinalityLimit` is an experimental API only
|
||||
available in pre-release builds. For details see:
|
||||
[OTEL1003](../../diagnostics/experimental-apis/OTEL1003.md).
|
||||
|
||||
```csharp
|
||||
var meterProvider = Sdk.CreateMeterProviderBuilder()
|
||||
.AddMeter("MyCompany.MyProduct.MyLibrary")
|
||||
.AddView(instrumentName: "MyFruitCounter", new MetricStreamConfiguration { CardinalityLimit = 10 })
|
||||
// Set a custom CardinalityLimit (10) for "MyFruitCounter"
|
||||
.AddView(
|
||||
instrumentName: "MyFruitCounter",
|
||||
new MetricStreamConfiguration { CardinalityLimit = 10 })
|
||||
.AddConsoleExporter()
|
||||
.Build();
|
||||
```
|
||||
|
|
|
@ -21,8 +21,12 @@
|
|||
|
||||
* **Experimental (pre-release builds only):** Added support for setting
|
||||
`CardinalityLimit` (the maximum number of data points allowed for a metric)
|
||||
when configuring a view.
|
||||
([#5312](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5312))
|
||||
when configuring a view (applies to individual metrics) and obsoleted
|
||||
`MeterProviderBuilderExtensions.SetMaxMetricPointsPerMetricStream` (previously
|
||||
applied to all metrics). The default cardinality limit for metrics remains at
|
||||
`2000`.
|
||||
([#5312](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5312),
|
||||
[#5328](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5328))
|
||||
|
||||
* Updated `LogRecord` to keep `CategoryName` and `Logger` in sync when using the
|
||||
experimental Log Bridge API.
|
||||
|
|
|
@ -13,6 +13,8 @@ internal sealed class AggregatorStore
|
|||
{
|
||||
internal readonly bool OutputDelta;
|
||||
internal readonly bool OutputDeltaWithUnusedMetricPointReclaimEnabled;
|
||||
internal readonly int CardinalityLimit;
|
||||
internal readonly bool EmitOverflowAttribute;
|
||||
internal long DroppedMeasurements = 0;
|
||||
|
||||
private static readonly string MetricPointCapHitFixMessage = "Consider opting in for the experimental SDK feature to emit all the throttled metrics under the overflow attribute by setting env variable OTEL_DOTNET_EXPERIMENTAL_METRICS_EMIT_OVERFLOW_ATTRIBUTE = true. You could also modify instrumentation to reduce the number of unique key/value pair combinations. Or use Views to drop unwanted tags. Or use MeterProviderBuilder.SetMaxMetricPointsPerMetricStream to set higher limit.";
|
||||
|
@ -42,8 +44,6 @@ internal sealed class AggregatorStore
|
|||
private readonly int exponentialHistogramMaxScale;
|
||||
private readonly UpdateLongDelegate updateLongCallback;
|
||||
private readonly UpdateDoubleDelegate updateDoubleCallback;
|
||||
private readonly int maxMetricPoints;
|
||||
private readonly bool emitOverflowAttribute;
|
||||
private readonly ExemplarFilter exemplarFilter;
|
||||
private readonly Func<KeyValuePair<string, object?>[], int, int> lookupAggregatorStore;
|
||||
|
||||
|
@ -57,17 +57,17 @@ internal sealed class AggregatorStore
|
|||
MetricStreamIdentity metricStreamIdentity,
|
||||
AggregationType aggType,
|
||||
AggregationTemporality temporality,
|
||||
int maxMetricPoints,
|
||||
int cardinalityLimit,
|
||||
bool emitOverflowAttribute,
|
||||
bool shouldReclaimUnusedMetricPoints,
|
||||
ExemplarFilter? exemplarFilter = null)
|
||||
{
|
||||
this.name = metricStreamIdentity.InstrumentName;
|
||||
this.maxMetricPoints = maxMetricPoints;
|
||||
this.CardinalityLimit = cardinalityLimit;
|
||||
|
||||
this.metricPointCapHitMessage = $"Maximum MetricPoints limit reached for this Metric stream. Configured limit: {this.maxMetricPoints}";
|
||||
this.metricPoints = new MetricPoint[maxMetricPoints];
|
||||
this.currentMetricPointBatch = new int[maxMetricPoints];
|
||||
this.metricPointCapHitMessage = $"Maximum MetricPoints limit reached for this Metric stream. Configured limit: {this.CardinalityLimit}";
|
||||
this.metricPoints = new MetricPoint[cardinalityLimit];
|
||||
this.currentMetricPointBatch = new int[cardinalityLimit];
|
||||
this.aggType = aggType;
|
||||
this.OutputDelta = temporality == AggregationTemporality.Delta;
|
||||
this.histogramBounds = metricStreamIdentity.HistogramBucketBounds ?? FindDefaultHistogramBounds(in metricStreamIdentity);
|
||||
|
@ -89,7 +89,7 @@ internal sealed class AggregatorStore
|
|||
this.tagsKeysInterestingCount = hs.Count;
|
||||
}
|
||||
|
||||
this.emitOverflowAttribute = emitOverflowAttribute;
|
||||
this.EmitOverflowAttribute = emitOverflowAttribute;
|
||||
|
||||
var reservedMetricPointsCount = 1;
|
||||
|
||||
|
@ -105,17 +105,17 @@ internal sealed class AggregatorStore
|
|||
|
||||
if (this.OutputDeltaWithUnusedMetricPointReclaimEnabled)
|
||||
{
|
||||
this.availableMetricPoints = new Queue<int>(maxMetricPoints - reservedMetricPointsCount);
|
||||
this.availableMetricPoints = new Queue<int>(cardinalityLimit - reservedMetricPointsCount);
|
||||
|
||||
// There is no overload which only takes capacity as the parameter
|
||||
// Using the DefaultConcurrencyLevel defined in the ConcurrentDictionary class: https://github.com/dotnet/runtime/blob/v7.0.5/src/libraries/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs#L2020
|
||||
// We expect at the most (maxMetricPoints - reservedMetricPointsCount) * 2 entries- one for sorted and one for unsorted input
|
||||
this.tagsToMetricPointIndexDictionaryDelta =
|
||||
new ConcurrentDictionary<Tags, LookupData>(concurrencyLevel: Environment.ProcessorCount, capacity: (maxMetricPoints - reservedMetricPointsCount) * 2);
|
||||
new ConcurrentDictionary<Tags, LookupData>(concurrencyLevel: Environment.ProcessorCount, capacity: (cardinalityLimit - reservedMetricPointsCount) * 2);
|
||||
|
||||
// Add all the indices except for the reserved ones to the queue so that threads have
|
||||
// readily available access to these MetricPoints for their use.
|
||||
for (int i = reservedMetricPointsCount; i < this.maxMetricPoints; i++)
|
||||
for (int i = reservedMetricPointsCount; i < this.CardinalityLimit; i++)
|
||||
{
|
||||
this.availableMetricPoints.Enqueue(i);
|
||||
}
|
||||
|
@ -164,12 +164,12 @@ internal sealed class AggregatorStore
|
|||
}
|
||||
else if (this.OutputDelta)
|
||||
{
|
||||
var indexSnapshot = Math.Min(this.metricPointIndex, this.maxMetricPoints - 1);
|
||||
var indexSnapshot = Math.Min(this.metricPointIndex, this.CardinalityLimit - 1);
|
||||
this.SnapshotDelta(indexSnapshot);
|
||||
}
|
||||
else
|
||||
{
|
||||
var indexSnapshot = Math.Min(this.metricPointIndex, this.maxMetricPoints - 1);
|
||||
var indexSnapshot = Math.Min(this.metricPointIndex, this.CardinalityLimit - 1);
|
||||
this.SnapshotCumulative(indexSnapshot);
|
||||
}
|
||||
|
||||
|
@ -227,7 +227,7 @@ internal sealed class AggregatorStore
|
|||
|
||||
int startIndexForReclaimableMetricPoints = 1;
|
||||
|
||||
if (this.emitOverflowAttribute)
|
||||
if (this.EmitOverflowAttribute)
|
||||
{
|
||||
startIndexForReclaimableMetricPoints = 2; // Index 0 and 1 are reserved for no tags and overflow
|
||||
|
||||
|
@ -249,7 +249,7 @@ internal sealed class AggregatorStore
|
|||
}
|
||||
}
|
||||
|
||||
for (int i = startIndexForReclaimableMetricPoints; i < this.maxMetricPoints; i++)
|
||||
for (int i = startIndexForReclaimableMetricPoints; i < this.CardinalityLimit; i++)
|
||||
{
|
||||
ref var metricPoint = ref this.metricPoints[i];
|
||||
|
||||
|
@ -440,7 +440,7 @@ internal sealed class AggregatorStore
|
|||
if (!this.tagsToMetricPointIndexDictionary.TryGetValue(sortedTags, out aggregatorIndex))
|
||||
{
|
||||
aggregatorIndex = this.metricPointIndex;
|
||||
if (aggregatorIndex >= this.maxMetricPoints)
|
||||
if (aggregatorIndex >= this.CardinalityLimit)
|
||||
{
|
||||
// sorry! out of data points.
|
||||
// TODO: Once we support cleanup of
|
||||
|
@ -469,7 +469,7 @@ internal sealed class AggregatorStore
|
|||
if (!this.tagsToMetricPointIndexDictionary.TryGetValue(sortedTags, out aggregatorIndex))
|
||||
{
|
||||
aggregatorIndex = ++this.metricPointIndex;
|
||||
if (aggregatorIndex >= this.maxMetricPoints)
|
||||
if (aggregatorIndex >= this.CardinalityLimit)
|
||||
{
|
||||
// sorry! out of data points.
|
||||
// TODO: Once we support cleanup of
|
||||
|
@ -496,7 +496,7 @@ internal sealed class AggregatorStore
|
|||
{
|
||||
// This else block is for tag length = 1
|
||||
aggregatorIndex = this.metricPointIndex;
|
||||
if (aggregatorIndex >= this.maxMetricPoints)
|
||||
if (aggregatorIndex >= this.CardinalityLimit)
|
||||
{
|
||||
// sorry! out of data points.
|
||||
// TODO: Once we support cleanup of
|
||||
|
@ -518,7 +518,7 @@ internal sealed class AggregatorStore
|
|||
if (!this.tagsToMetricPointIndexDictionary.TryGetValue(givenTags, out aggregatorIndex))
|
||||
{
|
||||
aggregatorIndex = ++this.metricPointIndex;
|
||||
if (aggregatorIndex >= this.maxMetricPoints)
|
||||
if (aggregatorIndex >= this.CardinalityLimit)
|
||||
{
|
||||
// sorry! out of data points.
|
||||
// TODO: Once we support cleanup of
|
||||
|
@ -929,7 +929,7 @@ internal sealed class AggregatorStore
|
|||
{
|
||||
Interlocked.Increment(ref this.DroppedMeasurements);
|
||||
|
||||
if (this.emitOverflowAttribute)
|
||||
if (this.EmitOverflowAttribute)
|
||||
{
|
||||
this.InitializeOverflowTagPointIfNotInitialized();
|
||||
this.metricPoints[1].Update(value);
|
||||
|
@ -973,7 +973,7 @@ internal sealed class AggregatorStore
|
|||
{
|
||||
Interlocked.Increment(ref this.DroppedMeasurements);
|
||||
|
||||
if (this.emitOverflowAttribute)
|
||||
if (this.EmitOverflowAttribute)
|
||||
{
|
||||
this.InitializeOverflowTagPointIfNotInitialized();
|
||||
this.metricPoints[1].Update(value);
|
||||
|
@ -1017,7 +1017,7 @@ internal sealed class AggregatorStore
|
|||
{
|
||||
Interlocked.Increment(ref this.DroppedMeasurements);
|
||||
|
||||
if (this.emitOverflowAttribute)
|
||||
if (this.EmitOverflowAttribute)
|
||||
{
|
||||
this.InitializeOverflowTagPointIfNotInitialized();
|
||||
this.metricPoints[1].Update(value);
|
||||
|
@ -1061,7 +1061,7 @@ internal sealed class AggregatorStore
|
|||
{
|
||||
Interlocked.Increment(ref this.DroppedMeasurements);
|
||||
|
||||
if (this.emitOverflowAttribute)
|
||||
if (this.EmitOverflowAttribute)
|
||||
{
|
||||
this.InitializeOverflowTagPointIfNotInitialized();
|
||||
this.metricPoints[1].Update(value);
|
||||
|
|
|
@ -218,7 +218,7 @@ public static class MeterProviderBuilderExtensions
|
|||
{
|
||||
if (builder is MeterProviderBuilderSdk meterProviderBuilderSdk)
|
||||
{
|
||||
meterProviderBuilderSdk.SetMaxMetricStreams(maxMetricStreams);
|
||||
meterProviderBuilderSdk.SetMetricLimit(maxMetricStreams);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -238,6 +238,9 @@ public static class MeterProviderBuilderExtensions
|
|||
/// <param name="meterProviderBuilder"><see cref="MeterProviderBuilder"/>.</param>
|
||||
/// <param name="maxMetricPointsPerMetricStream">Maximum number of metric points allowed per metric stream.</param>
|
||||
/// <returns>The supplied <see cref="MeterProviderBuilder"/> for chaining.</returns>
|
||||
#if EXPOSE_EXPERIMENTAL_FEATURES
|
||||
[Obsolete("Use MetricStreamConfiguration.CardinalityLimit via the AddView API instead. This method will be removed in a future version.")]
|
||||
#endif
|
||||
public static MeterProviderBuilder SetMaxMetricPointsPerMetricStream(this MeterProviderBuilder meterProviderBuilder, int maxMetricPointsPerMetricStream)
|
||||
{
|
||||
Guard.ThrowIfOutOfRange(maxMetricPointsPerMetricStream, min: 1);
|
||||
|
@ -246,7 +249,7 @@ public static class MeterProviderBuilderExtensions
|
|||
{
|
||||
if (builder is MeterProviderBuilderSdk meterProviderBuilderSdk)
|
||||
{
|
||||
meterProviderBuilderSdk.SetMaxMetricPointsPerMetricStream(maxMetricPointsPerMetricStream);
|
||||
meterProviderBuilderSdk.SetDefaultCardinalityLimit(maxMetricPointsPerMetricStream);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -15,8 +15,8 @@ namespace OpenTelemetry.Metrics;
|
|||
/// </summary>
|
||||
internal sealed class MeterProviderBuilderSdk : MeterProviderBuilder, IMeterProviderBuilder
|
||||
{
|
||||
public const int MaxMetricsDefault = 1000;
|
||||
public const int MaxMetricPointsPerMetricDefault = 2000;
|
||||
public const int DefaultMetricLimit = 1000;
|
||||
public const int DefaultCardinalityLimit = 2000;
|
||||
private const string DefaultInstrumentationVersion = "1.0.0.0";
|
||||
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
@ -49,9 +49,9 @@ internal sealed class MeterProviderBuilderSdk : MeterProviderBuilder, IMeterProv
|
|||
|
||||
public List<Func<Instrument, MetricStreamConfiguration?>> ViewConfigs { get; } = new();
|
||||
|
||||
public int MaxMetricStreams { get; private set; } = MaxMetricsDefault;
|
||||
public int MetricLimit { get; private set; } = DefaultMetricLimit;
|
||||
|
||||
public int MaxMetricPointsPerMetricStream { get; private set; } = MaxMetricPointsPerMetricDefault;
|
||||
public int CardinalityLimit { get; private set; } = DefaultCardinalityLimit;
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the given instrument name is valid according to the specification.
|
||||
|
@ -186,16 +186,16 @@ internal sealed class MeterProviderBuilderSdk : MeterProviderBuilder, IMeterProv
|
|||
return this;
|
||||
}
|
||||
|
||||
public MeterProviderBuilder SetMaxMetricStreams(int maxMetricStreams)
|
||||
public MeterProviderBuilder SetMetricLimit(int metricLimit)
|
||||
{
|
||||
this.MaxMetricStreams = maxMetricStreams;
|
||||
this.MetricLimit = metricLimit;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public MeterProviderBuilder SetMaxMetricPointsPerMetricStream(int maxMetricPointsPerMetricStream)
|
||||
public MeterProviderBuilder SetDefaultCardinalityLimit(int cardinalityLimit)
|
||||
{
|
||||
this.MaxMetricPointsPerMetricStream = maxMetricPointsPerMetricStream;
|
||||
this.CardinalityLimit = cardinalityLimit;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -76,9 +76,12 @@ internal sealed class MeterProviderSdk : MeterProvider
|
|||
Guard.ThrowIfNull(reader);
|
||||
|
||||
reader.SetParentProvider(this);
|
||||
reader.SetMaxMetricStreams(state.MaxMetricStreams);
|
||||
reader.SetMaxMetricPointsPerMetricStream(state.MaxMetricPointsPerMetricStream, isEmitOverflowAttributeKeySet);
|
||||
reader.SetExemplarFilter(state.ExemplarFilter);
|
||||
|
||||
reader.ApplyParentProviderSettings(
|
||||
state.MetricLimit,
|
||||
state.CardinalityLimit,
|
||||
state.ExemplarFilter,
|
||||
isEmitOverflowAttributeKeySet);
|
||||
|
||||
if (this.reader == null)
|
||||
{
|
||||
|
|
|
@ -41,12 +41,12 @@ public sealed class Metric
|
|||
("System.Net.Http", "http.client.connection.duration"),
|
||||
};
|
||||
|
||||
private readonly AggregatorStore aggStore;
|
||||
internal readonly AggregatorStore AggregatorStore;
|
||||
|
||||
internal Metric(
|
||||
MetricStreamIdentity instrumentIdentity,
|
||||
AggregationTemporality temporality,
|
||||
int maxMetricPointsPerMetricStream,
|
||||
int cardinalityLimit,
|
||||
bool emitOverflowAttribute,
|
||||
bool shouldReclaimUnusedMetricPoints,
|
||||
ExemplarFilter? exemplarFilter = null)
|
||||
|
@ -155,7 +155,7 @@ public sealed class Metric
|
|||
throw new NotSupportedException($"Unsupported Instrument Type: {instrumentIdentity.InstrumentType.FullName}");
|
||||
}
|
||||
|
||||
this.aggStore = new AggregatorStore(instrumentIdentity, aggType, temporality, maxMetricPointsPerMetricStream, emitOverflowAttribute, shouldReclaimUnusedMetricPoints, exemplarFilter);
|
||||
this.AggregatorStore = new AggregatorStore(instrumentIdentity, aggType, temporality, cardinalityLimit, emitOverflowAttribute, shouldReclaimUnusedMetricPoints, exemplarFilter);
|
||||
this.Temporality = temporality;
|
||||
}
|
||||
|
||||
|
@ -211,14 +211,14 @@ public sealed class Metric
|
|||
/// </summary>
|
||||
/// <returns><see cref="MetricPointsAccessor"/>.</returns>
|
||||
public MetricPointsAccessor GetMetricPoints()
|
||||
=> this.aggStore.GetMetricPoints();
|
||||
=> this.AggregatorStore.GetMetricPoints();
|
||||
|
||||
internal void UpdateLong(long value, ReadOnlySpan<KeyValuePair<string, object?>> tags)
|
||||
=> this.aggStore.Update(value, tags);
|
||||
=> this.AggregatorStore.Update(value, tags);
|
||||
|
||||
internal void UpdateDouble(double value, ReadOnlySpan<KeyValuePair<string, object?>> tags)
|
||||
=> this.aggStore.Update(value, tags);
|
||||
=> this.AggregatorStore.Update(value, tags);
|
||||
|
||||
internal int Snapshot()
|
||||
=> this.aggStore.Snapshot();
|
||||
=> this.AggregatorStore.Snapshot();
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@ public abstract partial class MetricReader
|
|||
private readonly HashSet<string> metricStreamNames = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly ConcurrentDictionary<MetricStreamIdentity, Metric> instrumentIdentityToMetric = new();
|
||||
private readonly object instrumentCreationLock = new();
|
||||
private int maxMetricStreams;
|
||||
private int maxMetricPointsPerMetricStream;
|
||||
private int metricLimit;
|
||||
private int cardinalityLimit;
|
||||
private Metric?[]? metrics;
|
||||
private Metric[]? metricsCurrentBatch;
|
||||
private int metricIndex = -1;
|
||||
|
@ -44,7 +44,7 @@ public abstract partial class MetricReader
|
|||
}
|
||||
|
||||
var index = ++this.metricIndex;
|
||||
if (index >= this.maxMetricStreams)
|
||||
if (index >= this.metricLimit)
|
||||
{
|
||||
OpenTelemetrySdkEventSource.Log.MetricInstrumentIgnored(metricStreamIdentity.InstrumentName, metricStreamIdentity.MeterName, "Maximum allowed Metric streams for the provider exceeded.", "Use MeterProviderBuilder.AddView to drop unused instruments. Or use MeterProviderBuilder.SetMaxMetricStreams to configure MeterProvider to allow higher limit.");
|
||||
return null;
|
||||
|
@ -55,7 +55,7 @@ public abstract partial class MetricReader
|
|||
try
|
||||
{
|
||||
bool shouldReclaimUnusedMetricPoints = this.parentProvider is MeterProviderSdk meterProviderSdk && meterProviderSdk.ShouldReclaimUnusedMetricPoints;
|
||||
metric = new Metric(metricStreamIdentity, this.GetAggregationTemporality(metricStreamIdentity.InstrumentType), this.maxMetricPointsPerMetricStream, this.emitOverflowAttribute, shouldReclaimUnusedMetricPoints, this.exemplarFilter);
|
||||
metric = new Metric(metricStreamIdentity, this.GetAggregationTemporality(metricStreamIdentity.InstrumentType), this.cardinalityLimit, this.emitOverflowAttribute, shouldReclaimUnusedMetricPoints, this.exemplarFilter);
|
||||
}
|
||||
catch (NotSupportedException nse)
|
||||
{
|
||||
|
@ -129,7 +129,7 @@ public abstract partial class MetricReader
|
|||
}
|
||||
|
||||
var index = ++this.metricIndex;
|
||||
if (index >= this.maxMetricStreams)
|
||||
if (index >= this.metricLimit)
|
||||
{
|
||||
OpenTelemetrySdkEventSource.Log.MetricInstrumentIgnored(metricStreamIdentity.InstrumentName, metricStreamIdentity.MeterName, "Maximum allowed Metric streams for the provider exceeded.", "Use MeterProviderBuilder.AddView to drop unused instruments. Or use MeterProviderBuilder.SetMaxMetricStreams to configure MeterProvider to allow higher limit.");
|
||||
}
|
||||
|
@ -137,12 +137,14 @@ public abstract partial class MetricReader
|
|||
{
|
||||
bool shouldReclaimUnusedMetricPoints = this.parentProvider is MeterProviderSdk meterProviderSdk && meterProviderSdk.ShouldReclaimUnusedMetricPoints;
|
||||
|
||||
var cardinalityLimit = this.cardinalityLimit;
|
||||
|
||||
if (metricStreamConfig != null && metricStreamConfig.CardinalityLimit != null)
|
||||
{
|
||||
this.maxMetricPointsPerMetricStream = metricStreamConfig.CardinalityLimit.Value;
|
||||
cardinalityLimit = metricStreamConfig.CardinalityLimit.Value;
|
||||
}
|
||||
|
||||
Metric metric = new(metricStreamIdentity, this.GetAggregationTemporality(metricStreamIdentity.InstrumentType), this.maxMetricPointsPerMetricStream, this.emitOverflowAttribute, shouldReclaimUnusedMetricPoints, this.exemplarFilter);
|
||||
Metric metric = new(metricStreamIdentity, this.GetAggregationTemporality(metricStreamIdentity.InstrumentType), cardinalityLimit, this.emitOverflowAttribute, shouldReclaimUnusedMetricPoints, this.exemplarFilter);
|
||||
|
||||
this.instrumentIdentityToMetric[metricStreamIdentity] = metric;
|
||||
this.metrics![index] = metric;
|
||||
|
@ -205,26 +207,22 @@ public abstract partial class MetricReader
|
|||
}
|
||||
}
|
||||
|
||||
internal void SetMaxMetricStreams(int maxMetricStreams)
|
||||
{
|
||||
this.maxMetricStreams = maxMetricStreams;
|
||||
this.metrics = new Metric[maxMetricStreams];
|
||||
this.metricsCurrentBatch = new Metric[maxMetricStreams];
|
||||
}
|
||||
|
||||
internal void SetExemplarFilter(ExemplarFilter? exemplarFilter)
|
||||
internal void ApplyParentProviderSettings(
|
||||
int metricLimit,
|
||||
int cardinalityLimit,
|
||||
ExemplarFilter? exemplarFilter,
|
||||
bool isEmitOverflowAttributeKeySet)
|
||||
{
|
||||
this.metricLimit = metricLimit;
|
||||
this.metrics = new Metric[metricLimit];
|
||||
this.metricsCurrentBatch = new Metric[metricLimit];
|
||||
this.cardinalityLimit = cardinalityLimit;
|
||||
this.exemplarFilter = exemplarFilter;
|
||||
}
|
||||
|
||||
internal void SetMaxMetricPointsPerMetricStream(int maxMetricPointsPerMetricStream, bool isEmitOverflowAttributeKeySet)
|
||||
{
|
||||
this.maxMetricPointsPerMetricStream = maxMetricPointsPerMetricStream;
|
||||
|
||||
if (isEmitOverflowAttributeKeySet)
|
||||
{
|
||||
// We need at least two metric points. One is reserved for zero tags and the other one for overflow attribute
|
||||
if (maxMetricPointsPerMetricStream > 1)
|
||||
if (cardinalityLimit > 1)
|
||||
{
|
||||
this.emitOverflowAttribute = true;
|
||||
}
|
||||
|
@ -273,7 +271,7 @@ public abstract partial class MetricReader
|
|||
|
||||
try
|
||||
{
|
||||
var indexSnapshot = Math.Min(this.metricIndex, this.maxMetricStreams - 1);
|
||||
var indexSnapshot = Math.Min(this.metricIndex, this.metricLimit - 1);
|
||||
var target = indexSnapshot + 1;
|
||||
int metricCountCurrentBatch = 0;
|
||||
for (int i = 0; i < target; i++)
|
||||
|
|
|
@ -109,10 +109,8 @@ public class MetricStreamConfiguration
|
|||
/// <para>Spec reference: <see
|
||||
/// href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#cardinality-limits">Cardinality
|
||||
/// limits</see>.</para>
|
||||
/// Note: If not set, the MeterProvider cardinality limit value will be
|
||||
/// used, which defaults to 2000. Call <see
|
||||
/// cref="MeterProviderBuilderExtensions.SetMaxMetricPointsPerMetricStream"/>
|
||||
/// to configure the MeterProvider default.
|
||||
/// Note: If not set the default MeterProvider cardinality limit of 2000
|
||||
/// will apply.
|
||||
/// </remarks>
|
||||
#if NET8_0_OR_GREATER
|
||||
[Experimental(DiagnosticDefinitions.CardinalityLimitExperimentalApi, UrlFormat = DiagnosticDefinitions.ExperimentalApiUrlFormat)]
|
||||
|
|
|
@ -255,7 +255,7 @@ public abstract class AggregatorTestsBase
|
|||
metricStreamIdentity,
|
||||
AggregationType.Histogram,
|
||||
AggregationTemporality.Cumulative,
|
||||
maxMetricPoints: 1024,
|
||||
cardinalityLimit: 1024,
|
||||
this.emitOverflowAttribute,
|
||||
this.shouldReclaimUnusedMetricPoints);
|
||||
|
||||
|
@ -332,7 +332,7 @@ public abstract class AggregatorTestsBase
|
|||
metricStreamIdentity,
|
||||
aggregationType,
|
||||
aggregationTemporality,
|
||||
maxMetricPoints: 1024,
|
||||
cardinalityLimit: 1024,
|
||||
this.emitOverflowAttribute,
|
||||
this.shouldReclaimUnusedMetricPoints,
|
||||
exemplarsEnabled ? new AlwaysOnExemplarFilter() : null);
|
||||
|
@ -442,7 +442,7 @@ public abstract class AggregatorTestsBase
|
|||
metricStreamIdentity,
|
||||
AggregationType.Base2ExponentialHistogram,
|
||||
AggregationTemporality.Cumulative,
|
||||
maxMetricPoints: 1024,
|
||||
cardinalityLimit: 1024,
|
||||
this.emitOverflowAttribute,
|
||||
this.shouldReclaimUnusedMetricPoints);
|
||||
|
||||
|
|
|
@ -1423,26 +1423,26 @@ public abstract class MetricApiTestsBase : MetricTestsBase
|
|||
// for no tag point!
|
||||
// This may be changed later.
|
||||
counterLong.Add(10);
|
||||
for (int i = 0; i < MeterProviderBuilderSdk.MaxMetricPointsPerMetricDefault + 1; i++)
|
||||
for (int i = 0; i < MeterProviderBuilderSdk.DefaultCardinalityLimit + 1; i++)
|
||||
{
|
||||
counterLong.Add(10, new KeyValuePair<string, object>("key", "value" + i));
|
||||
}
|
||||
|
||||
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
|
||||
Assert.Equal(MeterProviderBuilderSdk.MaxMetricPointsPerMetricDefault, MetricPointCount());
|
||||
Assert.Equal(MeterProviderBuilderSdk.DefaultCardinalityLimit, MetricPointCount());
|
||||
|
||||
exportedItems.Clear();
|
||||
counterLong.Add(10);
|
||||
for (int i = 0; i < MeterProviderBuilderSdk.MaxMetricPointsPerMetricDefault + 1; i++)
|
||||
for (int i = 0; i < MeterProviderBuilderSdk.DefaultCardinalityLimit + 1; i++)
|
||||
{
|
||||
counterLong.Add(10, new KeyValuePair<string, object>("key", "value" + i));
|
||||
}
|
||||
|
||||
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
|
||||
Assert.Equal(MeterProviderBuilderSdk.MaxMetricPointsPerMetricDefault, MetricPointCount());
|
||||
Assert.Equal(MeterProviderBuilderSdk.DefaultCardinalityLimit, MetricPointCount());
|
||||
|
||||
counterLong.Add(10);
|
||||
for (int i = 0; i < MeterProviderBuilderSdk.MaxMetricPointsPerMetricDefault + 1; i++)
|
||||
for (int i = 0; i < MeterProviderBuilderSdk.DefaultCardinalityLimit + 1; i++)
|
||||
{
|
||||
counterLong.Add(10, new KeyValuePair<string, object>("key", "value" + i));
|
||||
}
|
||||
|
@ -1453,7 +1453,7 @@ public abstract class MetricApiTestsBase : MetricTestsBase
|
|||
counterLong.Add(10, new KeyValuePair<string, object>("key", "valueC"));
|
||||
exportedItems.Clear();
|
||||
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
|
||||
Assert.Equal(MeterProviderBuilderSdk.MaxMetricPointsPerMetricDefault, MetricPointCount());
|
||||
Assert.Equal(MeterProviderBuilderSdk.DefaultCardinalityLimit, MetricPointCount());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OpenTelemetry.Tests;
|
||||
|
@ -66,12 +65,7 @@ public abstract class MetricOverflowAttributeTestsBase
|
|||
meterProvider.ForceFlush();
|
||||
|
||||
Assert.Single(exportedItems);
|
||||
var metric = exportedItems[0];
|
||||
|
||||
var aggregatorStore = typeof(Metric).GetField("aggStore", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(metric) as AggregatorStore;
|
||||
var emitOverflowAttribute = (bool)typeof(AggregatorStore).GetField("emitOverflowAttribute", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(aggregatorStore);
|
||||
|
||||
Assert.Equal(isEmitOverflowAttributeKeySet, emitOverflowAttribute);
|
||||
Assert.Equal(isEmitOverflowAttributeKeySet, exportedItems[0].AggregatorStore.EmitOverflowAttribute);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
@ -106,12 +100,7 @@ public abstract class MetricOverflowAttributeTestsBase
|
|||
meterProvider.ForceFlush();
|
||||
|
||||
Assert.Single(exportedItems);
|
||||
var metric = exportedItems[0];
|
||||
|
||||
var aggregatorStore = typeof(Metric).GetField("aggStore", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(metric) as AggregatorStore;
|
||||
var emitOverflowAttribute = (bool)typeof(AggregatorStore).GetField("emitOverflowAttribute", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(aggregatorStore);
|
||||
|
||||
Assert.Equal(isEmitOverflowAttributeKeySet, emitOverflowAttribute);
|
||||
Assert.Equal(isEmitOverflowAttributeKeySet, exportedItems[0].AggregatorStore.EmitOverflowAttribute);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
@ -140,12 +129,7 @@ public abstract class MetricOverflowAttributeTestsBase
|
|||
meterProvider.ForceFlush();
|
||||
|
||||
Assert.Single(exportedItems);
|
||||
var metric = exportedItems[0];
|
||||
|
||||
var aggregatorStore = typeof(Metric).GetField("aggStore", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(metric) as AggregatorStore;
|
||||
var emitOverflowAttribute = (bool)typeof(AggregatorStore).GetField("emitOverflowAttribute", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(aggregatorStore);
|
||||
|
||||
Assert.Equal(isEmitOverflowAttributeKeySet, emitOverflowAttribute);
|
||||
Assert.Equal(isEmitOverflowAttributeKeySet, exportedItems[0].AggregatorStore.EmitOverflowAttribute);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
@ -174,7 +158,7 @@ public abstract class MetricOverflowAttributeTestsBase
|
|||
counter.Add(10); // Record measurement for zero tags
|
||||
|
||||
// Max number for MetricPoints available for use when emitted with tags
|
||||
int maxMetricPointsForUse = MeterProviderBuilderSdk.MaxMetricPointsPerMetricDefault - 2;
|
||||
int maxMetricPointsForUse = MeterProviderBuilderSdk.DefaultCardinalityLimit - 2;
|
||||
|
||||
for (int i = 0; i < maxMetricPointsForUse; i++)
|
||||
{
|
||||
|
@ -325,7 +309,7 @@ public abstract class MetricOverflowAttributeTestsBase
|
|||
histogram.Record(10); // Record measurement for zero tags
|
||||
|
||||
// Max number for MetricPoints available for use when emitted with tags
|
||||
int maxMetricPointsForUse = MeterProviderBuilderSdk.MaxMetricPointsPerMetricDefault - 2;
|
||||
int maxMetricPointsForUse = MeterProviderBuilderSdk.DefaultCardinalityLimit - 2;
|
||||
|
||||
for (int i = 0; i < maxMetricPointsForUse; i++)
|
||||
{
|
||||
|
|
|
@ -286,17 +286,12 @@ public abstract class MetricPointReclaimTestsBase
|
|||
|
||||
private readonly bool assertNoDroppedMeasurements;
|
||||
|
||||
private readonly FieldInfo aggStoreFieldInfo;
|
||||
|
||||
private readonly FieldInfo metricPointLookupDictionaryFieldInfo;
|
||||
|
||||
public CustomExporter(bool assertNoDroppedMeasurements)
|
||||
{
|
||||
this.assertNoDroppedMeasurements = assertNoDroppedMeasurements;
|
||||
|
||||
var metricFields = typeof(Metric).GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
this.aggStoreFieldInfo = metricFields!.FirstOrDefault(field => field.Name == "aggStore");
|
||||
|
||||
var aggregatorStoreFields = typeof(AggregatorStore).GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
this.metricPointLookupDictionaryFieldInfo = aggregatorStoreFields!.FirstOrDefault(field => field.Name == "tagsToMetricPointIndexDictionaryDelta");
|
||||
}
|
||||
|
@ -305,7 +300,7 @@ public abstract class MetricPointReclaimTestsBase
|
|||
{
|
||||
foreach (var metric in batch)
|
||||
{
|
||||
var aggStore = this.aggStoreFieldInfo.GetValue(metric) as AggregatorStore;
|
||||
var aggStore = metric.AggregatorStore;
|
||||
var metricPointLookupDictionary = this.metricPointLookupDictionaryFieldInfo.GetValue(aggStore) as ConcurrentDictionary<Tags, LookupData>;
|
||||
|
||||
var droppedMeasurements = aggStore.DroppedMeasurements;
|
||||
|
@ -316,7 +311,7 @@ public abstract class MetricPointReclaimTestsBase
|
|||
}
|
||||
|
||||
// This is to ensure that the lookup dictionary does not have unbounded growth
|
||||
Assert.True(metricPointLookupDictionary.Count <= (MeterProviderBuilderSdk.MaxMetricPointsPerMetricDefault * 2));
|
||||
Assert.True(metricPointLookupDictionary.Count <= (MeterProviderBuilderSdk.DefaultCardinalityLimit * 2));
|
||||
|
||||
foreach (ref readonly var metricPoint in metric.GetMetricPoints())
|
||||
{
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Reflection;
|
||||
using OpenTelemetry.Internal;
|
||||
using OpenTelemetry.Tests;
|
||||
using Xunit;
|
||||
|
@ -920,32 +919,61 @@ public class MetricViewTests : MetricTestsBase
|
|||
Assert.Equal(10, metricPoint2.GetSumLong());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CardinalityLimitofMatchingViewTakesPrecedenceOverMetricProviderWhenBothWereSet()
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void CardinalityLimitofMatchingViewTakesPrecedenceOverMeterProvider(bool setDefault)
|
||||
{
|
||||
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) =>
|
||||
using var container = this.BuildMeterProvider(out var meterProvider, builder =>
|
||||
{
|
||||
if (setDefault)
|
||||
{
|
||||
return new MetricStreamConfiguration() { Name = "MetricStreamA", CardinalityLimit = 10000 };
|
||||
})
|
||||
.AddInMemoryExporter(exportedItems));
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
builder.SetMaxMetricPointsPerMetricStream(3);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
var counter = meter.CreateCounter<long>("counter");
|
||||
counter.Add(100);
|
||||
builder
|
||||
.AddMeter(meter.Name)
|
||||
.AddView((instrument) =>
|
||||
{
|
||||
if (instrument.Name == "counter2")
|
||||
{
|
||||
return new MetricStreamConfiguration() { Name = "MetricStreamA", CardinalityLimit = 10000 };
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
.AddInMemoryExporter(exportedItems);
|
||||
});
|
||||
|
||||
var counter1 = meter.CreateCounter<long>("counter1");
|
||||
counter1.Add(100);
|
||||
|
||||
var counter2 = meter.CreateCounter<long>("counter2");
|
||||
counter2.Add(100);
|
||||
|
||||
var counter3 = meter.CreateCounter<long>("counter3");
|
||||
counter3.Add(100);
|
||||
|
||||
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
|
||||
|
||||
var metric = exportedItems[0];
|
||||
Assert.Equal(3, exportedItems.Count);
|
||||
|
||||
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);
|
||||
Assert.Equal(10000, exportedItems[1].AggregatorStore.CardinalityLimit);
|
||||
if (setDefault)
|
||||
{
|
||||
Assert.Equal(3, exportedItems[0].AggregatorStore.CardinalityLimit);
|
||||
Assert.Equal(3, exportedItems[2].AggregatorStore.CardinalityLimit);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Equal(2000, exportedItems[0].AggregatorStore.CardinalityLimit);
|
||||
Assert.Equal(2000, exportedItems[2].AggregatorStore.CardinalityLimit);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -987,24 +1015,15 @@ public class MetricViewTests : MetricTestsBase
|
|||
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(256, metricA.AggregatorStore.CardinalityLimit);
|
||||
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(3, metricB.AggregatorStore.CardinalityLimit);
|
||||
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(200000, metricC.AggregatorStore.CardinalityLimit);
|
||||
Assert.Equal("MetricStreamC", metricC.Name);
|
||||
Assert.Equal(10, GetAggregatedValue(metricC));
|
||||
|
||||
|
|
Loading…
Reference in New Issue