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