From 38e1a86da13bc529664cfae1e7a9a9018c9ed375 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Fri, 22 Jul 2022 19:17:10 -0700 Subject: [PATCH 1/6] auto scale --- .../Metrics/CircularBufferBuckets.cs | 37 +++++++++++++++++++ .../Metrics/ExponentialBucketHistogram.cs | 22 ++++++++++- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs index 5c636d8a78f..1e3a86c2df9 100644 --- a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs +++ b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs @@ -14,6 +14,7 @@ // limitations under the License. // +using System.Diagnostics; using System.Runtime.CompilerServices; using OpenTelemetry.Internal; @@ -130,6 +131,28 @@ static int CalculateScaleReduction(int size, int capacity) } } + public void ScaleDown(int n) + { + Debug.Assert(n > 0, "The scale down level must be a positive integer."); + + if (this.trait == null) + { + return; + } + + // TODO: avoid allocating new array by doing the calculation in-place. + var array = new long[this.Capacity]; + + for (var index = this.begin; index <= this.end; index++) + { + array[this.ModuloIndex(index >> n)] += this[index]; + } + + this.begin >>= n; + this.end >>= n; + this.trait = array; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private int ModuloIndex(int value) { @@ -142,4 +165,18 @@ private int ModuloIndex(int value) return value; } + + /* + public override string ToString() + { + return nameof(CircularBufferBuckets) + + "{" + + nameof(this.Capacity) + "=" + this.Capacity + ", " + + nameof(this.Size) + "=" + this.Size + ", " + + nameof(this.begin) + "=" + this.begin + ", " + + nameof(this.end) + "=" + this.end + ", " + + (this.trait == null ? "null" : "[" + string.Join(", ", this.trait) + "]") + + "}"; + } + */ } diff --git a/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs b/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs index 7bcba194e62..0c2f2843244 100644 --- a/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs +++ b/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs @@ -124,11 +124,29 @@ public void Record(double value) if (c > 0) { - this.PositiveBuckets.TryIncrement(this.MapToIndex(value)); + var index = this.MapToIndex(value); + var n = this.PositiveBuckets.TryIncrement(index); + + if (n != 0) + { + this.PositiveBuckets.ScaleDown(n); + this.NegativeBuckets.ScaleDown(n); + n = this.PositiveBuckets.TryIncrement(index); + Debug.Assert(n == 0, "Increment should always succeed after scale down."); + } } else if (c < 0) { - this.NegativeBuckets.TryIncrement(this.MapToIndex(-value)); + var index = this.MapToIndex(-value); + var n = this.NegativeBuckets.TryIncrement(index); + + if (n != 0) + { + this.PositiveBuckets.ScaleDown(n); + this.NegativeBuckets.ScaleDown(n); + n = this.NegativeBuckets.TryIncrement(index); + Debug.Assert(n == 0, "Increment should always succeed after scale down."); + } } else { From 11064804ea2fa3beb45895c9ae4b91dace23f8e5 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Sat, 23 Jul 2022 08:50:09 -0700 Subject: [PATCH 2/6] in-place scale down --- src/OpenTelemetry/Internal/MathHelper.cs | 31 +++++++ .../Metrics/CircularBufferBuckets.cs | 88 ++++++++++++++++--- .../Metrics/CircularBufferBucketsTest.cs | 60 +++++++++++++ 3 files changed, 168 insertions(+), 11 deletions(-) diff --git a/src/OpenTelemetry/Internal/MathHelper.cs b/src/OpenTelemetry/Internal/MathHelper.cs index c8f786bb175..043fcac6654 100644 --- a/src/OpenTelemetry/Internal/MathHelper.cs +++ b/src/OpenTelemetry/Internal/MathHelper.cs @@ -15,6 +15,7 @@ // using System; +using System.Diagnostics; using System.Runtime.CompilerServices; namespace OpenTelemetry.Internal; @@ -96,6 +97,36 @@ public static int LeadingZero64(long value) } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int PositiveModulo32(int value, int divisor) + { + Debug.Assert(divisor > 0, $"{nameof(divisor)} must be a positive integer."); + + value %= divisor; + + if (value < 0) + { + value += divisor; + } + + return value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long PositiveModulo64(long value, long divisor) + { + Debug.Assert(divisor > 0, $"{nameof(divisor)} must be a positive integer."); + + value %= divisor; + + if (value < 0) + { + value += divisor; + } + + return value; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsFinite(double value) { diff --git a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs index 1e3a86c2df9..6dfa2f57ded 100644 --- a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs +++ b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs @@ -23,7 +23,7 @@ namespace OpenTelemetry.Metrics; /// /// A histogram buckets implementation based on circular buffer. /// -internal sealed class CircularBufferBuckets +public sealed class CircularBufferBuckets { private long[] trait; private int begin = 0; @@ -131,7 +131,7 @@ static int CalculateScaleReduction(int size, int capacity) } } - public void ScaleDown(int n) + public void ScaleDownOld(int n) { Debug.Assert(n > 0, "The scale down level must be a positive integer."); @@ -143,30 +143,91 @@ public void ScaleDown(int n) // TODO: avoid allocating new array by doing the calculation in-place. var array = new long[this.Capacity]; - for (var index = this.begin; index <= this.end; index++) + for (var index = this.begin; index < this.end; index++) { array[this.ModuloIndex(index >> n)] += this[index]; } + array[this.ModuloIndex(this.end >> n)] += this[this.end]; + this.begin >>= n; this.end >>= n; this.trait = array; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int ModuloIndex(int value) + public void ScaleDown(int n) { - value %= this.Capacity; + Debug.Assert(n > 0, "The scale down level must be a positive integer."); + + if (this.trait == null) + { + return; + } + + uint capacity = (uint)this.Capacity; + + var offset = (uint)this.ModuloIndex(this.begin); // offset [0, capacity), where capacity is between [1, 2147483647] + var currentBegin = this.begin; + var currentEnd = this.end; + + for (int i = 0; i < n; i++) + { + var newBegin = currentBegin >> 1; + var newEnd = currentEnd >> 1; + + if (currentBegin != currentEnd) + { + if (currentBegin % 2 == 0) + { + ScaleDownInternal(this.trait, offset, currentBegin, currentEnd, capacity); + } + else + { + currentBegin++; + + if (currentBegin != currentEnd) + { + ScaleDownInternal(this.trait, offset + 1, currentBegin, currentEnd, capacity); + } + } + } + + currentBegin = newBegin; + currentEnd = newEnd; + } + + this.begin = currentBegin; + this.end = currentEnd; + // Move(this.trait, this.begin, this.begin + newSize - 1, newBegin, capacity); - if (value < 0) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void ScaleDownInternal(long[] array, uint offset, int begin, int end, uint capacity) { - value += this.Capacity; + // System.Console.WriteLine($"ScaleDownInternal({offset}, {begin}, {end})"); + + for (var index = begin + 1; index < end; index++) + { + Consolidate(array, (offset + (uint)(index - begin)) % capacity, (offset + (uint)((index >> 1) - (begin >> 1))) % capacity); + } + + Consolidate(array, (offset + (uint)(end - begin)) % capacity, (offset + (uint)((end >> 1) - (begin >> 1))) % capacity); } - return value; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void Consolidate(long[] array, uint src, uint dst) + { + // System.Console.WriteLine($"Consolidate({src} => {dst})"); + array[dst] += array[src]; + array[src] = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void Move(long[] array, int begin, int end, int dst, int capacity) + { + // TODO + } } - /* public override string ToString() { return nameof(CircularBufferBuckets) @@ -178,5 +239,10 @@ public override string ToString() + (this.trait == null ? "null" : "[" + string.Join(", ", this.trait) + "]") + "}"; } - */ + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ModuloIndex(int value) + { + return MathHelper.PositiveModulo32(value, this.Capacity); + } } diff --git a/test/OpenTelemetry.Tests/Metrics/CircularBufferBucketsTest.cs b/test/OpenTelemetry.Tests/Metrics/CircularBufferBucketsTest.cs index 419cfc9400f..2c827b5af7b 100644 --- a/test/OpenTelemetry.Tests/Metrics/CircularBufferBucketsTest.cs +++ b/test/OpenTelemetry.Tests/Metrics/CircularBufferBucketsTest.cs @@ -132,4 +132,64 @@ public void IndexOperations() Assert.Equal(4, buckets[1]); Assert.Equal(5, buckets[2]); } + + [Fact] + public void EmptyScaleDown() + { + var buckets = new CircularBufferBuckets(1); + + buckets.ScaleDown(1); + buckets.ScaleDown(2); + buckets.ScaleDown(3); + buckets.ScaleDown(4); + + buckets.TryIncrement(0); + Assert.Equal(1, buckets[0]); + } + + [Fact] + public void BasicScaleDown() + { + var buckets = new CircularBufferBuckets(7); + + buckets.TryIncrement(60); + buckets.TryIncrement(61); + buckets.TryIncrement(62); + buckets.TryIncrement(63); + buckets.TryIncrement(64); + buckets.TryIncrement(65); + buckets.TryIncrement(66); + buckets.TryIncrement(67); + + buckets.ScaleDown(1); + + Assert.Equal(2, buckets[30]); + Assert.Equal(2, buckets[31]); + Assert.Equal(2, buckets[32]); + Assert.Equal(1, buckets[33]); + } + + [Fact] + public void ScaleDownIntMaxValue() + { + var buckets = new CircularBufferBuckets(1); + + buckets.TryIncrement(int.MaxValue); + + buckets.ScaleDown(1); + + Assert.Equal(1, buckets[0x3FFFFFFF]); + } + + [Fact] + public void ScaleDownIntMinValue() + { + var buckets = new CircularBufferBuckets(1); + + buckets.TryIncrement(int.MinValue); + + buckets.ScaleDown(1); + + Assert.Equal(1, buckets[-0x40000000]); + } } From 3d7ea8267ae99a77dc276e81b1a453b5814f230a Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Sat, 23 Jul 2022 19:21:51 -0700 Subject: [PATCH 3/6] refine --- .../Metrics/CircularBufferBuckets.cs | 47 +++++-------------- 1 file changed, 12 insertions(+), 35 deletions(-) diff --git a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs index 6dfa2f57ded..7105558241b 100644 --- a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs +++ b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs @@ -23,7 +23,7 @@ namespace OpenTelemetry.Metrics; /// /// A histogram buckets implementation based on circular buffer. /// -public sealed class CircularBufferBuckets +internal sealed class CircularBufferBuckets { private long[] trait; private int begin = 0; @@ -61,9 +61,10 @@ public long this[int index] } /// - /// Attempts to increment the value of Bucket[index]. + /// Attempts to increment the value of Bucket[index] by value. /// /// The index of the bucket. + /// The increment. /// /// Returns 0 if the increment attempt succeeded; /// Returns a positive integer Math.Ceiling(log_2(X)) if the @@ -74,7 +75,7 @@ public long this[int index] /// The "index" value can be positive, zero or negative. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int TryIncrement(int index) + public int TryIncrement(int index, long value = 1) { var capacity = this.Capacity; @@ -108,7 +109,7 @@ public int TryIncrement(int index) this.begin = index; } - this.trait[this.ModuloIndex(index)] += 1; + this.trait[this.ModuloIndex(index)] += value; return 0; @@ -131,30 +132,6 @@ static int CalculateScaleReduction(int size, int capacity) } } - public void ScaleDownOld(int n) - { - Debug.Assert(n > 0, "The scale down level must be a positive integer."); - - if (this.trait == null) - { - return; - } - - // TODO: avoid allocating new array by doing the calculation in-place. - var array = new long[this.Capacity]; - - for (var index = this.begin; index < this.end; index++) - { - array[this.ModuloIndex(index >> n)] += this[index]; - } - - array[this.ModuloIndex(this.end >> n)] += this[this.end]; - - this.begin >>= n; - this.end >>= n; - this.trait = array; - } - public void ScaleDown(int n) { Debug.Assert(n > 0, "The scale down level must be a positive integer."); @@ -164,9 +141,10 @@ public void ScaleDown(int n) return; } + // 0 <= offset < capacity <= 2147483647 uint capacity = (uint)this.Capacity; + var offset = (uint)this.ModuloIndex(this.begin); - var offset = (uint)this.ModuloIndex(this.begin); // offset [0, capacity), where capacity is between [1, 2147483647] var currentBegin = this.begin; var currentEnd = this.end; @@ -198,13 +176,14 @@ public void ScaleDown(int n) this.begin = currentBegin; this.end = currentEnd; - // Move(this.trait, this.begin, this.begin + newSize - 1, newBegin, capacity); + + Move(this.trait, offset, this.begin, this.end, capacity); + + return; [MethodImpl(MethodImplOptions.AggressiveInlining)] static void ScaleDownInternal(long[] array, uint offset, int begin, int end, uint capacity) { - // System.Console.WriteLine($"ScaleDownInternal({offset}, {begin}, {end})"); - for (var index = begin + 1; index < end; index++) { Consolidate(array, (offset + (uint)(index - begin)) % capacity, (offset + (uint)((index >> 1) - (begin >> 1))) % capacity); @@ -216,15 +195,13 @@ static void ScaleDownInternal(long[] array, uint offset, int begin, int end, uin [MethodImpl(MethodImplOptions.AggressiveInlining)] static void Consolidate(long[] array, uint src, uint dst) { - // System.Console.WriteLine($"Consolidate({src} => {dst})"); array[dst] += array[src]; array[src] = 0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - static void Move(long[] array, int begin, int end, int dst, int capacity) + static void Move(long[] array, uint offset, int begin, int end, uint capacity) { - // TODO } } From a5adfabffe339130a9e078b1af9015db3184dbbd Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Sun, 24 Jul 2022 07:41:28 -0700 Subject: [PATCH 4/6] in-place downscale --- .../Metrics/CircularBufferBuckets.cs | 76 ++++- .../Metrics/CircularBufferBucketsTest.cs | 264 ++++++++++++++++-- 2 files changed, 309 insertions(+), 31 deletions(-) diff --git a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs index 7105558241b..764fa21f4be 100644 --- a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs +++ b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs @@ -41,6 +41,11 @@ public CircularBufferBuckets(int capacity) /// public int Capacity { get; } + /// + /// Gets the size of the . + /// + public int Offset => this.begin; + /// /// Gets the size of the . /// @@ -67,9 +72,8 @@ public long this[int index] /// The increment. /// /// 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. + /// Returns a positive integer indicating the minimum scale reduction level + /// if the increment attempt failed. /// /// /// The "index" value can be positive, zero or negative. @@ -132,9 +136,9 @@ static int CalculateScaleReduction(int size, int capacity) } } - public void ScaleDown(int n) + public void ScaleDown(int level = 1) { - Debug.Assert(n > 0, "The scale down level must be a positive integer."); + Debug.Assert(level > 0, "The scale down level must be a positive integer."); if (this.trait == null) { @@ -148,7 +152,7 @@ public void ScaleDown(int n) var currentBegin = this.begin; var currentEnd = this.end; - for (int i = 0; i < n; i++) + for (int i = 0; i < level; i++) { var newBegin = currentBegin >> 1; var newEnd = currentEnd >> 1; @@ -177,7 +181,10 @@ public void ScaleDown(int n) this.begin = currentBegin; this.end = currentEnd; - Move(this.trait, offset, this.begin, this.end, capacity); + if (capacity > 1) + { + AdjustPosition(this.trait, offset, (uint)this.ModuloIndex(currentBegin), (uint)(currentEnd - currentBegin + 1), capacity); + } return; @@ -192,6 +199,47 @@ static void ScaleDownInternal(long[] array, uint offset, int begin, int end, uin Consolidate(array, (offset + (uint)(end - begin)) % capacity, (offset + (uint)((end >> 1) - (begin >> 1))) % capacity); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void AdjustPosition(long[] array, uint src, uint dst, uint size, uint capacity) + { + if (src == dst) + { + return; + } + + var advancement = (dst + capacity - src) % capacity; + + if ((size - 1) << 1 == capacity && advancement << 1 == capacity) + { + Exchange(array, src++, dst++); + + size -= 2; + + if (size == 0) + { + return; + } + } + + if (advancement >= size) + { + while (size-- != 0) + { + Move(array, src++ % capacity, dst++ % capacity); + } + } + else + { + src = src + size - 1; + dst = dst + size - 1; + + while (size-- != 0) + { + Move(array, src-- % capacity, dst-- % capacity); + } + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] static void Consolidate(long[] array, uint src, uint dst) { @@ -200,8 +248,18 @@ static void Consolidate(long[] array, uint src, uint dst) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - static void Move(long[] array, uint offset, int begin, int end, uint capacity) + static void Exchange(long[] array, uint src, uint dst) + { + var value = array[dst]; + array[dst] = array[src]; + array[src] = value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void Move(long[] array, uint src, uint dst) { + array[dst] = array[src]; + array[src] = 0; } } @@ -213,7 +271,7 @@ public override string ToString() + nameof(this.Size) + "=" + this.Size + ", " + nameof(this.begin) + "=" + this.begin + ", " + nameof(this.end) + "=" + this.end + ", " - + (this.trait == null ? "null" : "[" + string.Join(", ", this.trait) + "]") + + (this.trait == null ? "null" : "{" + string.Join(", ", this.trait) + "}") + "}"; } diff --git a/test/OpenTelemetry.Tests/Metrics/CircularBufferBucketsTest.cs b/test/OpenTelemetry.Tests/Metrics/CircularBufferBucketsTest.cs index 2c827b5af7b..68122562250 100644 --- a/test/OpenTelemetry.Tests/Metrics/CircularBufferBucketsTest.cs +++ b/test/OpenTelemetry.Tests/Metrics/CircularBufferBucketsTest.cs @@ -22,7 +22,7 @@ namespace OpenTelemetry.Metrics.Tests; public class CircularBufferBucketsTest { [Fact] - public void Constructor() + public void ConstructorThrowsOnInvalidCapacity() { Assert.Throws(() => new CircularBufferBuckets(0)); Assert.Throws(() => new CircularBufferBuckets(-1)); @@ -73,6 +73,9 @@ public void PositiveInsertions() Assert.Equal(0, buckets.TryIncrement(100)); Assert.Equal(0, buckets.TryIncrement(104)); + Assert.Equal(100, buckets.Offset); + Assert.Equal(5, buckets.Size); + Assert.Equal(1, buckets.TryIncrement(99)); Assert.Equal(1, buckets.TryIncrement(105)); } @@ -88,6 +91,9 @@ public void NegativeInsertions() Assert.Equal(0, buckets.TryIncrement(1)); Assert.Equal(0, buckets.TryIncrement(-1)); + Assert.Equal(-2, buckets.Offset); + Assert.Equal(5, buckets.Size); + Assert.Equal(1, buckets.TryIncrement(3)); Assert.Equal(1, buckets.TryIncrement(-3)); } @@ -98,6 +104,10 @@ public void IntegerOverflow() var buckets = new CircularBufferBuckets(1); Assert.Equal(0, buckets.TryIncrement(int.MaxValue)); + + Assert.Equal(int.MaxValue, buckets.Offset); + Assert.Equal(1, buckets.Size); + Assert.Equal(31, buckets.TryIncrement(1)); Assert.Equal(31, buckets.TryIncrement(0)); Assert.Equal(32, buckets.TryIncrement(-1)); @@ -126,6 +136,8 @@ public void IndexOperations() buckets.TryIncrement(-1); buckets.TryIncrement(-1); + Assert.Equal(-2, buckets.Offset); + Assert.Equal(1, buckets[-2]); Assert.Equal(2, buckets[-1]); Assert.Equal(3, buckets[0]); @@ -134,7 +146,7 @@ public void IndexOperations() } [Fact] - public void EmptyScaleDown() + public void ScaleDownCapacity1() { var buckets = new CircularBufferBuckets(1); @@ -144,52 +156,260 @@ public void EmptyScaleDown() buckets.ScaleDown(4); buckets.TryIncrement(0); + + Assert.Equal(0, buckets.Offset); + Assert.Equal(1, buckets.Size); Assert.Equal(1, buckets[0]); } [Fact] - public void BasicScaleDown() + public void ScaleDownIntMaxValue() { - var buckets = new CircularBufferBuckets(7); + var buckets = new CircularBufferBuckets(1); + + buckets.TryIncrement(int.MaxValue); - buckets.TryIncrement(60); - buckets.TryIncrement(61); - buckets.TryIncrement(62); - buckets.TryIncrement(63); - buckets.TryIncrement(64); - buckets.TryIncrement(65); - buckets.TryIncrement(66); - buckets.TryIncrement(67); + Assert.Equal(int.MaxValue, buckets.Offset); buckets.ScaleDown(1); - Assert.Equal(2, buckets[30]); - Assert.Equal(2, buckets[31]); - Assert.Equal(2, buckets[32]); - Assert.Equal(1, buckets[33]); + Assert.Equal(0x3FFFFFFF, buckets.Offset); + Assert.Equal(1, buckets[0x3FFFFFFF]); } [Fact] - public void ScaleDownIntMaxValue() + public void ScaleDownIntMinValue() { var buckets = new CircularBufferBuckets(1); + buckets.TryIncrement(int.MinValue); + + Assert.Equal(int.MinValue, buckets.Offset); + + buckets.ScaleDown(1); + + Assert.Equal(-0x40000000, buckets.Offset); + Assert.Equal(1, buckets[-0x40000000]); + } + + [Fact] + public void ScaleDownCapacity2() + { + var buckets = new CircularBufferBuckets(2); + + buckets.TryIncrement(int.MinValue, 2); + buckets.TryIncrement(int.MinValue + 1); + buckets.ScaleDown(1); + + Assert.Equal(1, buckets.Size); + Assert.Equal(3, buckets[buckets.Offset]); + + buckets = new CircularBufferBuckets(2); + + buckets.TryIncrement(int.MaxValue - 1, 2); buckets.TryIncrement(int.MaxValue); + buckets.ScaleDown(1); + Assert.Equal(1, buckets.Size); + Assert.Equal(3, buckets[buckets.Offset]); + Assert.Equal(0, buckets[buckets.Offset + 1]); + + buckets = new CircularBufferBuckets(2); + + buckets.TryIncrement(int.MaxValue - 2, 2); + buckets.TryIncrement(int.MaxValue - 1); buckets.ScaleDown(1); - Assert.Equal(1, buckets[0x3FFFFFFF]); + Assert.Equal(2, buckets.Size); + Assert.Equal(2, buckets[buckets.Offset]); + Assert.Equal(1, buckets[buckets.Offset + 1]); } [Fact] - public void ScaleDownIntMinValue() + public void ScaleDownCapacity3() { - var buckets = new CircularBufferBuckets(1); + var buckets = new CircularBufferBuckets(3); - buckets.TryIncrement(int.MinValue); + buckets.TryIncrement(0, 2); + buckets.TryIncrement(1, 4); + buckets.TryIncrement(2, 8); + buckets.ScaleDown(1); + + Assert.Equal(0, buckets.Offset); + Assert.Equal(2, buckets.Size); + Assert.Equal(6, buckets[buckets.Offset]); + Assert.Equal(8, buckets[buckets.Offset + 1]); + buckets = new CircularBufferBuckets(3); + + buckets.TryIncrement(1, 2); + buckets.TryIncrement(2, 4); + buckets.TryIncrement(3, 8); buckets.ScaleDown(1); - Assert.Equal(1, buckets[-0x40000000]); + Assert.Equal(0, buckets.Offset); + Assert.Equal(2, buckets.Size); + Assert.Equal(2, buckets[buckets.Offset]); + Assert.Equal(12, buckets[buckets.Offset + 1]); + + buckets = new CircularBufferBuckets(3); + + buckets.TryIncrement(2, 2); + buckets.TryIncrement(3, 4); + buckets.TryIncrement(4, 8); + buckets.ScaleDown(1); + + Assert.Equal(1, buckets.Offset); + Assert.Equal(2, buckets.Size); + Assert.Equal(6, buckets[buckets.Offset]); + Assert.Equal(8, buckets[buckets.Offset + 1]); + + buckets = new CircularBufferBuckets(3); + + buckets.TryIncrement(3, 2); + buckets.TryIncrement(4, 4); + buckets.TryIncrement(5, 8); + buckets.ScaleDown(1); + + Assert.Equal(1, buckets.Offset); + Assert.Equal(2, buckets.Size); + Assert.Equal(2, buckets[buckets.Offset]); + Assert.Equal(12, buckets[buckets.Offset + 1]); + + buckets = new CircularBufferBuckets(3); + + buckets.TryIncrement(4, 2); + buckets.TryIncrement(5, 4); + buckets.TryIncrement(6, 8); + buckets.ScaleDown(1); + + Assert.Equal(2, buckets.Offset); + Assert.Equal(2, buckets.Size); + Assert.Equal(6, buckets[buckets.Offset]); + Assert.Equal(8, buckets[buckets.Offset + 1]); + + buckets = new CircularBufferBuckets(3); + + buckets.TryIncrement(5, 2); + buckets.TryIncrement(6, 4); + buckets.TryIncrement(7, 8); + buckets.ScaleDown(1); + + Assert.Equal(2, buckets.Offset); + Assert.Equal(2, buckets.Size); + Assert.Equal(2, buckets[buckets.Offset]); + Assert.Equal(12, buckets[buckets.Offset + 1]); + } + + [Fact] + public void ScaleDownCapacity4() + { + var buckets = new CircularBufferBuckets(4); + + buckets.TryIncrement(0, 2); + buckets.TryIncrement(1, 4); + buckets.TryIncrement(2, 8); + buckets.TryIncrement(2, 16); + buckets.ScaleDown(1); + + Assert.Equal(0, buckets.Offset); + Assert.Equal(2, buckets.Size); + Assert.Equal(6, buckets[buckets.Offset]); + Assert.Equal(24, buckets[buckets.Offset + 1]); + + buckets = new CircularBufferBuckets(4); + + buckets.TryIncrement(1, 2); + buckets.TryIncrement(2, 4); + buckets.TryIncrement(3, 8); + buckets.TryIncrement(4, 16); + buckets.ScaleDown(1); + + Assert.Equal(0, buckets.Offset); + Assert.Equal(3, buckets.Size); + Assert.Equal(2, buckets[buckets.Offset]); + Assert.Equal(12, buckets[buckets.Offset + 1]); + Assert.Equal(16, buckets[buckets.Offset + 2]); + + buckets = new CircularBufferBuckets(4); + + buckets.TryIncrement(2, 2); + buckets.TryIncrement(3, 4); + buckets.TryIncrement(4, 8); + buckets.TryIncrement(5, 16); + buckets.ScaleDown(1); + + Assert.Equal(1, buckets.Offset); + Assert.Equal(2, buckets.Size); + Assert.Equal(6, buckets[buckets.Offset]); + Assert.Equal(24, buckets[buckets.Offset + 1]); + + buckets = new CircularBufferBuckets(4); + + buckets.TryIncrement(3, 2); + buckets.TryIncrement(4, 4); + buckets.TryIncrement(5, 8); + buckets.TryIncrement(6, 16); + buckets.ScaleDown(1); + + Assert.Equal(1, buckets.Offset); + Assert.Equal(3, buckets.Size); + Assert.Equal(2, buckets[buckets.Offset]); + Assert.Equal(12, buckets[buckets.Offset + 1]); + Assert.Equal(16, buckets[buckets.Offset + 2]); + + buckets = new CircularBufferBuckets(4); + + buckets.TryIncrement(4, 2); + buckets.TryIncrement(5, 4); + buckets.TryIncrement(6, 8); + buckets.TryIncrement(7, 16); + buckets.ScaleDown(1); + + Assert.Equal(2, buckets.Offset); + Assert.Equal(2, buckets.Size); + Assert.Equal(6, buckets[buckets.Offset]); + Assert.Equal(24, buckets[buckets.Offset + 1]); + + buckets = new CircularBufferBuckets(4); + + buckets.TryIncrement(5, 2); + buckets.TryIncrement(6, 4); + buckets.TryIncrement(7, 8); + buckets.TryIncrement(8, 16); + buckets.ScaleDown(1); + + Assert.Equal(2, buckets.Offset); + Assert.Equal(3, buckets.Size); + Assert.Equal(2, buckets[buckets.Offset]); + Assert.Equal(12, buckets[buckets.Offset + 1]); + Assert.Equal(16, buckets[buckets.Offset + 2]); + + buckets = new CircularBufferBuckets(4); + + buckets.TryIncrement(6, 2); + buckets.TryIncrement(7, 4); + buckets.TryIncrement(8, 8); + buckets.TryIncrement(9, 16); + buckets.ScaleDown(1); + + Assert.Equal(3, buckets.Offset); + Assert.Equal(2, buckets.Size); + Assert.Equal(6, buckets[buckets.Offset]); + Assert.Equal(24, buckets[buckets.Offset + 1]); + + buckets = new CircularBufferBuckets(4); + + buckets.TryIncrement(7, 2); + buckets.TryIncrement(8, 4); + buckets.TryIncrement(9, 8); + buckets.TryIncrement(10, 16); + buckets.ScaleDown(1); + + Assert.Equal(3, buckets.Offset); + Assert.Equal(3, buckets.Size); + Assert.Equal(2, buckets[buckets.Offset]); + Assert.Equal(12, buckets[buckets.Offset + 1]); + Assert.Equal(16, buckets[buckets.Offset + 2]); } } From 885010a7b7c46f5a40b4addaa5e6b29e217ff4af Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Sun, 24 Jul 2022 18:26:49 -0700 Subject: [PATCH 5/6] minor simplification --- .../Metrics/CircularBufferBuckets.cs | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs index 764fa21f4be..13086ab5ca7 100644 --- a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs +++ b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs @@ -202,33 +202,19 @@ static void ScaleDownInternal(long[] array, uint offset, int begin, int end, uin [MethodImpl(MethodImplOptions.AggressiveInlining)] static void AdjustPosition(long[] array, uint src, uint dst, uint size, uint capacity) { - if (src == dst) + var advancement = (dst + capacity - src) % capacity; + + if (advancement == 0) { return; } - var advancement = (dst + capacity - src) % capacity; - - if ((size - 1) << 1 == capacity && advancement << 1 == capacity) + if (size - 1 == advancement && advancement << 1 == capacity) { Exchange(array, src++, dst++); - size -= 2; - - if (size == 0) - { - return; - } } - - if (advancement >= size) - { - while (size-- != 0) - { - Move(array, src++ % capacity, dst++ % capacity); - } - } - else + else if (advancement < size) { src = src + size - 1; dst = dst + size - 1; @@ -237,6 +223,13 @@ static void AdjustPosition(long[] array, uint src, uint dst, uint size, uint cap { Move(array, src-- % capacity, dst-- % capacity); } + + return; + } + + while (size-- != 0) + { + Move(array, src++ % capacity, dst++ % capacity); } } From b3a812c91d829b80dd6fba4a50c8f75132777b01 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Mon, 25 Jul 2022 10:04:48 -0700 Subject: [PATCH 6/6] Update src/OpenTelemetry/Metrics/CircularBufferBuckets.cs Co-authored-by: Alan West <3676547+alanwest@users.noreply.github.com> --- src/OpenTelemetry/Metrics/CircularBufferBuckets.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs index 13086ab5ca7..06d318a25a7 100644 --- a/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs +++ b/src/OpenTelemetry/Metrics/CircularBufferBuckets.cs @@ -42,7 +42,7 @@ public CircularBufferBuckets(int capacity) public int Capacity { get; } /// - /// Gets the size of the . + /// Gets the offset of the start index for the . /// public int Offset => this.begin;