Support View part1 - rename an instrument (#2421)
This commit is contained in:
parent
d716bb17cd
commit
a6e1e98ec2
|
|
@ -60,7 +60,7 @@ public class Program
|
|||
// turn off the above default. i.e any
|
||||
// instrument which does not match any views
|
||||
// gets dropped.
|
||||
// .AddView(instrumentName: "*", new DropAggregationConfig())
|
||||
// .AddView(instrumentName: "*", new MetricStreamConfiguration() { Aggregation = Aggregation.Drop })
|
||||
.AddConsoleExporter()
|
||||
.Build();
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Text.RegularExpressions;
|
||||
using OpenTelemetry.Resources;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
|
|
@ -88,19 +89,26 @@ namespace OpenTelemetry.Metrics
|
|||
|
||||
internal MeterProviderBuilder AddView(string instrumentName, string name)
|
||||
{
|
||||
// TODO: Actually implement view.
|
||||
return this;
|
||||
return this.AddView(instrumentName, new MetricStreamConfiguration() { Name = name });
|
||||
}
|
||||
|
||||
internal MeterProviderBuilder AddView(string instrumentName, MetricStreamConfiguration aggregationConfig)
|
||||
internal MeterProviderBuilder AddView(string instrumentName, MetricStreamConfiguration metricStreamConfiguration)
|
||||
{
|
||||
// TODO: Actually implement view.
|
||||
return this;
|
||||
if (instrumentName.IndexOf('*') != -1)
|
||||
{
|
||||
var pattern = '^' + Regex.Escape(instrumentName).Replace("\\*", ".*");
|
||||
var regex = new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
return this.AddView(instrument => regex.IsMatch(instrument.Name) ? metricStreamConfiguration : null);
|
||||
}
|
||||
else
|
||||
{
|
||||
return this.AddView(instrument => instrument.Name.Equals(instrumentName, StringComparison.OrdinalIgnoreCase) ? metricStreamConfiguration : null);
|
||||
}
|
||||
}
|
||||
|
||||
internal MeterProviderBuilder AddView(Func<Instrument, MetricStreamConfiguration> viewConfig)
|
||||
{
|
||||
// TODO: Actually implement view.
|
||||
this.viewConfigs.Add(viewConfig);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using OpenTelemetry.Resources;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
|
|
@ -92,39 +91,122 @@ namespace OpenTelemetry.Metrics
|
|||
meterSourcesToSubscribe[name] = true;
|
||||
}
|
||||
|
||||
this.listener = new MeterListener()
|
||||
this.listener = new MeterListener();
|
||||
var viewConfigCount = this.viewConfigs.Count;
|
||||
if (viewConfigCount > 0)
|
||||
{
|
||||
InstrumentPublished = (instrument, listener) =>
|
||||
this.listener.InstrumentPublished = (instrument, listener) =>
|
||||
{
|
||||
if (meterSourcesToSubscribe.ContainsKey(instrument.Meter.Name))
|
||||
{
|
||||
// Creating list with initial capacity as the maximum
|
||||
// possible size, to avoid any array resize/copy internally.
|
||||
// There may be excess space wasted, but it'll eligible for
|
||||
// GC right after this method.
|
||||
var metricStreamConfigs = new List<MetricStreamConfiguration>(viewConfigCount);
|
||||
foreach (var viewConfig in this.viewConfigs)
|
||||
{
|
||||
var metricStreamConfig = viewConfig(instrument);
|
||||
if (metricStreamConfig != null)
|
||||
{
|
||||
metricStreamConfigs.Add(metricStreamConfig);
|
||||
}
|
||||
}
|
||||
|
||||
if (metricStreamConfigs.Count == 0)
|
||||
{
|
||||
// No views matched. Add null
|
||||
// which will apply defaults.
|
||||
// Users can turn off this default
|
||||
// by adding a view like below as the last view.
|
||||
// .AddView(instrumentName: "*", new MetricStreamConfiguration() { Aggregation = Aggregation.Drop })
|
||||
metricStreamConfigs.Add(null);
|
||||
}
|
||||
|
||||
var maxCountMetricsToBeCreated = metricStreamConfigs.Count;
|
||||
|
||||
// Create list with initial capacity as the max metric count.
|
||||
// Due to duplicate/max limit, we may not end up using them
|
||||
// all, and that memory is wasted until Meter disposed.
|
||||
// TODO: Revisit to see if we need to do metrics.TrimExcess()
|
||||
var metrics = new List<Metric>(maxCountMetricsToBeCreated);
|
||||
lock (this.instrumentCreationLock)
|
||||
{
|
||||
// TODO: This is where view config will be looked up
|
||||
// and zero, one or more Metric streams will be created.
|
||||
if (this.metricStreamNames.ContainsKey(instrument.Name))
|
||||
for (int i = 0; i < maxCountMetricsToBeCreated; i++)
|
||||
{
|
||||
// log and ignore this instrument.
|
||||
var metricStreamName = metricStreamConfigs[i]?.Name ?? instrument.Name;
|
||||
if (this.metricStreamNames.ContainsKey(metricStreamName))
|
||||
{
|
||||
// TODO: Log that instrument is ignored
|
||||
// as the resulting Metric name is conflicting
|
||||
// with existing name.
|
||||
continue;
|
||||
}
|
||||
|
||||
var index = ++this.metricIndex;
|
||||
if (index >= MaxMetrics)
|
||||
{
|
||||
// TODO: Log that instrument is ignored
|
||||
// as max number of Metrics have reached.
|
||||
}
|
||||
else
|
||||
{
|
||||
var metric = new Metric(instrument, temporality, metricStreamName);
|
||||
this.metrics[index] = metric;
|
||||
metrics.Add(metric);
|
||||
this.metricStreamNames.Add(metricStreamName, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (metrics.Count > 0)
|
||||
{
|
||||
listener.EnableMeasurementEvents(instrument, metrics);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
this.listener.InstrumentPublished = (instrument, listener) =>
|
||||
{
|
||||
if (meterSourcesToSubscribe.ContainsKey(instrument.Meter.Name))
|
||||
{
|
||||
var metricName = instrument.Name;
|
||||
List<Metric> metrics = null;
|
||||
lock (this.instrumentCreationLock)
|
||||
{
|
||||
if (this.metricStreamNames.ContainsKey(metricName))
|
||||
{
|
||||
// TODO: Log that instrument is ignored
|
||||
// as the resulting Metric name is conflicting
|
||||
// with existing name.
|
||||
return;
|
||||
}
|
||||
|
||||
var index = Interlocked.Increment(ref this.metricIndex);
|
||||
var index = ++this.metricIndex;
|
||||
if (index >= MaxMetrics)
|
||||
{
|
||||
// Log that all measurements are dropped from this instrument.
|
||||
// TODO: Log that instrument is ignored
|
||||
// as max number of Metrics have reached.
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
metrics = new List<Metric>(1);
|
||||
var metric = new Metric(instrument, temporality);
|
||||
this.metrics[index] = metric;
|
||||
this.metricStreamNames.Add(instrument.Name, true);
|
||||
listener.EnableMeasurementEvents(instrument, metric);
|
||||
metrics.Add(metric);
|
||||
this.metricStreamNames.Add(metricName, true);
|
||||
}
|
||||
}
|
||||
|
||||
listener.EnableMeasurementEvents(instrument, metrics);
|
||||
}
|
||||
},
|
||||
MeasurementsCompleted = (instrument, state) => this.MeasurementsCompleted(instrument, state),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
this.listener.MeasurementsCompleted = (instrument, state) => this.MeasurementsCompleted(instrument, state);
|
||||
|
||||
// Everything double
|
||||
this.listener.SetMeasurementEventCallback<double>((instrument, value, tags, state) => this.MeasurementRecordedDouble(instrument, value, tags, state));
|
||||
|
|
@ -153,29 +235,41 @@ namespace OpenTelemetry.Metrics
|
|||
internal void MeasurementRecordedDouble(Instrument instrument, double value, ReadOnlySpan<KeyValuePair<string, object>> tagsRos, object state)
|
||||
{
|
||||
// Get Instrument State
|
||||
var metric = state as Metric;
|
||||
// TODO: Benchmark and see if it makes
|
||||
// sense to use a different state
|
||||
// when there are no views registered.
|
||||
// In that case, storing Metric as state
|
||||
// might be faster than storing List<Metric>
|
||||
// of size one as state.
|
||||
var metrics = state as List<Metric>;
|
||||
|
||||
if (instrument == null || metric == null)
|
||||
if (instrument == null || metrics == null)
|
||||
{
|
||||
// TODO: log
|
||||
return;
|
||||
}
|
||||
|
||||
metric.UpdateDouble(value, tagsRos);
|
||||
foreach (var metric in metrics)
|
||||
{
|
||||
metric.UpdateDouble(value, tagsRos);
|
||||
}
|
||||
}
|
||||
|
||||
internal void MeasurementRecordedLong(Instrument instrument, long value, ReadOnlySpan<KeyValuePair<string, object>> tagsRos, object state)
|
||||
{
|
||||
// Get Instrument State
|
||||
var metric = state as Metric;
|
||||
var metrics = state as List<Metric>;
|
||||
|
||||
if (instrument == null || metric == null)
|
||||
if (instrument == null || metrics == null)
|
||||
{
|
||||
// TODO: log
|
||||
return;
|
||||
}
|
||||
|
||||
metric.UpdateLong(value, tagsRos);
|
||||
foreach (var metric in metrics)
|
||||
{
|
||||
metric.UpdateLong(value, tagsRos);
|
||||
}
|
||||
}
|
||||
|
||||
internal Batch<Metric> Collect()
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ namespace OpenTelemetry.Metrics
|
|||
{
|
||||
private AggregatorStore aggStore;
|
||||
|
||||
internal Metric(Instrument instrument, AggregationTemporality temporality)
|
||||
internal Metric(Instrument instrument, AggregationTemporality temporality, string metricName = "")
|
||||
{
|
||||
this.Name = instrument.Name;
|
||||
this.Name = string.IsNullOrWhiteSpace(metricName) ? instrument.Name : metricName;
|
||||
this.Description = instrument.Description;
|
||||
this.Unit = instrument.Unit;
|
||||
this.Meter = instrument.Meter;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,166 @@
|
|||
// <copyright file="MetricViewTests.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.Collections.Generic;
|
||||
using System.Diagnostics.Metrics;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace OpenTelemetry.Metrics.Tests
|
||||
{
|
||||
public class MetricViewTests
|
||||
{
|
||||
private const int MaxTimeToAllowForFlush = 10000;
|
||||
private readonly ITestOutputHelper output;
|
||||
|
||||
public MetricViewTests(ITestOutputHelper output)
|
||||
{
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ViewToRenameMetric()
|
||||
{
|
||||
using var meter1 = new Meter("ViewToRenameMetricTest");
|
||||
var exportedItems = new List<Metric>();
|
||||
using var meterProvider = Sdk.CreateMeterProviderBuilder()
|
||||
.AddSource(meter1.Name)
|
||||
.AddView("name1", "renamed")
|
||||
.AddInMemoryExporter(exportedItems)
|
||||
.Build();
|
||||
|
||||
// Expecting one metric stream.
|
||||
var counterLong = meter1.CreateCounter<long>("name1");
|
||||
counterLong.Add(10);
|
||||
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
|
||||
Assert.Single(exportedItems);
|
||||
var metric = exportedItems[0];
|
||||
Assert.Equal("renamed", metric.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ViewToRenameMetricConditionally()
|
||||
{
|
||||
using var meter1 = new Meter("ViewToRenameMetricConditionallyTest");
|
||||
using var meter2 = new Meter("ViewToRenameMetricConditionallyTest2");
|
||||
var exportedItems = new List<Metric>();
|
||||
using var meterProvider = Sdk.CreateMeterProviderBuilder()
|
||||
.AddSource(meter1.Name)
|
||||
.AddSource(meter2.Name)
|
||||
.AddView((instrument) =>
|
||||
{
|
||||
if (instrument.Meter.Name.Equals(meter2.Name, StringComparison.OrdinalIgnoreCase)
|
||||
&& instrument.Name.Equals("name1", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new MetricStreamConfiguration() { Name = "name1_Renamed" };
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.AddInMemoryExporter(exportedItems)
|
||||
.Build();
|
||||
|
||||
// Without views only 1 stream would be
|
||||
// exported (the 2nd one gets dropped due to
|
||||
// name conflict). Due to renaming with Views,
|
||||
// we expect 2 metric streams here.
|
||||
var counter1 = meter1.CreateCounter<long>("name1");
|
||||
var counter2 = meter2.CreateCounter<long>("name1");
|
||||
counter1.Add(10);
|
||||
counter2.Add(10);
|
||||
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
|
||||
Assert.Equal(2, exportedItems.Count);
|
||||
Assert.Equal("name1", exportedItems[0].Name);
|
||||
Assert.Equal("name1_Renamed", exportedItems[1].Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ViewToRenameMetricWildCardMatch()
|
||||
{
|
||||
using var meter1 = new Meter("ViewToRenameMetricWildCardMatchTest");
|
||||
var exportedItems = new List<Metric>();
|
||||
using var meterProvider = Sdk.CreateMeterProviderBuilder()
|
||||
.AddSource(meter1.Name)
|
||||
.AddView("counter*", "renamed")
|
||||
.AddInMemoryExporter(exportedItems)
|
||||
.Build();
|
||||
|
||||
// Expecting one metric stream.
|
||||
var counter1 = meter1.CreateCounter<long>("counterA");
|
||||
counter1.Add(10);
|
||||
var counter2 = meter1.CreateCounter<long>("counterB");
|
||||
counter2.Add(10);
|
||||
var counter3 = meter1.CreateCounter<long>("counterC");
|
||||
counter3.Add(10);
|
||||
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
|
||||
|
||||
// counter* matches all 3 instruments which all
|
||||
// becomes "renamed" and only 1st one is exported.
|
||||
Assert.Single(exportedItems);
|
||||
var metric = exportedItems[0];
|
||||
Assert.Equal("renamed", metric.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ViewToProduceMultipleStreamsFromInstrument()
|
||||
{
|
||||
using var meter1 = new Meter("ViewToProduceMultipleStreamsFromInstrumentTest");
|
||||
var exportedItems = new List<Metric>();
|
||||
using var meterProvider = Sdk.CreateMeterProviderBuilder()
|
||||
.AddSource(meter1.Name)
|
||||
.AddView("name1", "renamedStream1")
|
||||
.AddView("name1", "renamedStream2")
|
||||
.AddInMemoryExporter(exportedItems)
|
||||
.Build();
|
||||
|
||||
// Expecting two metric stream.
|
||||
var counterLong = meter1.CreateCounter<long>("name1");
|
||||
counterLong.Add(10);
|
||||
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
|
||||
Assert.Equal(2, exportedItems.Count);
|
||||
Assert.Equal("renamedStream1", exportedItems[0].Name);
|
||||
Assert.Equal("renamedStream2", exportedItems[1].Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ViewToProduceMultipleStreamsWithDuplicatesFromInstrument()
|
||||
{
|
||||
using var meter1 = new Meter("ViewToProduceMultipleStreamsWithDuplicatesFromInstrumentTest");
|
||||
var exportedItems = new List<Metric>();
|
||||
using var meterProvider = Sdk.CreateMeterProviderBuilder()
|
||||
.AddSource(meter1.Name)
|
||||
.AddView("name1", "renamedStream1")
|
||||
.AddView("name1", "renamedStream2")
|
||||
.AddView("name1", "renamedStream2")
|
||||
.AddInMemoryExporter(exportedItems)
|
||||
.Build();
|
||||
|
||||
// Expecting two metric stream.
|
||||
// the .AddView("name1", "renamedStream2")
|
||||
// won't produce new Metric as the name
|
||||
// conflicts.
|
||||
var counterLong = meter1.CreateCounter<long>("name1");
|
||||
counterLong.Add(10);
|
||||
meterProvider.ForceFlush(MaxTimeToAllowForFlush);
|
||||
Assert.Equal(2, exportedItems.Count);
|
||||
Assert.Equal("renamedStream1", exportedItems[0].Name);
|
||||
Assert.Equal("renamedStream2", exportedItems[1].Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue