Docs and fixes for histogram (#2269)
This commit is contained in:
parent
95e685165e
commit
d649b3d591
|
|
@ -161,6 +161,7 @@ EndProject
|
|||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "metrics", "metrics", "{3277B1C0-BDFE-4460-9B0D-D9A661FB48DB}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
docs\metrics\building-your-own-exporter.md = docs\metrics\building-your-own-exporter.md
|
||||
docs\metrics\README.md = docs\metrics\README.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "logs", "logs", "{3862190B-E2C5-418E-AFDC-DB281FB5C705}"
|
||||
|
|
@ -213,6 +214,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Instrumentati
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests", "test\OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests\OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.csproj", "{4D7201BC-7124-4401-AD65-FAB58A053D45}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "getting-started-histogram", "docs\metrics\getting-started-histogram\getting-started-histogram.csproj", "{92ED77A6-37B4-447D-B4C4-15DB005A589C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -423,6 +426,10 @@ Global
|
|||
{4D7201BC-7124-4401-AD65-FAB58A053D45}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4D7201BC-7124-4401-AD65-FAB58A053D45}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4D7201BC-7124-4401-AD65-FAB58A053D45}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{92ED77A6-37B4-447D-B4C4-15DB005A589C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{92ED77A6-37B4-447D-B4C4-15DB005A589C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{92ED77A6-37B4-447D-B4C4-15DB005A589C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{92ED77A6-37B4-447D-B4C4-15DB005A589C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -455,6 +462,7 @@ Global
|
|||
{08D29501-F0A3-468F-B18D-BD1821A72383} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818}
|
||||
{64E3D8BB-93AB-4571-93F7-ED8D64DFFD06} = {5B7FB835-3FFF-4BC2-99C5-A5B5FAE3C818}
|
||||
{DFB0AD2F-11BE-4BCD-A77B-1018C3344FA8} = {3277B1C0-BDFE-4460-9B0D-D9A661FB48DB}
|
||||
{92ED77A6-37B4-447D-B4C4-15DB005A589C} = {3277B1C0-BDFE-4460-9B0D-D9A661FB48DB}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {55639B5C-0770-4A22-AB56-859604650521}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
# Getting Started with OpenTelemetry .NET Metrics in 5 Minutes
|
||||
|
||||
* [Getting started with Counter](.\getting-started\README.md)
|
||||
* [Getting started with Histogram](.\getting-started-histogram\README.md)
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
// <copyright file="Program.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 System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using OpenTelemetry;
|
||||
using OpenTelemetry.Metrics;
|
||||
|
||||
public class Program
|
||||
{
|
||||
private static readonly Meter MyMeter = new Meter("TestMeter", "0.0.1");
|
||||
private static readonly Histogram<long> MyHistogram = MyMeter.CreateHistogram<long>("histogram");
|
||||
private static readonly Random RandomGenerator = new Random();
|
||||
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
using var meterProvider = Sdk.CreateMeterProviderBuilder()
|
||||
.AddSource("TestMeter")
|
||||
.AddConsoleExporter()
|
||||
.Build();
|
||||
|
||||
using var token = new CancellationTokenSource();
|
||||
Task writeMetricTask = new Task(() =>
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
MyHistogram.Record(
|
||||
RandomGenerator.Next(1, 1000),
|
||||
new KeyValuePair<string, object>("tag1", "value1"),
|
||||
new KeyValuePair<string, object>("tag2", "value2"));
|
||||
Task.Delay(10).Wait();
|
||||
}
|
||||
});
|
||||
writeMetricTask.Start();
|
||||
|
||||
token.CancelAfter(10000);
|
||||
await writeMetricTask;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
# Getting Started with OpenTelemetry .NET in 5 Minutes
|
||||
|
||||
First, download and install the [.NET Core
|
||||
SDK](https://dotnet.microsoft.com/download) on your computer.
|
||||
|
||||
Create a new console application and run it:
|
||||
|
||||
```sh
|
||||
dotnet new console --output getting-started-histogram
|
||||
cd getting-started
|
||||
dotnet run
|
||||
```
|
||||
|
||||
You should see the following output:
|
||||
|
||||
```text
|
||||
Hello World!
|
||||
```
|
||||
|
||||
Install the
|
||||
[OpenTelemetry.Exporter.Console](../../../src/OpenTelemetry.Exporter.Console/README.md)
|
||||
package:
|
||||
|
||||
```sh
|
||||
dotnet add package OpenTelemetry.Exporter.Console
|
||||
```
|
||||
|
||||
Update the `Program.cs` file with the code from [Program.cs](./Program.cs):
|
||||
|
||||
Run the application again (using `dotnet run`) and you should see the metric
|
||||
output from the console, similar to shown below:
|
||||
|
||||
<!-- markdownlint-disable MD013 -->
|
||||
```text
|
||||
Export 14:30:58.201 14:30:59.177 histogram [tag1=value1;tag2=value2] Histogram, Meter: TestMeter/0.0.1
|
||||
Value: Sum: 33862 Count: 62
|
||||
(-? - 0) : 0
|
||||
(0 - 5) : 0
|
||||
(5 - 10) : 0
|
||||
(10 - 25) : 2
|
||||
(25 - 50) : 0
|
||||
(50 - 75) : 1
|
||||
(75 - 100) : 1
|
||||
(100 - 250) : 6
|
||||
(250 - 500) : 18
|
||||
(500 - 1000) : 34
|
||||
(1000 - ?) : 0
|
||||
```
|
||||
<!-- markdownlint-enable MD013 -->
|
||||
|
||||
Congratulations! You are now collecting histogram metrics using OpenTelemetry.
|
||||
|
||||
What does the above program do?
|
||||
|
||||
The program creates a
|
||||
[Meter](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#meter)
|
||||
instance named "TestMeter" and then creates a
|
||||
[Histogram](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#histogram)
|
||||
instrument from it. This histogram is used to repeatedly report random metric
|
||||
measurements until exited after 10 seconds.
|
||||
|
||||
An OpenTelemetry
|
||||
[MeterProvider](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#meterprovider)
|
||||
is configured to subscribe to instruments from the Meter `TestMeter`, and
|
||||
aggregate the measurements in-memory. The pre-aggregated metrics are exported
|
||||
every 1 second to a `ConsoleExporter`. `ConsoleExporter` simply displays it on
|
||||
the console.
|
||||
|
|
@ -0,0 +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>
|
||||
|
|
@ -88,7 +88,15 @@ namespace OpenTelemetry.Exporter
|
|||
case MetricType.Histogram:
|
||||
{
|
||||
var histogramMetric = metric as IHistogramMetric;
|
||||
valueDisplay = string.Format("Sum: {0} Count: {1}", histogramMetric.PopulationSum, histogramMetric.PopulationCount);
|
||||
var bucketsBuilder = new StringBuilder();
|
||||
bucketsBuilder.Append($"Sum: {histogramMetric.PopulationSum} Count: {histogramMetric.PopulationCount} \n");
|
||||
foreach (var bucket in histogramMetric.Buckets)
|
||||
{
|
||||
bucketsBuilder.Append($"({bucket.LowBoundary} - {bucket.HighBoundary}) : {bucket.Count}");
|
||||
bucketsBuilder.AppendLine();
|
||||
}
|
||||
|
||||
valueDisplay = bucketsBuilder.ToString();
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +110,7 @@ namespace OpenTelemetry.Exporter
|
|||
|
||||
string time = $"{metric.StartTimeExclusive.ToLocalTime().ToString("HH:mm:ss.fff")} {metric.EndTimeInclusive.ToLocalTime().ToString("HH:mm:ss.fff")}";
|
||||
|
||||
var msg = new StringBuilder($"Export {time} {metric.Name} [{string.Join(";", tags)}] {metric.MetricType} Value: {valueDisplay}");
|
||||
var msg = new StringBuilder($"Export {time} {metric.Name} [{string.Join(";", tags)}] {metric.MetricType}");
|
||||
|
||||
if (!string.IsNullOrEmpty(metric.Description))
|
||||
{
|
||||
|
|
@ -124,6 +132,8 @@ namespace OpenTelemetry.Exporter
|
|||
}
|
||||
}
|
||||
|
||||
msg.AppendLine();
|
||||
msg.Append($"Value: {valueDisplay}");
|
||||
Console.WriteLine(msg);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ namespace OpenTelemetry.Metrics
|
|||
{
|
||||
public struct HistogramBucket
|
||||
{
|
||||
internal double LowBoundary;
|
||||
internal double HighBoundary;
|
||||
internal long Count;
|
||||
public double LowBoundary;
|
||||
public double HighBoundary;
|
||||
public long Count;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
// <copyright file="HistogramMetric.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;
|
||||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
internal class HistogramMetric : IHistogramMetric
|
||||
{
|
||||
internal HistogramMetric(string name, string description, string unit, Meter meter, DateTimeOffset startTimeExclusive, KeyValuePair<string, object>[] attributes, int bucketCount)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Description = description;
|
||||
this.Unit = unit;
|
||||
this.Meter = meter;
|
||||
this.StartTimeExclusive = startTimeExclusive;
|
||||
this.Attributes = attributes;
|
||||
this.MetricType = MetricType.Histogram;
|
||||
this.BucketsArray = new HistogramBucket[bucketCount];
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public string Description { get; private set; }
|
||||
|
||||
public string Unit { get; private set; }
|
||||
|
||||
public Meter Meter { get; private set; }
|
||||
|
||||
public DateTimeOffset StartTimeExclusive { get; internal set; }
|
||||
|
||||
public DateTimeOffset EndTimeInclusive { get; internal set; }
|
||||
|
||||
public KeyValuePair<string, object>[] Attributes { get; private set; }
|
||||
|
||||
public bool IsDeltaTemporality { get; internal set; }
|
||||
|
||||
public IEnumerable<IExemplar> Exemplars { get; private set; } = new List<IExemplar>();
|
||||
|
||||
public long PopulationCount { get; internal set; }
|
||||
|
||||
public double PopulationSum { get; internal set; }
|
||||
|
||||
public IEnumerable<HistogramBucket> Buckets => this.BucketsArray;
|
||||
|
||||
public MetricType MetricType { get; private set; }
|
||||
|
||||
internal HistogramBucket[] BucketsArray { get; set; }
|
||||
|
||||
public string ToDisplayString()
|
||||
{
|
||||
return $"Count={this.PopulationCount},Sum={this.PopulationSum}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,14 +20,17 @@ using System.Diagnostics.Metrics;
|
|||
|
||||
namespace OpenTelemetry.Metrics
|
||||
{
|
||||
internal class HistogramMetricAggregator : IHistogramMetric, IAggregator
|
||||
internal class HistogramMetricAggregator : IAggregator
|
||||
{
|
||||
private static readonly double[] DefaultBoundaries = new double[] { 0, 5, 10, 25, 50, 75, 100, 250, 500, 1000 };
|
||||
|
||||
private readonly object lockUpdate = new object();
|
||||
private HistogramBucket[] buckets;
|
||||
|
||||
private long populationCount;
|
||||
private double populationSum;
|
||||
private double[] boundaries;
|
||||
private DateTimeOffset startTimeExclusive;
|
||||
private HistogramMetric histogramMetric;
|
||||
|
||||
internal HistogramMetricAggregator(string name, string description, string unit, Meter meter, DateTimeOffset startTimeExclusive, KeyValuePair<string, object>[] attributes)
|
||||
: this(name, description, unit, meter, startTimeExclusive, attributes, DefaultBoundaries)
|
||||
|
|
@ -36,12 +39,8 @@ namespace OpenTelemetry.Metrics
|
|||
|
||||
internal HistogramMetricAggregator(string name, string description, string unit, Meter meter, DateTimeOffset startTimeExclusive, KeyValuePair<string, object>[] attributes, double[] boundaries)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Description = description;
|
||||
this.Unit = unit;
|
||||
this.Meter = meter;
|
||||
this.StartTimeExclusive = startTimeExclusive;
|
||||
this.Attributes = attributes;
|
||||
this.startTimeExclusive = startTimeExclusive;
|
||||
this.histogramMetric = new HistogramMetric(name, description, unit, meter, startTimeExclusive, attributes, boundaries.Length + 1);
|
||||
|
||||
if (boundaries.Length == 0)
|
||||
{
|
||||
|
|
@ -50,35 +49,8 @@ namespace OpenTelemetry.Metrics
|
|||
|
||||
this.boundaries = boundaries;
|
||||
this.buckets = this.InitializeBucket(boundaries);
|
||||
this.MetricType = MetricType.Summary;
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public string Description { get; private set; }
|
||||
|
||||
public string Unit { get; private set; }
|
||||
|
||||
public Meter Meter { get; private set; }
|
||||
|
||||
public DateTimeOffset StartTimeExclusive { get; private set; }
|
||||
|
||||
public DateTimeOffset EndTimeInclusive { get; private set; }
|
||||
|
||||
public KeyValuePair<string, object>[] Attributes { get; private set; }
|
||||
|
||||
public bool IsDeltaTemporality { get; private set; }
|
||||
|
||||
public IEnumerable<IExemplar> Exemplars { get; private set; } = new List<IExemplar>();
|
||||
|
||||
public long PopulationCount { get; private set; }
|
||||
|
||||
public double PopulationSum { get; private set; }
|
||||
|
||||
public MetricType MetricType { get; private set; }
|
||||
|
||||
public IEnumerable<HistogramBucket> Buckets => this.buckets;
|
||||
|
||||
public void Update<T>(T value)
|
||||
where T : struct
|
||||
{
|
||||
|
|
@ -111,37 +83,34 @@ namespace OpenTelemetry.Metrics
|
|||
|
||||
lock (this.lockUpdate)
|
||||
{
|
||||
this.PopulationCount++;
|
||||
this.PopulationSum += val;
|
||||
this.populationCount++;
|
||||
this.populationSum += val;
|
||||
this.buckets[i].Count++;
|
||||
}
|
||||
}
|
||||
|
||||
public IMetric Collect(DateTimeOffset dt, bool isDelta)
|
||||
{
|
||||
if (this.PopulationCount == 0)
|
||||
if (this.populationCount == 0)
|
||||
{
|
||||
// TODO: Output stale markers
|
||||
return null;
|
||||
}
|
||||
|
||||
var cloneItem = new HistogramMetricAggregator(this.Name, this.Description, this.Unit, this.Meter, this.StartTimeExclusive, this.Attributes, this.boundaries);
|
||||
|
||||
lock (this.lockUpdate)
|
||||
{
|
||||
cloneItem.Exemplars = this.Exemplars;
|
||||
cloneItem.EndTimeInclusive = dt;
|
||||
cloneItem.PopulationCount = this.PopulationCount;
|
||||
cloneItem.PopulationSum = this.PopulationSum;
|
||||
cloneItem.boundaries = this.boundaries;
|
||||
this.buckets.CopyTo(cloneItem.buckets, 0);
|
||||
cloneItem.IsDeltaTemporality = isDelta;
|
||||
this.histogramMetric.StartTimeExclusive = this.startTimeExclusive;
|
||||
this.histogramMetric.EndTimeInclusive = dt;
|
||||
this.histogramMetric.PopulationCount = this.populationCount;
|
||||
this.histogramMetric.PopulationSum = this.populationSum;
|
||||
this.buckets.CopyTo(this.histogramMetric.BucketsArray, 0);
|
||||
this.histogramMetric.IsDeltaTemporality = isDelta;
|
||||
|
||||
if (isDelta)
|
||||
{
|
||||
this.StartTimeExclusive = dt;
|
||||
this.PopulationCount = 0;
|
||||
this.PopulationSum = 0;
|
||||
this.startTimeExclusive = dt;
|
||||
this.populationCount = 0;
|
||||
this.populationSum = 0;
|
||||
for (int i = 0; i < this.buckets.Length; i++)
|
||||
{
|
||||
this.buckets[i].Count = 0;
|
||||
|
|
@ -149,12 +118,13 @@ namespace OpenTelemetry.Metrics
|
|||
}
|
||||
}
|
||||
|
||||
return cloneItem;
|
||||
}
|
||||
|
||||
public string ToDisplayString()
|
||||
{
|
||||
return $"Count={this.PopulationCount},Sum={this.PopulationSum}";
|
||||
// TODO: Confirm that this approach of
|
||||
// re-using the same instance is correct.
|
||||
// This avoids allocating a new instance.
|
||||
// It is read only for Exporters,
|
||||
// and also there is no parallel
|
||||
// Collect allowed.
|
||||
return this.histogramMetric;
|
||||
}
|
||||
|
||||
private HistogramBucket[] InitializeBucket(double[] boundaries)
|
||||
|
|
|
|||
|
|
@ -44,9 +44,9 @@ namespace OpenTelemetry.Metrics.Tests
|
|||
var metric = hist.Collect(DateTimeOffset.UtcNow, false);
|
||||
|
||||
Assert.NotNull(metric);
|
||||
Assert.IsType<HistogramMetricAggregator>(metric);
|
||||
Assert.IsType<HistogramMetric>(metric);
|
||||
|
||||
if (metric is HistogramMetricAggregator agg)
|
||||
if (metric is HistogramMetric agg)
|
||||
{
|
||||
int len = 0;
|
||||
foreach (var bucket in agg.Buckets)
|
||||
|
|
@ -71,9 +71,9 @@ namespace OpenTelemetry.Metrics.Tests
|
|||
var metric = hist.Collect(DateTimeOffset.UtcNow, false);
|
||||
|
||||
Assert.NotNull(metric);
|
||||
Assert.IsType<HistogramMetricAggregator>(metric);
|
||||
Assert.IsType<HistogramMetric>(metric);
|
||||
|
||||
if (metric is HistogramMetricAggregator agg)
|
||||
if (metric is HistogramMetric agg)
|
||||
{
|
||||
int len = 0;
|
||||
foreach (var bucket in agg.Buckets)
|
||||
|
|
@ -102,9 +102,9 @@ namespace OpenTelemetry.Metrics.Tests
|
|||
var metric = hist.Collect(DateTimeOffset.UtcNow, false);
|
||||
|
||||
Assert.NotNull(metric);
|
||||
Assert.IsType<HistogramMetricAggregator>(metric);
|
||||
Assert.IsType<HistogramMetric>(metric);
|
||||
|
||||
if (metric is HistogramMetricAggregator agg)
|
||||
if (metric is HistogramMetric agg)
|
||||
{
|
||||
var expectedCounts = new int[] { 3, 0, 2, 1 };
|
||||
int len = 0;
|
||||
|
|
|
|||
Loading…
Reference in New Issue