Add support for instrumentation scope attributes (Meter tags) (#5089)

This commit is contained in:
Cijo Thomas 2023-11-28 17:31:43 -08:00 committed by GitHub
parent e6f97bc92f
commit 97442efb5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 157 additions and 7 deletions

View File

@ -27,7 +27,14 @@ internal class TestMetrics
{
internal static object Run(MetricsOptions options)
{
using var meter = new Meter("TestMeter");
var meterVersion = "1.0";
var meterTags = new List<KeyValuePair<string, object>>
{
new(
"MeterTagKey",
"MeterTagValue"),
};
using var meter = new Meter("TestMeter", meterVersion, meterTags);
var providerBuilder = Sdk.CreateMeterProviderBuilder()
.ConfigureResource(r => r.AddService("myservice"))

View File

@ -33,10 +33,10 @@ internal static class TestOtlpExporter
* launch the OpenTelemetry Collector with an OTLP receiver, by running:
*
* - On Unix based systems use:
* docker run --rm -it -p 4317:4317 -p 4318:4318 -v $(pwd):/cfg otel/opentelemetry-collector:0.48.0 --config=/cfg/otlp-collector-example/config.yaml
* docker run --rm -it -p 4317:4317 -p 4318:4318 -v $(pwd):/cfg otel/opentelemetry-collector:latest --config=/cfg/otlp-collector-example/config.yaml
*
* - On Windows use:
* docker run --rm -it -p 4317:4317 -p 4318:4318 -v "%cd%":/cfg otel/opentelemetry-collector:0.48.0 --config=/cfg/otlp-collector-example/config.yaml
* docker run --rm -it -p 4317:4317 -p 4318:4318 -v "%cd%":/cfg otel/opentelemetry-collector:latest --config=/cfg/otlp-collector-example/config.yaml
*
* Open another terminal window at the examples/Console/ directory and
* launch the OTLP example by running:

View File

@ -2,6 +2,12 @@
## Unreleased
* Add support for Instrumentation Scope Attributes (i.e [Meter
Tags](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.meter.tags)),
fixing issue
[#4563](https://github.com/open-telemetry/opentelemetry-dotnet/issues/4563).
([#5089](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5089))
## 1.7.0-alpha.1
Released 2023-Oct-16

View File

@ -50,8 +50,8 @@ public class ConsoleMetricExporter : ConsoleExporter<Metric>
foreach (var metric in batch)
{
var msg = new StringBuilder($"\nExport ");
msg.Append(metric.Name);
var msg = new StringBuilder($"\n");
msg.Append($"Metric Name: {metric.Name}");
if (metric.Description != string.Empty)
{
msg.Append(", ");
@ -75,6 +75,15 @@ public class ConsoleMetricExporter : ConsoleExporter<Metric>
this.WriteLine(msg.ToString());
foreach (var meterTag in metric.MeterTags)
{
this.WriteLine("\tMeter Tags:");
if (ConsoleTagTransformer.Instance.TryTransformTag(meterTag, out var result))
{
this.WriteLine($"\t\t{result}");
}
}
foreach (ref readonly var metricPoint in metric.GetMetricPoints())
{
string valueDisplay = string.Empty;

View File

@ -15,6 +15,12 @@
accepts a `name` parameter to support named options.
([#4916](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4916))
* Add support for Instrumentation Scope Attributes (i.e [Meter
Tags](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.meter.tags)),
fixing issue
[#4563](https://github.com/open-telemetry/opentelemetry-dotnet/issues/4563).
([#5089](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5089))
## 1.7.0-alpha.1
Released 2023-Oct-16

View File

@ -58,7 +58,7 @@ internal static class MetricItemExtensions
var meterName = metric.MeterName;
if (!metricsByLibrary.TryGetValue(meterName, out var scopeMetrics))
{
scopeMetrics = GetMetricListFromPool(meterName, metric.MeterVersion);
scopeMetrics = GetMetricListFromPool(meterName, metric.MeterVersion, metric.MeterTags);
metricsByLibrary.Add(meterName, scopeMetrics);
resourceMetrics.ScopeMetrics.Add(scopeMetrics);
@ -85,7 +85,7 @@ internal static class MetricItemExtensions
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static OtlpMetrics.ScopeMetrics GetMetricListFromPool(string name, string version)
internal static OtlpMetrics.ScopeMetrics GetMetricListFromPool(string name, string version, IEnumerable<KeyValuePair<string, object>> meterTags)
{
if (!MetricListPool.TryTake(out var metrics))
{
@ -97,11 +97,21 @@ internal static class MetricItemExtensions
Version = version ?? string.Empty, // NRE throw by proto
},
};
if (meterTags != null)
{
AddAttributes(meterTags, metrics.Scope.Attributes);
}
}
else
{
metrics.Scope.Name = name;
metrics.Scope.Version = version ?? string.Empty;
if (meterTags != null)
{
metrics.Scope.Attributes.Clear();
AddAttributes(meterTags, metrics.Scope.Attributes);
}
}
return metrics;
@ -368,6 +378,17 @@ internal static class MetricItemExtensions
}
}
private static void AddAttributes(IEnumerable<KeyValuePair<string, object>> meterTags, RepeatedField<OtlpCommon.KeyValue> attributes)
{
foreach (var tag in meterTags)
{
if (OtlpKeyValueTransformer.Instance.TryTransformTag(tag, out var result))
{
attributes.Add(result);
}
}
}
/*
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static OtlpMetrics.Exemplar ToOtlpExemplar(this IExemplar exemplar)

View File

@ -0,0 +1 @@
OpenTelemetry.Metrics.Metric.MeterTags.get -> System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string!, object?>>?

View File

@ -58,6 +58,12 @@
implementationFactory)`.
([#4916](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4916))
* Add support for Instrumentation Scope Attributes (i.e [Meter
Tags](https://learn.microsoft.com/dotnet/api/system.diagnostics.metrics.meter.tags)),
fixing issue
[#4563](https://github.com/open-telemetry/opentelemetry-dotnet/issues/4563).
([#5089](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5089))
## 1.7.0-alpha.1
Released 2023-Oct-16

View File

@ -207,6 +207,11 @@ public sealed class Metric
/// </summary>
public string MeterVersion => this.InstrumentIdentity.MeterVersion;
/// <summary>
/// Gets the attributes (tags) for the metric stream.
/// </summary>
public IEnumerable<KeyValuePair<string, object?>>? MeterTags => this.InstrumentIdentity.MeterTags;
/// <summary>
/// Gets the <see cref="MetricStreamIdentity"/> for the metric stream.
/// </summary>

View File

@ -27,6 +27,7 @@ internal readonly struct MetricStreamIdentity : IEquatable<MetricStreamIdentity>
{
this.MeterName = instrument.Meter.Name;
this.MeterVersion = instrument.Meter.Version ?? string.Empty;
this.MeterTags = instrument.Meter.Tags;
this.InstrumentName = metricStreamConfiguration?.Name ?? instrument.Name;
this.Unit = instrument.Unit ?? string.Empty;
this.Description = metricStreamConfiguration?.Description ?? instrument.Description ?? string.Empty;
@ -75,6 +76,8 @@ internal readonly struct MetricStreamIdentity : IEquatable<MetricStreamIdentity>
hash = (hash * 31) + this.InstrumentType.GetHashCode();
hash = (hash * 31) + this.MeterName.GetHashCode();
hash = (hash * 31) + this.MeterVersion.GetHashCode();
// MeterTags is not part of identity, so not included here.
hash = (hash * 31) + this.InstrumentName.GetHashCode();
hash = (hash * 31) + this.HistogramRecordMinMax.GetHashCode();
hash = (hash * 31) + this.ExponentialHistogramMaxSize.GetHashCode();
@ -101,6 +104,8 @@ internal readonly struct MetricStreamIdentity : IEquatable<MetricStreamIdentity>
public string MeterVersion { get; }
public IEnumerable<KeyValuePair<string, object?>>? MeterTags { get; }
public string InstrumentName { get; }
public string Unit { get; }

View File

@ -178,6 +178,90 @@ public abstract class MetricApiTestsBase : MetricTestsBase
Assert.Equal(description ?? string.Empty, metric.Description);
}
[Fact]
public void MetricInstrumentationScopeIsExportedCorrectly()
{
var exportedItems = new List<Metric>();
var meterName = Utils.GetCurrentMethodName();
var meterVersion = "1.0";
var meterTags = new List<KeyValuePair<string, object>>
{
new(
"MeterTagKey",
"MeterTagValue"),
};
using var meter = new Meter($"{meterName}", meterVersion, meterTags);
using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meter.Name)
.AddInMemoryExporter(exportedItems));
var counter = meter.CreateCounter<long>("name1");
counter.Add(10);
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
Assert.Single(exportedItems);
var metric = exportedItems[0];
Assert.Equal(meterName, metric.MeterName);
Assert.Equal(meterVersion, metric.MeterVersion);
Assert.Single(metric.MeterTags.Where(kvp => kvp.Key == meterTags[0].Key && kvp.Value == meterTags[0].Value));
}
[Fact]
public void MetricInstrumentationScopeAttributesAreNotTreatedAsIdentifyingProperty()
{
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#get-a-meter
// Meters are identified by name, version, and schema_url fields
// and not with tags.
var exportedItems = new List<Metric>();
var meterName = "MyMeter";
var meterVersion = "1.0";
var meterTags1 = new List<KeyValuePair<string, object>>
{
new(
"Key1",
"Value1"),
};
var meterTags2 = new List<KeyValuePair<string, object>>
{
new(
"Key2",
"Value2"),
};
using var meter1 = new Meter(meterName, meterVersion, meterTags1);
using var meter2 = new Meter(meterName, meterVersion, meterTags2);
using var container = this.BuildMeterProvider(out var meterProvider, builder => builder
.AddMeter(meterName)
.AddInMemoryExporter(exportedItems));
var counter1 = meter1.CreateCounter<long>("my-counter");
counter1.Add(10);
var counter2 = meter2.CreateCounter<long>("my-counter");
counter2.Add(15);
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
// The instruments differ only in the Meter.Tags, which is not an identifying property.
// The first instrument's Meter.Tags is exported.
// It is considered a user-error to create Meters with same name,version but with
// different tags. TODO: See if we can emit an internal log about this.
Assert.Single(exportedItems);
var metric = exportedItems[0];
Assert.Equal(meterName, metric.MeterName);
Assert.Equal(meterVersion, metric.MeterVersion);
Assert.Single(metric.MeterTags.Where(kvp => kvp.Key == meterTags1[0].Key && kvp.Value == meterTags1[0].Value));
Assert.Empty(metric.MeterTags.Where(kvp => kvp.Key == meterTags2[0].Key && kvp.Value == meterTags2[0].Value));
List<MetricPoint> metricPoints = new List<MetricPoint>();
foreach (ref readonly var mp in metric.GetMetricPoints())
{
metricPoints.Add(mp);
}
Assert.Single(metricPoints);
var metricPoint1 = metricPoints[0];
Assert.Equal(25, metricPoint1.GetSumLong());
}
[Fact]
public void DuplicateInstrumentRegistration_NoViews_IdenticalInstruments()
{