Add Explicit Boundary Histogram Aggregator (#2216)
This commit is contained in:
parent
54d46d80a0
commit
d6d815e6a8
|
|
@ -22,10 +22,19 @@ namespace OpenTelemetry.Metrics
|
|||
{
|
||||
internal class HistogramMetricAggregator : IHistogramMetric, 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 List<HistogramBucket> buckets = new List<HistogramBucket>();
|
||||
private HistogramBucket[] buckets;
|
||||
|
||||
private double[] boundaries;
|
||||
|
||||
internal HistogramMetricAggregator(string name, string description, string unit, Meter meter, DateTimeOffset startTimeExclusive, KeyValuePair<string, object>[] attributes)
|
||||
: this(name, description, unit, meter, startTimeExclusive, attributes, DefaultBoundaries)
|
||||
{
|
||||
}
|
||||
|
||||
internal HistogramMetricAggregator(string name, string description, string unit, Meter meter, DateTimeOffset startTimeExclusive, KeyValuePair<string, object>[] attributes, double[] boundaries)
|
||||
{
|
||||
this.Name = name;
|
||||
this.Description = description;
|
||||
|
|
@ -33,6 +42,14 @@ namespace OpenTelemetry.Metrics
|
|||
this.Meter = meter;
|
||||
this.StartTimeExclusive = startTimeExclusive;
|
||||
this.Attributes = attributes;
|
||||
|
||||
if (boundaries.Length == 0)
|
||||
{
|
||||
boundaries = DefaultBoundaries;
|
||||
}
|
||||
|
||||
this.boundaries = boundaries;
|
||||
this.buckets = this.InitializeBucket(boundaries);
|
||||
}
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
|
@ -62,11 +79,38 @@ namespace OpenTelemetry.Metrics
|
|||
public void Update<T>(T value)
|
||||
where T : struct
|
||||
{
|
||||
// TODO: Implement Histogram!
|
||||
// promote value to be a double
|
||||
|
||||
double val;
|
||||
if (typeof(T) == typeof(long))
|
||||
{
|
||||
val = (long)(object)value;
|
||||
}
|
||||
else if (typeof(T) == typeof(double))
|
||||
{
|
||||
val = (double)(object)value;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Unsupported Type!");
|
||||
}
|
||||
|
||||
// Determine the bucket index
|
||||
|
||||
int i;
|
||||
for (i = 0; i < this.boundaries.Length; i++)
|
||||
{
|
||||
if (val < this.boundaries[i])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lock (this.lockUpdate)
|
||||
{
|
||||
this.PopulationCount++;
|
||||
this.PopulationSum += val;
|
||||
this.buckets[i].Count++;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -78,7 +122,7 @@ namespace OpenTelemetry.Metrics
|
|||
return null;
|
||||
}
|
||||
|
||||
var cloneItem = new HistogramMetricAggregator(this.Name, this.Description, this.Unit, this.Meter, this.StartTimeExclusive, this.Attributes);
|
||||
var cloneItem = new HistogramMetricAggregator(this.Name, this.Description, this.Unit, this.Meter, this.StartTimeExclusive, this.Attributes, this.boundaries);
|
||||
|
||||
lock (this.lockUpdate)
|
||||
{
|
||||
|
|
@ -86,7 +130,8 @@ namespace OpenTelemetry.Metrics
|
|||
cloneItem.EndTimeInclusive = dt;
|
||||
cloneItem.PopulationCount = this.PopulationCount;
|
||||
cloneItem.PopulationSum = this.PopulationSum;
|
||||
cloneItem.buckets = this.buckets;
|
||||
cloneItem.boundaries = this.boundaries;
|
||||
this.buckets.CopyTo(cloneItem.buckets, 0);
|
||||
cloneItem.IsDeltaTemporality = isDelta;
|
||||
|
||||
if (isDelta)
|
||||
|
|
@ -94,6 +139,10 @@ namespace OpenTelemetry.Metrics
|
|||
this.StartTimeExclusive = dt;
|
||||
this.PopulationCount = 0;
|
||||
this.PopulationSum = 0;
|
||||
for (int i = 0; i < this.buckets.Length; i++)
|
||||
{
|
||||
this.buckets[i].Count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -104,5 +153,51 @@ namespace OpenTelemetry.Metrics
|
|||
{
|
||||
return $"Count={this.PopulationCount},Sum={this.PopulationSum}";
|
||||
}
|
||||
|
||||
private HistogramBucket[] InitializeBucket(double[] boundaries)
|
||||
{
|
||||
var buckets = new HistogramBucket[boundaries.Length + 1];
|
||||
|
||||
var lastBoundary = boundaries[0];
|
||||
for (int i = 0; i < buckets.Length; i++)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
// LowBoundary is inclusive
|
||||
buckets[i].LowBoundary = double.NegativeInfinity;
|
||||
|
||||
// HighBoundary is exclusive
|
||||
buckets[i].HighBoundary = boundaries[i];
|
||||
}
|
||||
else if (i < boundaries.Length)
|
||||
{
|
||||
// LowBoundary is inclusive
|
||||
buckets[i].LowBoundary = lastBoundary;
|
||||
|
||||
// HighBoundary is exclusive
|
||||
buckets[i].HighBoundary = boundaries[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
// LowBoundary and HighBoundary are inclusive
|
||||
buckets[i].LowBoundary = lastBoundary;
|
||||
buckets[i].HighBoundary = double.PositiveInfinity;
|
||||
}
|
||||
|
||||
buckets[i].Count = 0;
|
||||
|
||||
if (i < boundaries.Length)
|
||||
{
|
||||
if (boundaries[i] < lastBoundary)
|
||||
{
|
||||
throw new ArgumentException("Boundary values must be increasing.", nameof(boundaries));
|
||||
}
|
||||
|
||||
lastBoundary = boundaries[i];
|
||||
}
|
||||
}
|
||||
|
||||
return buckets;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
// <copyright file="AggregatorTest.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;
|
||||
|
||||
namespace OpenTelemetry.Metrics.Tests
|
||||
{
|
||||
public class AggregatorTest
|
||||
{
|
||||
[Fact]
|
||||
public void HistogramDistributeToAllBuckets()
|
||||
{
|
||||
using var meter = new Meter("TestMeter", "0.0.1");
|
||||
|
||||
var hist = new HistogramMetricAggregator("test", "desc", "1", meter, DateTimeOffset.UtcNow, new KeyValuePair<string, object>[0]);
|
||||
|
||||
hist.Update<long>(-1);
|
||||
hist.Update<long>(0);
|
||||
hist.Update<long>(5);
|
||||
hist.Update<long>(10);
|
||||
hist.Update<long>(25);
|
||||
hist.Update<long>(50);
|
||||
hist.Update<long>(75);
|
||||
hist.Update<long>(100);
|
||||
hist.Update<long>(250);
|
||||
hist.Update<long>(500);
|
||||
hist.Update<long>(1000);
|
||||
var metric = hist.Collect(DateTimeOffset.UtcNow, false);
|
||||
|
||||
Assert.NotNull(metric);
|
||||
Assert.IsType<HistogramMetricAggregator>(metric);
|
||||
|
||||
if (metric is HistogramMetricAggregator agg)
|
||||
{
|
||||
int len = 0;
|
||||
foreach (var bucket in agg.Buckets)
|
||||
{
|
||||
Assert.Equal(1, bucket.Count);
|
||||
len++;
|
||||
}
|
||||
|
||||
Assert.Equal(11, len);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HistogramCustomBoundaries()
|
||||
{
|
||||
using var meter = new Meter("TestMeter", "0.0.1");
|
||||
|
||||
var hist = new HistogramMetricAggregator("test", "desc", "1", meter, DateTimeOffset.UtcNow, new KeyValuePair<string, object>[0], new double[] { 0 });
|
||||
|
||||
hist.Update<long>(-1);
|
||||
hist.Update<long>(0);
|
||||
var metric = hist.Collect(DateTimeOffset.UtcNow, false);
|
||||
|
||||
Assert.NotNull(metric);
|
||||
Assert.IsType<HistogramMetricAggregator>(metric);
|
||||
|
||||
if (metric is HistogramMetricAggregator agg)
|
||||
{
|
||||
int len = 0;
|
||||
foreach (var bucket in agg.Buckets)
|
||||
{
|
||||
Assert.Equal(1, bucket.Count);
|
||||
len++;
|
||||
}
|
||||
|
||||
Assert.Equal(2, len);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HistogramWithEmptyBuckets()
|
||||
{
|
||||
using var meter = new Meter("TestMeter", "0.0.1");
|
||||
|
||||
var hist = new HistogramMetricAggregator("test", "desc", "1", meter, DateTimeOffset.UtcNow, new KeyValuePair<string, object>[0], new double[] { 0, 5, 10 });
|
||||
|
||||
hist.Update<long>(-3);
|
||||
hist.Update<long>(-2);
|
||||
hist.Update<long>(-1);
|
||||
hist.Update<long>(6);
|
||||
hist.Update<long>(7);
|
||||
hist.Update<long>(12);
|
||||
var metric = hist.Collect(DateTimeOffset.UtcNow, false);
|
||||
|
||||
Assert.NotNull(metric);
|
||||
Assert.IsType<HistogramMetricAggregator>(metric);
|
||||
|
||||
if (metric is HistogramMetricAggregator agg)
|
||||
{
|
||||
var expectedCounts = new int[] { 3, 0, 2, 1 };
|
||||
int len = 0;
|
||||
foreach (var bucket in agg.Buckets)
|
||||
{
|
||||
if (len < expectedCounts.Length)
|
||||
{
|
||||
Assert.Equal(expectedCounts[len], bucket.Count);
|
||||
len++;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.Equal(4, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue