From 4325b4ed34ededd630ce85ac4b4412f59d9b6bdd Mon Sep 17 00:00:00 2001 From: Changhyun Kim Date: Fri, 21 Aug 2026 15:09:45 +0900 Subject: [PATCH 1/2] perf(histogram): render the export list straight from the store iterator Drops two intermediate arrays per scrape. Numbers are in the #804 thread. Signed-off-by: Changhyun Kim --- CHANGELOG.md | 1 + lib/histogram.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61b4bf66..a4a2f6e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ project adheres to [Semantic Versioning](http://semver.org/). ### Changed - Organized default metrics +- perf: Histogram rendering builds its export list straight from the store iterator instead of an intermediate array. Faster at high series counts on Node 24 and 26, can be slightly slower on Node 22 ### Added diff --git a/lib/histogram.js b/lib/histogram.js index c87852c3..3265b2e5 100644 --- a/lib/histogram.js +++ b/lib/histogram.js @@ -120,8 +120,8 @@ class Histogram extends Metric { const v = this.collect(); if (v instanceof Promise) await v; } - const data = Array.from(this.store.values()); - const values = data + const values = this.store + .values() .map(extractBucketValuesForExport(this)) .reduce(addSumAndCountForExport(this), []); From 8ad471b4ce45ec715c6a924846fe9dfe8d84224e Mon Sep 17 00:00:00 2001 From: Changhyun Kim Date: Sun, 23 Aug 2026 21:31:38 +0900 Subject: [PATCH 2/2] Add benchmarks for histogram.get() The read path was only ever timed through the registry suite, alongside counter collection and the rest of the export work. Signed-off-by: Changhyun Kim --- benchmarks/histogram.js | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/benchmarks/histogram.js b/benchmarks/histogram.js index 9e51dcfa..0a940632 100644 --- a/benchmarks/histogram.js +++ b/benchmarks/histogram.js @@ -14,7 +14,11 @@ 'use strict'; -const { getLabelNames, labelCombinationFactory } = require('./utils/labels'); +const { + getLabelNames, + getLabelCombinations, + labelCombinationFactory, +} = require('./utils/labels'); module.exports = setupHistogramSuite; @@ -100,6 +104,19 @@ function setupHistogramSuite(suite) { ), { teardown, setup: setup(6) }, ); + + // `method` repeats `delete`, so `1 x 64` stores 63 label sets and `2 x 8` stores 56. + [ + { name: 'no labels', counts: [] }, + { name: '1 x 64', counts: [64] }, + { name: '2 x 8', counts: [8, 8] }, + { name: '6 x 2', counts: [2, 2, 2, 2, 2, 2] }, + ].forEach(({ name, counts }) => { + suite.add(`get() ${name}`, (client, { histogram }) => histogram.get(), { + teardown, + setup: observed(counts), + }); + }); } function setup(labelCount) { @@ -117,6 +134,25 @@ function setup(labelCount) { }; } +function observed(labelCounts) { + return client => { + const registry = new client.Registry(); + + const histogram = new client.Histogram({ + name: 'histogram', + help: 'histogram', + labelNames: getLabelNames(labelCounts.length), + registers: [registry], + }); + + getLabelCombinations(labelCounts).forEach(labels => + histogram.observe({ ...labels }, 1), + ); + + return { registry, histogram }; + }; +} + function teardown(client, { registry }) { registry.clear(); }