From 6d7df0d62605db055e55220f4770f96a3b7aa696 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Thu, 21 Jul 2022 16:38:24 -0700 Subject: [PATCH 1/4] Automatically adjust the bucket range --- .../Metrics/CircularBufferBuckets.cs | 1 + .../Metrics/ExponentialBucketHistogram.cs | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs index 15584c5e57d..7f852633929 100644 --- a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs +++ b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs @@ -71,6 +71,7 @@ public long this[int index] /// /// The "index" value can be positive, zero or negative. /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryIncrement(int index) { if (this.trait == null) diff --git a/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs b/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs index a8c4fb5d22e..d11e0e56966 100644 --- a/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs +++ b/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs @@ -38,8 +38,11 @@ internal class ExponentialBucketHistogram public ExponentialBucketHistogram(int scale, int maxBuckets = 160) { Guard.ThrowIfOutOfRange(scale, min: -20, max: 20); // TODO: calculate the actual range + Guard.ThrowIfOutOfRange(maxBuckets, min: 1); this.Scale = scale; + this.PositiveBuckets = new CircularBufferBuckets(maxBuckets); + this.NegativeBuckets = new CircularBufferBuckets(maxBuckets); } internal int Scale @@ -53,8 +56,12 @@ private set } } + internal CircularBufferBuckets PositiveBuckets { get; } + internal long ZeroCount { get; private set; } + internal CircularBufferBuckets NegativeBuckets { get; } + /// public override string ToString() { @@ -117,6 +124,33 @@ public int MapToIndex(double value) } } + public void Record(double value) + { +#if NETCOREAPP3_1_OR_GREATER + if (!double.IsFinite(value)) +#else + if (double.IsInfinity(value) || double.IsNaN(value)) +#endif + { + return; + } + + var c = value.CompareTo(0); + + if (c > 0) + { + this.PositiveBuckets.TryIncrement(this.MapToIndex(value)); + } + else if (c < 0) + { + this.NegativeBuckets.TryIncrement(this.MapToIndex(-value)); + } + else // c == 0 + { + this.ZeroCount++; + } + } + public sealed class IEEE754Double { #pragma warning disable SA1310 // Field name should not contain an underscore From ea34f6c8e60922862ec71ee712d47d82dad49b04 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Fri, 22 Jul 2022 00:00:47 -0700 Subject: [PATCH 2/4] Lookup table --- src/OpenTelemetry/Internal/MathHelper.cs | 97 +++++++++++++++++++ .../Metrics/CircularBufferBuckets.cs | 1 - .../Metrics/ExponentialBucketHistogram.cs | 15 +-- .../Internal/MathHelperTest.cs | 83 ++++++++++++++++ 4 files changed, 182 insertions(+), 14 deletions(-) create mode 100644 src/OpenTelemetry/Internal/MathHelper.cs create mode 100644 test/OpenTelemetry.Tests/Internal/MathHelperTest.cs diff --git a/src/OpenTelemetry/Internal/MathHelper.cs b/src/OpenTelemetry/Internal/MathHelper.cs new file mode 100644 index 00000000000..717edd32594 --- /dev/null +++ b/src/OpenTelemetry/Internal/MathHelper.cs @@ -0,0 +1,97 @@ +// +// 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. +// + +using System.Runtime.CompilerServices; + +namespace OpenTelemetry.Internal; + +internal static class MathHelper +{ + // https://en.wikipedia.org/wiki/Leading_zero + private static readonly byte[] LeadingZeroLookupTable = new byte[] + { + 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LeadingZero8(byte value) + { + return LeadingZeroLookupTable[value]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LeadingZero16(short value) + { + unchecked + { + var high8 = (byte)(value >> 8); + + if (high8 != 0) + { + return LeadingZero8(high8); + } + + return LeadingZero8((byte)value) + 8; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LeadingZero32(int value) + { + unchecked + { + var high16 = (short)(value >> 16); + + if (high16 != 0) + { + return LeadingZero16(high16); + } + + return LeadingZero16((short)value) + 16; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LeadingZero64(long value) + { + unchecked + { + var high32 = (int)(value >> 32); + + if (high32 != 0) + { + return LeadingZero32(high32); + } + + return LeadingZero32((int)value) + 32; + } + } +} diff --git a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs index 7f852633929..322dfd61684 100644 --- a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs +++ b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs @@ -15,7 +15,6 @@ // using System.Runtime.CompilerServices; - using OpenTelemetry.Internal; namespace OpenTelemetry.Metrics; diff --git a/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs b/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs index d11e0e56966..c510322bcfa 100644 --- a/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs +++ b/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs @@ -102,18 +102,7 @@ public int MapToIndex(double value) 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--; - } + exp -= MathHelper.LeadingZero64(fraction - 1) - (64 - IEEE754Double.FRACTION_BITS); } else if (fraction == 0) { @@ -145,7 +134,7 @@ public void Record(double value) { this.NegativeBuckets.TryIncrement(this.MapToIndex(-value)); } - else // c == 0 + else { this.ZeroCount++; } diff --git a/test/OpenTelemetry.Tests/Internal/MathHelperTest.cs b/test/OpenTelemetry.Tests/Internal/MathHelperTest.cs new file mode 100644 index 00000000000..5ae95300632 --- /dev/null +++ b/test/OpenTelemetry.Tests/Internal/MathHelperTest.cs @@ -0,0 +1,83 @@ +// +// 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. +// + +using Xunit; + +namespace OpenTelemetry.Internal.Tests; + +public class MathHelperTest +{ + [Theory] + [InlineData(0b0000_0000, 8)] + [InlineData(0b0000_0001, 7)] + [InlineData(0b0000_0010, 6)] + [InlineData(0b0000_0011, 6)] + [InlineData(0b0000_0100, 5)] + [InlineData(0b0000_0101, 5)] + [InlineData(0b0000_0111, 5)] + [InlineData(0b0000_1000, 4)] + [InlineData(0b0000_1001, 4)] + [InlineData(0b0000_1111, 4)] + [InlineData(0b0001_0000, 3)] + [InlineData(0b0001_0001, 3)] + [InlineData(0b0001_1111, 3)] + [InlineData(0b0010_0000, 2)] + [InlineData(0b0010_0001, 2)] + [InlineData(0b0011_1111, 2)] + [InlineData(0b0100_0000, 1)] + [InlineData(0b0100_0001, 1)] + [InlineData(0b0111_1111, 1)] + [InlineData(0b1000_0000, 0)] + [InlineData(0b1000_0001, 0)] + [InlineData(0b1111_1111, 0)] + public void LeadingZero8(byte value, int numberOfLeaderZeros) + { + Assert.Equal(numberOfLeaderZeros, MathHelper.LeadingZero8(value)); + } + + [Theory] + [InlineData(unchecked((short)0b0000_0000_0000_0000), 16)] + [InlineData(unchecked((short)0b0000_0000_0000_0001), 15)] + [InlineData(unchecked((short)0b0000_0000_1000_0000), 8)] + [InlineData(unchecked((short)0b0000_0001_0000_0000), 7)] + [InlineData(unchecked((short)0b1000_0000_0000_0000), 0)] + public void LeadingZero16(short value, int numberOfLeaderZeros) + { + Assert.Equal(numberOfLeaderZeros, MathHelper.LeadingZero16(value)); + } + + [Theory] + [InlineData(0b0000_0000_0000_0000_0000_0000_0000_0000, 32)] + [InlineData(0b0000_0000_0000_0000_0000_0000_0000_0001, 31)] + [InlineData(0b0000_0000_0000_0000_1000_0000_0000_0000, 16)] + [InlineData(0b0000_0000_0000_0001_0000_0000_0000_0000, 15)] + [InlineData(unchecked((int)0b1000_0000_0000_0000_0000_0000_0000_0000), 0)] + public void LeadingZero32(int value, int numberOfLeaderZeros) + { + Assert.Equal(numberOfLeaderZeros, MathHelper.LeadingZero32(value)); + } + + [Theory] + [InlineData(0b0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000L, 64)] + [InlineData(0b0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0001L, 63)] + [InlineData(0b0000_0000_0000_0000_0000_0000_0000_0000_1000_0000_0000_0000_0000_0000_0000_0000L, 32)] + [InlineData(0b0000_0000_0000_0000_0000_0000_0000_0001_0000_0000_0000_0000_0000_0000_0000_0000L, 31)] + [InlineData(unchecked((long)0b1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000), 0)] + public void LeadingZero64(long value, int numberOfLeaderZeros) + { + Assert.Equal(numberOfLeaderZeros, MathHelper.LeadingZero64(value)); + } +} From 906be33eed70923a9b18c2e7124c5adcced0a202 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Fri, 22 Jul 2022 00:23:21 -0700 Subject: [PATCH 3/4] clean up --- .../Implementation/PrometheusSerializer.cs | 7 +--- src/OpenTelemetry/Internal/MathHelper.cs | 22 +++++++++++ .../Metrics/ExponentialBucketHistogram.cs | 37 +++---------------- 3 files changed, 30 insertions(+), 36 deletions(-) diff --git a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusSerializer.cs b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusSerializer.cs index dfe95008ae8..ac8bd042c62 100644 --- a/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusSerializer.cs +++ b/src/OpenTelemetry.Exporter.Prometheus/Implementation/PrometheusSerializer.cs @@ -20,6 +20,7 @@ using System.Diagnostics; using System.Globalization; using System.Runtime.CompilerServices; +using OpenTelemetry.Internal; namespace OpenTelemetry.Exporter.Prometheus { @@ -39,11 +40,7 @@ internal static partial class PrometheusSerializer [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int WriteDouble(byte[] buffer, int cursor, double value) { -#if NETCOREAPP3_1_OR_GREATER - if (double.IsFinite(value)) -#else - if (!double.IsInfinity(value) && !double.IsNaN(value)) -#endif + if (MathHelper.IsFinite(value)) { #if NETCOREAPP3_1_OR_GREATER Span span = stackalloc char[128]; diff --git a/src/OpenTelemetry/Internal/MathHelper.cs b/src/OpenTelemetry/Internal/MathHelper.cs index 717edd32594..c8f786bb175 100644 --- a/src/OpenTelemetry/Internal/MathHelper.cs +++ b/src/OpenTelemetry/Internal/MathHelper.cs @@ -14,6 +14,7 @@ // limitations under the License. // +using System; using System.Runtime.CompilerServices; namespace OpenTelemetry.Internal; @@ -94,4 +95,25 @@ public static int LeadingZero64(long value) return LeadingZero32((int)value) + 32; } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsFinite(double value) + { +#if NETCOREAPP3_1_OR_GREATER + return double.IsFinite(value); +#else + return !double.IsInfinity(value) && !double.IsNaN(value); +#endif + } + + public static string DoubleToString(double value) + { + var repr = Convert.ToString(BitConverter.DoubleToInt64Bits(value), 2); + return new string('0', 64 - repr.Length) + repr; + } + + public static double DoubleFromString(string value) + { + return BitConverter.Int64BitsToDouble(Convert.ToInt64(value, 2)); + } } diff --git a/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs b/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs index c510322bcfa..7bcba194e62 100644 --- a/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs +++ b/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs @@ -84,7 +84,7 @@ public override string ToString() /// public int MapToIndex(double value) { - Debug.Assert(double.IsFinite(value), "IEEE-754 +Inf, -Inf and NaN should be filtered out before calling this method."); + Debug.Assert(MathHelper.IsFinite(value), "IEEE-754 +Inf, -Inf and NaN should be filtered out before calling this method."); Debug.Assert(value != 0, "IEEE-754 zero values should be handled by ZeroCount."); Debug.Assert(!double.IsNegative(value), "IEEE-754 negative values should be normalized before calling this method."); @@ -97,29 +97,25 @@ public int MapToIndex(double value) else { var bits = BitConverter.DoubleToInt64Bits(value); - var exp = (int)((bits & IEEE754Double.EXPONENT_MASK) >> IEEE754Double.FRACTION_BITS); - var fraction = bits & IEEE754Double.FRACTION_MASK; + var exp = (int)((bits & 0x7FF0000000000000L /* exponent mask */) >> 52 /* fraction width */); + var fraction = bits & 0xFFFFFFFFFFFFFL /* fraction mask */; if (exp == 0) { - exp -= MathHelper.LeadingZero64(fraction - 1) - (64 - IEEE754Double.FRACTION_BITS); + exp -= MathHelper.LeadingZero64(fraction - 1) - 12 /* 64 - fraction width */; } else if (fraction == 0) { exp--; } - return (exp - IEEE754Double.EXPONENT_BIAS) >> -this.Scale; + return (exp - 1023 /* exponent bias */) >> -this.Scale; } } public void Record(double value) { -#if NETCOREAPP3_1_OR_GREATER - if (!double.IsFinite(value)) -#else - if (double.IsInfinity(value) || double.IsNaN(value)) -#endif + if (!MathHelper.IsFinite(value)) { return; } @@ -139,27 +135,6 @@ public void Record(double value) this.ZeroCount++; } } - - 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 + ")"; - } - - public static double FromString(string value) - { - return BitConverter.Int64BitsToDouble(Convert.ToInt64(value, 2)); - } - } } #endif From 91d7404e1ef798842e0e6e83a4351669c3f5d4a9 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Fri, 22 Jul 2022 11:10:58 -0700 Subject: [PATCH 4/4] calculate scale reduction --- .../Metrics/CircularBufferBuckets.cs | 37 ++++++++++---- .../Metrics/CircularBufferBucketsTest.cs | 48 +++++++++++-------- 2 files changed, 55 insertions(+), 30 deletions(-) diff --git a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs index 322dfd61684..175f4977041 100644 --- a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs +++ b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs @@ -64,36 +64,42 @@ public long this[int index] /// /// The index of the bucket. /// - /// Returns true if the increment attempt succeeded; - /// false if the underlying buffer is running out of capacity. + /// Returns 0 if the increment attempt succeeded; + /// Returns a positive integer Math.Ceiling(log_2(X)) if the + /// underlying buffer is running out of capacity, and the buffer has to + /// increase to X * Capacity at minimum. /// /// /// The "index" value can be positive, zero or negative. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryIncrement(int index) + public int TryIncrement(int index) { + var capacity = this.Capacity; + if (this.trait == null) { - this.trait = new long[this.Capacity]; + this.trait = new long[capacity]; this.begin = index; this.end = index; } else if (index > this.end) { - if (index - this.begin >= this.Capacity) + var diff = index - this.begin; + if (diff >= capacity) { - return false; + return CalculateScaleReduction(diff + 1, capacity); } this.end = index; } else if (index < this.begin) { - if (this.end - index >= this.Capacity) + var diff = this.end - index; + if (diff >= this.Capacity) { - return false; + return CalculateScaleReduction(diff + 1, capacity); } this.begin = index; @@ -101,7 +107,20 @@ public bool TryIncrement(int index) this.trait[this.ModuloIndex(index)] += 1; - return true; + return 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + int CalculateScaleReduction(int size, int capacity) + { + var shift = MathHelper.LeadingZero32(capacity) - MathHelper.LeadingZero32(size); + + if (size > (capacity << shift)) + { + shift++; + } + + return shift; + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/test/OpenTelemetry.Tests/Metrics/CircularBufferBucketsTest.cs b/test/OpenTelemetry.Tests/Metrics/CircularBufferBucketsTest.cs index 26f1477c121..ea6b71c338d 100644 --- a/test/OpenTelemetry.Tests/Metrics/CircularBufferBucketsTest.cs +++ b/test/OpenTelemetry.Tests/Metrics/CircularBufferBucketsTest.cs @@ -36,23 +36,29 @@ public void BasicInsertions() Assert.Equal(5, buckets.Capacity); Assert.Equal(0, buckets.Size); - Assert.True(buckets.TryIncrement(0)); + Assert.Equal(0, buckets.TryIncrement(0)); Assert.Equal(1, buckets.Size); - Assert.True(buckets.TryIncrement(1)); + Assert.Equal(0, buckets.TryIncrement(1)); Assert.Equal(2, buckets.Size); - Assert.True(buckets.TryIncrement(3)); + Assert.Equal(0, buckets.TryIncrement(3)); Assert.Equal(4, buckets.Size); - Assert.True(buckets.TryIncrement(4)); + Assert.Equal(0, buckets.TryIncrement(4)); Assert.Equal(5, buckets.Size); - Assert.True(buckets.TryIncrement(2)); + Assert.Equal(0, buckets.TryIncrement(2)); Assert.Equal(5, buckets.Size); - Assert.False(buckets.TryIncrement(5)); - Assert.False(buckets.TryIncrement(-1)); + Assert.Equal(1, buckets.TryIncrement(9)); + Assert.Equal(1, buckets.TryIncrement(5)); + Assert.Equal(1, buckets.TryIncrement(-1)); + Assert.Equal(2, buckets.TryIncrement(10)); + Assert.Equal(2, buckets.TryIncrement(19)); + Assert.Equal(3, buckets.TryIncrement(20)); + Assert.Equal(3, buckets.TryIncrement(39)); + Assert.Equal(4, buckets.TryIncrement(40)); Assert.Equal(5, buckets.Size); } @@ -61,14 +67,14 @@ public void PositiveInsertions() { var buckets = new CircularBufferBuckets(5); - Assert.True(buckets.TryIncrement(102)); - Assert.True(buckets.TryIncrement(103)); - Assert.True(buckets.TryIncrement(101)); - Assert.True(buckets.TryIncrement(100)); - Assert.True(buckets.TryIncrement(104)); + Assert.Equal(0, buckets.TryIncrement(102)); + Assert.Equal(0, buckets.TryIncrement(103)); + Assert.Equal(0, buckets.TryIncrement(101)); + Assert.Equal(0, buckets.TryIncrement(100)); + Assert.Equal(0, buckets.TryIncrement(104)); - Assert.False(buckets.TryIncrement(99)); - Assert.False(buckets.TryIncrement(105)); + Assert.Equal(1, buckets.TryIncrement(99)); + Assert.Equal(1, buckets.TryIncrement(105)); } [Fact] @@ -76,14 +82,14 @@ public void NegativeInsertions() { var buckets = new CircularBufferBuckets(5); - Assert.True(buckets.TryIncrement(2)); - Assert.True(buckets.TryIncrement(0)); - Assert.True(buckets.TryIncrement(-2)); - Assert.True(buckets.TryIncrement(1)); - Assert.True(buckets.TryIncrement(-1)); + Assert.Equal(0, buckets.TryIncrement(2)); + Assert.Equal(0, buckets.TryIncrement(0)); + Assert.Equal(0, buckets.TryIncrement(-2)); + Assert.Equal(0, buckets.TryIncrement(1)); + Assert.Equal(0, buckets.TryIncrement(-1)); - Assert.False(buckets.TryIncrement(3)); - Assert.False(buckets.TryIncrement(-3)); + Assert.Equal(1, buckets.TryIncrement(3)); + Assert.Equal(1, buckets.TryIncrement(-3)); } [Fact]