Socket
Socket
Sign inDemoInstall

prom-client

Package Overview
Dependencies
Maintainers
2
Versions
84
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

prom-client - npm Package Compare versions

Comparing version 12.0.0 to 13.0.0

lib/metric.js

49

CHANGELOG.md

@@ -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

78

index.d.ts

@@ -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 @@ {

@@ -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.)
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc