Add Explicit Boundary Histogram Aggregator (#2216)

This commit is contained in:
Victor Lu 2021-07-30 20:11:53 -07:00 committed by GitHub
parent 54d46d80a0
commit d6d815e6a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 223 additions and 4 deletions

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}
}