From e4468f847d70f6821c9bc069dc4cfe0f28cd1b1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Szymon=20=C5=81=C4=85giewka?= Date: Wed, 9 Sep 2026 16:20:25 +0200 Subject: [PATCH] fix: fill TimeWindowQuantiles with independent TDigest instances MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Current option stores the same TDigest reference for each item in the ringBuffer. This results in skewed results after rotation. Signed-off-by: Szymon Łągiewka --- CHANGELOG.md | 1 + lib/timeWindowQuantiles.js | 5 ++++- test/timeWindowQuantilesTest.js | 8 ++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de63ceba..53d5e94d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ project adheres to [Semantic Versioning](http://semver.org/). - perf: Remove truthy conditionals from default metric collectors - fix: Preserve zero-valued Counter exemplars - perf: Remove object and array fallback truthiness from core metric paths +- fix: use independent TDigest stores in TimeWindowQuantiles ### Added diff --git a/lib/timeWindowQuantiles.js b/lib/timeWindowQuantiles.js index 357e7dfd..34a76151 100644 --- a/lib/timeWindowQuantiles.js +++ b/lib/timeWindowQuantiles.js @@ -23,7 +23,10 @@ class TimeWindowQuantiles { this.shouldRotate = maxAgeSeconds && ageBuckets; - this.ringBuffer = Array(ageBuckets).fill(new TDigest()); + this.ringBuffer = Array.from( + { length: ageBuckets || 1 }, + () => new TDigest(), + ); this.currentBuffer = 0; this.lastRotateTimestampMillis = Date.now(); diff --git a/test/timeWindowQuantilesTest.js b/test/timeWindowQuantilesTest.js index 8adc9bd1..52226273 100644 --- a/test/timeWindowQuantilesTest.js +++ b/test/timeWindowQuantilesTest.js @@ -88,5 +88,13 @@ describe('timeWindowQuantiles', () => { expect(td.centroids.size).toEqual(0); }); }); + + it('should keep buckets independent during rotation', () => { + instance.push(0); + jest.advanceTimersByTime(1001); + instance.push(100); + + expect(instance.percentile(0.5)).toEqual(50); + }); }); });