prom-client
Advanced tools
Comparing version 12.0.0 to 13.0.0
@@ -12,2 +12,49 @@ # Changelog | ||
- changed: The following functions are now async (return a promise): | ||
`registry.metrics()` | ||
`registry.getMetricsAsJSON()` | ||
`registry.getMetricsAsArray()` | ||
`registry.getSingleMetricAsString()` | ||
If your metrics server has a line like `res.send(register.metrics())`, you | ||
should change it to `res.send(await register.metrics())`. | ||
Additionally, all metric types now accept an optional `collect` function, | ||
which is called when the metric's value should be collected and within which | ||
you should set the metric's value. You should provide a `collect` function for | ||
point-in-time metrics (e.g. current memory usage, as opposed to HTTP request | ||
durations that are continuously logged in a histogram). | ||
- changed: `register.clusterMetrics()` no longer accepts a callback; it only | ||
returns a promise. | ||
- removed: v12.0.0 added the undocumented functions `registry.registerCollector` | ||
and `registry.collectors()`. These have been removed. If you were using them, | ||
you should instead provide a `collect` function as described above. | ||
### Changed | ||
- fix: provide nodejs_version_info metric value after calling `registry.resetMetrics()` (#238) | ||
- fix: provide process_max_fds metric value after calling `registry.resetMetrics()` | ||
- fix: provide process_start_time_seconds metric value after calling `registry.resetMetrics()` | ||
- chore: improve performance of `registry.getMetricAsPrometheusString` | ||
- chore: refactor metrics to reduce code duplication | ||
- chore: replace `utils.getPropertiesFromObj` with `Object.values` | ||
- chore: remove unused `catch` bindings | ||
- chore: upgrade Prettier to 2.x | ||
- fix: startTimer returns `number` in typescript instead of `void` | ||
- fix: incorrect typings of `registry.getSingleMetric' (#388) | ||
- chore: stop testing node v13 on CI | ||
### Added | ||
- feat: exposed `registry.registerCollector()` and `registry.collectors()` methods in TypeScript declaration | ||
- Added: complete working example of a pushgateway push in `example/pushgateway.js` | ||
- feat: added support for adding labels to default metrics (#374) | ||
- Added CHANGELOG reminder | ||
## [12.0.0] - 2020-02-20 | ||
### Breaking | ||
- Dropped support for end-of-life Node.js versions 6.x and 8.x | ||
@@ -37,3 +84,3 @@ - Dropped the previously deprecated support for positional parameters in | ||
- feat: implement GC metrics collection without native(C++) modules. | ||
- faet: implement advanced event loop monitoring | ||
- feat: implement advanced event loop monitoring | ||
@@ -40,0 +87,0 @@ ## [11.5.3] - 2019-06-27 |
@@ -11,3 +11,3 @@ // Type definitions for prom-client | ||
*/ | ||
metrics(): string; | ||
metrics(): Promise<string>; | ||
@@ -33,3 +33,3 @@ /** | ||
*/ | ||
getMetricsAsJSON(): metric[]; | ||
getMetricsAsJSON(): Promise<metric[]>; | ||
@@ -39,3 +39,3 @@ /** | ||
*/ | ||
getMetricsAsArray(): metric[]; | ||
getMetricsAsArray(): Promise<metric[]>; | ||
@@ -52,3 +52,3 @@ /** | ||
*/ | ||
getSingleMetric<T extends string>(name: string): Metric<T>; | ||
getSingleMetric<T extends string>(name: string): Metric<T> | undefined; | ||
@@ -66,3 +66,3 @@ /** | ||
*/ | ||
getSingleMetricAsString(name: string): string; | ||
getSingleMetricAsString(name: string): Promise<string>; | ||
@@ -80,2 +80,3 @@ /** | ||
} | ||
export type Collector = () => void; | ||
@@ -89,11 +90,7 @@ /** | ||
/** | ||
* Gets aggregated metrics for all workers. The optional callback and | ||
* returned Promise resolve with the same value; either may be used. | ||
* @param {Function?} cb (err, metrics) => any | ||
* Gets aggregated metrics for all workers. | ||
* @return {Promise<string>} Promise that resolves with the aggregated | ||
* metrics. | ||
* metrics. | ||
*/ | ||
clusterMetrics( | ||
cb?: (err: Error | null, metrics?: string) => any | ||
): Promise<string>; | ||
clusterMetrics(): Promise<string>; | ||
@@ -109,3 +106,3 @@ /** | ||
*/ | ||
static aggregate(metricsArr: Array<Object>): Registry; | ||
static aggregate(metricsArr: Array<Object>): Registry; // TODO Promise? | ||
@@ -140,5 +137,7 @@ /** | ||
Histogram, | ||
Summary | ||
Summary, | ||
} | ||
type CollectFunction<T> = (this: T) => void | Promise<void>; | ||
interface metric { | ||
@@ -149,2 +148,3 @@ name: string; | ||
aggregator: Aggregator; | ||
collect: CollectFunction<any>; | ||
} | ||
@@ -154,3 +154,3 @@ | ||
export interface CounterConfiguration<T extends string> { | ||
interface MetricConfiguration<T extends string> { | ||
name: string; | ||
@@ -161,4 +161,10 @@ help: string; | ||
aggregator?: Aggregator; | ||
collect?: CollectFunction<any>; | ||
} | ||
export interface CounterConfiguration<T extends string> | ||
extends MetricConfiguration<T> { | ||
collect?: CollectFunction<Counter<T>>; | ||
} | ||
/** | ||
@@ -215,8 +221,5 @@ * A counter is a cumulative metric that represents a single numerical value that only ever goes up | ||
export interface GaugeConfiguration<T extends string> { | ||
name: string; | ||
help: string; | ||
labelNames?: T[]; | ||
registers?: Registry[]; | ||
aggregator?: Aggregator; | ||
export interface GaugeConfiguration<T extends string> | ||
extends MetricConfiguration<T> { | ||
collect?: CollectFunction<Gauge<T>>; | ||
} | ||
@@ -337,9 +340,6 @@ | ||
export interface HistogramConfiguration<T extends string> { | ||
name: string; | ||
help: string; | ||
labelNames?: T[]; | ||
export interface HistogramConfiguration<T extends string> | ||
extends MetricConfiguration<T> { | ||
buckets?: number[]; | ||
registers?: Registry[]; | ||
aggregator?: Aggregator; | ||
collect?: CollectFunction<Histogram<T>>; | ||
} | ||
@@ -373,3 +373,3 @@ | ||
*/ | ||
startTimer(labels?: LabelValues<T>): (labels?: LabelValues<T>) => void; | ||
startTimer(labels?: LabelValues<T>): (labels?: LabelValues<T>) => number; | ||
@@ -419,12 +419,9 @@ /** | ||
export interface SummaryConfiguration<T extends string> { | ||
name: string; | ||
help: string; | ||
labelNames?: T[]; | ||
export interface SummaryConfiguration<T extends string> | ||
extends MetricConfiguration<T> { | ||
percentiles?: number[]; | ||
registers?: Registry[]; | ||
aggregator?: Aggregator; | ||
maxAgeSeconds?: number; | ||
ageBuckets?: number; | ||
compressCount?: number; | ||
collect?: CollectFunction<Summary<T>>; | ||
} | ||
@@ -521,3 +518,3 @@ | ||
params: Pushgateway.Parameters, | ||
callback: (error?: Error, httpResponse?: any, body?: any) => void | ||
callback: (error?: Error, httpResponse?: any, body?: any) => void, | ||
): void; | ||
@@ -532,3 +529,3 @@ | ||
params: Pushgateway.Parameters, | ||
callback: (error?: Error, httpResponse?: any, body?: any) => void | ||
callback: (error?: Error, httpResponse?: any, body?: any) => void, | ||
): void; | ||
@@ -543,3 +540,3 @@ | ||
params: Pushgateway.Parameters, | ||
callback: (error?: Error, httpResponse?: any, body?: any) => void | ||
callback: (error?: Error, httpResponse?: any, body?: any) => void, | ||
): void; | ||
@@ -573,3 +570,3 @@ } | ||
width: number, | ||
count: number | ||
count: number, | ||
): number[]; | ||
@@ -587,3 +584,3 @@ | ||
factor: number, | ||
count: number | ||
count: number, | ||
): number[]; | ||
@@ -596,2 +593,3 @@ | ||
eventLoopMonitoringPrecision?: number; | ||
labels?: Object; | ||
} | ||
@@ -604,3 +602,3 @@ | ||
export function collectDefaultMetrics( | ||
config?: DefaultMetricsCollectorConfiguration | ||
config?: DefaultMetricsCollectorConfiguration, | ||
): void; | ||
@@ -607,0 +605,0 @@ |
@@ -39,18 +39,15 @@ 'use strict'; | ||
* returned Promise resolve with the same value; either may be used. | ||
* @param {Function?} callback (err, metrics) => any | ||
* @return {Promise<string>} Promise that resolves with the aggregated | ||
* metrics. | ||
*/ | ||
clusterMetrics(callback) { | ||
clusterMetrics() { | ||
const requestId = requestCtr++; | ||
return new Promise((resolve, reject) => { | ||
let settled = false; | ||
function done(err, result) { | ||
// Don't resolve/reject the promise if a callback is provided | ||
if (typeof callback === 'function') { | ||
callback(err, result); | ||
} else { | ||
if (err) reject(err); | ||
else resolve(result); | ||
} | ||
if (settled) return; | ||
settled = true; | ||
if (err) reject(err); | ||
else resolve(result); | ||
} | ||
@@ -63,7 +60,5 @@ | ||
errorTimeout: setTimeout(() => { | ||
request.failed = true; | ||
const err = new Error('Operation timed out.'); | ||
request.done(err); | ||
}, 5000), | ||
failed: false | ||
}; | ||
@@ -74,3 +69,3 @@ requests.set(requestId, request); | ||
type: GET_METRICS_REQ, | ||
requestId | ||
requestId, | ||
}; | ||
@@ -127,5 +122,5 @@ | ||
{ | ||
get: () => aggregatedMetric | ||
get: () => aggregatedMetric, | ||
}, | ||
aggregatedMetric | ||
aggregatedMetric, | ||
); | ||
@@ -171,2 +166,8 @@ aggregatedRegistry.registerMetric(aggregatedMetricWrapper); | ||
const request = requests.get(message.requestId); | ||
if (message.error) { | ||
request.done(new Error(message.error)); | ||
return; | ||
} | ||
message.metrics.forEach(registry => request.responses.push(registry)); | ||
@@ -180,4 +181,2 @@ request.pending--; | ||
if (request.failed) return; // Callback already run with Error. | ||
const registry = AggregatorRegistry.aggregate(request.responses); | ||
@@ -195,7 +194,17 @@ const promString = registry.metrics(); | ||
if (cluster().isWorker && message.type === GET_METRICS_REQ) { | ||
process.send({ | ||
type: GET_METRICS_RES, | ||
requestId: message.requestId, | ||
metrics: registries.map(r => r.getMetricsAsJSON()) | ||
}); | ||
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, | ||
}); | ||
}); | ||
} | ||
@@ -202,0 +211,0 @@ }); |
@@ -7,64 +7,9 @@ /** | ||
const util = require('util'); | ||
const { globalRegistry } = require('./registry'); | ||
const type = 'counter'; | ||
const { | ||
getPropertiesFromObj, | ||
hashObject, | ||
isObject, | ||
getLabels, | ||
removeLabels | ||
} = require('./util'); | ||
const { hashObject, isObject, getLabels, removeLabels } = require('./util'); | ||
const { validateLabel } = require('./validation'); | ||
const { Metric } = require('./metric'); | ||
const { | ||
validateLabel, | ||
validateMetricName, | ||
validateLabelName | ||
} = require('./validation'); | ||
class Counter { | ||
class Counter extends Metric { | ||
/** | ||
* Counter | ||
* @param {config} config - Configuration object. | ||
* @param {string} config.name - Name of the metric | ||
* @param {string} config.help - Help description for the metric | ||
* @param {Array.<string>} config.labels - Array with strings, all label keywords supported | ||
*/ | ||
constructor(config) { | ||
if (!isObject(config)) { | ||
throw new TypeError('constructor expected a config object'); | ||
} | ||
config = Object.assign( | ||
{ | ||
labelNames: [] | ||
}, | ||
config | ||
); | ||
if (!config.registers) { | ||
config.registers = [globalRegistry]; | ||
} | ||
if (!config.help) { | ||
throw new Error('Missing mandatory help parameter'); | ||
} | ||
if (!config.name) { | ||
throw new Error('Missing mandatory name parameter'); | ||
} | ||
if (!validateMetricName(config.name)) { | ||
throw new Error('Invalid metric name'); | ||
} | ||
if (!validateLabelName(config.labelNames)) { | ||
throw new Error('Invalid label name'); | ||
} | ||
this.name = config.name; | ||
this.labelNames = config.labelNames || []; | ||
this.reset(); | ||
this.help = config.help; | ||
this.aggregator = config.aggregator || 'sum'; | ||
config.registers.forEach(registryInstance => | ||
registryInstance.registerMetric(this) | ||
); | ||
} | ||
/** | ||
* Increment counter | ||
@@ -92,3 +37,7 @@ * @param {object} labels - What label you want to be incremented | ||
get() { | ||
async get() { | ||
if (this.collect) { | ||
const v = this.collect(); | ||
if (v instanceof Promise) await v; | ||
} | ||
return { | ||
@@ -98,4 +47,4 @@ help: this.help, | ||
type, | ||
values: getPropertiesFromObj(this.hashMap), | ||
aggregator: this.aggregator | ||
values: Object.values(this.hashMap), | ||
aggregator: this.aggregator, | ||
}; | ||
@@ -109,3 +58,3 @@ } | ||
return { | ||
inc: inc.call(this, labels, hash) | ||
inc: inc.call(this, labels, hash), | ||
}; | ||
@@ -120,3 +69,3 @@ } | ||
const reset = function() { | ||
const reset = function () { | ||
this.hashMap = {}; | ||
@@ -129,3 +78,3 @@ | ||
const inc = function(labels, hash) { | ||
const inc = function (labels, hash) { | ||
return value => { | ||
@@ -132,0 +81,0 @@ if (value && !Number.isFinite(value)) { |
'use strict'; | ||
const { isObject } = require('./util'); | ||
const { globalRegistry } = require('./registry'); | ||
@@ -32,3 +31,3 @@ // Default metrics. | ||
version, | ||
gc | ||
gc, | ||
}; | ||
@@ -42,39 +41,9 @@ const metricsList = Object.keys(metrics); | ||
config = Object.assign({ eventLoopMonitoringPrecision: 10 }, config); | ||
config = { eventLoopMonitoringPrecision: 10, ...config }; | ||
const registry = config.register || globalRegistry; | ||
const last = registry | ||
.collectors() | ||
.find(collector => collector._source === metrics); | ||
if (last) { | ||
throw new Error( | ||
'Cannot add the default metrics twice to the same registry' | ||
); | ||
for (const metric of Object.values(metrics)) { | ||
metric(config.register, config); | ||
} | ||
const scrapers = metricsList.map(key => { | ||
const metric = metrics[key]; | ||
return metric(config.register, config); | ||
}); | ||
// Ideally the library would be based around a concept of collectors and | ||
// async callbacks, but in the short-term, trigger scraping of the | ||
// current metric value synchronously. | ||
// - // https://prometheus.io/docs/instrumenting/writing_clientlibs/#overall-structure | ||
function defaultMetricCollector() { | ||
scrapers.forEach(scraper => scraper()); | ||
} | ||
// defaultMetricCollector has to be dynamic, because the scrapers are in | ||
// its closure, but we still want to identify a default collector, so | ||
// tag it with a value known only to this module (the const metric array | ||
// value) so we can find it later. | ||
defaultMetricCollector._source = metrics; | ||
registry.registerCollector(defaultMetricCollector); | ||
// Because the tests expect an immediate collection. | ||
defaultMetricCollector(); | ||
}; | ||
module.exports.metricsList = metricsList; |
@@ -7,3 +7,2 @@ /** | ||
const util = require('util'); | ||
const { globalRegistry } = require('./registry'); | ||
const type = 'gauge'; | ||
@@ -13,60 +12,12 @@ | ||
setValue, | ||
getPropertiesFromObj, | ||
getLabels, | ||
hashObject, | ||
isObject, | ||
removeLabels | ||
removeLabels, | ||
} = require('./util'); | ||
const { | ||
validateMetricName, | ||
validateLabel, | ||
validateLabelName | ||
} = require('./validation'); | ||
const { validateLabel } = require('./validation'); | ||
const { Metric } = require('./metric'); | ||
class Gauge { | ||
class Gauge extends Metric { | ||
/** | ||
* Gauge | ||
* @param {config} config - Configuration object | ||
* @param {string} config.name - Name of the metric | ||
* @param {string} config.help - Help description for the metric | ||
* @param {Array.<string>} config.labels - Array with strings, all label keywords supported | ||
*/ | ||
constructor(config) { | ||
if (!isObject(config)) { | ||
throw new TypeError('constructor expected a config object'); | ||
} | ||
config = Object.assign( | ||
{ | ||
labelNames: [] | ||
}, | ||
config | ||
); | ||
if (!config.registers) { | ||
config.registers = [globalRegistry]; | ||
} | ||
if (!config.help) { | ||
throw new Error('Missing mandatory help parameter'); | ||
} | ||
if (!config.name) { | ||
throw new Error('Missing mandatory name parameter'); | ||
} | ||
if (!validateMetricName(config.name)) { | ||
throw new Error('Invalid metric name'); | ||
} | ||
if (!validateLabelName(config.labelNames)) { | ||
throw new Error('Invalid label name'); | ||
} | ||
this.name = config.name; | ||
this.labelNames = config.labelNames || []; | ||
this.reset(); | ||
this.help = config.help; | ||
this.aggregator = config.aggregator || 'sum'; | ||
config.registers.forEach(registryInstance => | ||
registryInstance.registerMetric(this) | ||
); | ||
} | ||
/** | ||
* Set a gauge to a value | ||
@@ -135,3 +86,7 @@ * @param {object} labels - Object with labels and their values | ||
get() { | ||
async get() { | ||
if (this.collect) { | ||
const v = this.collect(); | ||
if (v instanceof Promise) await v; | ||
} | ||
return { | ||
@@ -141,4 +96,4 @@ help: this.help, | ||
type, | ||
values: getPropertiesFromObj(this.hashMap), | ||
aggregator: this.aggregator | ||
values: Object.values(this.hashMap), | ||
aggregator: this.aggregator, | ||
}; | ||
@@ -159,3 +114,3 @@ } | ||
setToCurrentTime: setToCurrentTime.call(this, labels), | ||
startTimer: startTimer.call(this, labels) | ||
startTimer: startTimer.call(this, labels), | ||
}; | ||
@@ -188,3 +143,3 @@ } | ||
Object.assign({}, startLabels, endLabels), | ||
delta[0] + delta[1] / 1e9 | ||
delta[0] + delta[1] / 1e9, | ||
); | ||
@@ -234,3 +189,3 @@ }; | ||
value: labels, | ||
labels: {} | ||
labels: {}, | ||
}; | ||
@@ -240,3 +195,3 @@ } | ||
labels, | ||
value | ||
value, | ||
}; | ||
@@ -243,0 +198,0 @@ } |
@@ -7,46 +7,20 @@ /** | ||
const util = require('util'); | ||
const globalRegistry = require('./registry').globalRegistry; | ||
const type = 'histogram'; | ||
const { | ||
getPropertiesFromObj, | ||
getLabels, | ||
hashObject, | ||
isObject, | ||
removeLabels | ||
} = require('./util'); | ||
const { | ||
validateMetricName, | ||
validateLabel, | ||
validateLabelName | ||
} = require('./validation'); | ||
const { getLabels, hashObject, isObject, removeLabels } = require('./util'); | ||
const { validateLabel } = require('./validation'); | ||
const { Metric } = require('./metric'); | ||
class Histogram { | ||
/** | ||
* Histogram | ||
* @param {config} config - Configuration object. | ||
* @param {string} config.name - Name of the metric | ||
* @param {string} config.help - Help for the metric | ||
* @param {Array.<string>} config.labels - Array with strings, all label keywords supported | ||
*/ | ||
class Histogram extends Metric { | ||
constructor(config) { | ||
if (!isObject(config)) { | ||
throw new TypeError('constructor expected a config object'); | ||
super(config, { | ||
buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], | ||
}); | ||
for (const label of this.labelNames) { | ||
if (label === 'le') { | ||
throw new Error('le is a reserved label keyword'); | ||
} | ||
} | ||
config = Object.assign( | ||
{ | ||
buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], | ||
labelNames: [] | ||
}, | ||
config | ||
); | ||
if (!config.registers) { | ||
config.registers = [globalRegistry]; | ||
} | ||
validateInput(config.name, config.help, config.labelNames); | ||
this.name = config.name; | ||
this.help = config.help; | ||
this.aggregator = config.aggregator || 'sum'; | ||
this.upperBounds = config.buckets; | ||
this.upperBounds = this.buckets; | ||
this.bucketValues = this.upperBounds.reduce((acc, upperBound) => { | ||
@@ -59,8 +33,3 @@ acc[upperBound] = 0; | ||
Object.freeze(this.upperBounds); | ||
this.sum = 0; | ||
this.count = 0; | ||
this.hashMap = {}; | ||
this.labelNames = config.labelNames || []; | ||
if (this.labelNames.length === 0) { | ||
@@ -70,10 +39,6 @@ this.hashMap = { | ||
{}, | ||
Object.assign({}, this.bucketValues) | ||
) | ||
Object.assign({}, this.bucketValues), | ||
), | ||
}; | ||
} | ||
config.registers.forEach(registryInstance => | ||
registryInstance.registerMetric(this) | ||
); | ||
} | ||
@@ -91,4 +56,8 @@ | ||
get() { | ||
const data = getPropertiesFromObj(this.hashMap); | ||
async get() { | ||
if (this.collect) { | ||
const v = this.collect(); | ||
if (v instanceof Promise) await v; | ||
} | ||
const data = Object.values(this.hashMap); | ||
const values = data | ||
@@ -103,3 +72,3 @@ .map(extractBucketValuesForExport(this)) | ||
values, | ||
aggregator: this.aggregator | ||
aggregator: this.aggregator, | ||
}; | ||
@@ -109,4 +78,2 @@ } | ||
reset() { | ||
this.sum = 0; | ||
this.count = 0; | ||
this.hashMap = {}; | ||
@@ -134,3 +101,3 @@ } | ||
observe: observe.call(this, labels), | ||
startTimer: startTimer.call(this, labels) | ||
startTimer: startTimer.call(this, labels), | ||
}; | ||
@@ -157,25 +124,2 @@ } | ||
function validateInput(name, help, labels) { | ||
if (!help) { | ||
throw new Error('Missing mandatory help parameter'); | ||
} | ||
if (!name) { | ||
throw new Error('Missing mandatory name parameter'); | ||
} | ||
if (!validateMetricName(name)) { | ||
throw new Error('Invalid metric name'); | ||
} | ||
if (!validateLabelName(labels)) { | ||
throw new Error('Invalid label name'); | ||
} | ||
labels.forEach(label => { | ||
if (label === 'le') { | ||
throw new Error('le is a reserved label keyword'); | ||
} | ||
}); | ||
} | ||
function setValuePair(labels, value, metricName) { | ||
@@ -185,3 +129,3 @@ return { | ||
value, | ||
metricName | ||
metricName, | ||
}; | ||
@@ -207,3 +151,3 @@ } | ||
throw new TypeError( | ||
`Value is not a valid number: ${util.format(labelValuePair.value)}` | ||
`Value is not a valid number: ${util.format(labelValuePair.value)}`, | ||
); | ||
@@ -217,3 +161,3 @@ } | ||
labelValuePair.labels, | ||
Object.assign({}, this.bucketValues) | ||
Object.assign({}, this.bucketValues), | ||
); | ||
@@ -240,3 +184,3 @@ } | ||
sum: 0, | ||
count: 0 | ||
count: 0, | ||
}; | ||
@@ -249,3 +193,3 @@ } | ||
value: labels, | ||
labels: {} | ||
labels: {}, | ||
}; | ||
@@ -255,3 +199,3 @@ } | ||
labels, | ||
value | ||
value, | ||
}; | ||
@@ -288,3 +232,3 @@ } | ||
setValuePair(d.data.labels, d.data.sum, `${histogram.name}_sum`), | ||
setValuePair(d.data.labels, d.data.count, `${histogram.name}_count`) | ||
setValuePair(d.data.labels, d.data.count, `${histogram.name}_count`), | ||
); | ||
@@ -291,0 +235,0 @@ return acc; |
@@ -18,3 +18,3 @@ 'use strict'; | ||
values: [], | ||
aggregator: metrics[0].aggregator | ||
aggregator: metrics[0].aggregator, | ||
}; | ||
@@ -34,3 +34,3 @@ // Gather metrics by metricName and labels. | ||
value: aggregatorFn(values), | ||
labels: values[0].labels | ||
labels: values[0].labels, | ||
}; | ||
@@ -69,3 +69,3 @@ if (values[0].metricName) { | ||
average: AggregatorFactory( | ||
v => v.reduce((p, c) => p + c.value, 0) / v.length | ||
v => v.reduce((p, c) => p + c.value, 0) / v.length, | ||
), | ||
@@ -76,3 +76,3 @@ /** | ||
min: AggregatorFactory(v => | ||
v.reduce((p, c) => Math.min(p, c.value), Infinity) | ||
v.reduce((p, c) => Math.min(p, c.value), Infinity), | ||
), | ||
@@ -83,4 +83,4 @@ /** | ||
max: AggregatorFactory(v => | ||
v.reduce((p, c) => Math.max(p, c.value), -Infinity) | ||
) | ||
v.reduce((p, c) => Math.max(p, c.value), -Infinity), | ||
), | ||
}; |
@@ -10,8 +10,7 @@ 'use strict'; | ||
perf_hooks = require('perf_hooks'); | ||
} catch (e) { | ||
} catch { | ||
// node version is too old | ||
} | ||
// Reported always, but because legacy lag_seconds is collected async, the value | ||
// will always be stale by one scrape interval. | ||
// Reported always. | ||
const NODEJS_EVENTLOOP_LAG = 'nodejs_eventloop_lag_seconds'; | ||
@@ -28,3 +27,3 @@ | ||
function reportEventloopLag(start, gauge) { | ||
function reportEventloopLag(start, gauge, labels) { | ||
const delta = process.hrtime(start); | ||
@@ -34,3 +33,3 @@ const nanosec = delta[0] * 1e9 + delta[1]; | ||
gauge.set(seconds); | ||
gauge.set(labels, seconds); | ||
} | ||
@@ -40,8 +39,40 @@ | ||
const namePrefix = config.prefix ? config.prefix : ''; | ||
const labels = config.labels ? config.labels : {}; | ||
const labelNames = Object.keys(labels); | ||
const registers = registry ? [registry] : undefined; | ||
let collect; | ||
if (!perf_hooks || !perf_hooks.monitorEventLoopDelay) { | ||
collect = () => { | ||
const start = process.hrtime(); | ||
setImmediate(reportEventloopLag, start, lag, labels); | ||
}; | ||
} else { | ||
const histogram = perf_hooks.monitorEventLoopDelay({ | ||
resolution: config.eventLoopMonitoringPrecision, | ||
}); | ||
histogram.enable(); | ||
collect = () => { | ||
const start = process.hrtime(); | ||
setImmediate(reportEventloopLag, start, lag, labels); | ||
lagMin.set(labels, histogram.min / 1e9); | ||
lagMax.set(labels, histogram.max / 1e9); | ||
lagMean.set(labels, histogram.mean / 1e9); | ||
lagStddev.set(labels, histogram.stddev / 1e9); | ||
lagP50.set(labels, histogram.percentile(50) / 1e9); | ||
lagP90.set(labels, histogram.percentile(90) / 1e9); | ||
lagP99.set(labels, histogram.percentile(99) / 1e9); | ||
}; | ||
} | ||
const lag = new Gauge({ | ||
name: namePrefix + NODEJS_EVENTLOOP_LAG, | ||
help: 'Lag of event loop in seconds.', | ||
registers: registry ? [registry] : undefined, | ||
aggregator: 'average' | ||
registers, | ||
labelNames, | ||
aggregator: 'average', | ||
// Use this one metric's `collect` to set all metrics' values. | ||
collect, | ||
}); | ||
@@ -51,3 +82,4 @@ const lagMin = new Gauge({ | ||
help: 'The minimum recorded event loop delay.', | ||
registers: registry ? [registry] : undefined | ||
registers, | ||
labelNames, | ||
}); | ||
@@ -57,3 +89,4 @@ const lagMax = new Gauge({ | ||
help: 'The maximum recorded event loop delay.', | ||
registers: registry ? [registry] : undefined | ||
registers, | ||
labelNames, | ||
}); | ||
@@ -63,3 +96,4 @@ const lagMean = new Gauge({ | ||
help: 'The mean of the recorded event loop delays.', | ||
registers: registry ? [registry] : undefined | ||
registers, | ||
labelNames, | ||
}); | ||
@@ -69,3 +103,4 @@ const lagStddev = new Gauge({ | ||
help: 'The standard deviation of the recorded event loop delays.', | ||
registers: registry ? [registry] : undefined | ||
registers, | ||
labelNames, | ||
}); | ||
@@ -75,3 +110,4 @@ const lagP50 = new Gauge({ | ||
help: 'The 50th percentile of the recorded event loop delays.', | ||
registers: registry ? [registry] : undefined | ||
registers, | ||
labelNames, | ||
}); | ||
@@ -81,3 +117,4 @@ const lagP90 = new Gauge({ | ||
help: 'The 90th percentile of the recorded event loop delays.', | ||
registers: registry ? [registry] : undefined | ||
registers, | ||
labelNames, | ||
}); | ||
@@ -87,29 +124,5 @@ const lagP99 = new Gauge({ | ||
help: 'The 99th percentile of the recorded event loop delays.', | ||
registers: registry ? [registry] : undefined | ||
registers, | ||
labelNames, | ||
}); | ||
if (!perf_hooks || !perf_hooks.monitorEventLoopDelay) { | ||
return () => { | ||
const start = process.hrtime(); | ||
setImmediate(reportEventloopLag, start, lag); | ||
}; | ||
} | ||
const histogram = perf_hooks.monitorEventLoopDelay({ | ||
resolution: config.eventLoopMonitoringPrecision | ||
}); | ||
histogram.enable(); | ||
return () => { | ||
const start = process.hrtime(); | ||
setImmediate(reportEventloopLag, start, lag); | ||
lagMin.set(histogram.min / 1e9); | ||
lagMax.set(histogram.max / 1e9); | ||
lagMean.set(histogram.mean / 1e9); | ||
lagStddev.set(histogram.stddev / 1e9); | ||
lagP50.set(histogram.percentile(50) / 1e9); | ||
lagP90.set(histogram.percentile(90) / 1e9); | ||
lagP99.set(histogram.percentile(99) / 1e9); | ||
}; | ||
}; | ||
@@ -125,3 +138,3 @@ | ||
NODEJS_EVENTLOOP_LAG_P90, | ||
NODEJS_EVENTLOOP_LAG_P99 | ||
NODEJS_EVENTLOOP_LAG_P99, | ||
]; |
@@ -9,3 +9,3 @@ 'use strict'; | ||
perf_hooks = require('perf_hooks'); | ||
} catch (e) { | ||
} catch { | ||
// node version is too old | ||
@@ -25,6 +25,8 @@ } | ||
if (!perf_hooks) { | ||
return () => {}; | ||
return; | ||
} | ||
const namePrefix = config.prefix ? config.prefix : ''; | ||
const labels = config.labels ? config.labels : {}; | ||
const labelNames = Object.keys(labels); | ||
const buckets = config.gcDurationBuckets | ||
@@ -37,5 +39,5 @@ ? config.gcDurationBuckets | ||
'Garbage collection duration by kind, one of major, minor, incremental or weakcb.', | ||
labelNames: ['kind'], | ||
labelNames: ['kind', ...labelNames], | ||
buckets, | ||
registers: registry ? [registry] : undefined | ||
registers: registry ? [registry] : undefined, | ||
}); | ||
@@ -45,6 +47,8 @@ | ||
const entry = list.getEntries()[0]; | ||
const labels = { kind: kinds[entry.kind] }; | ||
// Convert duration from milliseconds to seconds | ||
gcHistogram.observe(labels, entry.duration / 1000); | ||
gcHistogram.observe( | ||
Object.assign({ kind: kinds[entry.kind] }, labels), | ||
entry.duration / 1000, | ||
); | ||
}); | ||
@@ -54,6 +58,4 @@ | ||
obs.observe({ entryTypes: ['gc'], buffered: false }); | ||
return () => {}; | ||
}; | ||
module.exports.metricNames = [NODEJS_GC_DURATION_SECONDS]; |
@@ -12,7 +12,19 @@ 'use strict'; | ||
if (typeof process.memoryUsage !== 'function') { | ||
return () => {}; | ||
return; | ||
} | ||
const labels = config.labels ? config.labels : {}; | ||
const labelNames = Object.keys(labels); | ||
const registers = registry ? [registry] : undefined; | ||
const namePrefix = config.prefix ? config.prefix : ''; | ||
const collect = () => { | ||
const memUsage = safeMemoryUsage(); | ||
if (memUsage) { | ||
heapSizeTotal.set(labels, memUsage.heapTotal); | ||
heapSizeUsed.set(labels, memUsage.heapUsed); | ||
if (memUsage.external !== undefined) { | ||
externalMemUsed.set(labels, memUsage.external); | ||
} | ||
} | ||
}; | ||
@@ -22,3 +34,6 @@ const heapSizeTotal = new Gauge({ | ||
help: 'Process heap size from Node.js in bytes.', | ||
registers | ||
registers, | ||
labelNames, | ||
// Use this one metric's `collect` to set all metrics' values. | ||
collect, | ||
}); | ||
@@ -28,3 +43,4 @@ const heapSizeUsed = new Gauge({ | ||
help: 'Process heap size used from Node.js in bytes.', | ||
registers | ||
registers, | ||
labelNames, | ||
}); | ||
@@ -34,22 +50,5 @@ const externalMemUsed = new Gauge({ | ||
help: 'Node.js external memory size in bytes.', | ||
registers | ||
registers, | ||
labelNames, | ||
}); | ||
return () => { | ||
// process.memoryUsage() can throw on some platforms, see #67 | ||
const memUsage = safeMemoryUsage(); | ||
if (memUsage) { | ||
heapSizeTotal.set(memUsage.heapTotal); | ||
heapSizeUsed.set(memUsage.heapUsed); | ||
if (memUsage.external && externalMemUsed) { | ||
externalMemUsed.set(memUsage.external); | ||
} | ||
} | ||
return { | ||
total: heapSizeTotal, | ||
used: heapSizeUsed, | ||
external: externalMemUsed | ||
}; | ||
}; | ||
}; | ||
@@ -60,3 +59,3 @@ | ||
NODEJS_HEAP_SIZE_USED, | ||
NODEJS_EXTERNAL_MEMORY | ||
NODEJS_EXTERNAL_MEMORY, | ||
]; |
@@ -17,2 +17,5 @@ 'use strict'; | ||
const labels = config.labels ? config.labels : {}; | ||
const labelNames = ['space', ...Object.keys(labels)]; | ||
const gauges = {}; | ||
@@ -24,40 +27,25 @@ | ||
help: `Process heap space size ${metricType} from Node.js in bytes.`, | ||
labelNames: ['space'], | ||
registers | ||
labelNames, | ||
registers, | ||
}); | ||
}); | ||
return () => { | ||
const data = { | ||
total: {}, | ||
used: {}, | ||
available: {} | ||
}; | ||
const now = Date.now(); | ||
v8.getHeapSpaceStatistics().forEach(space => { | ||
// Use this one metric's `collect` to set all metrics' values. | ||
gauges.total.collect = () => { | ||
for (const space of v8.getHeapSpaceStatistics()) { | ||
const spaceName = space.space_name.substr( | ||
0, | ||
space.space_name.indexOf('_space') | ||
space.space_name.indexOf('_space'), | ||
); | ||
data.total[spaceName] = space.space_size; | ||
data.used[spaceName] = space.space_used_size; | ||
data.available[spaceName] = space.space_available_size; | ||
gauges.total.set({ space: spaceName }, space.space_size, now); | ||
gauges.used.set({ space: spaceName }, space.space_used_size, now); | ||
gauges.total.set({ space: spaceName, ...labels }, space.space_size); | ||
gauges.used.set({ space: spaceName, ...labels }, space.space_used_size); | ||
gauges.available.set( | ||
{ space: spaceName }, | ||
{ space: spaceName, ...labels }, | ||
space.space_available_size, | ||
now | ||
); | ||
}); | ||
return data; | ||
} | ||
}; | ||
}; | ||
module.exports.metricNames = METRICS.map( | ||
metricType => NODEJS_HEAP_SIZE[metricType] | ||
); | ||
module.exports.metricNames = Object.values(NODEJS_HEAP_SIZE); |
@@ -22,6 +22,6 @@ 'use strict'; | ||
function updateMetrics(gauge, data) { | ||
function updateMetrics(gauge, data, labels) { | ||
gauge.reset(); | ||
for (const key in data) { | ||
gauge.set({ type: key }, data[key]); | ||
gauge.set(Object.assign({ type: key }, labels || {}), data[key]); | ||
} | ||
@@ -32,3 +32,3 @@ } | ||
aggregateByObjectName, | ||
updateMetrics | ||
updateMetrics, | ||
}; |
'use strict'; | ||
// process.memoryUsage() can throw on some platforms, see #67 | ||
function safeMemoryUsage() { | ||
try { | ||
return process.memoryUsage(); | ||
} catch (ex) { | ||
} catch { | ||
return; | ||
@@ -8,0 +9,0 @@ } |
@@ -11,17 +11,19 @@ 'use strict'; | ||
const namePrefix = config.prefix ? config.prefix : ''; | ||
const labels = config.labels ? config.labels : {}; | ||
const labelNames = Object.keys(labels); | ||
const residentMemGauge = new Gauge({ | ||
new Gauge({ | ||
name: namePrefix + PROCESS_RESIDENT_MEMORY, | ||
help: 'Resident memory size in bytes.', | ||
registers: registry ? [registry] : undefined | ||
registers: registry ? [registry] : undefined, | ||
labelNames, | ||
collect() { | ||
const memUsage = safeMemoryUsage(); | ||
// I don't think the other things returned from `process.memoryUsage()` is relevant to a standard export | ||
if (memUsage) { | ||
this.set(labels, memUsage.rss); | ||
} | ||
}, | ||
}); | ||
return () => { | ||
const memUsage = safeMemoryUsage(); | ||
// I don't think the other things returned from `process.memoryUsage()` is relevant to a standard export | ||
if (memUsage) { | ||
residentMemGauge.set(memUsage.rss, Date.now()); | ||
} | ||
}; | ||
} | ||
@@ -28,0 +30,0 @@ |
@@ -41,3 +41,22 @@ 'use strict'; | ||
help: 'Resident memory size in bytes.', | ||
registers | ||
registers, | ||
// Use this one metric's `collect` to set all metrics' values. | ||
collect() { | ||
try { | ||
// Sync I/O is often problematic, but /proc isn't really I/O, it | ||
// a virtual filesystem that maps directly to in-kernel data | ||
// structures and never blocks. | ||
// | ||
// Node.js/libuv do this already for process.memoryUsage(), see: | ||
// - https://github.com/libuv/libuv/blob/a629688008694ed8022269e66826d4d6ec688b83/src/unix/linux-core.c#L506-L523 | ||
const stat = fs.readFileSync('/proc/self/status', 'utf8'); | ||
const structuredOutput = structureOutput(stat); | ||
residentMemGauge.set(structuredOutput.VmRSS); | ||
virtualMemGauge.set(structuredOutput.VmSize); | ||
heapSizeMemGauge.set(structuredOutput.VmData); | ||
} catch { | ||
// noop | ||
} | ||
}, | ||
}); | ||
@@ -47,3 +66,3 @@ const virtualMemGauge = new Gauge({ | ||
help: 'Virtual memory size in bytes.', | ||
registers | ||
registers, | ||
}); | ||
@@ -53,23 +72,4 @@ const heapSizeMemGauge = new Gauge({ | ||
help: 'Process heap size in bytes.', | ||
registers | ||
registers, | ||
}); | ||
// Sync I/O is often problematic, but /proc isn't really I/O, it a | ||
// virtual filesystem that maps directly to in-kernel data structures | ||
// and never blocks. | ||
// | ||
// Node.js/libuv do this already for process.memoryUsage(), see: | ||
// - https://github.com/libuv/libuv/blob/a629688008694ed8022269e66826d4d6ec688b83/src/unix/linux-core.c#L506-L523 | ||
return () => { | ||
try { | ||
const stat = fs.readFileSync('/proc/self/status', 'utf8'); | ||
const structuredOutput = structureOutput(stat); | ||
residentMemGauge.set(structuredOutput.VmRSS); | ||
virtualMemGauge.set(structuredOutput.VmSize); | ||
heapSizeMemGauge.set(structuredOutput.VmData); | ||
} catch (er) { | ||
return; | ||
} | ||
}; | ||
}; | ||
@@ -80,3 +80,3 @@ | ||
PROCESS_VIRTUAL_MEMORY, | ||
PROCESS_HEAP | ||
PROCESS_HEAP, | ||
]; |
@@ -11,7 +11,25 @@ 'use strict'; | ||
const namePrefix = config.prefix ? config.prefix : ''; | ||
const labels = config.labels ? config.labels : {}; | ||
const labelNames = Object.keys(labels); | ||
let lastCpuUsage = process.cpuUsage(); | ||
const cpuUserUsageCounter = new Counter({ | ||
name: namePrefix + PROCESS_CPU_USER_SECONDS, | ||
help: 'Total user CPU time spent in seconds.', | ||
registers | ||
registers, | ||
labelNames, | ||
// Use this one metric's `collect` to set all metrics' values. | ||
collect() { | ||
const cpuUsage = process.cpuUsage(); | ||
const userUsageMicros = cpuUsage.user - lastCpuUsage.user; | ||
const systemUsageMicros = cpuUsage.system - lastCpuUsage.system; | ||
lastCpuUsage = cpuUsage; | ||
cpuUserUsageCounter.inc(labels, userUsageMicros / 1e6); | ||
cpuSystemUsageCounter.inc(labels, systemUsageMicros / 1e6); | ||
cpuUsageCounter.inc(labels, (userUsageMicros + systemUsageMicros) / 1e6); | ||
}, | ||
}); | ||
@@ -21,3 +39,4 @@ const cpuSystemUsageCounter = new Counter({ | ||
help: 'Total system CPU time spent in seconds.', | ||
registers | ||
registers, | ||
labelNames, | ||
}); | ||
@@ -27,20 +46,5 @@ const cpuUsageCounter = new Counter({ | ||
help: 'Total user and system CPU time spent in seconds.', | ||
registers | ||
registers, | ||
labelNames, | ||
}); | ||
let lastCpuUsage = process.cpuUsage(); | ||
return () => { | ||
const cpuUsage = process.cpuUsage(); | ||
const now = Date.now(); | ||
const userUsageMicros = cpuUsage.user - lastCpuUsage.user; | ||
const systemUsageMicros = cpuUsage.system - lastCpuUsage.system; | ||
lastCpuUsage = cpuUsage; | ||
cpuUserUsageCounter.inc(userUsageMicros / 1e6, now); | ||
cpuSystemUsageCounter.inc(systemUsageMicros / 1e6, now); | ||
cpuUsageCounter.inc((userUsageMicros + systemUsageMicros) / 1e6, now); | ||
}; | ||
}; | ||
@@ -51,3 +55,3 @@ | ||
PROCESS_CPU_SYSTEM_SECONDS, | ||
PROCESS_CPU_SECONDS | ||
PROCESS_CPU_SECONDS, | ||
]; |
@@ -11,27 +11,33 @@ 'use strict'; | ||
module.exports = (registry, config = {}) => { | ||
// Don't do anything if the function is removed in later nodes (exists in node@6) | ||
// Don't do anything if the function is removed in later nodes (exists in node@6-12...) | ||
if (typeof process._getActiveHandles !== 'function') { | ||
return () => {}; | ||
return; | ||
} | ||
const registers = registry ? [registry] : undefined; | ||
const namePrefix = config.prefix ? config.prefix : ''; | ||
const labels = config.labels ? config.labels : {}; | ||
const labelNames = Object.keys(labels); | ||
const gauge = new Gauge({ | ||
new Gauge({ | ||
name: namePrefix + NODEJS_ACTIVE_HANDLES, | ||
help: | ||
'Number of active libuv handles grouped by handle type. Every handle type is C++ class name.', | ||
labelNames: ['type'], | ||
registers: registry ? [registry] : undefined | ||
labelNames: ['type', ...labelNames], | ||
registers, | ||
collect() { | ||
const handles = process._getActiveHandles(); | ||
updateMetrics(this, aggregateByObjectName(handles), labels); | ||
}, | ||
}); | ||
const totalGauge = new Gauge({ | ||
new Gauge({ | ||
name: namePrefix + NODEJS_ACTIVE_HANDLES_TOTAL, | ||
help: 'Total number of active handles.', | ||
registers: registry ? [registry] : undefined | ||
registers, | ||
labelNames, | ||
collect() { | ||
const handles = process._getActiveHandles(); | ||
this.set(labels, handles.length); | ||
}, | ||
}); | ||
return () => { | ||
const handles = process._getActiveHandles(); | ||
updateMetrics(gauge, aggregateByObjectName(handles)); | ||
totalGauge.set(handles.length); | ||
}; | ||
}; | ||
@@ -41,3 +47,3 @@ | ||
NODEJS_ACTIVE_HANDLES, | ||
NODEJS_ACTIVE_HANDLES_TOTAL | ||
NODEJS_ACTIVE_HANDLES_TOTAL, | ||
]; |
@@ -16,28 +16,31 @@ 'use strict'; | ||
const lines = limits.split('\n'); | ||
lines.find(line => { | ||
for (const line of lines) { | ||
if (line.startsWith('Max open files')) { | ||
const parts = line.split(/ +/); | ||
maxFds = Number(parts[1]); | ||
return true; | ||
break; | ||
} | ||
}); | ||
} catch (er) { | ||
return () => {}; | ||
} | ||
} catch { | ||
return; | ||
} | ||
} | ||
if (maxFds === undefined) return () => {}; | ||
if (maxFds === undefined) return; | ||
const namePrefix = config.prefix ? config.prefix : ''; | ||
const fileDescriptorsGauge = new Gauge({ | ||
const labels = config.labels ? config.labels : {}; | ||
const labelNames = Object.keys(labels); | ||
new Gauge({ | ||
name: namePrefix + PROCESS_MAX_FDS, | ||
help: 'Maximum number of open file descriptors.', | ||
registers: registry ? [registry] : undefined | ||
registers: registry ? [registry] : undefined, | ||
labelNames, | ||
collect() { | ||
if (maxFds !== undefined) this.set(labels, maxFds); | ||
}, | ||
}); | ||
fileDescriptorsGauge.set(Number(maxFds)); | ||
return () => {}; | ||
}; | ||
module.exports.metricNames = [PROCESS_MAX_FDS]; |
@@ -11,25 +11,27 @@ 'use strict'; | ||
if (process.platform !== 'linux') { | ||
return () => {}; | ||
return; | ||
} | ||
const namePrefix = config.prefix ? config.prefix : ''; | ||
const labels = config.labels ? config.labels : {}; | ||
const labelNames = Object.keys(labels); | ||
const fileDescriptorsGauge = new Gauge({ | ||
new Gauge({ | ||
name: namePrefix + PROCESS_OPEN_FDS, | ||
help: 'Number of open file descriptors.', | ||
registers: registry ? [registry] : undefined | ||
registers: registry ? [registry] : undefined, | ||
labelNames, | ||
collect() { | ||
try { | ||
const fds = fs.readdirSync('/proc/self/fd'); | ||
// Minus 1 to not count the fd that was used by readdirSync(), | ||
// it's now closed. | ||
this.set(labels, fds.length - 1); | ||
} catch { | ||
// noop | ||
} | ||
}, | ||
}); | ||
return () => { | ||
try { | ||
const fds = fs.readdirSync('/proc/self/fd'); | ||
// Minus 1 to not count the fd that was used by readdirSync(), its now | ||
// closed. | ||
fileDescriptorsGauge.set(fds.length - 1); | ||
} catch (er) { | ||
return; | ||
} | ||
}; | ||
}; | ||
module.exports.metricNames = [PROCESS_OPEN_FDS]; |
@@ -12,26 +12,31 @@ 'use strict'; | ||
if (typeof process._getActiveRequests !== 'function') { | ||
return () => {}; | ||
return; | ||
} | ||
const namePrefix = config.prefix ? config.prefix : ''; | ||
const labels = config.labels ? config.labels : {}; | ||
const labelNames = Object.keys(labels); | ||
const gauge = new Gauge({ | ||
new Gauge({ | ||
name: namePrefix + NODEJS_ACTIVE_REQUESTS, | ||
help: | ||
'Number of active libuv requests grouped by request type. Every request type is C++ class name.', | ||
labelNames: ['type'], | ||
registers: registry ? [registry] : undefined | ||
labelNames: ['type', ...labelNames], | ||
registers: registry ? [registry] : undefined, | ||
collect() { | ||
const requests = process._getActiveRequests(); | ||
updateMetrics(this, aggregateByObjectName(requests), labels); | ||
}, | ||
}); | ||
const totalGauge = new Gauge({ | ||
new Gauge({ | ||
name: namePrefix + NODEJS_ACTIVE_REQUESTS_TOTAL, | ||
help: 'Total number of active requests.', | ||
registers: registry ? [registry] : undefined | ||
registers: registry ? [registry] : undefined, | ||
labelNames, | ||
collect() { | ||
const requests = process._getActiveRequests(); | ||
this.set(labels, requests.length); | ||
}, | ||
}); | ||
return () => { | ||
const requests = process._getActiveRequests(); | ||
updateMetrics(gauge, aggregateByObjectName(requests)); | ||
totalGauge.set(requests.length, Date.now()); | ||
}; | ||
}; | ||
@@ -41,3 +46,3 @@ | ||
NODEJS_ACTIVE_REQUESTS, | ||
NODEJS_ACTIVE_REQUESTS_TOTAL | ||
NODEJS_ACTIVE_REQUESTS_TOTAL, | ||
]; |
@@ -10,14 +10,17 @@ 'use strict'; | ||
const namePrefix = config.prefix ? config.prefix : ''; | ||
const labels = config.labels ? config.labels : {}; | ||
const labelNames = Object.keys(labels); | ||
const cpuUserGauge = new Gauge({ | ||
new Gauge({ | ||
name: namePrefix + PROCESS_START_TIME, | ||
help: 'Start time of the process since unix epoch in seconds.', | ||
registers: registry ? [registry] : undefined, | ||
aggregator: 'omit' | ||
labelNames, | ||
aggregator: 'omit', | ||
collect() { | ||
this.set(labels, startInSeconds); | ||
}, | ||
}); | ||
cpuUserGauge.set(startInSeconds); | ||
return () => {}; | ||
}; | ||
module.exports.metricNames = [PROCESS_START_TIME]; |
@@ -5,6 +5,3 @@ 'use strict'; | ||
const version = process.version; | ||
const versionSegments = version | ||
.slice(1) | ||
.split('.') | ||
.map(Number); | ||
const versionSegments = version.slice(1).split('.').map(Number); | ||
@@ -15,17 +12,24 @@ const NODE_VERSION_INFO = 'nodejs_version_info'; | ||
const namePrefix = config.prefix ? config.prefix : ''; | ||
const labels = config.labels ? config.labels : {}; | ||
const labelNames = Object.keys(labels); | ||
const nodeVersionGauge = new Gauge({ | ||
new Gauge({ | ||
name: namePrefix + NODE_VERSION_INFO, | ||
help: 'Node.js version info.', | ||
labelNames: ['version', 'major', 'minor', 'patch'], | ||
labelNames: ['version', 'major', 'minor', 'patch', ...labelNames], | ||
registers: registry ? [registry] : undefined, | ||
aggregator: 'first' | ||
aggregator: 'first', | ||
collect() { | ||
// Needs to be in collect() so value is present even if reg is reset | ||
this.labels( | ||
version, | ||
versionSegments[0], | ||
versionSegments[1], | ||
versionSegments[2], | ||
...Object.values(labels), | ||
).set(1); | ||
}, | ||
}); | ||
nodeVersionGauge | ||
.labels(version, versionSegments[0], versionSegments[1], versionSegments[2]) | ||
.set(1); | ||
return () => {}; | ||
}; | ||
module.exports.metricNames = [NODE_VERSION_INFO]; |
@@ -51,3 +51,3 @@ 'use strict'; | ||
const path = `${gatewayUrlPath}/metrics/job/${encodeURIComponent( | ||
job | ||
job, | ||
)}${generateGroupings(groupings)}`; | ||
@@ -61,3 +61,3 @@ | ||
const options = Object.assign(requestParams, this.requestOptions, { | ||
method | ||
method, | ||
}); | ||
@@ -91,3 +91,4 @@ | ||
.map( | ||
key => `/${encodeURIComponent(key)}/${encodeURIComponent(groupings[key])}` | ||
key => | ||
`/${encodeURIComponent(key)}/${encodeURIComponent(groupings[key])}`, | ||
) | ||
@@ -94,0 +95,0 @@ .join(''); |
@@ -22,7 +22,7 @@ 'use strict'; | ||
getMetricsAsArray() { | ||
return Object.keys(this._metrics).map(this.getSingleMetric, this); | ||
return Object.values(this._metrics); | ||
} | ||
getMetricAsPrometheusString(metric) { | ||
const item = metric.get(); | ||
async getMetricAsPrometheusString(metric) { | ||
const item = await metric.get(); | ||
const name = escapeString(item.name); | ||
@@ -47,10 +47,14 @@ const help = `# HELP ${name} ${escapeString(item.help)}`; | ||
let labels = ''; | ||
for (const key of Object.keys(val.labels)) { | ||
labels += `${key}="${escapeLabelValue(val.labels[key])}",`; | ||
} | ||
let metricName = val.metricName || item.name; | ||
let metricName = val.metricName || item.name; | ||
if (labels) { | ||
metricName += `{${labels.substring(0, labels.length - 1)}}`; | ||
const keys = Object.keys(val.labels); | ||
const size = keys.length; | ||
if (size > 0) { | ||
let labels = ''; | ||
let i = 0; | ||
for (; i < size - 1; i++) { | ||
labels += `${keys[i]}="${escapeLabelValue(val.labels[keys[i]])}",`; | ||
} | ||
labels += `${keys[i]}="${escapeLabelValue(val.labels[keys[i]])}"`; | ||
metricName += `{${labels}}`; | ||
} | ||
@@ -64,12 +68,12 @@ | ||
metrics() { | ||
let metrics = ''; | ||
async metrics() { | ||
const promises = []; | ||
this.collect(); | ||
for (const metric of this.getMetricsAsArray()) { | ||
metrics += `${this.getMetricAsPrometheusString(metric)}\n\n`; | ||
promises.push(this.getMetricAsPrometheusString(metric)); | ||
} | ||
return metrics.substring(0, metrics.length - 1); | ||
const resolves = await Promise.all(promises); | ||
return `${resolves.join('\n\n')}\n`; | ||
} | ||
@@ -80,3 +84,3 @@ | ||
throw new Error( | ||
`A metric with the name ${metric.name} has already been registered.` | ||
`A metric with the name ${metric.name} has already been registered.`, | ||
); | ||
@@ -88,32 +92,20 @@ } | ||
registerCollector(collectorFn) { | ||
if (this._collectors.includes(collectorFn)) { | ||
return; // Silently ignore repeated registration. | ||
} | ||
this._collectors.push(collectorFn); | ||
} | ||
collectors() { | ||
return this._collectors; | ||
} | ||
collect() { | ||
this._collectors.forEach(collector => collector()); | ||
} | ||
clear() { | ||
this._metrics = {}; | ||
this._collectors = []; | ||
this._defaultLabels = {}; | ||
} | ||
getMetricsAsJSON() { | ||
async getMetricsAsJSON() { | ||
const metrics = []; | ||
const defaultLabelNames = Object.keys(this._defaultLabels); | ||
this.collect(); | ||
const promises = []; | ||
for (const metric of this.getMetricsAsArray()) { | ||
const item = metric.get(); | ||
promises.push(metric.get()); | ||
} | ||
const resolves = await Promise.all(promises); | ||
for (const item of resolves) { | ||
if (item.values && defaultLabelNames.length > 0) { | ||
@@ -168,3 +160,3 @@ for (const val of item.values) { | ||
(acc, reg) => acc.concat(reg.getMetricsAsArray()), | ||
[] | ||
[], | ||
); | ||
@@ -171,0 +163,0 @@ |
@@ -7,16 +7,6 @@ /** | ||
const util = require('util'); | ||
const { globalRegistry } = require('./registry'); | ||
const type = 'summary'; | ||
const { | ||
getPropertiesFromObj, | ||
getLabels, | ||
hashObject, | ||
isObject, | ||
removeLabels | ||
} = require('./util'); | ||
const { | ||
validateLabel, | ||
validateMetricName, | ||
validateLabelName | ||
} = require('./validation'); | ||
const { getLabels, hashObject, removeLabels } = require('./util'); | ||
const { validateLabel } = require('./validation'); | ||
const { Metric } = require('./metric'); | ||
const timeWindowQuantiles = require('./timeWindowQuantiles'); | ||
@@ -26,38 +16,15 @@ | ||
class Summary { | ||
/** | ||
* Summary | ||
* @param {config} config - Configuration object. | ||
* @param {string} config.name - Name of the metric | ||
* @param {string} config.help - Help for the metric | ||
* @param {Array.<string>} config.labels - Array with strings, all label keywords supported | ||
*/ | ||
class Summary extends Metric { | ||
constructor(config) { | ||
if (!isObject(config)) { | ||
throw new TypeError('constructor expected a config object'); | ||
super(config, { | ||
percentiles: [0.01, 0.05, 0.5, 0.9, 0.95, 0.99, 0.999], | ||
compressCount: DEFAULT_COMPRESS_COUNT, | ||
hashMap: {}, | ||
}); | ||
for (const label of this.labelNames) { | ||
if (label === 'quantile') | ||
throw new Error('quantile is a reserved label keyword'); | ||
} | ||
config = Object.assign( | ||
{ | ||
percentiles: [0.01, 0.05, 0.5, 0.9, 0.95, 0.99, 0.999], | ||
labelNames: [] | ||
}, | ||
config | ||
); | ||
if (!config.registers) { | ||
config.registers = [globalRegistry]; | ||
} | ||
validateInput(config.name, config.help, config.labelNames); | ||
this.maxAgeSeconds = config.maxAgeSeconds; | ||
this.ageBuckets = config.ageBuckets; | ||
this.name = config.name; | ||
this.help = config.help; | ||
this.aggregator = config.aggregator || 'sum'; | ||
this.percentiles = config.percentiles; | ||
this.hashMap = {}; | ||
this.labelNames = config.labelNames || []; | ||
this.compressCount = config.compressCount || DEFAULT_COMPRESS_COUNT; | ||
if (this.labelNames.length === 0) { | ||
@@ -69,10 +36,6 @@ this.hashMap = { | ||
count: 0, | ||
sum: 0 | ||
} | ||
sum: 0, | ||
}, | ||
}; | ||
} | ||
config.registers.forEach(registryInstance => | ||
registryInstance.registerMetric(this) | ||
); | ||
} | ||
@@ -90,4 +53,8 @@ | ||
get() { | ||
const data = getPropertiesFromObj(this.hashMap); | ||
async get() { | ||
if (this.collect) { | ||
const v = this.collect(); | ||
if (v instanceof Promise) await v; | ||
} | ||
const data = Object.values(this.hashMap); | ||
const values = []; | ||
@@ -107,3 +74,3 @@ data.forEach(s => { | ||
values, | ||
aggregator: this.aggregator | ||
aggregator: this.aggregator, | ||
}; | ||
@@ -113,3 +80,3 @@ } | ||
reset() { | ||
const data = getPropertiesFromObj(this.hashMap); | ||
const data = Object.values(this.hashMap); | ||
data.forEach(s => { | ||
@@ -140,3 +107,3 @@ s.td.reset(); | ||
observe: observe.call(this, labels), | ||
startTimer: startTimer.call(this, labels) | ||
startTimer: startTimer.call(this, labels), | ||
}; | ||
@@ -158,3 +125,3 @@ } | ||
labels: Object.assign({ quantile: percentile }, summaryOfLabels.labels), | ||
value: percentileValue ? percentileValue : 0 | ||
value: percentileValue ? percentileValue : 0, | ||
}; | ||
@@ -168,3 +135,3 @@ }); | ||
labels: value.labels, | ||
value: value.count | ||
value: value.count, | ||
}; | ||
@@ -177,3 +144,3 @@ } | ||
labels: value.labels, | ||
value: value.sum | ||
value: value.sum, | ||
}; | ||
@@ -189,3 +156,3 @@ } | ||
Object.assign({}, startLabels, endLabels), | ||
delta[0] + delta[1] / 1e9 | ||
delta[0] + delta[1] / 1e9, | ||
); | ||
@@ -196,25 +163,2 @@ }; | ||
function validateInput(name, help, labels) { | ||
if (!help) { | ||
throw new Error('Missing mandatory help parameter'); | ||
} | ||
if (!name) { | ||
throw new Error('Missing mandatory name parameter'); | ||
} | ||
if (!validateMetricName(name)) { | ||
throw new Error('Invalid metric name'); | ||
} | ||
if (!validateLabelName(labels)) { | ||
throw new Error('Invalid label name'); | ||
} | ||
labels.forEach(label => { | ||
if (label === 'quantile') { | ||
throw new Error('quantile is a reserved label keyword'); | ||
} | ||
}); | ||
} | ||
function observe(labels) { | ||
@@ -227,3 +171,3 @@ return value => { | ||
throw new TypeError( | ||
`Value is not a valid number: ${util.format(labelValuePair.value)}` | ||
`Value is not a valid number: ${util.format(labelValuePair.value)}`, | ||
); | ||
@@ -239,3 +183,3 @@ } | ||
count: 0, | ||
sum: 0 | ||
sum: 0, | ||
}; | ||
@@ -258,3 +202,3 @@ } | ||
value: labels, | ||
labels: {} | ||
labels: {}, | ||
}; | ||
@@ -265,3 +209,3 @@ } | ||
labels, | ||
value | ||
value, | ||
}; | ||
@@ -268,0 +212,0 @@ } |
'use strict'; | ||
exports.getPropertiesFromObj = function(hashMap) { | ||
const obj = Object.keys(hashMap).map(x => hashMap[x]); | ||
return obj; | ||
}; | ||
exports.getValueAsString = function getValueString(value) { | ||
@@ -31,3 +26,3 @@ if (Number.isNaN(value)) { | ||
value: typeof value === 'number' ? value : 0, | ||
labels: labels || {} | ||
labels: labels || {}, | ||
}; | ||
@@ -38,3 +33,3 @@ return hashMap; | ||
// TODO: For node 6, use rest params | ||
exports.getLabels = function(labelNames, args) { | ||
exports.getLabels = function (labelNames, args) { | ||
if (labelNames.length !== args.length) { | ||
@@ -41,0 +36,0 @@ throw new Error('Invalid number of arguments'); |
@@ -9,7 +9,7 @@ 'use strict'; | ||
exports.validateMetricName = function(name) { | ||
exports.validateMetricName = function (name) { | ||
return metricRegexp.test(name); | ||
}; | ||
exports.validateLabelName = function(names) { | ||
exports.validateLabelName = function (names) { | ||
let valid = true; | ||
@@ -29,4 +29,4 @@ (names || []).forEach(name => { | ||
`Added label "${label}" is not included in initial labelset: ${util.inspect( | ||
savedLabels | ||
)}` | ||
savedLabels, | ||
)}`, | ||
); | ||
@@ -33,0 +33,0 @@ } |
{ | ||
"name": "prom-client", | ||
"version": "12.0.0", | ||
"version": "13.0.0", | ||
"description": "Client for prometheus", | ||
@@ -35,3 +35,3 @@ "main": "index.js", | ||
"@clevernature/benchmark-regression": "^1.0.0", | ||
"eslint": "^6.8.0", | ||
"eslint": "^7.7.0", | ||
"eslint-config-prettier": "^6.10.0", | ||
@@ -42,7 +42,6 @@ "eslint-plugin-node": "^11.0.0", | ||
"husky": "^4.2.1", | ||
"jest": "^25.1.0", | ||
"jest": "^26.0.1", | ||
"lint-staged": "^10.0.4", | ||
"lolex": "^5.1.2", | ||
"prettier": "1.19.1", | ||
"typescript": "^3.0.3" | ||
"prettier": "2.0.5", | ||
"typescript": "^4.0.2" | ||
}, | ||
@@ -65,2 +64,4 @@ "dependencies": { | ||
"useTabs": true, | ||
"arrowParens": "avoid", | ||
"trailingComma": "all", | ||
"overrides": [ | ||
@@ -67,0 +68,0 @@ { |
278
README.md
@@ -1,4 +0,4 @@ | ||
# Prometheus client for node.js [![Build Status](https://travis-ci.org/siimon/prom-client.svg?branch=master)](https://travis-ci.org/siimon/prom-client) [![Build status](https://ci.appveyor.com/api/projects/status/k2e0gwonkcee3lp9/branch/master?svg=true)](https://ci.appveyor.com/project/siimon/prom-client/branch/master) [![Actions Status](https://github.com/siimon/prom-client/workflows/Node.js%20CI/badge.svg?branch=master)](https://github.com/siimon/prom-client/actions) | ||
# 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) | ||
A prometheus client for node.js that supports histogram, summaries, gauges and | ||
A prometheus client for Node.js that supports histogram, summaries, gauges and | ||
counters. | ||
@@ -9,4 +9,4 @@ | ||
See example folder for a sample usage. The library does not bundle any web | ||
framework, to expose the metrics just return the `metrics()` function in the | ||
registry. | ||
framework. To expose the metrics, respond to Prometheus's scrape requests with | ||
the result of `await registry.metrics()`. | ||
@@ -38,6 +38,2 @@ ### Usage with Node.js's `cluster` module | ||
### Configuration | ||
All metric types has 2 mandatory parameters, name and help. | ||
### Default metrics | ||
@@ -47,3 +43,6 @@ | ||
[itself](https://prometheus.io/docs/instrumenting/writing_clientlibs/#standard-and-runtime-collectors). | ||
To collect these, call `collectDefaultMetrics` | ||
To collect these, call `collectDefaultMetrics`. In addition, some | ||
Node.js-specific metrics are included, such as event loop lag, active handles, | ||
GC and Node.js version. See [lib/metrics](lib/metrics) for a list of all | ||
metrics. | ||
@@ -53,6 +52,2 @@ NOTE: Some of the metrics, concerning File Descriptors and Memory, are only | ||
In addition, some Node-specific metrics are included, such as event loop lag, | ||
active handles, GC and Node.js version. See what metrics there are in | ||
[lib/metrics](lib/metrics). | ||
`collectDefaultMetrics` optionally accepts a config object with following entries: | ||
@@ -69,3 +64,2 @@ | ||
const client = require('prom-client'); | ||
const collectDefaultMetrics = client.collectDefaultMetrics; | ||
@@ -81,5 +75,3 @@ const Registry = client.Registry; | ||
const client = require('prom-client'); | ||
const collectDefaultMetrics = client.collectDefaultMetrics; | ||
collectDefaultMetrics({ gcDurationBuckets: [0.1, 0.2, 0.3] }); | ||
@@ -97,12 +89,18 @@ ``` | ||
To apply generic labels to all default metrics, pass an object to the `labels` property (useful if you're working in a clustered environment): | ||
```js | ||
const client = require('prom-client'); | ||
const collectDefaultMetrics = client.collectDefaultMetrics; | ||
collectDefaultMetrics({ | ||
labels: { NODE_APP_INSTANCE: process.env.NODE_APP_INSTANCE }, | ||
}); | ||
``` | ||
You can get the full list of metrics by inspecting | ||
`client.collectDefaultMetrics.metricsList`. | ||
`collectDefaultMetrics` returns an identification when invoked, which is a | ||
reference to the `Timer` used to keep the probes going. This can be passed to | ||
`clearInterval` in order to stop all probes. | ||
Default metrics are collected on scrape of metrics endpoint, | ||
not on an interval. | ||
NOTE: Existing intervals are automatically cleared when calling | ||
`collectDefaultMetrics`. | ||
```js | ||
@@ -113,29 +111,23 @@ const client = require('prom-client'); | ||
const interval = collectDefaultMetrics(); | ||
// ... some time later | ||
clearInterval(interval); | ||
collectDefaultMetrics(); | ||
``` | ||
NOTE: `unref` is called on the `interval` internally, so it will not keep your | ||
node process going indefinitely if it's the only thing keeping it from shutting | ||
down. | ||
### Custom Metrics | ||
#### Stop polling default metrics | ||
All metric types have two mandatory parameters: `name` and `help`. Refer to | ||
<https://prometheus.io/docs/practices/naming/> for guidance on naming metrics. | ||
To stop collecting the default metrics, you have to call the function and pass | ||
it to `clearInterval`. | ||
For metrics based on point-in-time observations (e.g. current memory usage, as | ||
opposed to HTTP request durations observed continuously in a histogram), you | ||
should provide a `collect()` function, which will be invoked when Prometheus | ||
scrapes your metrics endpoint. `collect()` can either be synchronous or return a | ||
promise. See **Gauge** below for an example. (Note that you should not update | ||
metric values in a `setInterval` callback; do so in this `collect` function | ||
instead.) | ||
```js | ||
const client = require('prom-client'); | ||
See [**Labels**](#labels) for information on how to configure labels for all | ||
metric types. | ||
clearInterval(client.collectDefaultMetrics()); | ||
#### Counter | ||
// Clear the register | ||
client.register.clear(); | ||
``` | ||
### Counter | ||
Counters go up, and reset when the process restarts. | ||
@@ -147,11 +139,11 @@ | ||
name: 'metric_name', | ||
help: 'metric_help' | ||
help: 'metric_help', | ||
}); | ||
counter.inc(); // Inc with 1 | ||
counter.inc(10); // Inc with 10 | ||
counter.inc(); // Increment by 1 | ||
counter.inc(10); // Increment by 10 | ||
``` | ||
### Gauge | ||
#### Gauge | ||
Gauges are similar to Counters but Gauges value can be decreased. | ||
Gauges are similar to Counters but a Gauge's value can be decreased. | ||
@@ -162,40 +154,66 @@ ```js | ||
gauge.set(10); // Set to 10 | ||
gauge.inc(); // Inc with 1 | ||
gauge.inc(10); // Inc with 10 | ||
gauge.dec(); // Dec with 1 | ||
gauge.dec(10); // Dec with 10 | ||
gauge.inc(); // Increment 1 | ||
gauge.inc(10); // Increment 10 | ||
gauge.dec(); // Decrement by 1 | ||
gauge.dec(10); // Decrement by 10 | ||
``` | ||
There are some utilities for common use cases: | ||
##### Configuration | ||
If the gauge is used for a point-in-time observation, you should provide a | ||
`collect` function: | ||
```js | ||
gauge.setToCurrentTime(); // Sets value to current time | ||
const end = gauge.startTimer(); | ||
xhrRequest(function(err, res) { | ||
end(); // Sets value to xhrRequests duration in seconds | ||
const client = require('prom-client'); | ||
new client.Gauge({ | ||
name: 'metric_name', | ||
help: 'metric_help', | ||
collect() { | ||
// Invoked when the registry collects its metrics' values. | ||
// This can be synchronous or it can return a promise/be an async function. | ||
this.set(/* the current value */); | ||
}, | ||
}); | ||
``` | ||
### Histogram | ||
Histograms track sizes and frequency of events. | ||
**Configuration** | ||
The defaults buckets are intended to cover usual web/rpc requests, this can | ||
however be overridden. | ||
```js | ||
// Async version: | ||
const client = require('prom-client'); | ||
new client.Histogram({ | ||
new client.Gauge({ | ||
name: 'metric_name', | ||
help: 'metric_help', | ||
buckets: [0.1, 5, 15, 50, 100, 500] | ||
async collect() { | ||
// Invoked when the registry collects its metrics' values. | ||
const currentValue = await somethingAsync(); | ||
this.set(currentValue); | ||
}, | ||
}); | ||
``` | ||
You can include all label names as a property as well. | ||
Note that you should not use arrow functions for `collect` because arrow | ||
functions will not have the correct value for `this`. | ||
##### Utility Functions | ||
```js | ||
// Set value to current time: | ||
gauge.setToCurrentTime(); | ||
// Record durations: | ||
const end = gauge.startTimer(); | ||
http.get('url', res => { | ||
end(); | ||
}); | ||
``` | ||
#### Histogram | ||
Histograms track sizes and frequency of events. | ||
##### Configuration | ||
The defaults buckets are intended to cover usual web/RPC requests, but they can | ||
be overridden. (See also [**Bucket Generators**](#bucket-generators).) | ||
```js | ||
const client = require('prom-client'); | ||
@@ -205,8 +223,7 @@ new client.Histogram({ | ||
help: 'metric_help', | ||
labelNames: ['status_code'], | ||
buckets: [0.1, 5, 15, 50, 100, 500] | ||
buckets: [0.1, 5, 15, 50, 100, 500], | ||
}); | ||
``` | ||
Examples | ||
##### Examples | ||
@@ -217,3 +234,3 @@ ```js | ||
name: 'metric_name', | ||
help: 'metric_help' | ||
help: 'metric_help', | ||
}); | ||
@@ -223,7 +240,7 @@ histogram.observe(10); // Observe value in histogram | ||
Utility to observe request durations | ||
##### Utility Methods | ||
```js | ||
const end = histogram.startTimer(); | ||
xhrRequest(function(err, res) { | ||
xhrRequest(function (err, res) { | ||
const seconds = end(); // Observes and returns the value to xhrRequests duration in seconds | ||
@@ -233,10 +250,11 @@ }); | ||
### Summary | ||
#### Summary | ||
Summaries calculate percentiles of observed values. | ||
**Configuration** | ||
##### Configuration | ||
The default percentiles are: 0.01, 0.05, 0.5, 0.9, 0.95, 0.99, 0.999. But they | ||
can be overridden like this: | ||
can be overridden by specifying a `percentiles` array. (See also | ||
[**Bucket Generators**](#bucket-generators).) | ||
@@ -248,3 +266,3 @@ ```js | ||
help: 'metric_help', | ||
percentiles: [0.01, 0.1, 0.9, 0.99] | ||
percentiles: [0.01, 0.1, 0.9, 0.99], | ||
}); | ||
@@ -262,11 +280,11 @@ ``` | ||
maxAgeSeconds: 600, | ||
ageBuckets: 5 | ||
ageBuckets: 5, | ||
}); | ||
``` | ||
The `maxAgeSeconds` will tell how old an bucket can be before it is reset and | ||
The `maxAgeSeconds` will tell how old a bucket can be before it is reset and | ||
`ageBuckets` configures how many buckets we will have in our sliding window for | ||
the summary. | ||
Usage example | ||
##### Examples | ||
@@ -277,3 +295,3 @@ ```js | ||
name: 'metric_name', | ||
help: 'metric_help' | ||
help: 'metric_help', | ||
}); | ||
@@ -283,7 +301,7 @@ summary.observe(10); | ||
Utility to observe request durations | ||
##### Utility Methods | ||
```js | ||
const end = summary.startTimer(); | ||
xhrRequest(function(err, res) { | ||
xhrRequest(function (err, res) { | ||
end(); // Observes the value to xhrRequests duration in seconds | ||
@@ -295,5 +313,5 @@ }); | ||
All metrics can take a labelNames property in the configuration object. All | ||
labelNames that the metric support needs to be declared here. There are 2 ways | ||
to add values to the labels | ||
All metrics can take a `labelNames` property in the configuration object. All | ||
label names that the metric support needs to be declared here. There are two | ||
ways to add values to the labels: | ||
@@ -305,7 +323,9 @@ ```js | ||
help: 'metric_help', | ||
labelNames: ['method', 'statusCode'] | ||
labelNames: ['method', 'statusCode'], | ||
}); | ||
gauge.set({ method: 'GET', statusCode: '200' }, 100); // 1st version, Set value 100 with method set to GET and statusCode to 200 | ||
gauge.labels('GET', '200').set(100); // 2nd version, Same as above | ||
// 1st version: Set value to 100 with "method" set to "GET" and "statusCode" to "200" | ||
gauge.set({ method: 'GET', statusCode: '200' }, 100); | ||
// 2nd version: Same effect as above | ||
gauge.labels('GET', '200').set(100); | ||
``` | ||
@@ -318,3 +338,3 @@ | ||
const end = startTimer({ method: 'GET' }); // Set method to GET, we don't know statusCode yet | ||
xhrRequest(function(err, res) { | ||
xhrRequest(function (err, res) { | ||
if (err) { | ||
@@ -353,9 +373,8 @@ end({ statusCode: '500' }); // Sets value to xhrRequest duration in seconds with statusCode 500 | ||
By default, metrics are automatically registered to the global registry (located | ||
at `require('prom-client').register`). You can prevent this by setting last | ||
parameter when creating the metric to `false` (depending on metric, this might | ||
be 4th or 5th parameter). | ||
at `require('prom-client').register`). You can prevent this by specifying | ||
`registers: []` in the metric constructor configuration. | ||
Using non-global registries requires creating Registry instance and adding it | ||
inside `registers` inside the configuration object. Alternatively you can pass | ||
an empty `registers` array and register it manually. | ||
Using non-global registries requires creating a Registry instance and passing it | ||
inside `registers` in the metric configuration object. Alternatively you can | ||
pass an empty `registers` array and register it manually. | ||
@@ -372,3 +391,3 @@ Registry has a `merge` function that enables you to expose multiple registries | ||
help: 'metric_help', | ||
registers: [registry] | ||
registers: [registry], // specify a non-default registry | ||
}); | ||
@@ -378,5 +397,5 @@ const histogram = new client.Histogram({ | ||
help: 'metric_help', | ||
registers: [] | ||
registers: [], // don't automatically register this metric | ||
}); | ||
registry.registerMetric(histogram); | ||
registry.registerMetric(histogram); // register metric manually | ||
counter.inc(); | ||
@@ -399,10 +418,10 @@ | ||
You can get all metrics by running `register.metrics()`, which will output a | ||
string for prometheus to consume. | ||
You can get all metrics by running `await register.metrics()`, which will return | ||
a string in the Prometheus exposition format. | ||
#### Getting a single metric for Prometheus displaying | ||
#### Getting a single metric value in Prometheus exposition format | ||
If you need to output a single metric for Prometheus, you can use | ||
`register.getSingleMetricAsString(*name of metric*)`, it will output a string | ||
for Prometheus to consume. | ||
If you need to output a single metric in the Prometheus exposition format, you | ||
can use `await register.getSingleMetricAsString(*name of metric*)`, which will | ||
return a string for Prometheus to consume. | ||
@@ -412,3 +431,3 @@ #### Getting a single metric | ||
If you need to get a reference to a previously registered metric, you can use | ||
`register.getSingleMetric(*name of metric*)`. | ||
`await register.getSingleMetric(*name of metric*)`. | ||
@@ -428,8 +447,11 @@ #### Removing metrics | ||
You can get aggregated metrics for all workers in a node.js cluster with | ||
`register.clusterMetrics()`. This method both returns a promise and accepts a | ||
callback, both of which resolve with a metrics string suitable for Prometheus to | ||
consume. | ||
You can get aggregated metrics for all workers in a Node.js cluster with | ||
`await register.clusterMetrics()`. This method returns a promise that resolves | ||
with a metrics string suitable for Prometheus to consume. | ||
```js | ||
const metrics = await register.clusterMetrics(); | ||
// - or - | ||
register | ||
@@ -443,8 +465,2 @@ .clusterMetrics() | ||
}); | ||
// - or - | ||
register.clusterMetrics((err, metrics) => { | ||
// ... | ||
}); | ||
``` | ||
@@ -461,11 +477,11 @@ | ||
gateway.pushAdd({ jobName: 'test' }, function(err, resp, body) {}); //Add metric and overwrite old ones | ||
gateway.push({ jobName: 'test' }, function(err, resp, body) {}); //Overwrite all metrics (use PUT) | ||
gateway.delete({ jobName: 'test' }, function(err, resp, body) {}); //Delete all metrics for jobName | ||
gateway.pushAdd({ jobName: 'test' }, function (err, resp, body) {}); //Add metric and overwrite old ones | ||
gateway.push({ jobName: 'test' }, function (err, resp, body) {}); //Overwrite all metrics (use PUT) | ||
gateway.delete({ jobName: 'test' }, function (err, resp, body) {}); //Delete all metrics for jobName | ||
//All gateway requests can have groupings on it | ||
gateway.pushAdd({ jobName: 'test', groupings: { key: 'value' } }, function( | ||
gateway.pushAdd({ jobName: 'test', groupings: { key: 'value' } }, function ( | ||
err, | ||
resp, | ||
body | ||
body, | ||
) {}); | ||
@@ -477,5 +493,5 @@ | ||
### Utilities | ||
### Bucket Generators | ||
For convenience, there are 2 bucket generator functions - linear and | ||
For convenience, there are two bucket generator functions - linear and | ||
exponential. | ||
@@ -488,3 +504,3 @@ | ||
help: 'metric_help', | ||
buckets: client.linearBuckets(0, 10, 20) //Create 20 buckets, starting on 0 and a width of 10 | ||
buckets: client.linearBuckets(0, 10, 20), //Create 20 buckets, starting on 0 and a width of 10 | ||
}); | ||
@@ -495,3 +511,3 @@ | ||
help: 'metric_help', | ||
buckets: client.exponentialBuckets(1, 2, 5) //Create 5 buckets, starting on 1 and with a factor of 2 | ||
buckets: client.exponentialBuckets(1, 2, 5), //Create 5 buckets, starting on 1 and with a factor of 2 | ||
}); | ||
@@ -503,5 +519,7 @@ ``` | ||
## Garbage Collection | ||
### Garbage Collection Metrics | ||
To avoid dependencies in this module, GC stats are kept outside of it. If you | ||
want GC stats, you can use https://github.com/SimenB/node-prometheus-gc-stats | ||
To avoid native dependencies in this module, GC statistics for bytes reclaimed | ||
in each GC sweep are kept in a separate module: | ||
https://github.com/SimenB/node-prometheus-gc-stats. (Note that that metric may | ||
no longer be accurate now that v8 uses parallel garbage collection.) |
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
11
35
493
111730
2532