-
Notifications
You must be signed in to change notification settings - Fork 773
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Exponential Bucket Histogram - part 1 (#3462)
- Loading branch information
Showing
3 changed files
with
279 additions
and
0 deletions.
There are no files selected for viewing
133 changes: 133 additions & 0 deletions
133
src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
// <copyright file="ExponentialBucketHistogram.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> | ||
|
||
#if NET6_0_OR_GREATER | ||
|
||
using System; | ||
using System.Diagnostics; | ||
|
||
using OpenTelemetry.Internal; | ||
|
||
namespace OpenTelemetry.Metrics | ||
{ | ||
/// <summary> | ||
/// Represents an exponential bucket histogram with base = 2 ^ (2 ^ (-scale)). | ||
/// An exponential bucket histogram has infinite number of buckets, which are | ||
/// identified by <c>Bucket[i] = ( base ^ i, base ^ (i + 1) ]</c>, where <c>i</c> | ||
/// is an integer. | ||
/// </summary> | ||
internal class ExponentialBucketHistogram | ||
{ | ||
private static readonly double Log2E = Math.Log2(Math.E); // 1 / Math.Log(2) | ||
|
||
private int scale; | ||
private double scalingFactor; // 2 ^ scale / log(2) | ||
|
||
public ExponentialBucketHistogram(int scale, int maxBuckets = 160) | ||
{ | ||
Guard.ThrowIfOutOfRange(scale, min: -20, max: 20); // TODO: calculate the actual range | ||
|
||
this.Scale = scale; | ||
} | ||
|
||
internal int Scale | ||
{ | ||
get | ||
{ | ||
return this.scale; | ||
} | ||
|
||
private set | ||
{ | ||
this.scale = value; | ||
this.scalingFactor = Math.ScaleB(Log2E, value); | ||
} | ||
} | ||
|
||
internal long ZeroCount { get; private set; } | ||
|
||
/// <inheritdoc/> | ||
public override string ToString() | ||
{ | ||
return nameof(ExponentialBucketHistogram) | ||
+ "{" | ||
+ nameof(this.Scale) + "=" + this.Scale | ||
+ "}"; | ||
} | ||
|
||
public int MapToIndex(double value) | ||
{ | ||
Debug.Assert(value != 0, "IEEE-754 zero values should be handled by ZeroCount."); | ||
|
||
// TODO: handle +Inf, -Inf, NaN | ||
|
||
value = Math.Abs(value); | ||
|
||
if (this.Scale > 0) | ||
{ | ||
// TODO: due to precision issue, the values that are close to the bucket | ||
// boundaries should be closely examined to avoid off-by-one. | ||
return (int)Math.Ceiling(Math.Log(value) * this.scalingFactor) - 1; | ||
} | ||
else | ||
{ | ||
var bits = BitConverter.DoubleToInt64Bits(value); | ||
var exp = (int)((bits & IEEE754Double.EXPONENT_MASK) >> IEEE754Double.FRACTION_BITS); | ||
var fraction = bits & IEEE754Double.FRACTION_MASK; | ||
|
||
if (exp == 0) | ||
{ | ||
// TODO: benchmark and see if this should be changed to a lookup table. | ||
fraction--; | ||
|
||
for (int i = IEEE754Double.FRACTION_BITS - 1; i >= 0; i--) | ||
{ | ||
if ((fraction >> i) != 0) | ||
{ | ||
break; | ||
} | ||
|
||
exp--; | ||
} | ||
} | ||
else if (fraction == 0) | ||
{ | ||
exp--; | ||
} | ||
|
||
return (exp - IEEE754Double.EXPONENT_BIAS) >> -this.Scale; | ||
} | ||
} | ||
|
||
public sealed class IEEE754Double | ||
{ | ||
#pragma warning disable SA1310 // Field name should not contain an underscore | ||
internal const int EXPONENT_BIAS = 1023; | ||
internal const long EXPONENT_MASK = 0x7FF0000000000000L; | ||
internal const int FRACTION_BITS = 52; | ||
internal const long FRACTION_MASK = 0xFFFFFFFFFFFFFL; | ||
#pragma warning restore SA1310 // Field name should not contain an underscore | ||
|
||
public static string ToString(double value) | ||
{ | ||
var repr = Convert.ToString(BitConverter.DoubleToInt64Bits(value), 2); | ||
return new string('0', 64 - repr.Length) + repr + ":" + "(" + value + ")"; | ||
} | ||
} | ||
} | ||
} | ||
|
||
#endif |
32 changes: 32 additions & 0 deletions
32
src/OpenTelemetry/Metrics/ExponentialBucketHistogramConfiguration.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// <copyright file="ExponentialBucketHistogramConfiguration.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> | ||
|
||
namespace OpenTelemetry.Metrics | ||
{ | ||
/// <summary> | ||
/// Stores configuration for a histogram metric stream with exponential bucket boundaries. | ||
/// </summary> | ||
internal class ExponentialBucketHistogramConfiguration : MetricStreamConfiguration | ||
{ | ||
/// <summary> | ||
/// Gets or sets the maximum number of buckets in each of the positive and negative ranges, not counting the special zero bucket. | ||
/// </summary> | ||
/// <remarks> | ||
/// The default value is 160. | ||
/// </remarks> | ||
public int MaxSize { get; set; } = 160; | ||
} | ||
} |
114 changes: 114 additions & 0 deletions
114
test/OpenTelemetry.Tests/Metrics/ExponentialBucketHistogramTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
// <copyright file="ExponentialBucketHistogramTest.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> | ||
|
||
#if NET6_0_OR_GREATER | ||
|
||
using System; | ||
using Xunit; | ||
|
||
namespace OpenTelemetry.Metrics.Tests | ||
{ | ||
public class ExponentialBucketHistogramTest | ||
{ | ||
[Fact] | ||
public void IndexLookup() | ||
{ | ||
// An exponential bucket histogram with scale = 0. | ||
// The base is 2 ^ (2 ^ -0) = 2. | ||
// The buckets are: | ||
// | ||
// ... | ||
// bucket[-3]: (1/8, 1/4] | ||
// bucket[-2]: (1/4, 1/2] | ||
// bucket[-1]: (1/2, 1] | ||
// bucket[0]: (1, 2] | ||
// bucket[1]: (2, 4] | ||
// bucket[2]: (4, 8] | ||
// bucket[3]: (8, 16] | ||
// ... | ||
|
||
var histogram_scale0 = new ExponentialBucketHistogram(0); | ||
|
||
Assert.Equal(-1075, histogram_scale0.MapToIndex(double.Epsilon)); | ||
|
||
Assert.Equal(-1074, histogram_scale0.MapToIndex(double.Epsilon * 2)); | ||
|
||
Assert.Equal(-1073, histogram_scale0.MapToIndex(double.Epsilon * 3)); | ||
Assert.Equal(-1073, histogram_scale0.MapToIndex(double.Epsilon * 4)); | ||
|
||
Assert.Equal(-1072, histogram_scale0.MapToIndex(double.Epsilon * 5)); | ||
Assert.Equal(-1072, histogram_scale0.MapToIndex(double.Epsilon * 6)); | ||
Assert.Equal(-1072, histogram_scale0.MapToIndex(double.Epsilon * 7)); | ||
Assert.Equal(-1072, histogram_scale0.MapToIndex(double.Epsilon * 8)); | ||
|
||
Assert.Equal(-1023, histogram_scale0.MapToIndex(2.2250738585072009E-308)); | ||
Assert.Equal(-1023, histogram_scale0.MapToIndex(2.2250738585072014E-308)); | ||
|
||
Assert.Equal(-3, histogram_scale0.MapToIndex(0.25)); | ||
|
||
Assert.Equal(-2, histogram_scale0.MapToIndex(0.375)); | ||
Assert.Equal(-2, histogram_scale0.MapToIndex(0.5)); | ||
|
||
Assert.Equal(-1, histogram_scale0.MapToIndex(0.75)); | ||
Assert.Equal(-1, histogram_scale0.MapToIndex(1)); | ||
|
||
Assert.Equal(0, histogram_scale0.MapToIndex(1.5)); | ||
Assert.Equal(0, histogram_scale0.MapToIndex(2)); | ||
|
||
Assert.Equal(1, histogram_scale0.MapToIndex(3)); | ||
Assert.Equal(1, histogram_scale0.MapToIndex(4)); | ||
|
||
Assert.Equal(2, histogram_scale0.MapToIndex(5)); | ||
Assert.Equal(2, histogram_scale0.MapToIndex(6)); | ||
Assert.Equal(2, histogram_scale0.MapToIndex(7)); | ||
Assert.Equal(2, histogram_scale0.MapToIndex(8)); | ||
|
||
Assert.Equal(3, histogram_scale0.MapToIndex(9)); | ||
Assert.Equal(3, histogram_scale0.MapToIndex(16)); | ||
|
||
Assert.Equal(4, histogram_scale0.MapToIndex(17)); | ||
Assert.Equal(4, histogram_scale0.MapToIndex(32)); | ||
|
||
// An exponential bucket histogram with scale = 1. | ||
// The base is 2 ^ (2 ^ -1) = sqrt(2) = 1.41421356237. | ||
// The buckets are: | ||
// | ||
// ... | ||
// bucket[-3]: (0.35355339059, 1/2] | ||
// bucket[-2]: (1/2, 0.70710678118] | ||
// bucket[-1]: (0.70710678118, 1] | ||
// bucket[0]: (1, 1.41421356237] | ||
// bucket[1]: (1.41421356237, 2] | ||
// bucket[2]: (2, 2.82842712474] | ||
// bucket[3]: (2.82842712474, 4] | ||
// ... | ||
|
||
var histogram_scale1 = new ExponentialBucketHistogram(1); | ||
|
||
Assert.Equal(-3, histogram_scale1.MapToIndex(0.5)); | ||
|
||
Assert.Equal(-2, histogram_scale1.MapToIndex(0.6)); | ||
|
||
Assert.Equal(-1, histogram_scale1.MapToIndex(1)); | ||
|
||
Assert.Equal(1, histogram_scale1.MapToIndex(2)); | ||
|
||
Assert.Equal(3, histogram_scale1.MapToIndex(4)); | ||
} | ||
} | ||
} | ||
|
||
#endif |