Skip to content

Commit

Permalink
Refactor MeterProvider to be similar to TracerProvider (#2141)
Browse files Browse the repository at this point in the history
  • Loading branch information
cijothomas authored Jul 16, 2021
1 parent 9a5d9fd commit 63c28f0
Show file tree
Hide file tree
Showing 26 changed files with 372 additions and 343 deletions.
2 changes: 1 addition & 1 deletion docs/metrics/getting-started/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public static async Task Main(string[] args)
{
using var meterProvider = Sdk.CreateMeterProviderBuilder()
.AddSource("TestMeter")
.AddExportProcessor(new MetricConsoleExporter())
.AddConsoleExporter()
.Build();

using var token = new CancellationTokenSource();
Expand Down
4 changes: 2 additions & 2 deletions docs/metrics/getting-started/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ Hello World!
```

Install the
[OpenTelemetry](../../../src/OpenTelemetry/README.md)
[OpenTelemetry.Exporter.Console](../../../src/OpenTelemetry.Exporter.Console/README.md)
package:

```sh
dotnet add package OpenTelemetry
dotnet add package OpenTelemetry.Exporter.Console
```

Update the `Program.cs` file with the code from [Program.cs](./Program.cs):
Expand Down
1 change: 1 addition & 0 deletions docs/metrics/getting-started/getting-started.csproj
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry\OpenTelemetry.csproj" />
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.Console\OpenTelemetry.Exporter.Console.csproj" />
</ItemGroup>
</Project>
6 changes: 2 additions & 4 deletions examples/Console/TestMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,8 @@ internal static object Run(MetricsOptions options)
{
using var provider = Sdk.CreateMeterProviderBuilder()
.AddSource("TestMeter") // All instruments from this meter are enabled.
.SetDefaultCollectionPeriod(options.DefaultCollectionPeriodMilliseconds)
.AddProcessor(new TagEnrichmentProcessor("resource", "here"))
.AddExportProcessor(new MetricConsoleExporter("A"))
.AddExportProcessor(new MetricConsoleExporter("B"), 5 * options.DefaultCollectionPeriodMilliseconds)
.AddMeasurementProcessor(new TagEnrichmentProcessor("resource", "here"))
.AddConsoleExporter(o => o.MetricExportInterval = options.DefaultCollectionPeriodMilliseconds)
.Build();

using var meter = new Meter("TestMeter", "0.0.1");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// <copyright file="ConsoleExporterMetricHelperExtensions.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 OpenTelemetry.Exporter;

namespace OpenTelemetry.Metrics
{
public static class ConsoleExporterMetricHelperExtensions
{
/// <summary>
/// Adds Console exporter to the TracerProvider.
/// </summary>
/// <param name="builder"><see cref="MeterProviderBuilder"/> builder to use.</param>
/// <param name="configure">Exporter configuration options.</param>
/// <returns>The instance of <see cref="MeterProviderBuilder"/> to chain the calls.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The objects should not be disposed.")]
public static MeterProviderBuilder AddConsoleExporter(this MeterProviderBuilder builder, Action<ConsoleExporterOptions> configure = null)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}

var options = new ConsoleExporterOptions();
configure?.Invoke(options);
return builder.AddMetricProcessor(new PushMetricProcessor(new ConsoleMetricExporter(options), options.MetricExportInterval));
}
}
}
5 changes: 5 additions & 0 deletions src/OpenTelemetry.Exporter.Console/ConsoleExporterOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,10 @@ public class ConsoleExporterOptions
/// Gets or sets the output targets for the console exporter.
/// </summary>
public ConsoleExporterOutputTargets Targets { get; set; } = ConsoleExporterOutputTargets.Console;

/// <summary>
/// Gets or sets the metric export interval.
/// </summary>
public int MetricExportInterval { get; set; } = 1000;
}
}
85 changes: 85 additions & 0 deletions src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// <copyright file="ConsoleMetricExporter.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.Globalization;
using System.Linq;
using OpenTelemetry.Metrics;

namespace OpenTelemetry.Exporter
{
public class ConsoleMetricExporter : ConsoleExporter<MetricItem>
{
public ConsoleMetricExporter(ConsoleExporterOptions options)
: base(options)
{
}

public override ExportResult Export(in Batch<MetricItem> batch)
{
foreach (var metricItem in batch)
{
foreach (var metric in metricItem.Metrics)
{
var tags = metric.Attributes.ToArray().Select(k => $"{k.Key}={k.Value?.ToString()}");

string valueDisplay = string.Empty;
if (metric is ISumMetric sumMetric)
{
if (sumMetric.Sum.Value is double doubleSum)
{
valueDisplay = ((double)doubleSum).ToString(CultureInfo.InvariantCulture);
}
else if (sumMetric.Sum.Value is long longSum)
{
valueDisplay = ((long)longSum).ToString();
}
}
else if (metric is IGaugeMetric gaugeMetric)
{
if (gaugeMetric.LastValue.Value is double doubleValue)
{
valueDisplay = ((double)doubleValue).ToString();
}
else if (gaugeMetric.LastValue.Value is long longValue)
{
valueDisplay = ((long)longValue).ToString();
}

// Qn: tags again ? gaugeMetric.LastValue.Tags
}
else if (metric is ISummaryMetric summaryMetric)
{
valueDisplay = string.Format("Sum: {0} Count: {1}", summaryMetric.PopulationSum, summaryMetric.PopulationCount);
}
else if (metric is IHistogramMetric histogramMetric)
{
valueDisplay = string.Format("Sum: {0} Count: {1}", histogramMetric.PopulationSum, histogramMetric.PopulationCount);
}

var kind = metric.GetType().Name;

string time = $"{metric.StartTimeExclusive.ToLocalTime().ToString("HH:mm:ss.fff")} {metric.EndTimeInclusive.ToLocalTime().ToString("HH:mm:ss.fff")}";

var msg = $"Export {time} {metric.Name} [{string.Join(";", tags)}] {kind} Value: {valueDisplay}, Details: {metric.ToDisplayString()}";
Console.WriteLine(msg);
}
}

return ExportResult.Success;
}
}
}
91 changes: 32 additions & 59 deletions src/OpenTelemetry/Metrics/AggregatorStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,31 +25,23 @@ internal class AggregatorStore
{
private static readonly string[] EmptySeqKey = new string[0];
private static readonly object[] EmptySeqValue = new object[0];

private readonly Instrument instrument;
private readonly MeterProviderSdk sdk;

private readonly object lockKeyValue2MetricAggs = new object();

// Two-Level lookup. TagKeys x [ TagValues x Metrics ]
private readonly Dictionary<string[], Dictionary<object[], MetricAgg[]>> keyValue2MetricAggs =
new Dictionary<string[], Dictionary<object[], MetricAgg[]>>(new StringArrayEqualityComparer());
private readonly Dictionary<string[], Dictionary<object[], IAggregator[]>> keyValue2MetricAggs =
new Dictionary<string[], Dictionary<object[], IAggregator[]>>(new StringArrayEqualityComparer());

private MetricAgg[] tag0Metrics = null;
private IAggregator[] tag0Metrics = null;

private IEnumerable<int> timePeriods;

internal AggregatorStore(MeterProviderSdk sdk, Instrument instrument)
internal AggregatorStore(Instrument instrument)
{
this.sdk = sdk;
this.instrument = instrument;

this.timePeriods = this.sdk.ExportProcessors.Select(k => k.Value).Distinct();
}

internal MetricAgg[] MapToMetrics(string[] seqKey, object[] seqVal)
internal IAggregator[] MapToMetrics(string[] seqKey, object[] seqVal)
{
var metricpairs = new List<MetricAgg>();
var aggregators = new List<IAggregator>();

var name = $"{this.instrument.Meter.Name}:{this.instrument.Name}";

Expand All @@ -61,32 +53,28 @@ internal MetricAgg[] MapToMetrics(string[] seqKey, object[] seqVal)

var dt = DateTimeOffset.UtcNow;

foreach (var timeperiod in this.timePeriods)
// TODO: Need to map each instrument to metrics (based on View API)
if (this.instrument.GetType().Name.Contains("Counter"))
{
// TODO: Need to map each instrument to metrics (based on View API)

if (this.instrument.GetType().Name.Contains("Counter"))
{
metricpairs.Add(new MetricAgg(timeperiod, new SumMetricAggregator(name, dt, tags, true)));
}
else if (this.instrument.GetType().Name.Contains("Gauge"))
{
metricpairs.Add(new MetricAgg(timeperiod, new GaugeMetricAggregator(name, dt, tags)));
}
else if (this.instrument.GetType().Name.Contains("Histogram"))
{
metricpairs.Add(new MetricAgg(timeperiod, new HistogramMetricAggregator(name, dt, tags, false)));
}
else
{
metricpairs.Add(new MetricAgg(timeperiod, new SummaryMetricAggregator(name, dt, tags, false)));
}
aggregators.Add(new SumMetricAggregator(name, dt, tags, true));
}
else if (this.instrument.GetType().Name.Contains("Gauge"))
{
aggregators.Add(new GaugeMetricAggregator(name, dt, tags));
}
else if (this.instrument.GetType().Name.Contains("Histogram"))
{
aggregators.Add(new HistogramMetricAggregator(name, dt, tags, false));
}
else
{
aggregators.Add(new SummaryMetricAggregator(name, dt, tags, false));
}

return metricpairs.ToArray();
return aggregators.ToArray();
}

internal MetricAgg[] FindMetricAggregators(ReadOnlySpan<KeyValuePair<string, object>> tags)
internal IAggregator[] FindMetricAggregators(ReadOnlySpan<KeyValuePair<string, object>> tags)
{
int len = tags.Length;

Expand All @@ -109,7 +97,7 @@ internal MetricAgg[] FindMetricAggregators(ReadOnlySpan<KeyValuePair<string, obj
Array.Sort<string, object>(tagKey, tagValue);
}

MetricAgg[] metrics;
IAggregator[] metrics;

lock (this.lockKeyValue2MetricAggs)
{
Expand All @@ -124,7 +112,7 @@ internal MetricAgg[] FindMetricAggregators(ReadOnlySpan<KeyValuePair<string, obj
seqKey = new string[len];
tagKey.CopyTo(seqKey, 0);

value2metrics = new Dictionary<object[], MetricAgg[]>(new ObjectArrayEqualityComparer());
value2metrics = new Dictionary<object[], IAggregator[]>(new ObjectArrayEqualityComparer());
this.keyValue2MetricAggs.Add(seqKey, value2metrics);
}

Expand Down Expand Up @@ -160,15 +148,15 @@ internal void Update<T>(T value, ReadOnlySpan<KeyValuePair<string, object>> tags
// part of the Collect() instead. Thus, we only pay for the price
// of queueing a DataPoint in the Hot Path

var metricPairs = this.FindMetricAggregators(tags);
var metricAggregators = this.FindMetricAggregators(tags);

foreach (var pair in metricPairs)
foreach (var metricAggregator in metricAggregators)
{
pair.Metric.Update(value);
metricAggregator.Update(value);
}
}

internal List<IMetric> Collect(int periodMilliseconds)
internal List<IMetric> Collect()
{
var collectedMetrics = new List<IMetric>();

Expand All @@ -180,31 +168,16 @@ internal List<IMetric> Collect(int periodMilliseconds)
{
foreach (var metric in values.Value)
{
if (metric.TimePeriod == periodMilliseconds)
var m = metric.Collect(dt);
if (m != null)
{
var m = metric.Metric.Collect(dt);
if (m != null)
{
collectedMetrics.Add(m);
}
collectedMetrics.Add(m);
}
}
}
}

return collectedMetrics;
}

internal class MetricAgg
{
internal int TimePeriod;
internal IAggregator Metric;

internal MetricAgg(int timePeriod, IAggregator metric)
{
this.TimePeriod = timePeriod;
this.Metric = metric;
}
}
}
}
2 changes: 1 addition & 1 deletion src/OpenTelemetry/Metrics/InstrumentState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ internal class InstrumentState

internal InstrumentState(MeterProviderSdk sdk, Instrument instrument)
{
this.store = new AggregatorStore(sdk, instrument);
this.store = new AggregatorStore(instrument);
sdk.AggregatorStores.TryAdd(this.store, true);
}

Expand Down
Loading

0 comments on commit 63c28f0

Please sign in to comment.