prom-client
Advanced tools
Comparing version 13.1.0 to 13.2.0
@@ -16,2 +16,17 @@ # Changelog | ||
## [13.2.0] - 2021-08-08 | ||
### 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 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 | ||
- feat: added `zero()` to `Histogram` for setting the metrics for a given label combination to zero | ||
- fix: allow `Gauge.inc/dec(0)` without defaulting to 1 | ||
## [13.1.0] - 2021-01-24 | ||
@@ -18,0 +33,0 @@ |
@@ -393,2 +393,7 @@ // Type definitions for prom-client | ||
/** | ||
* Initialize the metrics for the given combination of labels to zero | ||
*/ | ||
zero(labels: LabelValues<T>): void; | ||
/** | ||
* Return the child for given labels | ||
@@ -395,0 +400,0 @@ * @param values Label values |
@@ -183,25 +183,27 @@ 'use strict'; | ||
} | ||
} | ||
// Respond to master's requests for worker's local metrics. | ||
process.on('message', message => { | ||
if (cluster().isWorker && message.type === GET_METRICS_REQ) { | ||
Promise.all(registries.map(r => r.getMetricsAsJSON())) | ||
.then(metrics => { | ||
process.send({ | ||
type: GET_METRICS_RES, | ||
requestId: message.requestId, | ||
metrics, | ||
}); | ||
}) | ||
.catch(error => { | ||
process.send({ | ||
type: GET_METRICS_RES, | ||
requestId: message.requestId, | ||
error: error.message, | ||
}); | ||
}); | ||
if (cluster().isWorker) { | ||
// Respond to master's requests for worker's local metrics. | ||
process.on('message', message => { | ||
if (message.type === GET_METRICS_REQ) { | ||
Promise.all(registries.map(r => r.getMetricsAsJSON())) | ||
.then(metrics => { | ||
process.send({ | ||
type: GET_METRICS_RES, | ||
requestId: message.requestId, | ||
metrics, | ||
}); | ||
}) | ||
.catch(error => { | ||
process.send({ | ||
type: GET_METRICS_RES, | ||
requestId: message.requestId, | ||
error: error.message, | ||
}); | ||
}); | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
module.exports = AggregatorRegistry; |
@@ -20,8 +20,21 @@ /** | ||
inc(labels, value) { | ||
if (!isObject(labels)) { | ||
return inc.call(this, null)(labels, value); | ||
let hash; | ||
if (isObject(labels)) { | ||
hash = hashObject(labels); | ||
validateLabel(this.labelNames, labels); | ||
} else { | ||
value = labels; | ||
labels = {}; | ||
} | ||
const hash = hashObject(labels); | ||
return inc.call(this, labels, hash)(value); | ||
if (value && !Number.isFinite(value)) { | ||
throw new TypeError(`Value is not a valid number: ${util.format(value)}`); | ||
} | ||
if (value < 0) { | ||
throw new Error('It is not possible to decrease a counter'); | ||
} | ||
if (value === null || value === undefined) value = 1; | ||
setValue(this.hashMap, value, labels, hash); | ||
} | ||
@@ -34,3 +47,6 @@ | ||
reset() { | ||
return reset.call(this); | ||
this.hashMap = {}; | ||
if (this.labelNames.length === 0) { | ||
setValue(this.hashMap, 0); | ||
} | ||
} | ||
@@ -52,8 +68,6 @@ | ||
labels() { | ||
const labels = getLabels(this.labelNames, arguments) || {}; | ||
validateLabel(this.labelNames, labels); | ||
const hash = hashObject(labels); | ||
labels(...args) { | ||
const labels = getLabels(this.labelNames, args) || {}; | ||
return { | ||
inc: inc.call(this, labels, hash), | ||
inc: this.inc.bind(this, labels), | ||
}; | ||
@@ -69,34 +83,7 @@ } | ||
const reset = function () { | ||
this.hashMap = {}; | ||
if (this.labelNames.length === 0) { | ||
this.hashMap = setValue({}, 0); | ||
} | ||
}; | ||
const inc = function (labels, hash) { | ||
return value => { | ||
if (value && !Number.isFinite(value)) { | ||
throw new TypeError(`Value is not a valid number: ${util.format(value)}`); | ||
} | ||
if (value < 0) { | ||
throw new Error('It is not possible to decrease a counter'); | ||
} | ||
labels = labels || {}; | ||
validateLabel(this.labelNames, labels); | ||
const incValue = value === null || value === undefined ? 1 : value; | ||
this.hashMap = setValue(this.hashMap, incValue, labels, hash); | ||
}; | ||
}; | ||
function setValue(hashMap, value, labels, hash) { | ||
hash = hash || ''; | ||
function setValue(hashMap, value, labels = {}, hash = '') { | ||
if (hashMap[hash]) { | ||
hashMap[hash].value += value; | ||
} else { | ||
hashMap[hash] = { value, labels: labels || {} }; | ||
hashMap[hash] = { value, labels }; | ||
} | ||
@@ -103,0 +90,0 @@ return hashMap; |
120
lib/gauge.js
@@ -27,6 +27,5 @@ /** | ||
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); | ||
} | ||
@@ -39,3 +38,6 @@ | ||
reset() { | ||
return reset.call(this); | ||
this.hashMap = {}; | ||
if (this.labelNames.length === 0) { | ||
setValue(this.hashMap, 0, {}); | ||
} | ||
} | ||
@@ -50,3 +52,6 @@ | ||
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); | ||
} | ||
@@ -61,3 +66,6 @@ | ||
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); | ||
} | ||
@@ -71,3 +79,8 @@ | ||
setToCurrentTime(labels) { | ||
return setToCurrentTime.call(this, labels)(); | ||
const now = Date.now() / 1000; | ||
if (labels === undefined) { | ||
this.set(now); | ||
} else { | ||
this.set(labels, now); | ||
} | ||
} | ||
@@ -86,3 +99,7 @@ | ||
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); | ||
}; | ||
} | ||
@@ -113,7 +130,7 @@ | ||
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), | ||
}; | ||
@@ -129,74 +146,19 @@ } | ||
function setToCurrentTime(labels) { | ||
return () => { | ||
const now = Date.now() / 1000; | ||
if (labels === undefined) { | ||
this.set(now); | ||
} else { | ||
this.set(labels, now); | ||
} | ||
}; | ||
} | ||
function set(gauge, labels, value) { | ||
if (typeof value !== 'number') { | ||
throw new TypeError(`Value is not a valid number: ${util.format(value)}`); | ||
} | ||
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, | ||
); | ||
}; | ||
}; | ||
validateLabel(gauge.labelNames, labels); | ||
setValue(gauge.hashMap, value, labels); | ||
} | ||
function dec(labels) { | ||
return value => { | ||
const data = convertLabelsAndValues(labels, value); | ||
this.set(data.labels, this._getValue(data.labels) - (data.value || 1)); | ||
}; | ||
function getLabelArg(labels) { | ||
return isObject(labels) ? labels : {}; | ||
} | ||
function inc(labels) { | ||
return value => { | ||
const data = convertLabelsAndValues(labels, value); | ||
this.set(data.labels, this._getValue(data.labels) + (data.value || 1)); | ||
}; | ||
function getValueArg(labels, value) { | ||
return isObject(labels) ? value : labels; | ||
} | ||
function set(labels) { | ||
return value => { | ||
if (typeof value !== 'number') { | ||
throw new TypeError(`Value is not a valid number: ${util.format(value)}`); | ||
} | ||
labels = labels || {}; | ||
validateLabel(this.labelNames, labels); | ||
this.hashMap = setValue(this.hashMap, value, labels); | ||
}; | ||
} | ||
function reset() { | ||
this.hashMap = {}; | ||
if (this.labelNames.length === 0) { | ||
this.hashMap = setValue({}, 0, {}); | ||
} | ||
} | ||
function convertLabelsAndValues(labels, value) { | ||
if (!isObject(labels)) { | ||
return { | ||
value: labels, | ||
labels: {}, | ||
}; | ||
} | ||
return { | ||
labels, | ||
value, | ||
}; | ||
} | ||
module.exports = Gauge; |
@@ -77,2 +77,15 @@ /** | ||
/** | ||
* Initialize the metrics for the given combination of labels to zero | ||
* @param {object} labels - Object with labels where key is the label key and value is label value. Can only be one level deep | ||
* @returns {void} | ||
*/ | ||
zero(labels) { | ||
const hash = hashObject(labels); | ||
this.hashMap[hash] = createBaseValues( | ||
labels, | ||
Object.assign({}, this.bucketValues), | ||
); | ||
} | ||
/** | ||
* Start a timer that could be used to logging durations | ||
@@ -79,0 +92,0 @@ * @param {object} labels - Object with labels where key is the label key and value is label value. Can only be one level deep |
@@ -80,2 +80,3 @@ 'use strict'; | ||
labelNames, | ||
aggregator: 'min', | ||
}); | ||
@@ -87,2 +88,3 @@ const lagMax = new Gauge({ | ||
labelNames, | ||
aggregator: 'max', | ||
}); | ||
@@ -94,2 +96,3 @@ const lagMean = new Gauge({ | ||
labelNames, | ||
aggregator: 'average', | ||
}); | ||
@@ -101,2 +104,3 @@ const lagStddev = new Gauge({ | ||
labelNames, | ||
aggregator: 'average', | ||
}); | ||
@@ -108,2 +112,3 @@ const lagP50 = new Gauge({ | ||
labelNames, | ||
aggregator: 'average', | ||
}); | ||
@@ -115,2 +120,3 @@ const lagP90 = new Gauge({ | ||
labelNames, | ||
aggregator: 'average', | ||
}); | ||
@@ -122,2 +128,3 @@ const lagP99 = new Gauge({ | ||
labelNames, | ||
aggregator: 'average', | ||
}); | ||
@@ -124,0 +131,0 @@ }; |
@@ -44,14 +44,14 @@ 'use strict'; | ||
const entry = list.getEntries()[0]; | ||
// Node < 16 uses entry.kind | ||
// Node >= 16 uses entry.detail.kind | ||
// See: https://nodejs.org/docs/latest-v16.x/api/deprecations.html#deprecations_dep0152_extension_performanceentry_properties | ||
const kind = entry.detail ? kinds[entry.detail.kind] : kinds[entry.kind]; | ||
// Convert duration from milliseconds to seconds | ||
gcHistogram.observe( | ||
Object.assign({ kind: kinds[entry.kind] }, labels), | ||
entry.duration / 1000, | ||
); | ||
gcHistogram.observe(Object.assign({ kind }, labels), entry.duration / 1000); | ||
}); | ||
// We do not expect too many gc events per second, so we do not use buffering | ||
obs.observe({ entryTypes: ['gc'], buffered: false }); | ||
obs.observe({ entryTypes: ['gc'] }); | ||
}; | ||
module.exports.metricNames = [NODEJS_GC_DURATION_SECONDS]; |
@@ -37,2 +37,4 @@ 'use strict'; | ||
const namePrefix = config.prefix ? config.prefix : ''; | ||
const labels = config.labels ? config.labels : {}; | ||
const labelNames = Object.keys(labels); | ||
@@ -43,2 +45,3 @@ const residentMemGauge = new Gauge({ | ||
registers, | ||
labelNames, | ||
// Use this one metric's `collect` to set all metrics' values. | ||
@@ -56,5 +59,5 @@ collect() { | ||
residentMemGauge.set(structuredOutput.VmRSS); | ||
virtualMemGauge.set(structuredOutput.VmSize); | ||
heapSizeMemGauge.set(structuredOutput.VmData); | ||
residentMemGauge.set(labels, structuredOutput.VmRSS); | ||
virtualMemGauge.set(labels, structuredOutput.VmSize); | ||
heapSizeMemGauge.set(labels, structuredOutput.VmData); | ||
} catch { | ||
@@ -69,2 +72,3 @@ // noop | ||
registers, | ||
labelNames, | ||
}); | ||
@@ -75,2 +79,3 @@ const heapSizeMemGauge = new Gauge({ | ||
registers, | ||
labelNames, | ||
}); | ||
@@ -77,0 +82,0 @@ }; |
@@ -13,15 +13,9 @@ 'use strict'; | ||
exports.validateLabelName = function (names) { | ||
let valid = true; | ||
(names || []).forEach(name => { | ||
if (!labelRegexp.test(name)) { | ||
valid = false; | ||
} | ||
}); | ||
return valid; | ||
exports.validateLabelName = function (names = []) { | ||
return names.every(name => labelRegexp.test(name)); | ||
}; | ||
exports.validateLabel = function validateLabel(savedLabels, labels) { | ||
Object.keys(labels).forEach(label => { | ||
if (savedLabels.indexOf(label) === -1) { | ||
for (const label in labels) { | ||
if (!savedLabels.includes(label)) { | ||
throw new Error( | ||
@@ -33,3 +27,3 @@ `Added label "${label}" is not included in initial labelset: ${util.inspect( | ||
} | ||
}); | ||
} | ||
}; |
{ | ||
"name": "prom-client", | ||
"version": "13.1.0", | ||
"version": "13.2.0", | ||
"description": "Client for prometheus", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -20,6 +20,8 @@ # Prometheus client for node.js [![Actions Status](https://github.com/siimon/prom-client/workflows/Node.js%20CI/badge.svg?branch=master)](https://github.com/siimon/prom-client/actions) | ||
Default metrics use sensible aggregation methods. Custom metrics are summed | ||
across workers by default. To use a different aggregation method, set the | ||
`aggregator` property in the metric config to one of 'sum', 'first', 'min', | ||
'max', 'average' or 'omit'. (See `lib/metrics/version.js` for an example.) | ||
Default metrics use sensible aggregation methods. (Note, however, that the event | ||
loop lag mean and percentiles are averaged, which is not perfectly accurate.) | ||
Custom metrics are summed across workers by default. To use a different | ||
aggregation method, set the `aggregator` property in the metric config to one of | ||
'sum', 'first', 'min', 'max', 'average' or 'omit'. (See `lib/metrics/version.js` | ||
for an example.) | ||
@@ -53,3 +55,3 @@ If you need to expose metrics about an individual worker, you can include a | ||
- `prefix` an optional prefix for metric names. Default: no prefix. | ||
- `registry` to which metrics should be registered. Default: the global default registry. | ||
- `register` to which metrics should be registered. Default: the global default registry. | ||
- `gcDurationBuckets` with custom buckets for GC duration histogram. Default buckets of GC duration histogram are `[0.001, 0.01, 0.1, 1, 2, 5]` (in seconds). | ||
@@ -331,2 +333,20 @@ - `eventLoopMonitoringPrecision` with sampling rate in milliseconds. Must be greater than zero. Default: 10. | ||
#### Zeroing metrics with Labels | ||
Metrics with labels can not be exported before they have been observed at least | ||
once since the possible label values are not known before they're observed. | ||
For histograms, this can be solved by explicitly zeroing all expected label values: | ||
```js | ||
const histogram = new client.Histogram({ | ||
name: 'metric_name', | ||
help: 'metric_help', | ||
buckets: [0.1, 5, 15, 50, 100, 500], | ||
labels: ['method'], | ||
}); | ||
histogram.zero({ method: 'GET' }); | ||
histogram.zero({ method: 'POST' }); | ||
``` | ||
#### Strongly typed Labels | ||
@@ -333,0 +353,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
116513
546
2
2579