Add exponential histogram MaxScale configuration (#4327)

This commit is contained in:
Alan West 2023-03-23 19:31:33 -07:00 committed by GitHub
parent 3d1c489998
commit 094124e9ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 104 additions and 17 deletions

View File

@ -39,6 +39,7 @@ namespace OpenTelemetry.Metrics
private readonly AggregationType aggType;
private readonly double[] histogramBounds;
private readonly int exponentialHistogramMaxSize;
private readonly int exponentialHistogramMaxScale;
private readonly UpdateLongDelegate updateLongCallback;
private readonly UpdateDoubleDelegate updateDoubleCallback;
private readonly int maxMetricPoints;
@ -64,6 +65,7 @@ namespace OpenTelemetry.Metrics
this.outputDelta = temporality == AggregationTemporality.Delta;
this.histogramBounds = metricStreamIdentity.HistogramBucketBounds ?? Metric.DefaultHistogramBounds;
this.exponentialHistogramMaxSize = metricStreamIdentity.ExponentialHistogramMaxSize;
this.exponentialHistogramMaxScale = metricStreamIdentity.ExponentialHistogramMaxScale;
this.StartTimeExclusive = DateTimeOffset.UtcNow;
this.exemplarFilter = exemplarFilter ?? new AlwaysOffExemplarFilter();
if (metricStreamIdentity.TagKeys == null)
@ -188,7 +190,7 @@ namespace OpenTelemetry.Metrics
{
if (!this.zeroTagMetricPointInitialized)
{
this.metricPoints[0] = new MetricPoint(this, this.aggType, null, this.histogramBounds, this.exponentialHistogramMaxSize);
this.metricPoints[0] = new MetricPoint(this, this.aggType, null, this.histogramBounds, this.exponentialHistogramMaxSize, this.exponentialHistogramMaxScale);
this.zeroTagMetricPointInitialized = true;
}
}
@ -255,7 +257,7 @@ namespace OpenTelemetry.Metrics
}
ref var metricPoint = ref this.metricPoints[aggregatorIndex];
metricPoint = new MetricPoint(this, this.aggType, sortedTags.KeyValuePairs, this.histogramBounds, this.exponentialHistogramMaxSize);
metricPoint = new MetricPoint(this, this.aggType, sortedTags.KeyValuePairs, this.histogramBounds, this.exponentialHistogramMaxSize, this.exponentialHistogramMaxScale);
// Add to dictionary *after* initializing MetricPoint
// as other threads can start writing to the
@ -304,7 +306,7 @@ namespace OpenTelemetry.Metrics
}
ref var metricPoint = ref this.metricPoints[aggregatorIndex];
metricPoint = new MetricPoint(this, this.aggType, givenTags.KeyValuePairs, this.histogramBounds, this.exponentialHistogramMaxSize);
metricPoint = new MetricPoint(this, this.aggType, givenTags.KeyValuePairs, this.histogramBounds, this.exponentialHistogramMaxSize, this.exponentialHistogramMaxScale);
// Add to dictionary *after* initializing MetricPoint
// as other threads can start writing to the

View File

@ -49,12 +49,10 @@ internal sealed class Base2ExponentialBucketHistogram
/// <param name="maxBuckets">
/// The maximum number of buckets in each of the positive and negative ranges, not counting the special zero bucket. The default value is 160.
/// </param>
public Base2ExponentialBucketHistogram(int maxBuckets = 160)
: this(maxBuckets, 20)
{
}
internal Base2ExponentialBucketHistogram(int maxBuckets, int scale)
/// <param name="scale">
/// Maximum scale factor. The default value is 20.
/// </param>
public Base2ExponentialBucketHistogram(int maxBuckets = 160, int scale = 20)
{
/*
The following table is calculated based on [ MapToIndex(double.Epsilon), MapToIndex(double.MaxValue) ]:

View File

@ -21,7 +21,8 @@ namespace OpenTelemetry.Metrics;
/// </summary>
internal sealed class Base2ExponentialBucketHistogramConfiguration : HistogramConfiguration
{
private int maxSize = 160;
private int maxSize = Metric.DefaultExponentialHistogramMaxBuckets;
private int maxScale = Metric.DefaultExponentialHistogramMaxScale;
/// <summary>
/// Gets or sets the maximum number of buckets in each of the positive and negative ranges, not counting the special zero bucket.
@ -46,4 +47,29 @@ internal sealed class Base2ExponentialBucketHistogramConfiguration : HistogramCo
this.maxSize = value;
}
}
/// <summary>
/// Gets or sets the maximum scale factor used to determine the resolution of bucket boundaries.
/// The higher the scale the higher the resolution.
/// </summary>
/// <remarks>
/// The default value is 20. The minimum size is -11. The maximum size is 20.
/// </remarks>
public int MaxScale
{
get
{
return this.maxScale;
}
set
{
if (value < -11 || value > 20)
{
throw new ArgumentException($"Histogram max scale is invalid. Max scale must be in the range [-11, 20].", nameof(value));
}
this.maxScale = value;
}
}
}

View File

@ -25,6 +25,8 @@ namespace OpenTelemetry.Metrics
{
internal const int DefaultExponentialHistogramMaxBuckets = 160;
internal const int DefaultExponentialHistogramMaxScale = 20;
internal static readonly double[] DefaultHistogramBounds = new double[] { 0, 5, 10, 25, 50, 75, 100, 250, 500, 750, 1000, 2500, 5000, 7500, 10000 };
private readonly AggregatorStore aggStore;

View File

@ -48,7 +48,8 @@ namespace OpenTelemetry.Metrics
AggregationType aggType,
KeyValuePair<string, object>[] tagKeysAndValues,
double[] histogramExplicitBounds,
int exponentialHistogramMaxSize)
int exponentialHistogramMaxSize,
int exponentialHistogramMaxScale)
{
Debug.Assert(aggregatorStore != null, "AggregatorStore was null.");
Debug.Assert(histogramExplicitBounds != null, "Histogram explicit Bounds was null.");
@ -81,7 +82,7 @@ namespace OpenTelemetry.Metrics
this.aggType == AggregationType.Base2ExponentialHistogramWithMinMax)
{
this.mpComponents = new MetricPointOptionalComponents();
this.mpComponents.Base2ExponentialBucketHistogram = new Base2ExponentialBucketHistogram(exponentialHistogramMaxSize);
this.mpComponents.Base2ExponentialBucketHistogram = new Base2ExponentialBucketHistogram(exponentialHistogramMaxSize, exponentialHistogramMaxScale);
}
else
{

View File

@ -36,6 +36,7 @@ namespace OpenTelemetry.Metrics
this.TagKeys = metricStreamConfiguration?.CopiedTagKeys;
this.HistogramBucketBounds = (metricStreamConfiguration as ExplicitBucketHistogramConfiguration)?.CopiedBoundaries;
this.ExponentialHistogramMaxSize = (metricStreamConfiguration as Base2ExponentialBucketHistogramConfiguration)?.MaxSize ?? 0;
this.ExponentialHistogramMaxScale = (metricStreamConfiguration as Base2ExponentialBucketHistogramConfiguration)?.MaxScale ?? 0;
this.HistogramRecordMinMax = (metricStreamConfiguration as HistogramConfiguration)?.RecordMinMax ?? true;
#if NETSTANDARD2_1 || NET6_0_OR_GREATER
@ -50,6 +51,7 @@ namespace OpenTelemetry.Metrics
hashCode.Add(this.ViewId);
hashCode.Add(this.TagKeys, StringArrayComparer);
hashCode.Add(this.ExponentialHistogramMaxSize);
hashCode.Add(this.ExponentialHistogramMaxScale);
if (this.HistogramBucketBounds != null)
{
for (var i = 0; i < this.HistogramBucketBounds.Length; ++i)
@ -69,6 +71,7 @@ namespace OpenTelemetry.Metrics
hash = (hash * 31) + this.InstrumentName.GetHashCode();
hash = (hash * 31) + this.HistogramRecordMinMax.GetHashCode();
hash = (hash * 31) + this.ExponentialHistogramMaxSize.GetHashCode();
hash = (hash * 31) + this.ExponentialHistogramMaxScale.GetHashCode();
hash = (hash * 31) + (this.Unit?.GetHashCode() ?? 0);
hash = (hash * 31) + (this.Description?.GetHashCode() ?? 0);
hash = (hash * 31) + (this.ViewId ?? 0);
@ -109,6 +112,8 @@ namespace OpenTelemetry.Metrics
public int ExponentialHistogramMaxSize { get; }
public int ExponentialHistogramMaxScale { get; }
public bool HistogramRecordMinMax { get; }
public static bool operator ==(MetricStreamIdentity metricIdentity1, MetricStreamIdentity metricIdentity2) => metricIdentity1.Equals(metricIdentity2);
@ -131,6 +136,7 @@ namespace OpenTelemetry.Metrics
&& this.ViewId == other.ViewId
&& this.HistogramRecordMinMax == other.HistogramRecordMinMax
&& this.ExponentialHistogramMaxSize == other.ExponentialHistogramMaxSize
&& this.ExponentialHistogramMaxScale == other.ExponentialHistogramMaxScale
&& StringArrayComparer.Equals(this.TagKeys, other.TagKeys)
&& HistogramBoundsEqual(this.HistogramBucketBounds, other.HistogramBucketBounds);
}

View File

@ -30,7 +30,7 @@ namespace OpenTelemetry.Metrics.Tests
[Fact]
public void HistogramDistributeToAllBucketsDefault()
{
var histogramPoint = new MetricPoint(this.aggregatorStore, AggregationType.HistogramWithBuckets, null, Metric.DefaultHistogramBounds, Metric.DefaultExponentialHistogramMaxBuckets);
var histogramPoint = new MetricPoint(this.aggregatorStore, AggregationType.HistogramWithBuckets, null, Metric.DefaultHistogramBounds, Metric.DefaultExponentialHistogramMaxBuckets, Metric.DefaultExponentialHistogramMaxScale);
histogramPoint.Update(-1);
histogramPoint.Update(0);
histogramPoint.Update(2);
@ -81,7 +81,7 @@ namespace OpenTelemetry.Metrics.Tests
public void HistogramDistributeToAllBucketsCustom()
{
var boundaries = new double[] { 10, 20 };
var histogramPoint = new MetricPoint(this.aggregatorStore, AggregationType.HistogramWithBuckets, null, boundaries, Metric.DefaultExponentialHistogramMaxBuckets);
var histogramPoint = new MetricPoint(this.aggregatorStore, AggregationType.HistogramWithBuckets, null, boundaries, Metric.DefaultExponentialHistogramMaxBuckets, Metric.DefaultExponentialHistogramMaxScale);
// 5 recordings <=10
histogramPoint.Update(-10);
@ -129,7 +129,7 @@ namespace OpenTelemetry.Metrics.Tests
boundaries[i] = i;
}
var histogramPoint = new MetricPoint(this.aggregatorStore, AggregationType.HistogramWithBuckets, null, boundaries, Metric.DefaultExponentialHistogramMaxBuckets);
var histogramPoint = new MetricPoint(this.aggregatorStore, AggregationType.HistogramWithBuckets, null, boundaries, Metric.DefaultExponentialHistogramMaxBuckets, Metric.DefaultExponentialHistogramMaxScale);
// Act
histogramPoint.Update(-1);
@ -162,7 +162,7 @@ namespace OpenTelemetry.Metrics.Tests
public void HistogramWithOnlySumCount()
{
var boundaries = Array.Empty<double>();
var histogramPoint = new MetricPoint(this.aggregatorStore, AggregationType.Histogram, null, boundaries, Metric.DefaultExponentialHistogramMaxBuckets);
var histogramPoint = new MetricPoint(this.aggregatorStore, AggregationType.Histogram, null, boundaries, Metric.DefaultExponentialHistogramMaxBuckets, Metric.DefaultExponentialHistogramMaxScale);
histogramPoint.Update(-10);
histogramPoint.Update(0);
@ -331,5 +331,46 @@ namespace OpenTelemetry.Metrics.Tests
}
}
}
[Theory]
[InlineData(-5)]
[InlineData(0)]
[InlineData(5)]
[InlineData(null)]
internal void ExponentialMaxScaleConfigWorks(int? maxScale)
{
var streamConfiguration = new Base2ExponentialBucketHistogramConfiguration();
if (maxScale.HasValue)
{
streamConfiguration.MaxScale = maxScale.Value;
}
var metricStreamIdentity = new MetricStreamIdentity(Instrument, streamConfiguration);
var aggregatorStore = new AggregatorStore(
metricStreamIdentity,
AggregationType.Base2ExponentialHistogram,
AggregationTemporality.Cumulative,
maxMetricPoints: 1024);
aggregatorStore.Update(10, Array.Empty<KeyValuePair<string, object>>());
aggregatorStore.Snapshot();
var metricPoints = new List<MetricPoint>();
foreach (ref readonly var mp in aggregatorStore.GetMetricPoints())
{
metricPoints.Add(mp);
}
Assert.Single(metricPoints);
var metricPoint = metricPoints[0];
// After a single measurement there will not have been a scale down.
// Scale will equal MaxScale.
var expectedScale = maxScale.HasValue ? maxScale : Metric.DefaultExponentialHistogramMaxScale;
Assert.Equal(expectedScale, metricPoint.GetExponentialHistogramData().Scale);
}
}
}

View File

@ -208,7 +208,7 @@ namespace OpenTelemetry.Metrics.Tests
[InlineData(-1)]
[InlineData(0)]
[InlineData(1)]
public void AddViewWithInvalidExponentialHistogramConfigThrowsArgumentException(int maxSize)
public void AddViewWithInvalidExponentialHistogramMaxSizeConfigThrowsArgumentException(int maxSize)
{
var ex = Assert.Throws<ArgumentException>(() => Sdk.CreateMeterProviderBuilder()
.AddView("name1", new Base2ExponentialBucketHistogramConfiguration { MaxSize = maxSize }));
@ -216,6 +216,17 @@ namespace OpenTelemetry.Metrics.Tests
Assert.Contains("Histogram max size is invalid", ex.Message);
}
[Theory]
[InlineData(-12)]
[InlineData(21)]
public void AddViewWithInvalidExponentialHistogramMaxScaleConfigThrowsArgumentException(int maxScale)
{
var ex = Assert.Throws<ArgumentException>(() => Sdk.CreateMeterProviderBuilder()
.AddView("name1", new Base2ExponentialBucketHistogramConfiguration { MaxScale = maxScale }));
Assert.Contains("Histogram max scale is invalid", ex.Message);
}
[Theory]
[MemberData(nameof(MetricTestData.InvalidHistogramBoundaries), MemberType = typeof(MetricTestData))]
public void AddViewWithInvalidHistogramBoundsIgnored(double[] boundaries)