From 66f50b65f3b1fcc57e7317850d64fc584852fa33 Mon Sep 17 00:00:00 2001 From: Zach Bjornson Date: Sun, 1 Aug 2021 10:44:26 -0700 Subject: [PATCH] Reduce memory allocations/improve perf of Gauge --- CHANGELOG.md | 5 +- benchmarks/gauge.js | 42 +++++++++++++++ benchmarks/index.js | 1 + lib/gauge.js | 122 +++++++++++++++----------------------------- 4 files changed, 87 insertions(+), 83 deletions(-) create mode 100644 benchmarks/gauge.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 19a63e64..a8f55199 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,9 +12,10 @@ project adheres to [Semantic Versioning](http://semver.org/). ### Changed - Don't add event listener to `process` if cluster module is not used. -- fix: set labels for default memory metrics on linux -- fix: fix DEP0152 deprecation warning in Node.js v16+ +- fix: set labels for default memory metrics on linux. +- fix: fix DEP0152 deprecation warning in Node.js v16+. - fix: Set aggregation mode for newer event loop metrics. (Fixes [#418](https://github.com/siimon/prom-client/issues/418)) +- Improve performance of/reduce memory allocations in Gauge. ### Added diff --git a/benchmarks/gauge.js b/benchmarks/gauge.js new file mode 100644 index 00000000..85530549 --- /dev/null +++ b/benchmarks/gauge.js @@ -0,0 +1,42 @@ +'use strict'; + +const { getLabelNames, labelCombinationFactory } = require('./utils/labels'); + +module.exports = setupGaugeSuite; + +function setupGaugeSuite(suite) { + suite.add( + 'inc', + labelCombinationFactory([], (client, { Gauge }, labels) => + Gauge.inc(labels, 1), + ), + { teardown, setup: setup(0) }, + ); + + suite.add( + 'inc with labels', + labelCombinationFactory([8, 8], (client, { Gauge }, labels) => + Gauge.inc(labels, 1), + ), + { teardown, setup: setup(2) }, + ); +} + +function setup(labelCount) { + return client => { + const registry = new client.Registry(); + + const Gauge = new client.Gauge({ + name: 'Gauge', + help: 'Gauge', + labelNames: getLabelNames(labelCount), + registers: [registry], + }); + + return { registry, Gauge }; + }; +} + +function teardown(client, { registry }) { + registry.clear(); +} diff --git a/benchmarks/index.js b/benchmarks/index.js index 2450ac3d..645a4acd 100644 --- a/benchmarks/index.js +++ b/benchmarks/index.js @@ -9,6 +9,7 @@ const benchmarks = createRegressionBenchmark(currentClient, [ benchmarks.suite('registry', require('./registry')); benchmarks.suite('histogram', require('./histogram')); +benchmarks.suite('gauge', require('./gauge')); benchmarks.suite('summary', require('./summary')); benchmarks.run().catch(err => { console.error(err.stack); diff --git a/lib/gauge.js b/lib/gauge.js index 156722e4..6c5e4857 100644 --- a/lib/gauge.js +++ b/lib/gauge.js @@ -24,10 +24,9 @@ class Gauge extends Metric { * @returns {void} */ set(labels, value) { - if (!isObject(labels)) { - return set.call(this, null)(labels, value); - } - return set.call(this, labels)(value); + value = getValueArg(labels, value); + labels = getLabelArg(labels); + set(this, labels, value); } /** @@ -35,7 +34,10 @@ class Gauge extends Metric { * @returns {void} */ reset() { - return reset.call(this); + this.hashMap = {}; + if (this.labelNames.length === 0) { + setValue(this.hashMap, 0, {}); + } } /** @@ -45,7 +47,10 @@ class Gauge extends Metric { * @returns {void} */ inc(labels, value) { - inc.call(this, labels)(value); + value = getValueArg(labels, value); + labels = getLabelArg(labels); + if (value === undefined) value = 1; + set(this, labels, this._getValue(labels) + value); } /** @@ -55,7 +60,10 @@ class Gauge extends Metric { * @returns {void} */ dec(labels, value) { - dec.call(this, labels)(value); + value = getValueArg(labels, value); + labels = getLabelArg(labels); + if (value === undefined) value = 1; + set(this, labels, this._getValue(labels) - value); } /** @@ -64,7 +72,12 @@ class Gauge extends Metric { * @returns {void} */ setToCurrentTime(labels) { - return setToCurrentTime.call(this, labels)(); + const now = Date.now() / 1000; + if (labels === undefined) { + this.set(now); + } else { + this.set(labels, now); + } } /** @@ -78,7 +91,11 @@ class Gauge extends Metric { * }); */ startTimer(labels) { - return startTimer.call(this, labels)(); + const start = process.hrtime(); + return endLabels => { + const delta = process.hrtime(start); + this.set(Object.assign({}, labels, endLabels), delta[0] + delta[1] / 1e9); + }; } async get() { @@ -104,11 +121,11 @@ class Gauge extends Metric { const labels = getLabels(this.labelNames, arguments); validateLabel(this.labelNames, labels); return { - inc: inc.call(this, labels), - dec: dec.call(this, labels), - set: set.call(this, labels), - setToCurrentTime: setToCurrentTime.call(this, labels), - startTimer: startTimer.call(this, labels), + inc: this.inc.bind(this, labels), + dec: this.dec.bind(this, labels), + set: this.set.bind(this, labels), + setToCurrentTime: this.setToCurrentTime.bind(this, labels), + startTimer: this.startTimer.bind(this, labels), }; } @@ -119,78 +136,21 @@ class Gauge extends Metric { } } -function setToCurrentTime(labels) { - return () => { - const now = Date.now() / 1000; - if (labels === undefined) { - this.set(now); - } else { - this.set(labels, now); - } - }; -} - -function startTimer(startLabels) { - return () => { - const start = process.hrtime(); - return endLabels => { - const delta = process.hrtime(start); - this.set( - Object.assign({}, startLabels, endLabels), - delta[0] + delta[1] / 1e9, - ); - }; - }; -} - -function dec(labels) { - return value => { - const data = convertLabelsAndValues(labels, value); - if (data.value === undefined) data.value = 1; - this.set(data.labels, this._getValue(data.labels) - data.value); - }; -} - -function inc(labels) { - return value => { - const data = convertLabelsAndValues(labels, value); - if (data.value === undefined) data.value = 1; - this.set(data.labels, this._getValue(data.labels) + data.value); - }; -} - -function set(labels) { - return value => { - if (typeof value !== 'number') { - throw new TypeError(`Value is not a valid number: ${util.format(value)}`); - } - - labels = labels || {}; +function set(gauge, labels, value) { + if (typeof value !== 'number') { + throw new TypeError(`Value is not a valid number: ${util.format(value)}`); + } - validateLabel(this.labelNames, labels); - this.hashMap = setValue(this.hashMap, value, labels); - }; + validateLabel(gauge.labelNames, labels); + setValue(gauge.hashMap, value, labels); } -function reset() { - this.hashMap = {}; - - if (this.labelNames.length === 0) { - this.hashMap = setValue({}, 0, {}); - } +function getLabelArg(labels) { + return isObject(labels) ? labels : {}; } -function convertLabelsAndValues(labels, value) { - if (!isObject(labels)) { - return { - value: labels, - labels: {}, - }; - } - return { - labels, - value, - }; +function getValueArg(labels, value) { + return isObject(labels) ? value : labels; } module.exports = Gauge;