Add support for instrumentation scope attributes (Meter tags) (#5089)
This commit is contained in:
parent
e6f97bc92f
commit
97442efb5d
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
OpenTelemetry.Metrics.Metric.MeterTags.get -> System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<string!, object?>>?
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue