From 00dd7818eb416caf7de0a92451d8d1a15073ec1b Mon Sep 17 00:00:00 2001 From: Jason Marshall Date: Fri, 2 Jan 2026 23:03:01 -0800 Subject: [PATCH 1/5] Enable benchmarks for Utils that have landed on trunk before continuing to attempt to merge Matteo's commits. Remove kludge used in benchmark-regression code to see into the utils. Faceoff has a better way to handle this. --- benchmarks/util.js | 2 +- index.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/benchmarks/util.js b/benchmarks/util.js index 2d0c41cb..b21c8152 100644 --- a/benchmarks/util.js +++ b/benchmarks/util.js @@ -5,7 +5,7 @@ const Path = require('path'); module.exports = setupUtilSuite; function setupUtilSuite(suite) { - const skip = ['prom-client@latest', 'prom-client@trunk']; + const skip = ['prom-client@latest']; suite.add( 'hashObject', diff --git a/index.js b/index.js index dfef5adc..dc752cd2 100644 --- a/index.js +++ b/index.js @@ -38,4 +38,3 @@ exports.ClusterRegistry = require('./lib/cluster'); exports.WorkerRegistry = require('./lib/worker'); /** @deprecated */ exports.AggregatorRegistry = exports.ClusterRegistry; -exports[Symbol('util')] = require('./lib/util'); From 3b47245b604e120b2c55ea25e66537cd27b6e910 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Sun, 9 Nov 2025 13:09:15 +0100 Subject: [PATCH 2/5] perf: optimize string escaping for better metrics serialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Refactor escapeLabelValue() and escapeString() to single-pass traversal - Eliminate multiple .replace() calls in string escaping Performance improvements: - ~4% improvement in registry.metrics() serialization (3,160 -> 3,298 calls/sec) - Reduced average time per call from 0.316ms to 0.303ms - More efficient string processing with switch-based character escaping 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Modified by Jason Marshall --- lib/registry.js | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/lib/registry.js b/lib/registry.js index 4d0906b1..f6257820 100644 --- a/lib/registry.js +++ b/lib/registry.js @@ -293,11 +293,46 @@ function escapeLabelValue(str) { if (typeof str !== 'string') { return str; } - return escapeString(str).replace(/"/g, '\\"'); + + let result = ''; + for (let i = 0; i < str.length; i++) { + const char = str[i]; + switch (char) { + case '\\': + result += '\\\\'; + break; + case '\n': + result += '\\n'; + break; + case '"': + result += '\\"'; + break; + default: + result += char; + } + } + + return result; } + function escapeString(str) { - return str.replace(/\\/g, '\\\\').replace(/\n/g, '\\n'); + let result = ''; + for (let i = 0; i < str.length; i++) { + const char = str[i]; + switch (char) { + case '\\': + result += '\\\\'; + break; + case '\n': + result += '\\n'; + break; + default: + result += char; + } + } + return result; } + function standardizeCounterName(name) { return name.replace(/_total$/, ''); } From 98b270d67d4e404686a474602b50d0d315a647d2 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 17 Nov 2025 11:32:04 +0100 Subject: [PATCH 3/5] perf: avoid array conversion in getMetricsAsJSON MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace this.getMetricsAsArray() with direct iteration over this._metrics.values() to eliminate unnecessary Array.from() conversion. Performance improvement: ~1.3% faster (3,216 -> 3,259 calls/sec) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- lib/registry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/registry.js b/lib/registry.js index f6257820..c9451717 100644 --- a/lib/registry.js +++ b/lib/registry.js @@ -137,7 +137,7 @@ class Registry { const promises = []; - for (const metric of this.getMetricsAsArray()) { + for (const metric of this._metrics.values()) { promises.push(metric.get()); } From 34fd4af7a8a01dc1b7a31213d32ae6064586d1f3 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Mon, 17 Nov 2025 16:25:25 +0100 Subject: [PATCH 4/5] docs: update CHANGELOG.md with recent performance improvements and test migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Comprehensive changelog update including: - Performance optimizations (promise allocation, array operations, histogram, tdigest) - Various bug fixes and refactoring Covers commits from f6dc1a3 to 4d589c6 (17 commits total). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Modified by: Jason Marshall --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 057e81fb..9af48213 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,8 @@ project adheres to [Semantic Versioning](http://semver.org/). - ci: switch out deprecated benchmark-regression library for replacement - AggregatorRegistry renamed to ClusterRegistry, old name deprecated - chore: update faceoff to 1.1 +- perf: Avoid array conversion in getMetricsAsJSON by directly iterating over metric values (~1.3% faster) +- perf: Optimize string escaping for better metrics serialization ### Added From 957e4c82b72f2efcf735395273da892c960184c9 Mon Sep 17 00:00:00 2001 From: Jason Marshall Date: Sat, 3 Jan 2026 14:41:28 -0800 Subject: [PATCH 5/5] Start benchmarks for the default metrics. Setup to see if the osMemoryHeap is a hot path as reported. --- CHANGELOG.md | 2 +- benchmarks/defaultMetrics.js | 58 ++++++++++++++++++++++++++++++++++++ benchmarks/index.js | 1 + 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 benchmarks/defaultMetrics.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 9af48213..d3df249c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,7 @@ project adheres to [Semantic Versioning](http://semver.org/). ### Added -- Expanded benchmarking code +- Expanded benchmarks and using new benchmarking library - new WorkerRegistry to provide equivalent support to AggregatorRegistry ## [15.1.3] - 2024-06-27 diff --git a/benchmarks/defaultMetrics.js b/benchmarks/defaultMetrics.js new file mode 100644 index 00000000..6663859a --- /dev/null +++ b/benchmarks/defaultMetrics.js @@ -0,0 +1,58 @@ +'use strict'; + +const Path = require('path'); +const { createRequire } = require('node:module'); +const { getLabelCombinations } = require('./utils/labels'); + +let count = 1; + +module.exports = function setupSuites(benchmark) { + benchmark.suite('osMemoryHeap', suite => { + suite.add( + 'new', + (client, { labels, metric: osMemoryHeap, registry }) => + osMemoryHeap(registry, { + prefix: `heap${count++}`, + help: 'HeapUsed', + labels, + }), + { + setup: (client, location) => + loadMetrics(client, location, 'osMemoryHeap'), + teardown, + }, + ); + + suite.add('collect', async (client, { registry }) => registry.metrics(), { + setup: (client, location) => { + const ctx = loadMetrics(client, location, 'osMemoryHeap'); + const { metric: osMemoryHeap, labels, registry } = ctx; + + osMemoryHeap(registry, { + prefix: `heap${count++}`, + help: 'HeapUsed', + labels, + }); + + return { registry }; + }, + teardown, + }); + }); +}; + +function loadMetrics(client, location, metricName) { + const require = createRequire(location); + const fromModule = Path.join(location, `./lib/metrics/${metricName}`); + const combinations = getLabelCombinations([1], ['region', 'env']); + + return { + metric: require(fromModule), + labels: combinations[0], + registry: new client.Registry(), + }; +} + +function teardown(client, { registry }) { + registry.clear(); +} diff --git a/benchmarks/index.js b/benchmarks/index.js index f6d4324e..341eff22 100644 --- a/benchmarks/index.js +++ b/benchmarks/index.js @@ -24,6 +24,7 @@ benchmarks.suite('histogram', require('./histogram')); benchmarks.suite('util', require('./util')); benchmarks.suite('summary', require('./summary')); benchmarks.suite('registry', require('./registry')); +benchmarks.suite('default metrics', require('./defaultMetrics')); benchmarks.suite('cluster', require('./cluster')); benchmarks