diff --git a/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs b/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs
index f1670d3ba87..87edd9908b9 100644
--- a/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs
+++ b/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs
@@ -35,7 +35,18 @@ internal class ExponentialBucketHistogram
private int scale;
private double scalingFactor; // 2 ^ scale / log(2)
- public ExponentialBucketHistogram(int scale, int maxBuckets = 160)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The maximum number of buckets in each of the positive and negative ranges, not counting the special zero bucket. The default value is 160.
+ ///
+ public ExponentialBucketHistogram(int maxBuckets = 160)
+ : this(maxBuckets, 20)
+ {
+ }
+
+ internal ExponentialBucketHistogram(int maxBuckets, int scale)
{
/*
The following table is calculated based on [ MapToIndex(double.Epsilon), MapToIndex(double.MaxValue) ]:
@@ -80,7 +91,12 @@ The following table is calculated based on [ MapToIndex(double.Epsilon), MapToIn
*/
Guard.ThrowIfOutOfRange(scale, min: -11, max: 20);
- Guard.ThrowIfOutOfRange(maxBuckets, min: 1);
+ /*
+ Regardless of the scale, MapToIndex(1) will always be -1, so we need two buckets at minimum:
+ bucket[-1] = (1/base, 1]
+ bucket[0] = (1, base]
+ */
+ Guard.ThrowIfOutOfRange(maxBuckets, min: 2);
this.Scale = scale;
this.PositiveBuckets = new CircularBufferBuckets(maxBuckets);
diff --git a/test/OpenTelemetry.Tests/Metrics/ExponentialBucketHistogramTest.cs b/test/OpenTelemetry.Tests/Metrics/ExponentialBucketHistogramTest.cs
index 1244449f429..59f80ad0984 100644
--- a/test/OpenTelemetry.Tests/Metrics/ExponentialBucketHistogramTest.cs
+++ b/test/OpenTelemetry.Tests/Metrics/ExponentialBucketHistogramTest.cs
@@ -27,21 +27,21 @@ 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 = new ExponentialBucketHistogram(0);
+ /*
+ 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 = new ExponentialBucketHistogram(maxBuckets: 2, scale: 0);
Assert.Equal(-1075, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000001"))); // ~4.9406564584124654E-324 (minimum subnormal positive, double.Epsilon)
Assert.Equal(-1074, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000010"))); // double.Epsilon * 2
@@ -51,10 +51,12 @@ public void IndexLookup()
Assert.Equal(-1072, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000110"))); // double.Epsilon * 6
Assert.Equal(-1072, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000111"))); // double.Epsilon * 7
Assert.Equal(-1072, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000001000"))); // double.Epsilon * 8
- Assert.Equal(-1024, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000000"))); // ~1.1125369292536007E-308
+ Assert.Equal(-1025, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000000"))); // ~5.562684646268003E-309 (2 ^ -1024)
+ Assert.Equal(-1024, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000001"))); // ~5.56268464626801E-309
+ Assert.Equal(-1024, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000000"))); // ~1.1125369292536007E-308 (2 ^ -1023)
Assert.Equal(-1023, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000001"))); // ~1.112536929253601E-308
Assert.Equal(-1023, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1111111111111111111111111111111111111111111111111111"))); // ~2.2250738585072009E-308 (maximum subnormal positive)
- Assert.Equal(-1023, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive)
+ Assert.Equal(-1023, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive, 2 ^ -1022)
Assert.Equal(-1022, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000001"))); // ~2.225073858507202E-308
Assert.Equal(-3, histogram.MapToIndex(IEEE754Double.FromString("0 01111111101 0000000000000000000000000000000000000000000000000000"))); // 0.25
Assert.Equal(-2, histogram.MapToIndex(IEEE754Double.FromString("0 01111111110 0000000000000000000000000000000000000000000000000000"))); // 0.5
@@ -71,21 +73,21 @@ public void IndexLookup()
Assert.Equal(1023, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111110"))); // ~1.7976931348623155E+308
Assert.Equal(1023, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111111"))); // ~1.7976931348623157E+308 (maximum normal positive, double.MaxValue)
- // An exponential bucket histogram with scale = -1.
- // The base is 2 ^ (2 ^ 1) = 4.
- // The buckets are:
- //
- // ...
- // bucket[-3]: (1/64, 1/16]
- // bucket[-2]: (1/16, 1/4]
- // bucket[-1]: (1/4, 1]
- // bucket[0]: (1, 4]
- // bucket[1]: (4, 16]
- // bucket[2]: (16, 64]
- // bucket[3]: (64, 256]
- // ...
-
- histogram = new ExponentialBucketHistogram(-1);
+ /*
+ An exponential bucket histogram with scale = -1.
+ The base is 2 ^ (2 ^ 1) = 4.
+ The buckets are:
+ ...
+ bucket[-3]: (1/64, 1/16]
+ bucket[-2]: (1/16, 1/4]
+ bucket[-1]: (1/4, 1]
+ bucket[0]: (1, 4]
+ bucket[1]: (4, 16]
+ bucket[2]: (16, 64]
+ bucket[3]: (64, 256]
+ ...
+ */
+ histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: -1);
Assert.Equal(-538, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000001"))); // ~4.9406564584124654E-324 (minimum subnormal positive, double.Epsilon)
Assert.Equal(-537, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000010"))); // double.Epsilon * 2
@@ -95,10 +97,12 @@ public void IndexLookup()
Assert.Equal(-536, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000110"))); // double.Epsilon * 6
Assert.Equal(-536, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000111"))); // double.Epsilon * 7
Assert.Equal(-536, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000001000"))); // double.Epsilon * 8
- Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000000"))); // ~1.1125369292536007E-308
+ Assert.Equal(-513, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000000"))); // ~5.562684646268003E-309 (2 ^ -1024)
+ Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000001"))); // ~5.56268464626801E-309
+ Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000000"))); // ~1.1125369292536007E-308 (2 ^ -1023)
Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000001"))); // ~1.112536929253601E-308
Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1111111111111111111111111111111111111111111111111111"))); // ~2.2250738585072009E-308 (maximum subnormal positive)
- Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive)
+ Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive, 2 ^ -1022)
Assert.Equal(-511, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000001"))); // ~2.225073858507202E-308
Assert.Equal(-2, histogram.MapToIndex(IEEE754Double.FromString("0 01111111101 0000000000000000000000000000000000000000000000000000"))); // 0.25
Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111110 0000000000000000000000000000000000000000000000000000"))); // 0.5
@@ -115,21 +119,21 @@ public void IndexLookup()
Assert.Equal(511, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111110"))); // ~1.7976931348623155E+308
Assert.Equal(511, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111111"))); // ~1.7976931348623157E+308 (maximum normal positive, double.MaxValue)
- // An exponential bucket histogram with scale = -2.
- // The base is 2 ^ (2 ^ 2) = 16.
- // The buckets are:
- //
- // ...
- // bucket[-3]: (1/4096, 1/256]
- // bucket[-2]: (1/256, 1/16]
- // bucket[-1]: (1/16, 1]
- // bucket[0]: (1, 16]
- // bucket[1]: (16, 256]
- // bucket[2]: (256, 4096]
- // bucket[3]: (4096, 65536]
- // ...
-
- histogram = new ExponentialBucketHistogram(-2);
+ /*
+ An exponential bucket histogram with scale = -2.
+ The base is 2 ^ (2 ^ 2) = 16.
+ The buckets are:
+ ...
+ bucket[-3]: (1/4096, 1/256]
+ bucket[-2]: (1/256, 1/16]
+ bucket[-1]: (1/16, 1]
+ bucket[0]: (1, 16]
+ bucket[1]: (16, 256]
+ bucket[2]: (256, 4096]
+ bucket[3]: (4096, 65536]
+ ...
+ */
+ histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: -2);
Assert.Equal(-269, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000001"))); // ~4.9406564584124654E-324 (minimum subnormal positive, double.Epsilon)
Assert.Equal(-269, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000010"))); // double.Epsilon * 2
@@ -139,10 +143,12 @@ public void IndexLookup()
Assert.Equal(-268, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000110"))); // double.Epsilon * 6
Assert.Equal(-268, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000111"))); // double.Epsilon * 7
Assert.Equal(-268, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000001000"))); // double.Epsilon * 8
- Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000000"))); // ~1.1125369292536007E-308
+ Assert.Equal(-257, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000000"))); // ~5.562684646268003E-309 (2 ^ -1024)
+ Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000001"))); // ~5.56268464626801E-309
+ Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000000"))); // ~1.1125369292536007E-308 (2 ^ -1023)
Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000001"))); // ~1.112536929253601E-308
Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1111111111111111111111111111111111111111111111111111"))); // ~2.2250738585072009E-308 (maximum subnormal positive)
- Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive)
+ Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive, 2 ^ -1022)
Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000001"))); // ~2.225073858507202E-308
Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111101 0000000000000000000000000000000000000000000000000000"))); // 0.25
Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111110 0000000000000000000000000000000000000000000000000000"))); // 0.5
@@ -159,21 +165,92 @@ public void IndexLookup()
Assert.Equal(255, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111110"))); // ~1.7976931348623155E+308
Assert.Equal(255, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111111"))); // ~1.7976931348623157E+308 (maximum normal positive, double.MaxValue)
- // 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]
- // ...
-
- histogram = new ExponentialBucketHistogram(1);
+ /*
+ An exponential bucket histogram with scale = -10.
+ The base is 2 ^ (2 ^ 10) = 2 ^ 1024 = double.MaxValue + 2 ^ -52 (slightly bigger than double.MaxValue).
+ The buckets are:
+ bucket[-2]: [double.Epsilon, 2 ^ -1024]
+ bucket[-1]: (2 ^ -1024, 1]
+ bucket[0]: (1, double.MaxValue]
+ */
+ histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: -10);
+
+ Assert.Equal(-2, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000001"))); // ~4.9406564584124654E-324 (minimum subnormal positive, double.Epsilon)
+ Assert.Equal(-2, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000000"))); // ~5.562684646268003E-309 (2 ^ -1024)
+ Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000001"))); // ~5.56268464626801E-309
+ Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000001"))); // ~1.1125369292536007E-308 (2 ^ -1023)
+ Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1111111111111111111111111111111111111111111111111111"))); // ~2.2250738585072009E-308 (maximum subnormal positive)
+ Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive, 2 ^ -1022)
+ Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000000"))); // 1
+ Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000001"))); // ~1.0000000000000002
+ Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111111"))); // ~1.7976931348623157E+308 (maximum normal positive, double.MaxValue)
+
+ /*
+ An exponential bucket histogram with scale = -11.
+ The base is 2 ^ (2 ^ 11) = 2 ^ 2048 (much bigger than double.MaxValue).
+ The buckets are:
+ bucket[-1]: [double.Epsilon, 1]
+ bucket[0]: (1, double.MaxValue]
+ */
+ histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: -11);
+
+ Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000001"))); // ~4.9406564584124654E-324 (minimum subnormal positive, double.Epsilon)
+ Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1111111111111111111111111111111111111111111111111111"))); // ~2.2250738585072009E-308 (maximum subnormal positive)
+ Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive, 2 ^ -1022)
+ Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000000"))); // 1
+ Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000001"))); // ~1.0000000000000002
+ Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111111"))); // ~1.7976931348623157E+308 (maximum normal positive, double.MaxValue)
+
+ /*
+ 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]
+ ...
+ */
+ histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: 1);
+ }
+
+ [Fact]
+ public void InfinityHandling()
+ {
+ var histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: 0);
+
+ histogram.Record(double.PositiveInfinity);
+ histogram.Record(double.NegativeInfinity);
+
+ Assert.Equal(0, histogram.ZeroCount + histogram.PositiveBuckets.Size + histogram.NegativeBuckets.Size);
+ }
+
+ [Fact]
+ public void NaNHandling()
+ {
+ var histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: 0);
+
+ histogram.Record(double.NaN); // NaN (language/runtime native)
+ histogram.Record(IEEE754Double.FromString("0 11111111111 0000000000000000000000000000000000000000000000000001").DoubleValue); // sNaN on x86/64 and ARM
+ histogram.Record(IEEE754Double.FromString("0 11111111111 1000000000000000000000000000000000000000000000000001").DoubleValue); // qNaN on x86/64 and ARM
+ histogram.Record(IEEE754Double.FromString("0 11111111111 1111111111111111111111111111111111111111111111111111").DoubleValue); // NaN (alternative encoding)
+
+ Assert.Equal(0, histogram.ZeroCount + histogram.PositiveBuckets.Size + histogram.NegativeBuckets.Size);
+ }
+
+ [Fact]
+ public void ZeroHandling()
+ {
+ var histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: 0);
+
+ histogram.Record(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000000").DoubleValue); // +0
+ histogram.Record(IEEE754Double.FromString("1 00000000000 0000000000000000000000000000000000000000000000000000").DoubleValue); // -0
+
+ Assert.Equal(2, histogram.ZeroCount);
}
}