diff --git a/CHANGELOG.md b/CHANGELOG.md index 057e81fb..d3df249c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,10 +31,12 @@ 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 -- 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 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'); diff --git a/lib/registry.js b/lib/registry.js index 4d0906b1..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()); } @@ -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$/, ''); }