Allow duplicate instrument registration (#2916)

This commit is contained in:
Alan West 2022-03-04 17:16:14 -08:00 committed by GitHub
parent 62217ce2df
commit ffffe5cc5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 514 additions and 81 deletions

View File

@ -62,13 +62,13 @@ namespace OpenTelemetry.Exporter
msg.Append($", Unit: {metric.Unit}");
}
if (!string.IsNullOrEmpty(metric.Meter.Name))
if (!string.IsNullOrEmpty(metric.MeterName))
{
msg.Append($", Meter: {metric.Meter.Name}");
msg.Append($", Meter: {metric.MeterName}");
if (!string.IsNullOrEmpty(metric.Meter.Version))
if (!string.IsNullOrEmpty(metric.MeterVersion))
{
msg.Append($"/{metric.Meter.Version}");
msg.Append($"/{metric.MeterVersion}");
}
}

View File

@ -60,10 +60,10 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation
continue;
}
var meterName = metric.Meter.Name;
var meterName = metric.MeterName;
if (!metricsByLibrary.TryGetValue(meterName, out var instrumentationLibraryMetrics))
{
instrumentationLibraryMetrics = GetMetricListFromPool(meterName, metric.Meter.Version);
instrumentationLibraryMetrics = GetMetricListFromPool(meterName, metric.MeterVersion);
metricsByLibrary.Add(meterName, instrumentationLibraryMetrics);
resourceMetrics.InstrumentationLibraryMetrics.Add(instrumentationLibraryMetrics);

View File

@ -63,7 +63,8 @@ OpenTelemetry.Metrics.MeterProviderExtensions
OpenTelemetry.Metrics.Metric
OpenTelemetry.Metrics.Metric.Description.get -> string
OpenTelemetry.Metrics.Metric.GetMetricPoints() -> OpenTelemetry.Metrics.MetricPointsAccessor
OpenTelemetry.Metrics.Metric.Meter.get -> System.Diagnostics.Metrics.Meter
OpenTelemetry.Metrics.Metric.MeterName.get -> string
OpenTelemetry.Metrics.Metric.MeterVersion.get -> string
OpenTelemetry.Metrics.Metric.MetricType.get -> OpenTelemetry.Metrics.MetricType
OpenTelemetry.Metrics.Metric.Name.get -> string
OpenTelemetry.Metrics.Metric.Temporality.get -> OpenTelemetry.Metrics.AggregationTemporality

View File

@ -63,7 +63,8 @@ OpenTelemetry.Metrics.MeterProviderExtensions
OpenTelemetry.Metrics.Metric
OpenTelemetry.Metrics.Metric.Description.get -> string
OpenTelemetry.Metrics.Metric.GetMetricPoints() -> OpenTelemetry.Metrics.MetricPointsAccessor
OpenTelemetry.Metrics.Metric.Meter.get -> System.Diagnostics.Metrics.Meter
OpenTelemetry.Metrics.Metric.MeterName.get -> string
OpenTelemetry.Metrics.Metric.MeterVersion.get -> string
OpenTelemetry.Metrics.Metric.MetricType.get -> OpenTelemetry.Metrics.MetricType
OpenTelemetry.Metrics.Metric.Name.get -> string
OpenTelemetry.Metrics.Metric.Temporality.get -> OpenTelemetry.Metrics.AggregationTemporality

View File

@ -2,14 +2,29 @@
## Unreleased
* Instantiating multiple metric instruments with the same name and also
identical in all other respects - same type, description, and unit - result
in a single metric stream aggregating measurements from all the identical
instruments.
Instantiating multiple metric instruments with the same name but differ in
some respect - different type, description, or unit - will result in a
separate metric stream for each distinct instrument.
([#2916](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2916))
* The `Meter` property on `OpenTelemetry.Metrics.Metric` has been removed.
It now has `MeterName` and `MeterVersion` properties.
([#2916](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2916))
* Added support for implementing custom `ResourceDetector`.
([#2949](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2949/)
[#2897](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2897))
* Perf improvement for Histogram and HistogramSumCount by implementing lock-free
updates.
([2951](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2951))
([2961](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2961))
([#2951](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2951)
[#2961](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2961))
## 1.2.0-rc2
@ -27,7 +42,7 @@ Released 2022-Feb-02
* Performance improvement: when emitting metrics, users are strongly advised to
provide tags with same Key order, to achieve maximum performance.
([#2805](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2805/files))
([#2805](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2805))
## 1.2.0-rc1

View File

@ -360,6 +360,12 @@ namespace OpenTelemetry.Internal
this.WriteEvent(37, providerName);
}
[Event(38, Message = "Duplicate Instrument '{0}', Meter '{1}' encountered. Reason: '{2}'. Suggested action: '{3}'", Level = EventLevel.Warning)]
public void DuplicateMetricInstrument(string instrumentName, string meterName, string reason, string fix)
{
this.WriteEvent(38, instrumentName, meterName, reason, fix);
}
#if DEBUG
public class OpenTelemetryEventListener : EventListener
{

View File

@ -0,0 +1,81 @@
// <copyright file="InstrumentIdentity.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System;
using System.Diagnostics.Metrics;
namespace OpenTelemetry.Metrics
{
internal readonly struct InstrumentIdentity : IEquatable<InstrumentIdentity>
{
private readonly int hashCode;
public InstrumentIdentity(Meter meter, string instrumentName, string unit, string description, Type instrumentType)
{
this.MeterName = meter.Name;
this.MeterVersion = meter.Version ?? string.Empty;
this.InstrumentName = instrumentName;
this.Unit = unit ?? string.Empty;
this.Description = description ?? string.Empty;
this.InstrumentType = instrumentType;
unchecked
{
var hash = 17;
hash = (hash * 31) + this.InstrumentType.GetHashCode();
hash = (hash * 31) + this.MeterName.GetHashCode();
hash = (hash * 31) + this.MeterVersion.GetHashCode();
hash = (hash * 31) + this.InstrumentName.GetHashCode();
hash = this.Unit == null ? hash : (hash * 31) + this.Unit.GetHashCode();
hash = this.Description == null ? hash : (hash * 31) + this.Description.GetHashCode();
this.hashCode = hash;
}
}
public readonly string MeterName { get; }
public readonly string MeterVersion { get; }
public readonly string InstrumentName { get; }
public readonly string Unit { get; }
public readonly string Description { get; }
public readonly Type InstrumentType { get; }
public static bool operator ==(InstrumentIdentity metricIdentity1, InstrumentIdentity metricIdentity2) => metricIdentity1.Equals(metricIdentity2);
public static bool operator !=(InstrumentIdentity metricIdentity1, InstrumentIdentity metricIdentity2) => !metricIdentity1.Equals(metricIdentity2);
public readonly override bool Equals(object obj)
{
return obj is InstrumentIdentity other && this.Equals(other);
}
public bool Equals(InstrumentIdentity other)
{
return this.InstrumentType == other.InstrumentType
&& this.MeterName == other.MeterName
&& this.MeterVersion == other.MeterVersion
&& this.InstrumentName == other.InstrumentName
&& this.Unit == other.Unit
&& this.Description == other.Description;
}
public readonly override int GetHashCode() => this.hashCode;
}
}

View File

@ -30,68 +30,63 @@ namespace OpenTelemetry.Metrics
private readonly AggregatorStore aggStore;
internal Metric(
Instrument instrument,
InstrumentIdentity instrumentIdentity,
AggregationTemporality temporality,
string metricName,
string metricDescription,
int maxMetricPointsPerMetricStream,
double[] histogramBounds = null,
string[] tagKeysInteresting = null)
{
this.Name = metricName;
this.Description = metricDescription ?? string.Empty;
this.Unit = instrument.Unit ?? string.Empty;
this.Meter = instrument.Meter;
this.InstrumentIdentity = instrumentIdentity;
AggregationType aggType;
if (instrument.GetType() == typeof(ObservableCounter<long>)
|| instrument.GetType() == typeof(ObservableCounter<int>)
|| instrument.GetType() == typeof(ObservableCounter<short>)
|| instrument.GetType() == typeof(ObservableCounter<byte>))
if (instrumentIdentity.InstrumentType == typeof(ObservableCounter<long>)
|| instrumentIdentity.InstrumentType == typeof(ObservableCounter<int>)
|| instrumentIdentity.InstrumentType == typeof(ObservableCounter<short>)
|| instrumentIdentity.InstrumentType == typeof(ObservableCounter<byte>))
{
aggType = AggregationType.LongSumIncomingCumulative;
this.MetricType = MetricType.LongSum;
}
else if (instrument.GetType() == typeof(Counter<long>)
|| instrument.GetType() == typeof(Counter<int>)
|| instrument.GetType() == typeof(Counter<short>)
|| instrument.GetType() == typeof(Counter<byte>))
else if (instrumentIdentity.InstrumentType == typeof(Counter<long>)
|| instrumentIdentity.InstrumentType == typeof(Counter<int>)
|| instrumentIdentity.InstrumentType == typeof(Counter<short>)
|| instrumentIdentity.InstrumentType == typeof(Counter<byte>))
{
aggType = AggregationType.LongSumIncomingDelta;
this.MetricType = MetricType.LongSum;
}
else if (instrument.GetType() == typeof(Counter<double>)
|| instrument.GetType() == typeof(Counter<float>))
else if (instrumentIdentity.InstrumentType == typeof(Counter<double>)
|| instrumentIdentity.InstrumentType == typeof(Counter<float>))
{
aggType = AggregationType.DoubleSumIncomingDelta;
this.MetricType = MetricType.DoubleSum;
}
else if (instrument.GetType() == typeof(ObservableCounter<double>)
|| instrument.GetType() == typeof(ObservableCounter<float>))
else if (instrumentIdentity.InstrumentType == typeof(ObservableCounter<double>)
|| instrumentIdentity.InstrumentType == typeof(ObservableCounter<float>))
{
aggType = AggregationType.DoubleSumIncomingCumulative;
this.MetricType = MetricType.DoubleSum;
}
else if (instrument.GetType() == typeof(ObservableGauge<double>)
|| instrument.GetType() == typeof(ObservableGauge<float>))
else if (instrumentIdentity.InstrumentType == typeof(ObservableGauge<double>)
|| instrumentIdentity.InstrumentType == typeof(ObservableGauge<float>))
{
aggType = AggregationType.DoubleGauge;
this.MetricType = MetricType.DoubleGauge;
}
else if (instrument.GetType() == typeof(ObservableGauge<long>)
|| instrument.GetType() == typeof(ObservableGauge<int>)
|| instrument.GetType() == typeof(ObservableGauge<short>)
|| instrument.GetType() == typeof(ObservableGauge<byte>))
else if (instrumentIdentity.InstrumentType == typeof(ObservableGauge<long>)
|| instrumentIdentity.InstrumentType == typeof(ObservableGauge<int>)
|| instrumentIdentity.InstrumentType == typeof(ObservableGauge<short>)
|| instrumentIdentity.InstrumentType == typeof(ObservableGauge<byte>))
{
aggType = AggregationType.LongGauge;
this.MetricType = MetricType.LongGauge;
}
else if (instrument.GetType() == typeof(Histogram<long>)
|| instrument.GetType() == typeof(Histogram<int>)
|| instrument.GetType() == typeof(Histogram<short>)
|| instrument.GetType() == typeof(Histogram<byte>)
|| instrument.GetType() == typeof(Histogram<float>)
|| instrument.GetType() == typeof(Histogram<double>))
else if (instrumentIdentity.InstrumentType == typeof(Histogram<long>)
|| instrumentIdentity.InstrumentType == typeof(Histogram<int>)
|| instrumentIdentity.InstrumentType == typeof(Histogram<short>)
|| instrumentIdentity.InstrumentType == typeof(Histogram<byte>)
|| instrumentIdentity.InstrumentType == typeof(Histogram<float>)
|| instrumentIdentity.InstrumentType == typeof(Histogram<double>))
{
this.MetricType = MetricType.Histogram;
@ -107,10 +102,10 @@ namespace OpenTelemetry.Metrics
}
else
{
throw new NotSupportedException($"Unsupported Instrument Type: {instrument.GetType().FullName}");
throw new NotSupportedException($"Unsupported Instrument Type: {instrumentIdentity.InstrumentType.FullName}");
}
this.aggStore = new AggregatorStore(metricName, aggType, temporality, maxMetricPointsPerMetricStream, histogramBounds ?? DefaultHistogramBounds, tagKeysInteresting);
this.aggStore = new AggregatorStore(instrumentIdentity.InstrumentName, aggType, temporality, maxMetricPointsPerMetricStream, histogramBounds ?? DefaultHistogramBounds, tagKeysInteresting);
this.Temporality = temporality;
this.InstrumentDisposed = false;
}
@ -119,13 +114,17 @@ namespace OpenTelemetry.Metrics
public AggregationTemporality Temporality { get; private set; }
public string Name { get; private set; }
public string Name => this.InstrumentIdentity.InstrumentName;
public string Description { get; private set; }
public string Description => this.InstrumentIdentity.Description;
public string Unit { get; private set; }
public string Unit => this.InstrumentIdentity.Unit;
public Meter Meter { get; private set; }
public string MeterName => this.InstrumentIdentity.MeterName;
public string MeterVersion => this.InstrumentIdentity.MeterVersion;
internal InstrumentIdentity InstrumentIdentity { get; private set; }
internal bool InstrumentDisposed { get; set; }

View File

@ -15,6 +15,7 @@
// </copyright>
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.Metrics;
using OpenTelemetry.Internal;
@ -27,6 +28,7 @@ namespace OpenTelemetry.Metrics
public abstract partial class MetricReader
{
private readonly HashSet<string> metricStreamNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private readonly ConcurrentDictionary<InstrumentIdentity, Metric> instrumentIdentityToMetric = new ConcurrentDictionary<InstrumentIdentity, Metric>();
private readonly object instrumentCreationLock = new object();
private int maxMetricStreams;
private int maxMetricPointsPerMetricStream;
@ -37,14 +39,24 @@ namespace OpenTelemetry.Metrics
internal Metric AddMetricWithNoViews(Instrument instrument)
{
var meterName = instrument.Meter.Name;
var meterVersion = instrument.Meter.Version;
var metricName = instrument.Name;
var metricStreamName = $"{meterName}.{metricName}";
var metricStreamName = $"{meterName}.{meterVersion}.{metricName}";
var instrumentIdentity = new InstrumentIdentity(instrument.Meter, metricName, instrument.Unit, instrument.Description, instrument.GetType());
lock (this.instrumentCreationLock)
{
if (this.instrumentIdentityToMetric.TryGetValue(instrumentIdentity, out var existingMetric))
{
return existingMetric;
}
if (this.metricStreamNames.Contains(metricStreamName))
{
OpenTelemetrySdkEventSource.Log.MetricInstrumentIgnored(metricName, instrument.Meter.Name, "Metric name conflicting with existing name.", "Either change the name of the instrument or change name using View.");
return null;
OpenTelemetrySdkEventSource.Log.DuplicateMetricInstrument(
metricName,
meterName,
"Metric instrument has the same name as an existing one but differs by description, unit, or instrument type. Measurements from this instrument will still be exported but may result in conflicts.",
"Either change the name of the instrument or use MeterProviderBuilder.AddView to resolve the conflict.");
}
var index = ++this.metricIndex;
@ -55,7 +67,8 @@ namespace OpenTelemetry.Metrics
}
else
{
var metric = new Metric(instrument, this.Temporality, metricName, instrument.Description, this.maxMetricPointsPerMetricStream);
var metric = new Metric(instrumentIdentity, this.Temporality, this.maxMetricPointsPerMetricStream);
this.instrumentIdentityToMetric[instrumentIdentity] = metric;
this.metrics[index] = metric;
this.metricStreamNames.Add(metricStreamName);
return metric;
@ -88,8 +101,11 @@ namespace OpenTelemetry.Metrics
{
var metricStreamConfig = metricStreamConfigs[i];
var meterName = instrument.Meter.Name;
var meterVersion = instrument.Meter.Version;
var metricName = metricStreamConfig?.Name ?? instrument.Name;
var metricStreamName = $"{meterName}.{metricName}";
var metricStreamName = $"{meterName}.{meterVersion}.{metricName}";
var metricDescription = metricStreamConfig?.Description ?? instrument.Description;
var instrumentIdentity = new InstrumentIdentity(instrument.Meter, metricName, instrument.Unit, metricDescription, instrument.GetType());
if (!MeterProviderBuilderSdk.IsValidInstrumentName(metricName))
{
@ -102,10 +118,19 @@ namespace OpenTelemetry.Metrics
continue;
}
if (this.instrumentIdentityToMetric.TryGetValue(instrumentIdentity, out var existingMetric))
{
metrics.Add(existingMetric);
continue;
}
if (this.metricStreamNames.Contains(metricStreamName))
{
OpenTelemetrySdkEventSource.Log.MetricInstrumentIgnored(metricName, instrument.Meter.Name, "Metric name conflicting with existing name.", "Either change the name of the instrument or change name using MeterProviderBuilder.AddView.");
continue;
OpenTelemetrySdkEventSource.Log.DuplicateMetricInstrument(
metricName,
meterName,
"Metric instrument has the same name as an existing one but differs by description, unit, or instrument type. Measurements from this instrument will still be exported but may result in conflicts.",
"Either change the name of the instrument or use MeterProviderBuilder.AddView to resolve the conflict.");
}
if (metricStreamConfig?.Aggregation == Aggregation.Drop)
@ -122,12 +147,12 @@ namespace OpenTelemetry.Metrics
else
{
Metric metric;
var metricDescription = metricStreamConfig?.Description ?? instrument.Description;
string[] tagKeysInteresting = metricStreamConfig?.TagKeys;
double[] histogramBucketBounds = (metricStreamConfig is ExplicitBucketHistogramConfiguration histogramConfig
&& histogramConfig.Boundaries != null) ? histogramConfig.Boundaries : null;
metric = new Metric(instrument, this.Temporality, metricName, metricDescription, this.maxMetricPointsPerMetricStream, histogramBucketBounds, tagKeysInteresting);
metric = new Metric(instrumentIdentity, this.Temporality, this.maxMetricPointsPerMetricStream, histogramBucketBounds, tagKeysInteresting);
this.instrumentIdentityToMetric[instrumentIdentity] = metric;
this.metrics[index] = metric;
metrics.Add(metric);
this.metricStreamNames.Add(metricStreamName);
@ -215,6 +240,7 @@ namespace OpenTelemetry.Metrics
if (metric.InstrumentDisposed)
{
metricPointSize = metric.Snapshot();
this.instrumentIdentityToMetric.TryRemove(metric.InstrumentIdentity, out var _);
this.metrics[i] = null;
}
else

View File

@ -144,43 +144,29 @@ namespace OpenTelemetry.Metrics.Tests
Assert.Equal(description ?? string.Empty, metric.Description);
}
[Theory]
[InlineData(AggregationTemporality.Cumulative, true)]
[InlineData(AggregationTemporality.Cumulative, false)]
[InlineData(AggregationTemporality.Delta, true)]
[InlineData(AggregationTemporality.Delta, false)]
public void DuplicateInstrumentNamesFromSameMeterAreNotAllowed(AggregationTemporality temporality, bool hasView)
[Fact]
public void DuplicateInstrumentRegistration_NoViews_IdenticalInstruments()
{
var exportedItems = new List<Metric>();
using var meter = new Meter($"{Utils.GetCurrentMethodName()}.{temporality}");
using var meter = new Meter($"{Utils.GetCurrentMethodName()}");
var meterProviderBuilder = Sdk.CreateMeterProviderBuilder()
.AddMeter(meter.Name)
.AddInMemoryExporter(exportedItems, metricReaderOptions =>
{
metricReaderOptions.Temporality = temporality;
});
if (hasView)
{
meterProviderBuilder.AddView("name1", new MetricStreamConfiguration() { Description = "description" });
}
.AddInMemoryExporter(exportedItems);
using var meterProvider = meterProviderBuilder.Build();
var counterLong = meter.CreateCounter<long>("name1");
var anotherCounterSameName = meter.CreateCounter<long>("name1");
var instrument = meter.CreateCounter<long>("instrumentName", "instrumentUnit", "instrumentDescription");
var duplicateInstrument = meter.CreateCounter<long>("instrumentName", "instrumentUnit", "instrumentDescription");
counterLong.Add(10);
anotherCounterSameName.Add(20);
counterLong.Add(10);
anotherCounterSameName.Add(20);
instrument.Add(10);
duplicateInstrument.Add(20);
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
Assert.Single(exportedItems);
var metric = exportedItems[0];
Assert.Equal("name1", metric.Name);
Assert.Equal("instrumentName", metric.Name);
List<MetricPoint> metricPoints = new List<MetricPoint>();
foreach (ref readonly var mp in metric.GetMetricPoints())
{
@ -189,7 +175,325 @@ namespace OpenTelemetry.Metrics.Tests
Assert.Single(metricPoints);
var metricPoint1 = metricPoints[0];
Assert.Equal(20, metricPoint1.GetSumLong());
Assert.Equal(30, metricPoint1.GetSumLong());
}
[Fact]
public void DuplicateInstrumentRegistration_NoViews_DuplicateInstruments_DifferentDescription()
{
var exportedItems = new List<Metric>();
using var meter = new Meter($"{Utils.GetCurrentMethodName()}");
var meterProviderBuilder = Sdk.CreateMeterProviderBuilder()
.AddMeter(meter.Name)
.AddInMemoryExporter(exportedItems);
using var meterProvider = meterProviderBuilder.Build();
var instrument = meter.CreateCounter<long>("instrumentName", "instrumentUnit", "instrumentDescription1");
var duplicateInstrument = meter.CreateCounter<long>("instrumentName", "instrumentUnit", "instrumentDescription2");
instrument.Add(10);
duplicateInstrument.Add(20);
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
Assert.Equal(2, exportedItems.Count);
var metric1 = exportedItems[0];
var metric2 = exportedItems[1];
Assert.Equal("instrumentDescription1", metric1.Description);
Assert.Equal("instrumentDescription2", metric2.Description);
List<MetricPoint> metric1MetricPoints = new List<MetricPoint>();
foreach (ref readonly var mp in metric1.GetMetricPoints())
{
metric1MetricPoints.Add(mp);
}
Assert.Single(metric1MetricPoints);
var metricPoint1 = metric1MetricPoints[0];
Assert.Equal(10, metricPoint1.GetSumLong());
List<MetricPoint> metric2MetricPoints = new List<MetricPoint>();
foreach (ref readonly var mp in metric2.GetMetricPoints())
{
metric2MetricPoints.Add(mp);
}
Assert.Single(metric2MetricPoints);
var metricPoint2 = metric2MetricPoints[0];
Assert.Equal(20, metricPoint2.GetSumLong());
}
[Fact]
public void DuplicateInstrumentRegistration_NoViews_DuplicateInstruments_DifferentUnit()
{
var exportedItems = new List<Metric>();
using var meter = new Meter($"{Utils.GetCurrentMethodName()}");
var meterProviderBuilder = Sdk.CreateMeterProviderBuilder()
.AddMeter(meter.Name)
.AddInMemoryExporter(exportedItems);
using var meterProvider = meterProviderBuilder.Build();
var instrument = meter.CreateCounter<long>("instrumentName", "instrumentUnit1", "instrumentDescription");
var duplicateInstrument = meter.CreateCounter<long>("instrumentName", "instrumentUnit2", "instrumentDescription");
instrument.Add(10);
duplicateInstrument.Add(20);
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
Assert.Equal(2, exportedItems.Count);
var metric1 = exportedItems[0];
var metric2 = exportedItems[1];
Assert.Equal("instrumentUnit1", metric1.Unit);
Assert.Equal("instrumentUnit2", metric2.Unit);
List<MetricPoint> metric1MetricPoints = new List<MetricPoint>();
foreach (ref readonly var mp in metric1.GetMetricPoints())
{
metric1MetricPoints.Add(mp);
}
Assert.Single(metric1MetricPoints);
var metricPoint1 = metric1MetricPoints[0];
Assert.Equal(10, metricPoint1.GetSumLong());
List<MetricPoint> metric2MetricPoints = new List<MetricPoint>();
foreach (ref readonly var mp in metric2.GetMetricPoints())
{
metric2MetricPoints.Add(mp);
}
Assert.Single(metric2MetricPoints);
var metricPoint2 = metric2MetricPoints[0];
Assert.Equal(20, metricPoint2.GetSumLong());
}
[Fact]
public void DuplicateInstrumentRegistration_NoViews_DuplicateInstruments_DifferentDataType()
{
var exportedItems = new List<Metric>();
using var meter = new Meter($"{Utils.GetCurrentMethodName()}");
var meterProviderBuilder = Sdk.CreateMeterProviderBuilder()
.AddMeter(meter.Name)
.AddInMemoryExporter(exportedItems);
using var meterProvider = meterProviderBuilder.Build();
var instrument = meter.CreateCounter<long>("instrumentName", "instrumentUnit", "instrumentDescription");
var duplicateInstrument = meter.CreateCounter<double>("instrumentName", "instrumentUnit", "instrumentDescription");
instrument.Add(10);
duplicateInstrument.Add(20);
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
Assert.Equal(2, exportedItems.Count);
var metric1 = exportedItems[0];
var metric2 = exportedItems[1];
List<MetricPoint> metric1MetricPoints = new List<MetricPoint>();
foreach (ref readonly var mp in metric1.GetMetricPoints())
{
metric1MetricPoints.Add(mp);
}
Assert.Single(metric1MetricPoints);
var metricPoint1 = metric1MetricPoints[0];
Assert.Equal(10, metricPoint1.GetSumLong());
List<MetricPoint> metric2MetricPoints = new List<MetricPoint>();
foreach (ref readonly var mp in metric2.GetMetricPoints())
{
metric2MetricPoints.Add(mp);
}
Assert.Single(metric2MetricPoints);
var metricPoint2 = metric2MetricPoints[0];
Assert.Equal(20D, metricPoint2.GetSumDouble());
}
[Fact]
public void DuplicateInstrumentRegistration_NoViews_DuplicateInstruments_DifferentInstrumentType()
{
var exportedItems = new List<Metric>();
using var meter = new Meter($"{Utils.GetCurrentMethodName()}");
var meterProviderBuilder = Sdk.CreateMeterProviderBuilder()
.AddMeter(meter.Name)
.AddInMemoryExporter(exportedItems);
using var meterProvider = meterProviderBuilder.Build();
var instrument = meter.CreateCounter<long>("instrumentName", "instrumentUnit", "instrumentDescription");
var duplicateInstrument = meter.CreateHistogram<long>("instrumentName", "instrumentUnit", "instrumentDescription");
instrument.Add(10);
duplicateInstrument.Record(20);
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
Assert.Equal(2, exportedItems.Count);
var metric1 = exportedItems[0];
var metric2 = exportedItems[1];
List<MetricPoint> metric1MetricPoints = new List<MetricPoint>();
foreach (ref readonly var mp in metric1.GetMetricPoints())
{
metric1MetricPoints.Add(mp);
}
Assert.Single(metric1MetricPoints);
var metricPoint1 = metric1MetricPoints[0];
Assert.Equal(10, metricPoint1.GetSumLong());
List<MetricPoint> metric2MetricPoints = new List<MetricPoint>();
foreach (ref readonly var mp in metric2.GetMetricPoints())
{
metric2MetricPoints.Add(mp);
}
Assert.Single(metric2MetricPoints);
var metricPoint2 = metric2MetricPoints[0];
Assert.Equal(1, metricPoint2.GetHistogramCount());
Assert.Equal(20D, metricPoint2.GetHistogramSum());
}
[Fact]
public void DuplicateInstrumentRegistration_WithViews_DuplicateInstruments_DifferentDescription()
{
var exportedItems = new List<Metric>();
using var meter = new Meter($"{Utils.GetCurrentMethodName()}");
var meterProviderBuilder = Sdk.CreateMeterProviderBuilder()
.AddMeter(meter.Name)
.AddView("instrumentName", new MetricStreamConfiguration { Description = "newDescription1" })
.AddView("instrumentName", new MetricStreamConfiguration { Description = "newDescription2" })
.AddInMemoryExporter(exportedItems);
using var meterProvider = meterProviderBuilder.Build();
var instrument = meter.CreateCounter<long>("instrumentName", "instrumentUnit", "instrumentDescription");
instrument.Add(10);
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
Assert.Equal(2, exportedItems.Count);
var metric1 = exportedItems[0];
var metric2 = exportedItems[1];
Assert.Equal("newDescription1", metric1.Description);
Assert.Equal("newDescription2", metric2.Description);
List<MetricPoint> metric1MetricPoints = new List<MetricPoint>();
foreach (ref readonly var mp in metric1.GetMetricPoints())
{
metric1MetricPoints.Add(mp);
}
Assert.Single(metric1MetricPoints);
var metricPoint1 = metric1MetricPoints[0];
Assert.Equal(10, metricPoint1.GetSumLong());
List<MetricPoint> metric2MetricPoints = new List<MetricPoint>();
foreach (ref readonly var mp in metric2.GetMetricPoints())
{
metric2MetricPoints.Add(mp);
}
Assert.Single(metric2MetricPoints);
var metricPoint2 = metric2MetricPoints[0];
Assert.Equal(10, metricPoint2.GetSumLong());
}
[Fact]
public void DuplicateInstrumentRegistration_WithViews_TwoInstruments_ThreeStreams()
{
var exportedItems = new List<Metric>();
using var meter = new Meter($"{Utils.GetCurrentMethodName()}");
var meterProviderBuilder = Sdk.CreateMeterProviderBuilder()
.AddMeter(meter.Name)
.AddView((instrument) =>
{
return new MetricStreamConfiguration { Name = "MetricStreamA", Description = "description" };
})
.AddView((instrument) =>
{
return instrument.Description == "description1"
? new MetricStreamConfiguration { Name = "MetricStreamB" }
: new MetricStreamConfiguration { Name = "MetricStreamC" };
})
.AddInMemoryExporter(exportedItems);
using var meterProvider = meterProviderBuilder.Build();
var instrument1 = meter.CreateCounter<long>("name", "unit", "description1");
var instrument2 = meter.CreateCounter<long>("name", "unit", "description2");
instrument1.Add(10);
instrument2.Add(10);
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
Assert.Equal(3, exportedItems.Count);
var metricA = exportedItems[0];
var metricB = exportedItems[1];
var metricC = exportedItems[2];
Assert.Equal("MetricStreamA", metricA.Name);
Assert.Equal(20, GetAggregatedValue(metricA));
Assert.Equal("MetricStreamB", metricB.Name);
Assert.Equal(10, GetAggregatedValue(metricB));
Assert.Equal("MetricStreamC", metricC.Name);
Assert.Equal(10, GetAggregatedValue(metricC));
long GetAggregatedValue(Metric metric)
{
var metricPoints = new List<MetricPoint>();
foreach (ref readonly var mp in metric.GetMetricPoints())
{
metricPoints.Add(mp);
}
Assert.Single(metricPoints);
return metricPoints[0].GetSumLong();
}
}
[Fact]
public void DuplicateInstrumentNamesFromDifferentMetersWithSameNameDifferentVersion()
{
var exportedItems = new List<Metric>();
using var meter1 = new Meter($"{Utils.GetCurrentMethodName()}", "1.0");
using var meter2 = new Meter($"{Utils.GetCurrentMethodName()}", "2.0");
var meterProviderBuilder = Sdk.CreateMeterProviderBuilder()
.AddMeter(meter1.Name)
.AddMeter(meter2.Name)
.AddInMemoryExporter(exportedItems);
using var meterProvider = meterProviderBuilder.Build();
// Expecting one metric stream.
var counterLong = meter1.CreateCounter<long>("name1");
counterLong.Add(10);
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
Assert.Single(exportedItems);
// Expeecting another metric stream since the meter differs by version
var anotherCounterSameNameDiffMeter = meter2.CreateCounter<long>("name1");
anotherCounterSameNameDiffMeter.Add(10);
counterLong.Add(10);
exportedItems.Clear();
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
Assert.Equal(2, exportedItems.Count);
}
[Theory]