prom-client
Advanced tools
| 'use strict'; | ||
| const { globalRegistry } = require('./registry'); | ||
| const { isObject } = require('./util'); | ||
| const { validateMetricName, validateLabelName } = require('./validation'); | ||
| /** | ||
| * @abstract | ||
| */ | ||
| class Metric { | ||
| constructor(config, defaults = {}) { | ||
| if (!isObject(config)) { | ||
| throw new TypeError('constructor expected a config object'); | ||
| } | ||
| Object.assign( | ||
| this, | ||
| { | ||
| labelNames: [], | ||
| registers: [globalRegistry], | ||
| aggregator: 'sum', | ||
| }, | ||
| defaults, | ||
| config, | ||
| ); | ||
| if (!this.registers) { | ||
| // in case config.registers is `undefined` | ||
| this.registers = [globalRegistry]; | ||
| } | ||
| if (!this.help) { | ||
| throw new Error('Missing mandatory help parameter'); | ||
| } | ||
| if (!this.name) { | ||
| throw new Error('Missing mandatory name parameter'); | ||
| } | ||
| if (!validateMetricName(this.name)) { | ||
| throw new Error('Invalid metric name'); | ||
| } | ||
| if (!validateLabelName(this.labelNames)) { | ||
| throw new Error('Invalid label name'); | ||
| } | ||
| if (this.collect && typeof this.collect !== 'function') { | ||
| throw new Error('Optional "collect" parameter must be a function'); | ||
| } | ||
| this.reset(); | ||
| for (const register of this.registers) { | ||
| register.registerMetric(this); | ||
| } | ||
| } | ||
| reset() { | ||
| /* abstract */ | ||
| } | ||
| } | ||
| module.exports = { Metric }; |
+48
-1
@@ -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 |
+38
-40
@@ -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 @@ |
+30
-21
@@ -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 @@ }); |
+14
-65
@@ -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; |
+15
-60
@@ -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 @@ } |
+30
-86
@@ -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, | ||
| ]; |
+10
-8
@@ -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]; |
+16
-12
@@ -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(''); |
+29
-37
@@ -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 @@ |
+32
-88
@@ -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 @@ } |
+2
-7
| '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 @@ } |
+7
-6
| { | ||
| "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 @@ { |
+148
-130
@@ -1,4 +0,4 @@ | ||
| # Prometheus client for node.js [](https://travis-ci.org/siimon/prom-client) [](https://ci.appveyor.com/project/siimon/prom-client/branch/master) [](https://github.com/siimon/prom-client/actions) | ||
| # Prometheus client for node.js [](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.) |
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 2 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 2 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
11
-8.33%35
2.94%493
3.79%111730
-0.21%2532
-4.56%8
14.29%