Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/OpenTelemetry/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ Notes](../../RELEASENOTES.md).

## Unreleased

* Added a verification to ensure that a `MetricReader` can only be registered
to a single `MeterProvider`, as required by the OpenTelemetry specification.
([#6458](https://github.com/open-telemetry/opentelemetry-dotnet/pull/6458))

* Added `FormatMessage` configuration option to self-diagnostics feature. When
set to `true` (default is false), log messages will be formatted by replacing
placeholders with actual parameter values for improved readability.
Expand Down
5 changes: 5 additions & 0 deletions src/OpenTelemetry/Metrics/Reader/MetricReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,11 @@ public void Dispose()

internal virtual void SetParentProvider(BaseProvider parentProvider)
{
if (this.parentProvider != null && this.parentProvider != parentProvider)
{
throw new NotSupportedException("A MetricReader must not be registered with multiple MeterProviders.");
}

this.parentProvider = parentProvider;
}

Expand Down
91 changes: 91 additions & 0 deletions test/OpenTelemetry.Tests/Metrics/MultipleReadersTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,104 @@

using System.Diagnostics.Metrics;
using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry.Exporter;
using OpenTelemetry.Tests;
using Xunit;

namespace OpenTelemetry.Metrics.Tests;

public class MultipleReadersTests
{
[Fact]
public void ReaderCannotBeRegisteredMoreThanOnce()
{
var exportedItems = new List<Metric>();
using var exporter = new InMemoryExporter<Metric>(exportedItems);
using var reader = new BaseExportingMetricReader(exporter);

using var meter = new Meter($"{Utils.GetCurrentMethodName()}");

var meterProviderBuilder1 = Sdk.CreateMeterProviderBuilder()
.AddMeter(meter.Name)
.AddReader(reader);
var meterProviderBuilder2 = Sdk.CreateMeterProviderBuilder()
.AddMeter(meter.Name)
.AddReader(reader);

using var meterProvider1 = meterProviderBuilder1.Build();
Assert.Throws<NotSupportedException>(() => meterProviderBuilder2.Build());
}

[Fact]
public void MultipleReadersOneCollectsIndependently()
{
var exportedItems1 = new List<Metric>();
var exportedItems2 = new List<Metric>();

using var exporter1 = new InMemoryExporter<Metric>(exportedItems1);
using var exporter2 = new InMemoryExporter<Metric>(exportedItems2);
using var reader1 = new BaseExportingMetricReader(exporter1);
using var reader2 = new BaseExportingMetricReader(exporter2);

using var meter = new Meter($"{Utils.GetCurrentMethodName()}");
var counter = meter.CreateCounter<long>("counter");

var meterProviderBuilder = Sdk.CreateMeterProviderBuilder()
.AddMeter(meter.Name)
.AddReader(reader1)
.AddReader(reader2);

using var meterProvider = meterProviderBuilder.Build();

counter.Add(1);

reader1.Collect();

Assert.Single(exportedItems1);
Assert.Empty(exportedItems2);
}

[Fact]
public void MultipleReadersDifferentTemporality()
{
var exportedItems1 = new List<Metric>();
var exportedItems2 = new List<Metric>();

using var exporter1 = new InMemoryExporter<Metric>(exportedItems1);
using var exporter2 = new InMemoryExporter<Metric>(exportedItems2);
using var reader1 = new BaseExportingMetricReader(exporter1);
using var reader2 = new BaseExportingMetricReader(exporter2);
reader1.TemporalityPreference = MetricReaderTemporalityPreference.Delta;
reader2.TemporalityPreference = MetricReaderTemporalityPreference.Cumulative;

using var meter = new Meter($"{Utils.GetCurrentMethodName()}");
var counter = meter.CreateCounter<long>("counter");

var meterProviderBuilder = Sdk.CreateMeterProviderBuilder()
.AddMeter(meter.Name)
.AddReader(reader1)
.AddReader(reader2);

using var meterProvider = meterProviderBuilder.Build();

counter.Add(1);

reader1.Collect();
reader2.Collect();

AssertLongSumValueForMetric(exportedItems1[0], 1);
AssertLongSumValueForMetric(exportedItems2[0], 1);

exportedItems1.Clear();

counter.Add(10);
reader1.Collect();
reader2.Collect();

AssertLongSumValueForMetric(exportedItems1[0], 10);
AssertLongSumValueForMetric(exportedItems2[0], 11);
}

[Theory]
[InlineData(MetricReaderTemporalityPreference.Delta, false)]
[InlineData(MetricReaderTemporalityPreference.Delta, true)]
Expand Down
Loading