prom-client
Advanced tools
Comparing version 11.5.3 to 12.0.0
@@ -12,6 +12,28 @@ # Changelog | ||
- Dropped support for end-of-life Node.js versions 6.x and 8.x | ||
- Dropped the previously deprecated support for positional parameters in | ||
constructors, only the config object forms remain. | ||
- Default metrics are collected on scrape of metrics endpoint, not on an | ||
interval. The `timeout` option to `collectDefaultMetrics(conf)` is no longer | ||
supported or needed, and the function no longer returns a `Timeout` object. | ||
### Changed | ||
- chore: remove ignored package-lock.json | ||
- fix: `process_max_fds` is process limit, not OS (#314) | ||
- Changed `Metric` labelNames & labelValues in TypeScript declaration to a generic type `T extends string`, instead of `string` | ||
- Lazy-load Node.js Cluster module to fix Passenger support (#293) | ||
- fix: avoid mutation bug in `registry.getMetricsAsJSON()` | ||
- fix: improve performance of `registry.getMetrics*` | ||
- End function of histogram `startTimer`, when invoked returns the number of seconds | ||
- chore: reindent package.json | ||
- chore: correct var name in processStartTime | ||
- chore: add test for `process_start_time_seconds` | ||
- chore: spelling corrections in README | ||
### Added | ||
- feat: implement GC metrics collection without native(C++) modules. | ||
- faet: implement advanced event loop monitoring | ||
## [11.5.3] - 2019-06-27 | ||
@@ -58,2 +80,3 @@ | ||
### Added | ||
- Added a `remove()` method on each metric type, based on [Prometheus "Writing Client Libraries" section on labels](https://prometheus.io/docs/instrumenting/writing_clientlibs/#labels) | ||
@@ -60,0 +83,0 @@ |
195
index.d.ts
@@ -5,12 +5,2 @@ // Type definitions for prom-client | ||
/** | ||
* Options pass to Registry.metrics() | ||
*/ | ||
export interface MetricsOpts { | ||
/** | ||
* Whether to include timestamps in the output, defaults to true | ||
*/ | ||
timestamps?: boolean; | ||
} | ||
/** | ||
* Container for all registered metrics | ||
@@ -22,3 +12,3 @@ */ | ||
*/ | ||
metrics(opts?: MetricsOpts): string; | ||
metrics(): string; | ||
@@ -39,3 +29,3 @@ /** | ||
*/ | ||
registerMetric(metric: Metric): void; | ||
registerMetric<T extends string>(metric: Metric<T>): void; | ||
@@ -62,3 +52,3 @@ /** | ||
*/ | ||
getSingleMetric(name: string): Metric; | ||
getSingleMetric<T extends string>(name: string): Metric<T>; | ||
@@ -131,3 +121,7 @@ /** | ||
*/ | ||
export type Metric = Counter | Gauge | Summary | Histogram; | ||
export type Metric<T extends string> = | ||
| Counter<T> | ||
| Gauge<T> | ||
| Summary<T> | ||
| Histogram<T>; | ||
@@ -153,10 +147,8 @@ /** | ||
interface labelValues { | ||
[key: string]: string | number; | ||
} | ||
type LabelValues<T extends string> = Partial<Record<T, string | number>>; | ||
export interface CounterConfiguration { | ||
export interface CounterConfiguration<T extends string> { | ||
name: string; | ||
help: string; | ||
labelNames?: string[]; | ||
labelNames?: T[]; | ||
registers?: Registry[]; | ||
@@ -169,23 +161,14 @@ aggregator?: Aggregator; | ||
*/ | ||
export class Counter { | ||
export class Counter<T extends string> { | ||
/** | ||
* @param configuration Configuration when creating a Counter metric. Name and Help is required. | ||
*/ | ||
constructor(configuration: CounterConfiguration); | ||
constructor(configuration: CounterConfiguration<T>); | ||
/** | ||
* @param name The name of the metric | ||
* @param help Help description | ||
* @param labels Label keys | ||
* @deprecated | ||
*/ | ||
constructor(name: string, help: string, labels?: string[]); | ||
/** | ||
* Increment for given labels | ||
* @param labels Object with label keys and values | ||
* @param value The number to increment with | ||
* @param timestamp Timestamp to associate the time series with | ||
*/ | ||
inc(labels: labelValues, value?: number, timestamp?: number | Date): void; | ||
inc(labels: LabelValues<T>, value?: number): void; | ||
@@ -195,5 +178,4 @@ /** | ||
* @param value The value to increment with | ||
* @param timestamp Timestamp to associate the time series with | ||
*/ | ||
inc(value?: number, timestamp?: number | Date): void; | ||
inc(value?: number): void; | ||
@@ -224,12 +206,11 @@ /** | ||
* @param value The value to increment with | ||
* @param timestamp Timestamp to associate the time series with | ||
*/ | ||
inc(value?: number, timestamp?: number | Date): void; | ||
inc(value?: number): void; | ||
} | ||
} | ||
export interface GaugeConfiguration { | ||
export interface GaugeConfiguration<T extends string> { | ||
name: string; | ||
help: string; | ||
labelNames?: string[]; | ||
labelNames?: T[]; | ||
registers?: Registry[]; | ||
@@ -242,23 +223,14 @@ aggregator?: Aggregator; | ||
*/ | ||
export class Gauge { | ||
export class Gauge<T extends string> { | ||
/** | ||
* @param configuration Configuration when creating a Gauge metric. Name and Help is mandatory | ||
*/ | ||
constructor(configuration: GaugeConfiguration); | ||
constructor(configuration: GaugeConfiguration<T>); | ||
/** | ||
* @param name The name of the metric | ||
* @param help Help description | ||
* @param labels Label keys | ||
* @deprecated | ||
*/ | ||
constructor(name: string, help: string, labels?: string[]); | ||
/** | ||
* Increment gauge for given labels | ||
* @param labels Object with label keys and values | ||
* @param value The value to increment with | ||
* @param timestamp Timestamp to associate the time series with | ||
*/ | ||
inc(labels: labelValues, value?: number, timestamp?: number | Date): void; | ||
inc(labels: LabelValues<T>, value?: number): void; | ||
@@ -268,5 +240,4 @@ /** | ||
* @param value The value to increment with | ||
* @param timestamp Timestamp to associate the time series with | ||
*/ | ||
inc(value?: number, timestamp?: number | Date): void; | ||
inc(value?: number): void; | ||
@@ -277,5 +248,4 @@ /** | ||
* @param value Value to decrement with | ||
* @param timestamp Timestamp to associate the time series with | ||
*/ | ||
dec(labels: labelValues, value?: number, timestamp?: number | Date): void; | ||
dec(labels: LabelValues<T>, value?: number): void; | ||
@@ -285,5 +255,4 @@ /** | ||
* @param value The value to decrement with | ||
* @param timestamp Timestamp to associate the time series with | ||
*/ | ||
dec(value?: number, timestamp?: number | Date): void; | ||
dec(value?: number): void; | ||
@@ -294,5 +263,4 @@ /** | ||
* @param value The value to set | ||
* @param timestamp Timestamp to associate the time series with | ||
*/ | ||
set(labels: labelValues, value: number, timestamp?: number | Date): void; | ||
set(labels: LabelValues<T>, value: number): void; | ||
@@ -302,5 +270,4 @@ /** | ||
* @param value The value to set | ||
* @param timestamp Timestamp to associate the time series with | ||
*/ | ||
set(value: number, timestamp?: number | Date): void; | ||
set(value: number): void; | ||
@@ -311,3 +278,3 @@ /** | ||
*/ | ||
setToCurrentTime(labels?: labelValues): void; | ||
setToCurrentTime(labels?: LabelValues<T>): void; | ||
@@ -319,3 +286,3 @@ /** | ||
*/ | ||
startTimer(labels?: labelValues): (labels?: labelValues) => void; | ||
startTimer(labels?: LabelValues<T>): (labels?: LabelValues<T>) => void; | ||
@@ -327,3 +294,3 @@ /** | ||
*/ | ||
labels(...values: string[]): Gauge.Internal; | ||
labels(...values: string[]): Gauge.Internal<T>; | ||
@@ -343,9 +310,8 @@ /** | ||
export namespace Gauge { | ||
interface Internal { | ||
interface Internal<T extends string> { | ||
/** | ||
* Increment gauge with value | ||
* @param value The value to increment with | ||
* @param timestamp Timestamp to associate the time series with | ||
*/ | ||
inc(value?: number, timestamp?: number | Date): void; | ||
inc(value?: number): void; | ||
@@ -355,5 +321,4 @@ /** | ||
* @param value The value to decrement with | ||
* @param timestamp Timestamp to associate the time series with | ||
*/ | ||
dec(value?: number, timestamp?: number | Date): void; | ||
dec(value?: number): void; | ||
@@ -363,5 +328,4 @@ /** | ||
* @param value The value to set | ||
* @param timestamp Timestamp to associate the time series with | ||
*/ | ||
set(value: number, timestamp?: number | Date): void; | ||
set(value: number): void; | ||
@@ -377,10 +341,10 @@ /** | ||
*/ | ||
startTimer(): (labels?: labelValues) => void; | ||
startTimer(): (labels?: LabelValues<T>) => void; | ||
} | ||
} | ||
export interface HistogramConfiguration { | ||
export interface HistogramConfiguration<T extends string> { | ||
name: string; | ||
help: string; | ||
labelNames?: string[]; | ||
labelNames?: T[]; | ||
buckets?: number[]; | ||
@@ -394,29 +358,9 @@ registers?: Registry[]; | ||
*/ | ||
export class Histogram { | ||
export class Histogram<T extends string> { | ||
/** | ||
* @param configuration Configuration when creating the Histogram. Name and Help is mandatory | ||
*/ | ||
constructor(configuration: HistogramConfiguration); | ||
constructor(configuration: HistogramConfiguration<T>); | ||
/** | ||
* @param name The name of metric | ||
* @param help Help description | ||
* @param labels Label keys | ||
* @param config Configuration object for Histograms | ||
*/ | ||
constructor( | ||
name: string, | ||
help: string, | ||
labels?: string[], | ||
config?: Histogram.Config | ||
); | ||
/** | ||
* @param name The name of metric | ||
* @param help Help description | ||
* @param config Configuration object for Histograms | ||
* @deprecated | ||
*/ | ||
constructor(name: string, help: string, config: Histogram.Config); | ||
/** | ||
* Observe value | ||
@@ -431,3 +375,3 @@ * @param value The value to observe | ||
*/ | ||
observe(labels: labelValues, value: number): void; | ||
observe(labels: LabelValues<T>, value: number): void; | ||
@@ -439,3 +383,3 @@ /** | ||
*/ | ||
startTimer(labels?: labelValues): (labels?: labelValues) => void; | ||
startTimer(labels?: LabelValues<T>): (labels?: LabelValues<T>) => void; | ||
@@ -452,3 +396,3 @@ /** | ||
*/ | ||
labels(...values: string[]): Histogram.Internal; | ||
labels(...values: string[]): Histogram.Internal<T>; | ||
@@ -463,3 +407,3 @@ /** | ||
export namespace Histogram { | ||
interface Internal { | ||
interface Internal<T extends string> { | ||
/** | ||
@@ -476,3 +420,3 @@ * Observe value | ||
*/ | ||
startTimer(): (labels?: labelValues) => void; | ||
startTimer(): (labels?: LabelValues<T>) => void; | ||
} | ||
@@ -488,6 +432,6 @@ | ||
export interface SummaryConfiguration { | ||
export interface SummaryConfiguration<T extends string> { | ||
name: string; | ||
help: string; | ||
labelNames?: string[]; | ||
labelNames?: T[]; | ||
percentiles?: number[]; | ||
@@ -504,29 +448,9 @@ registers?: Registry[]; | ||
*/ | ||
export class Summary { | ||
export class Summary<T extends string> { | ||
/** | ||
* @param configuration Configuration when creating Summary metric. Name and Help is mandatory | ||
*/ | ||
constructor(configuration: SummaryConfiguration); | ||
constructor(configuration: SummaryConfiguration<T>); | ||
/** | ||
* @param name The name of the metric | ||
* @param help Help description | ||
* @param labels Label keys | ||
* @param config Configuration object | ||
*/ | ||
constructor( | ||
name: string, | ||
help: string, | ||
labels?: string[], | ||
config?: Summary.Config | ||
); | ||
/** | ||
* @param name The name of the metric | ||
* @param help Help description | ||
* @param config Configuration object | ||
* @deprecated | ||
*/ | ||
constructor(name: string, help: string, config: Summary.Config); | ||
/** | ||
* Observe value in summary | ||
@@ -541,3 +465,3 @@ * @param value The value to observe | ||
*/ | ||
observe(labels: labelValues, value: number): void; | ||
observe(labels: LabelValues<T>, value: number): void; | ||
@@ -549,3 +473,3 @@ /** | ||
*/ | ||
startTimer(labels?: labelValues): (labels?: labelValues) => void; | ||
startTimer(labels?: LabelValues<T>): (labels?: LabelValues<T>) => void; | ||
@@ -562,3 +486,3 @@ /** | ||
*/ | ||
labels(...values: string[]): Summary.Internal; | ||
labels(...values: string[]): Summary.Internal<T>; | ||
@@ -573,3 +497,3 @@ /** | ||
export namespace Summary { | ||
interface Internal { | ||
interface Internal<T extends string> { | ||
/** | ||
@@ -586,3 +510,3 @@ * Observe value in summary | ||
*/ | ||
startTimer(): (labels?: labelValues) => void; | ||
startTimer(): (labels?: LabelValues<T>) => void; | ||
} | ||
@@ -682,6 +606,6 @@ | ||
export interface DefaultMetricsCollectorConfiguration { | ||
timeout?: number; | ||
timestamps?: boolean; | ||
register?: Registry; | ||
prefix?: string; | ||
gcDurationBuckets?: number[]; | ||
eventLoopMonitoringPrecision?: number; | ||
} | ||
@@ -692,16 +616,7 @@ | ||
* @param config Configuration object for default metrics collector | ||
* @return The setInterval number | ||
*/ | ||
export function collectDefaultMetrics( | ||
config?: DefaultMetricsCollectorConfiguration | ||
): ReturnType<typeof setInterval>; | ||
): void; | ||
/** | ||
* Configure default metrics | ||
* @param timeout The interval how often the default metrics should be probed | ||
* @deprecated A number to defaultMetrics is deprecated, please use \`collectDefaultMetrics({ timeout: ${timeout} })\`. | ||
* @return The setInterval number | ||
*/ | ||
export function collectDefaultMetrics(timeout: number): number; | ||
export interface defaultMetrics { | ||
@@ -708,0 +623,0 @@ /** |
@@ -11,6 +11,12 @@ 'use strict'; | ||
const cluster = require('cluster'); | ||
const Registry = require('./registry'); | ||
const { Grouper } = require('./util'); | ||
const { aggregators } = require('./metricAggregators'); | ||
// We need to lazy-load the 'cluster' module as some application servers - | ||
// namely Passenger - crash when it is imported. | ||
let cluster = () => { | ||
const data = require('cluster'); | ||
cluster = () => data; | ||
return data; | ||
}; | ||
@@ -70,7 +76,7 @@ const GET_METRICS_REQ = 'prom-client:getMetricsReq'; | ||
for (const id in cluster.workers) { | ||
for (const id in cluster().workers) { | ||
// If the worker exits abruptly, it may still be in the workers | ||
// list but not able to communicate. | ||
if (cluster.workers[id].isConnected()) { | ||
cluster.workers[id].send(message); | ||
if (cluster().workers[id].isConnected()) { | ||
cluster().workers[id].send(message); | ||
request.pending++; | ||
@@ -158,5 +164,5 @@ } | ||
if (cluster.isMaster) { | ||
if (cluster().isMaster) { | ||
// Listen for worker responses to requests for local metrics | ||
cluster.on('message', (worker, message) => { | ||
cluster().on('message', (worker, message) => { | ||
if (message.type === GET_METRICS_RES) { | ||
@@ -183,15 +189,13 @@ const request = requests.get(message.requestId); | ||
if (cluster.isWorker) { | ||
// Respond to master's requests for worker's local metrics. | ||
process.on('message', message => { | ||
if (message.type === GET_METRICS_REQ) { | ||
process.send({ | ||
type: GET_METRICS_RES, | ||
requestId: message.requestId, | ||
metrics: registries.map(r => r.getMetricsAsJSON()) | ||
}); | ||
} | ||
}); | ||
} | ||
// Respond to master's requests for worker's local metrics. | ||
process.on('message', message => { | ||
if (cluster().isWorker && message.type === GET_METRICS_REQ) { | ||
process.send({ | ||
type: GET_METRICS_RES, | ||
requestId: message.requestId, | ||
metrics: registries.map(r => r.getMetricsAsJSON()) | ||
}); | ||
} | ||
}); | ||
module.exports = AggregatorRegistry; |
@@ -10,7 +10,5 @@ /** | ||
const { | ||
isDate, | ||
getPropertiesFromObj, | ||
hashObject, | ||
isObject, | ||
printDeprecationObjectConstructor, | ||
getLabels, | ||
@@ -29,30 +27,20 @@ removeLabels | ||
* Counter | ||
* @param {string} name - Name of the metric | ||
* @param {string} help - Help description for the metric | ||
* @param {Array.<string>} labels - Labels | ||
* @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(name, help, labels) { | ||
let config; | ||
if (isObject(name)) { | ||
config = Object.assign( | ||
{ | ||
labelNames: [] | ||
}, | ||
name | ||
); | ||
if (!config.registers) { | ||
config.registers = [globalRegistry]; | ||
} | ||
} else { | ||
printDeprecationObjectConstructor(); | ||
config = { | ||
name, | ||
help, | ||
labelNames: labels, | ||
registers: [globalRegistry] | ||
}; | ||
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) { | ||
@@ -67,3 +55,2 @@ throw new Error('Missing mandatory help parameter'); | ||
} | ||
if (!validateLabelName(config.labelNames)) { | ||
@@ -74,7 +61,4 @@ throw new Error('Invalid label name'); | ||
this.name = config.name; | ||
this.labelNames = config.labelNames || []; | ||
this.reset(); | ||
this.help = config.help; | ||
@@ -92,6 +76,5 @@ this.aggregator = config.aggregator || 'sum'; | ||
* @param {Number} value - Value to increment, if omitted increment with 1 | ||
* @param {(Number|Date)} timestamp - Timestamp to set the counter to | ||
* @returns {void} | ||
*/ | ||
inc(labels, value, timestamp) { | ||
inc(labels, value) { | ||
if (!isObject(labels)) { | ||
@@ -102,3 +85,3 @@ return inc.call(this, null)(labels, value); | ||
const hash = hashObject(labels); | ||
return inc.call(this, labels, hash)(value, timestamp); | ||
return inc.call(this, labels, hash)(value); | ||
} | ||
@@ -148,11 +131,6 @@ | ||
const inc = function(labels, hash) { | ||
return (value, timestamp) => { | ||
return value => { | ||
if (value && !Number.isFinite(value)) { | ||
throw new TypeError(`Value is not a valid number: ${util.format(value)}`); | ||
} | ||
if (timestamp && !isDate(timestamp) && !Number.isFinite(timestamp)) { | ||
throw new TypeError( | ||
`Timestamp is not a valid date or number: ${util.format(timestamp)}` | ||
); | ||
} | ||
if (value < 0) { | ||
@@ -167,18 +145,12 @@ throw new Error('It is not possible to decrease a counter'); | ||
this.hashMap = setValue(this.hashMap, incValue, timestamp, labels, hash); | ||
this.hashMap = setValue(this.hashMap, incValue, labels, hash); | ||
}; | ||
}; | ||
function setValue(hashMap, value, timestamp, labels, hash) { | ||
function setValue(hashMap, value, labels, hash) { | ||
hash = hash || ''; | ||
timestamp = isDate(timestamp) | ||
? timestamp.valueOf() | ||
: Number.isFinite(timestamp) | ||
? timestamp | ||
: undefined; | ||
if (hashMap[hash]) { | ||
hashMap[hash].value += value; | ||
hashMap[hash].timestamp = timestamp; | ||
} else { | ||
hashMap[hash] = { value, labels: labels || {}, timestamp }; | ||
hashMap[hash] = { value, labels: labels || {} }; | ||
} | ||
@@ -185,0 +157,0 @@ return hashMap; |
'use strict'; | ||
const { isObject } = require('./util'); | ||
const { globalRegistry } = require('./registry'); | ||
// Default metrics. | ||
const processCpuTotal = require('./metrics/processCpuTotal'); | ||
@@ -14,4 +18,3 @@ const processStartTime = require('./metrics/processStartTime'); | ||
const version = require('./metrics/version'); | ||
const { globalRegistry } = require('./registry'); | ||
const { printDeprecationCollectDefaultMetricsNumber } = require('./util'); | ||
const gc = require('./metrics/gc'); | ||
@@ -29,59 +32,49 @@ const metrics = { | ||
heapSpacesSizeAndUsed, | ||
version | ||
version, | ||
gc | ||
}; | ||
const metricsList = Object.keys(metrics); | ||
let existingInterval = null; | ||
// This is used to ensure the program throws on duplicate metrics during first run | ||
// We might want to consider not supporting running the default metrics function more than once | ||
let init = true; | ||
module.exports = function collectDefaultMetrics(config) { | ||
if (config !== null && config !== undefined && !isObject(config)) { | ||
throw new TypeError('config must be null, undefined, or an object'); | ||
} | ||
module.exports = function startDefaultMetrics(config) { | ||
let normalizedConfig = config; | ||
if (typeof config === 'number') { | ||
printDeprecationCollectDefaultMetricsNumber(config); | ||
config = Object.assign({ eventLoopMonitoringPrecision: 10 }, config); | ||
normalizedConfig = { timeout: config }; | ||
} | ||
const registry = config.register || globalRegistry; | ||
const last = registry | ||
.collectors() | ||
.find(collector => collector._source === metrics); | ||
normalizedConfig = Object.assign( | ||
{ | ||
timestamps: true, | ||
timeout: 10000 | ||
}, | ||
normalizedConfig | ||
); | ||
if (existingInterval !== null) { | ||
clearInterval(existingInterval); | ||
if (last) { | ||
throw new Error( | ||
'Cannot add the default metrics twice to the same registry' | ||
); | ||
} | ||
const initialisedMetrics = metricsList.map(metric => { | ||
const defaultMetric = metrics[metric]; | ||
if (!init) { | ||
defaultMetric.metricNames.map( | ||
globalRegistry.removeSingleMetric, | ||
globalRegistry | ||
); | ||
} | ||
return defaultMetric(normalizedConfig.register, normalizedConfig); | ||
const scrapers = metricsList.map(key => { | ||
const metric = metrics[key]; | ||
return metric(config.register, config); | ||
}); | ||
function updateAllMetrics() { | ||
initialisedMetrics.forEach(metric => metric.call()); | ||
// 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()); | ||
} | ||
updateAllMetrics(); | ||
// 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); | ||
existingInterval = setInterval( | ||
updateAllMetrics, | ||
normalizedConfig.timeout | ||
).unref(); | ||
init = false; | ||
return existingInterval; | ||
// Because the tests expect an immediate collection. | ||
defaultMetricCollector(); | ||
}; | ||
module.exports.metricsList = metricsList; |
@@ -11,3 +11,2 @@ /** | ||
const { | ||
isDate, | ||
setValue, | ||
@@ -18,3 +17,2 @@ getPropertiesFromObj, | ||
isObject, | ||
printDeprecationObjectConstructor, | ||
removeLabels | ||
@@ -31,29 +29,20 @@ } = require('./util'); | ||
* Gauge | ||
* @param {string} name - Name of the metric | ||
* @param {string} help - Help for the metric | ||
* @param {Array.<string>} labels - Array with strings, all label keywords supported | ||
* @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(name, help, labels) { | ||
let config; | ||
if (isObject(name)) { | ||
config = Object.assign( | ||
{ | ||
labelNames: [] | ||
}, | ||
name | ||
); | ||
if (!config.registers) { | ||
config.registers = [globalRegistry]; | ||
} | ||
} else { | ||
printDeprecationObjectConstructor(); | ||
config = { | ||
name, | ||
help, | ||
labelNames: labels, | ||
registers: [globalRegistry] | ||
}; | ||
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) { | ||
@@ -87,10 +76,9 @@ throw new Error('Missing mandatory help parameter'); | ||
* @param {Number} value - Value to set the gauge to, must be positive | ||
* @param {(Number|Date)} timestamp - Timestamp to set the gauge to | ||
* @returns {void} | ||
*/ | ||
set(labels, value, timestamp) { | ||
set(labels, value) { | ||
if (!isObject(labels)) { | ||
return set.call(this, null)(labels, value); | ||
} | ||
return set.call(this, labels)(value, timestamp); | ||
return set.call(this, labels)(value); | ||
} | ||
@@ -110,7 +98,6 @@ | ||
* @param {Number} value - Value to increment - if omitted, increment with 1 | ||
* @param {(Number|Date)} timestamp - Timestamp to set the gauge to | ||
* @returns {void} | ||
*/ | ||
inc(labels, value, timestamp) { | ||
inc.call(this, labels)(value, timestamp); | ||
inc(labels, value) { | ||
inc.call(this, labels)(value); | ||
} | ||
@@ -122,7 +109,6 @@ | ||
* @param {Number} value - Value to decrement - if omitted, decrement with 1 | ||
* @param {(Number|Date)} timestamp - Timestamp to set the gauge to | ||
* @returns {void} | ||
*/ | ||
dec(labels, value, timestamp) { | ||
dec.call(this, labels)(value, timestamp); | ||
dec(labels, value) { | ||
dec.call(this, labels)(value); | ||
} | ||
@@ -210,9 +196,5 @@ | ||
function dec(labels) { | ||
return (value, timestamp) => { | ||
return value => { | ||
const data = convertLabelsAndValues(labels, value); | ||
this.set( | ||
data.labels, | ||
this._getValue(data.labels) - (data.value || 1), | ||
timestamp | ||
); | ||
this.set(data.labels, this._getValue(data.labels) - (data.value || 1)); | ||
}; | ||
@@ -222,9 +204,5 @@ } | ||
function inc(labels) { | ||
return (value, timestamp) => { | ||
return value => { | ||
const data = convertLabelsAndValues(labels, value); | ||
this.set( | ||
data.labels, | ||
this._getValue(data.labels) + (data.value || 1), | ||
timestamp | ||
); | ||
this.set(data.labels, this._getValue(data.labels) + (data.value || 1)); | ||
}; | ||
@@ -234,11 +212,6 @@ } | ||
function set(labels) { | ||
return (value, timestamp) => { | ||
return value => { | ||
if (typeof value !== 'number') { | ||
throw new TypeError(`Value is not a valid number: ${util.format(value)}`); | ||
} | ||
if (timestamp && !isDate(timestamp) && !Number.isFinite(timestamp)) { | ||
throw new TypeError( | ||
`Timestamp is not a valid date or number: ${util.format(timestamp)}` | ||
); | ||
} | ||
@@ -248,3 +221,3 @@ labels = labels || {}; | ||
validateLabel(this.labelNames, labels); | ||
this.hashMap = setValue(this.hashMap, value, labels, timestamp); | ||
this.hashMap = setValue(this.hashMap, value, labels); | ||
}; | ||
@@ -251,0 +224,0 @@ } |
@@ -14,3 +14,2 @@ /** | ||
isObject, | ||
printDeprecationObjectConstructor, | ||
removeLabels | ||
@@ -27,43 +26,21 @@ } = require('./util'); | ||
* Histogram | ||
* @param {string} name - Name of the metric | ||
* @param {string} help - Help for the metric | ||
* @param {object|Array.<string>} labelsOrConf - Either array of label names or config object as a key-value object | ||
* @param {object} conf - Configuration object | ||
* @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 | ||
*/ | ||
constructor(name, help, labelsOrConf, conf) { | ||
let config; | ||
if (isObject(name)) { | ||
config = Object.assign( | ||
{ | ||
buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], | ||
labelNames: [] | ||
}, | ||
name | ||
); | ||
if (!config.registers) { | ||
config.registers = [globalRegistry]; | ||
} | ||
} else { | ||
let obj; | ||
let labels = []; | ||
if (Array.isArray(labelsOrConf)) { | ||
obj = conf || {}; | ||
labels = labelsOrConf; | ||
} else { | ||
obj = labelsOrConf || {}; | ||
} | ||
printDeprecationObjectConstructor(); | ||
config = { | ||
name, | ||
labelNames: labels, | ||
help, | ||
buckets: configureUpperbounds(obj.buckets), | ||
registers: [globalRegistry] | ||
}; | ||
constructor(config) { | ||
if (!isObject(config)) { | ||
throw new TypeError('constructor expected a config object'); | ||
} | ||
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); | ||
@@ -141,3 +118,4 @@ | ||
* makeExpensiveXHRRequest(function(err, res) { | ||
* end(); //Observe the duration of expensiveXHRRequest | ||
* const duration = end(); //Observe the duration of expensiveXHRRequest and returns duration in seconds | ||
* console.log('Duration', duration); | ||
* }); | ||
@@ -168,9 +146,9 @@ */ | ||
const delta = process.hrtime(start); | ||
this.observe( | ||
Object.assign({}, startLabels, endLabels), | ||
delta[0] + delta[1] / 1e9 | ||
); | ||
const value = delta[0] + delta[1] / 1e9; | ||
this.observe(Object.assign({}, startLabels, endLabels), value); | ||
return value; | ||
}; | ||
}; | ||
} | ||
function validateInput(name, help, labels) { | ||
@@ -199,23 +177,2 @@ if (!help) { | ||
function configureUpperbounds(configuredBuckets) { | ||
const defaultBuckets = [ | ||
0.005, | ||
0.01, | ||
0.025, | ||
0.05, | ||
0.1, | ||
0.25, | ||
0.5, | ||
1, | ||
2.5, | ||
5, | ||
10 | ||
]; | ||
return [].concat(configuredBuckets || defaultBuckets).sort(sortAscending); | ||
} | ||
function sortAscending(x, y) { | ||
return x - y; | ||
} | ||
function setValuePair(labels, value, metricName) { | ||
@@ -264,3 +221,3 @@ return { | ||
if (valueFromMap.bucketValues.hasOwnProperty(b)) { | ||
if (Object.prototype.hasOwnProperty.call(valueFromMap.bucketValues, b)) { | ||
valueFromMap.bucketValues[b] += 1; | ||
@@ -267,0 +224,0 @@ } |
@@ -5,4 +5,24 @@ 'use strict'; | ||
// Check if perf_hooks module is available | ||
let perf_hooks; | ||
try { | ||
/* eslint-disable node/no-unsupported-features/node-builtins */ | ||
perf_hooks = require('perf_hooks'); | ||
} catch (e) { | ||
// 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. | ||
const NODEJS_EVENTLOOP_LAG = 'nodejs_eventloop_lag_seconds'; | ||
// Reported only when perf_hooks is available. | ||
const NODEJS_EVENTLOOP_LAG_MIN = 'nodejs_eventloop_lag_min_seconds'; | ||
const NODEJS_EVENTLOOP_LAG_MAX = 'nodejs_eventloop_lag_max_seconds'; | ||
const NODEJS_EVENTLOOP_LAG_MEAN = 'nodejs_eventloop_lag_mean_seconds'; | ||
const NODEJS_EVENTLOOP_LAG_STDDEV = 'nodejs_eventloop_lag_stddev_seconds'; | ||
const NODEJS_EVENTLOOP_LAG_P50 = 'nodejs_eventloop_lag_p50_seconds'; | ||
const NODEJS_EVENTLOOP_LAG_P90 = 'nodejs_eventloop_lag_p90_seconds'; | ||
const NODEJS_EVENTLOOP_LAG_P99 = 'nodejs_eventloop_lag_p99_seconds'; | ||
function reportEventloopLag(start, gauge) { | ||
@@ -13,3 +33,3 @@ const delta = process.hrtime(start); | ||
gauge.set(seconds, Date.now()); | ||
gauge.set(seconds); | ||
} | ||
@@ -20,3 +40,3 @@ | ||
const gauge = new Gauge({ | ||
const lag = new Gauge({ | ||
name: namePrefix + NODEJS_EVENTLOOP_LAG, | ||
@@ -27,9 +47,73 @@ help: 'Lag of event loop in seconds.', | ||
}); | ||
const lagMin = new Gauge({ | ||
name: namePrefix + NODEJS_EVENTLOOP_LAG_MIN, | ||
help: 'The minimum recorded event loop delay.', | ||
registers: registry ? [registry] : undefined | ||
}); | ||
const lagMax = new Gauge({ | ||
name: namePrefix + NODEJS_EVENTLOOP_LAG_MAX, | ||
help: 'The maximum recorded event loop delay.', | ||
registers: registry ? [registry] : undefined | ||
}); | ||
const lagMean = new Gauge({ | ||
name: namePrefix + NODEJS_EVENTLOOP_LAG_MEAN, | ||
help: 'The mean of the recorded event loop delays.', | ||
registers: registry ? [registry] : undefined | ||
}); | ||
const lagStddev = new Gauge({ | ||
name: namePrefix + NODEJS_EVENTLOOP_LAG_STDDEV, | ||
help: 'The standard deviation of the recorded event loop delays.', | ||
registers: registry ? [registry] : undefined | ||
}); | ||
const lagP50 = new Gauge({ | ||
name: namePrefix + NODEJS_EVENTLOOP_LAG_P50, | ||
help: 'The 50th percentile of the recorded event loop delays.', | ||
registers: registry ? [registry] : undefined | ||
}); | ||
const lagP90 = new Gauge({ | ||
name: namePrefix + NODEJS_EVENTLOOP_LAG_P90, | ||
help: 'The 90th percentile of the recorded event loop delays.', | ||
registers: registry ? [registry] : undefined | ||
}); | ||
const lagP99 = new Gauge({ | ||
name: namePrefix + NODEJS_EVENTLOOP_LAG_P99, | ||
help: 'The 99th percentile of the recorded event loop delays.', | ||
registers: registry ? [registry] : undefined | ||
}); | ||
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, gauge); | ||
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); | ||
}; | ||
}; | ||
module.exports.metricNames = [NODEJS_EVENTLOOP_LAG]; | ||
module.exports.metricNames = [ | ||
NODEJS_EVENTLOOP_LAG, | ||
NODEJS_EVENTLOOP_LAG_MIN, | ||
NODEJS_EVENTLOOP_LAG_MAX, | ||
NODEJS_EVENTLOOP_LAG_MEAN, | ||
NODEJS_EVENTLOOP_LAG_STDDEV, | ||
NODEJS_EVENTLOOP_LAG_P50, | ||
NODEJS_EVENTLOOP_LAG_P90, | ||
NODEJS_EVENTLOOP_LAG_P99 | ||
]; |
@@ -20,3 +20,3 @@ 'use strict'; | ||
name: namePrefix + NODEJS_HEAP_SIZE_TOTAL, | ||
help: 'Process heap size from node.js in bytes.', | ||
help: 'Process heap size from Node.js in bytes.', | ||
registers | ||
@@ -26,33 +26,19 @@ }); | ||
name: namePrefix + NODEJS_HEAP_SIZE_USED, | ||
help: 'Process heap size used from node.js in bytes.', | ||
help: 'Process heap size used from Node.js in bytes.', | ||
registers | ||
}); | ||
let externalMemUsed; | ||
const externalMemUsed = new Gauge({ | ||
name: namePrefix + NODEJS_EXTERNAL_MEMORY, | ||
help: 'Node.js external memory size in bytes.', | ||
registers | ||
}); | ||
const usage = safeMemoryUsage(); | ||
if (usage && usage.external) { | ||
externalMemUsed = new Gauge({ | ||
name: namePrefix + NODEJS_EXTERNAL_MEMORY, | ||
help: 'Nodejs external memory size in bytes.', | ||
registers | ||
}); | ||
} | ||
return () => { | ||
// process.memoryUsage() can throw EMFILE errors, see #67 | ||
// process.memoryUsage() can throw on some platforms, see #67 | ||
const memUsage = safeMemoryUsage(); | ||
if (memUsage) { | ||
if (config.timestamps) { | ||
const now = Date.now(); | ||
heapSizeTotal.set(memUsage.heapTotal, now); | ||
heapSizeUsed.set(memUsage.heapUsed, now); | ||
if (memUsage.external && externalMemUsed) { | ||
externalMemUsed.set(memUsage.external, now); | ||
} | ||
} else { | ||
heapSizeTotal.set(memUsage.heapTotal); | ||
heapSizeUsed.set(memUsage.heapUsed); | ||
if (memUsage.external && externalMemUsed) { | ||
externalMemUsed.set(memUsage.external); | ||
} | ||
heapSizeTotal.set(memUsage.heapTotal); | ||
heapSizeUsed.set(memUsage.heapUsed); | ||
if (memUsage.external && externalMemUsed) { | ||
externalMemUsed.set(memUsage.external); | ||
} | ||
@@ -59,0 +45,0 @@ } |
'use strict'; | ||
const Gauge = require('../gauge'); | ||
let v8; | ||
const v8 = require('v8'); | ||
try { | ||
v8 = require('v8'); | ||
} catch (e) { | ||
// node version is too old | ||
// probably we can use v8-heap-space-statistics for >=node-4.0.0 and <node-6.0.0 | ||
} | ||
const METRICS = ['total', 'used', 'available']; | ||
const NODEJS_HEAP_SIZE = {}; | ||
@@ -22,9 +14,2 @@ | ||
module.exports = (registry, config = {}) => { | ||
if ( | ||
typeof v8 === 'undefined' || | ||
typeof v8.getHeapSpaceStatistics !== 'function' | ||
) { | ||
return () => {}; | ||
} | ||
const registers = registry ? [registry] : undefined; | ||
@@ -38,3 +23,3 @@ const namePrefix = config.prefix ? config.prefix : ''; | ||
name: namePrefix + NODEJS_HEAP_SIZE[metricType], | ||
help: `Process heap space size ${metricType} from node.js in bytes.`, | ||
help: `Process heap space size ${metricType} from Node.js in bytes.`, | ||
labelNames: ['space'], | ||
@@ -41,0 +26,0 @@ registers |
@@ -13,3 +13,3 @@ 'use strict'; | ||
if (data.hasOwnProperty(listElement.constructor.name)) { | ||
if (Object.hasOwnProperty.call(data, listElement.constructor.name)) { | ||
data[listElement.constructor.name] += 1; | ||
@@ -23,10 +23,6 @@ } else { | ||
function updateMetrics(gauge, data, includeTimestamp) { | ||
function updateMetrics(gauge, data) { | ||
gauge.reset(); | ||
for (const key in data) { | ||
if (includeTimestamp) { | ||
gauge.set({ type: key }, data[key], Date.now()); | ||
} else { | ||
gauge.set({ type: key }, data[key]); | ||
} | ||
gauge.set({ type: key }, data[key]); | ||
} | ||
@@ -33,0 +29,0 @@ } |
'use strict'; | ||
function safeMemoryUsage() { | ||
let memoryUsage; | ||
try { | ||
memoryUsage = process.memoryUsage(); | ||
return process.memoryUsage(); | ||
} catch (ex) { | ||
// empty | ||
return; | ||
} | ||
return memoryUsage; | ||
} | ||
module.exports = safeMemoryUsage; |
@@ -54,14 +54,19 @@ 'use strict'; | ||
// 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 () => { | ||
fs.readFile('/proc/self/status', 'utf8', (err, status) => { | ||
if (err) { | ||
return; | ||
} | ||
const now = Date.now(); | ||
const structuredOutput = structureOutput(status); | ||
try { | ||
const stat = fs.readFileSync('/proc/self/status', 'utf8'); | ||
const structuredOutput = structureOutput(stat); | ||
residentMemGauge.set(structuredOutput.VmRSS, now); | ||
virtualMemGauge.set(structuredOutput.VmSize, now); | ||
heapSizeMemGauge.set(structuredOutput.VmData, now); | ||
}); | ||
residentMemGauge.set(structuredOutput.VmRSS); | ||
virtualMemGauge.set(structuredOutput.VmSize); | ||
heapSizeMemGauge.set(structuredOutput.VmData); | ||
} catch (er) { | ||
return; | ||
} | ||
}; | ||
@@ -68,0 +73,0 @@ }; |
@@ -9,7 +9,2 @@ 'use strict'; | ||
module.exports = (registry, config = {}) => { | ||
// Don't do anything if the function doesn't exist (introduced in node@6.1.0) | ||
if (typeof process.cpuUsage !== 'function') { | ||
return () => {}; | ||
} | ||
const registers = registry ? [registry] : undefined; | ||
@@ -16,0 +11,0 @@ const namePrefix = config.prefix ? config.prefix : ''; |
@@ -31,15 +31,7 @@ 'use strict'; | ||
const updater = config.timestamps | ||
? () => { | ||
const handles = process._getActiveHandles(); | ||
updateMetrics(gauge, aggregateByObjectName(handles), true); | ||
totalGauge.set(handles.length, Date.now()); | ||
} | ||
: () => { | ||
const handles = process._getActiveHandles(); | ||
updateMetrics(gauge, aggregateByObjectName(handles), false); | ||
totalGauge.set(handles.length); | ||
}; | ||
return updater; | ||
return () => { | ||
const handles = process._getActiveHandles(); | ||
updateMetrics(gauge, aggregateByObjectName(handles)); | ||
totalGauge.set(handles.length); | ||
}; | ||
}; | ||
@@ -46,0 +38,0 @@ |
@@ -8,11 +8,25 @@ 'use strict'; | ||
let maxFds; | ||
module.exports = (registry, config = {}) => { | ||
let isSet = false; | ||
if (process.platform !== 'linux') { | ||
return () => {}; | ||
if (maxFds === undefined) { | ||
// This will fail if a linux-like procfs is not available. | ||
try { | ||
const limits = fs.readFileSync('/proc/self/limits', 'utf8'); | ||
const lines = limits.split('\n'); | ||
lines.find(line => { | ||
if (line.startsWith('Max open files')) { | ||
const parts = line.split(/ +/); | ||
maxFds = Number(parts[1]); | ||
return true; | ||
} | ||
}); | ||
} catch (er) { | ||
return () => {}; | ||
} | ||
} | ||
if (maxFds === undefined) return () => {}; | ||
const namePrefix = config.prefix ? config.prefix : ''; | ||
const fileDescriptorsGauge = new Gauge({ | ||
@@ -24,19 +38,7 @@ name: namePrefix + PROCESS_MAX_FDS, | ||
return () => { | ||
if (isSet) { | ||
return; | ||
} | ||
fileDescriptorsGauge.set(Number(maxFds)); | ||
fs.readFile('/proc/sys/fs/file-max', 'utf8', (err, maxFds) => { | ||
if (err) { | ||
return; | ||
} | ||
isSet = true; | ||
fileDescriptorsGauge.set(Number(maxFds)); | ||
}); | ||
}; | ||
return () => {}; | ||
}; | ||
module.exports.metricNames = [PROCESS_MAX_FDS]; |
@@ -23,10 +23,10 @@ 'use strict'; | ||
return () => { | ||
fs.readdir('/proc/self/fd', (err, list) => { | ||
if (err) { | ||
return; | ||
} | ||
// Minus 1, as this invocation created one | ||
fileDescriptorsGauge.set(list.length - 1, Date.now()); | ||
}); | ||
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; | ||
} | ||
}; | ||
@@ -33,0 +33,0 @@ }; |
'use strict'; | ||
const Gauge = require('../gauge'); | ||
const nowInSeconds = Math.round(Date.now() / 1000 - process.uptime()); | ||
const startInSeconds = Math.round(Date.now() / 1000 - process.uptime()); | ||
@@ -17,13 +17,7 @@ const PROCESS_START_TIME = 'process_start_time_seconds'; | ||
}); | ||
let isSet = false; | ||
cpuUserGauge.set(startInSeconds); | ||
return () => { | ||
if (isSet) { | ||
return; | ||
} | ||
cpuUserGauge.set(nowInSeconds); | ||
isSet = true; | ||
}; | ||
return () => {}; | ||
}; | ||
module.exports.metricNames = [PROCESS_START_TIME]; |
@@ -22,20 +22,9 @@ 'use strict'; | ||
}); | ||
let isSet = false; | ||
nodeVersionGauge | ||
.labels(version, versionSegments[0], versionSegments[1], versionSegments[2]) | ||
.set(1); | ||
return () => { | ||
if (isSet) { | ||
return; | ||
} | ||
nodeVersionGauge | ||
.labels( | ||
version, | ||
versionSegments[0], | ||
versionSegments[1], | ||
versionSegments[2] | ||
) | ||
.set(1); | ||
isSet = true; | ||
}; | ||
return () => {}; | ||
}; | ||
module.exports.metricNames = [NODE_VERSION_INFO]; |
@@ -78,3 +78,3 @@ 'use strict'; | ||
if (method !== 'DELETE') { | ||
req.write(this.registry.metrics({ timestamps: false })); | ||
req.write(this.registry.metrics()); | ||
} | ||
@@ -81,0 +81,0 @@ req.end(); |
@@ -14,9 +14,6 @@ 'use strict'; | ||
const defaultMetricsOpts = { | ||
timestamps: true | ||
}; | ||
class Registry { | ||
constructor() { | ||
this._metrics = {}; | ||
this._collectors = []; | ||
this._defaultLabels = {}; | ||
@@ -29,4 +26,3 @@ } | ||
getMetricAsPrometheusString(metric, conf) { | ||
const opts = Object.assign({}, defaultMetricsOpts, conf); | ||
getMetricAsPrometheusString(metric) { | ||
const item = metric.get(); | ||
@@ -45,7 +41,7 @@ const name = escapeString(item.name); | ||
val.labels = Object.assign({}, val.labels); | ||
} | ||
for (const labelName of defaultLabelNames) { | ||
val.labels[labelName] = | ||
val.labels[labelName] || this._defaultLabels[labelName]; | ||
for (const labelName of defaultLabelNames) { | ||
val.labels[labelName] = | ||
val.labels[labelName] || this._defaultLabels[labelName]; | ||
} | ||
} | ||
@@ -63,7 +59,3 @@ | ||
let line = `${metricName} ${getValueAsString(val.value)}`; | ||
if (opts.timestamps && val.timestamp) { | ||
line += ` ${val.timestamp}`; | ||
} | ||
values += `${line.trim()}\n`; | ||
values += `${metricName} ${getValueAsString(val.value)}\n`; | ||
} | ||
@@ -74,7 +66,9 @@ | ||
metrics(opts) { | ||
metrics() { | ||
let metrics = ''; | ||
this.collect(); | ||
for (const metric of this.getMetricsAsArray()) { | ||
metrics += `${this.getMetricAsPrometheusString(metric, opts)}\n\n`; | ||
metrics += `${this.getMetricAsPrometheusString(metric)}\n\n`; | ||
} | ||
@@ -85,17 +79,30 @@ | ||
registerMetric(metricFn) { | ||
if ( | ||
this._metrics[metricFn.name] && | ||
this._metrics[metricFn.name] !== metricFn | ||
) { | ||
registerMetric(metric) { | ||
if (this._metrics[metric.name] && this._metrics[metric.name] !== metric) { | ||
throw new Error( | ||
`A metric with the name ${metricFn.name} has already been registered.` | ||
`A metric with the name ${metric.name} has already been registered.` | ||
); | ||
} | ||
this._metrics[metricFn.name] = metricFn; | ||
this._metrics[metric.name] = metric; | ||
} | ||
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 = {}; | ||
@@ -108,7 +115,12 @@ } | ||
this.collect(); | ||
for (const metric of this.getMetricsAsArray()) { | ||
const item = metric.get(); | ||
if (item.values) { | ||
if (item.values && defaultLabelNames.length > 0) { | ||
for (const val of item.values) { | ||
// Make a copy before mutating | ||
val.labels = Object.assign({}, val.labels); | ||
for (const labelName of defaultLabelNames) { | ||
@@ -115,0 +127,0 @@ val.labels[labelName] = |
@@ -14,3 +14,2 @@ /** | ||
isObject, | ||
printDeprecationObjectConstructor, | ||
removeLabels | ||
@@ -30,45 +29,21 @@ } = require('./util'); | ||
* Summary | ||
* @param {string} name - Name of the metric | ||
* @param {string} help - Help for the metric | ||
* @param {object|Array.<string>} labelsOrConf - Either array of label names or config object as a key-value object | ||
* @param {object} conf - Configuration object | ||
* @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 | ||
*/ | ||
constructor(name, help, labelsOrConf, conf) { | ||
let config; | ||
if (isObject(name)) { | ||
config = Object.assign( | ||
{ | ||
percentiles: [0.01, 0.05, 0.5, 0.9, 0.95, 0.99, 0.999], | ||
labelNames: [] | ||
}, | ||
name | ||
); | ||
if (!config.registers) { | ||
config.registers = [globalRegistry]; | ||
} | ||
} else { | ||
let obj; | ||
let labels = []; | ||
if (Array.isArray(labelsOrConf)) { | ||
obj = conf || {}; | ||
labels = labelsOrConf; | ||
} else { | ||
obj = labelsOrConf || {}; | ||
} | ||
printDeprecationObjectConstructor(); | ||
config = { | ||
name, | ||
help, | ||
labelNames: labels, | ||
percentiles: configurePercentiles(obj.percentiles), | ||
registers: [globalRegistry], | ||
maxAgeSeconds: obj.maxAgeSeconds, | ||
ageBuckets: obj.ageBuckets | ||
}; | ||
constructor(config) { | ||
if (!isObject(config)) { | ||
throw new TypeError('constructor expected a config object'); | ||
} | ||
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); | ||
@@ -82,3 +57,2 @@ | ||
this.aggregator = config.aggregator || 'sum'; | ||
this.percentiles = config.percentiles; | ||
@@ -237,13 +211,2 @@ this.hashMap = {}; | ||
function configurePercentiles(configuredPercentiles) { | ||
const defaultPercentiles = [0.01, 0.05, 0.5, 0.9, 0.95, 0.99, 0.999]; | ||
return [] | ||
.concat(configuredPercentiles || defaultPercentiles) | ||
.sort(sortAscending); | ||
} | ||
function sortAscending(x, y) { | ||
return x - y; | ||
} | ||
function observe(labels) { | ||
@@ -250,0 +213,0 @@ return value => { |
'use strict'; | ||
const deprecationsEmitted = {}; | ||
exports.isDate = isDate; | ||
exports.getPropertiesFromObj = function(hashMap) { | ||
@@ -31,12 +27,7 @@ const obj = Object.keys(hashMap).map(x => hashMap[x]); | ||
exports.setValue = function setValue(hashMap, value, labels, timestamp) { | ||
exports.setValue = function setValue(hashMap, value, labels) { | ||
const hash = hashObject(labels); | ||
hashMap[hash] = { | ||
value: typeof value === 'number' ? value : 0, | ||
labels: labels || {}, | ||
timestamp: isDate(timestamp) | ||
? timestamp.valueOf() | ||
: Number.isFinite(timestamp) | ||
? timestamp | ||
: undefined | ||
labels: labels || {} | ||
}; | ||
@@ -83,5 +74,2 @@ return hashMap; | ||
function isDate(obj) { | ||
return obj instanceof Date && !isNaN(obj.valueOf()); | ||
} | ||
exports.isObject = function isObject(obj) { | ||
@@ -91,30 +79,2 @@ return obj === Object(obj); | ||
function printDeprecation(msg) { | ||
if (deprecationsEmitted[msg]) { | ||
return; | ||
} | ||
deprecationsEmitted[msg] = true; | ||
if (process.emitWarning) { | ||
process.emitWarning(msg, 'DeprecationWarning'); | ||
} else { | ||
// Check can be removed when we only support node@>=6 | ||
// eslint-disable-next-line no-console | ||
console.warn(new Error(msg)); | ||
} | ||
} | ||
exports.printDeprecationObjectConstructor = () => { | ||
printDeprecation( | ||
'prom-client - Passing a non-object to metrics constructor is deprecated' | ||
); | ||
}; | ||
exports.printDeprecationCollectDefaultMetricsNumber = timeout => { | ||
printDeprecation( | ||
`prom-client - A number to defaultMetrics is deprecated, please use \`collectDefaultMetrics({ timeout: ${timeout} })\`.` | ||
); | ||
}; | ||
class Grouper extends Map { | ||
@@ -121,0 +81,0 @@ /** |
171
package.json
{ | ||
"name": "prom-client", | ||
"version": "11.5.3", | ||
"description": "Client for prometheus", | ||
"main": "index.js", | ||
"files": [ | ||
"lib/", | ||
"index.js", | ||
"index.d.ts" | ||
], | ||
"engines": { | ||
"node": ">=6.1" | ||
}, | ||
"scripts": { | ||
"benchmarks": "node ./benchmarks/index.js", | ||
"test": "npm run lint && npm run compile-typescript && npm run test-unit", | ||
"lint": "eslint .", | ||
"test-unit": "jest", | ||
"compile-typescript": "tsc --project ." | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git@github.com:siimon/prom-client.git" | ||
}, | ||
"keywords": [ | ||
"Prometheus", | ||
"Metrics", | ||
"Client" | ||
], | ||
"author": "Simon Nyberg", | ||
"license": "Apache-2.0", | ||
"homepage": "https://github.com/siimon/prom-client", | ||
"devDependencies": { | ||
"@clevernature/benchmark-regression": "^1.0.0", | ||
"eslint": "^5.6.0", | ||
"eslint-config-prettier": "^4.3.0", | ||
"eslint-plugin-node": "^8.0.0", | ||
"eslint-plugin-prettier": "^3.0.1", | ||
"express": "^4.13.3", | ||
"husky": "^1.3.1", | ||
"jest": "^24.8.0", | ||
"lint-staged": "^7.0.0", | ||
"lolex": "^4.0.1", | ||
"prettier": "1.17.1", | ||
"typescript": "^3.0.3" | ||
}, | ||
"dependencies": { | ||
"tdigest": "^0.1.1" | ||
}, | ||
"types": "./index.d.ts", | ||
"jest": { | ||
"testEnvironment": "node", | ||
"testRegex": ".*Test\\.js$" | ||
}, | ||
"lint-staged": { | ||
"*.js": [ | ||
"eslint --fix", | ||
"git add" | ||
], | ||
"*.{ts,md,json,yml}": [ | ||
"prettier --write", | ||
"git add" | ||
], | ||
".{eslintrc,travis.yml}": [ | ||
"prettier --write", | ||
"git add" | ||
] | ||
}, | ||
"prettier": { | ||
"singleQuote": true, | ||
"useTabs": true, | ||
"overrides": [ | ||
{ | ||
"files": "*.md", | ||
"options": { | ||
"useTabs": false | ||
} | ||
}, | ||
{ | ||
"files": ".eslintrc", | ||
"options": { | ||
"parser": "json" | ||
} | ||
} | ||
] | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "lint-staged" | ||
} | ||
} | ||
"name": "prom-client", | ||
"version": "12.0.0", | ||
"description": "Client for prometheus", | ||
"main": "index.js", | ||
"files": [ | ||
"lib/", | ||
"index.js", | ||
"index.d.ts" | ||
], | ||
"engines": { | ||
"node": ">=10" | ||
}, | ||
"scripts": { | ||
"benchmarks": "node ./benchmarks/index.js", | ||
"test": "npm run lint && npm run compile-typescript && npm run test-unit", | ||
"lint": "eslint .", | ||
"test-unit": "jest", | ||
"compile-typescript": "tsc --project ." | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git@github.com:siimon/prom-client.git" | ||
}, | ||
"keywords": [ | ||
"Prometheus", | ||
"Metrics", | ||
"Client" | ||
], | ||
"author": "Simon Nyberg", | ||
"license": "Apache-2.0", | ||
"homepage": "https://github.com/siimon/prom-client", | ||
"devDependencies": { | ||
"@clevernature/benchmark-regression": "^1.0.0", | ||
"eslint": "^6.8.0", | ||
"eslint-config-prettier": "^6.10.0", | ||
"eslint-plugin-node": "^11.0.0", | ||
"eslint-plugin-prettier": "^3.0.1", | ||
"express": "^4.13.3", | ||
"husky": "^4.2.1", | ||
"jest": "^25.1.0", | ||
"lint-staged": "^10.0.4", | ||
"lolex": "^5.1.2", | ||
"prettier": "1.19.1", | ||
"typescript": "^3.0.3" | ||
}, | ||
"dependencies": { | ||
"tdigest": "^0.1.1" | ||
}, | ||
"types": "./index.d.ts", | ||
"jest": { | ||
"testEnvironment": "node", | ||
"testRegex": ".*Test\\.js$" | ||
}, | ||
"lint-staged": { | ||
"*.js": "eslint --fix", | ||
"*.{ts,md,json,yml}": "prettier --write", | ||
".{eslintrc,travis.yml}": "prettier --write" | ||
}, | ||
"prettier": { | ||
"singleQuote": true, | ||
"useTabs": true, | ||
"overrides": [ | ||
{ | ||
"files": "*.md", | ||
"options": { | ||
"useTabs": false | ||
} | ||
}, | ||
{ | ||
"files": ".eslintrc", | ||
"options": { | ||
"parser": "json" | ||
} | ||
} | ||
] | ||
}, | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "lint-staged" | ||
} | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
# 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) | ||
# 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) | ||
@@ -51,19 +51,12 @@ A prometheus client for node.js that supports histogram, summaries, gauges and | ||
In addition, some Node-specific metrics are included, such as event loop lag, | ||
active handles and Node.js version. See what metrics there are in | ||
active handles, GC and Node.js version. See what metrics there are in | ||
[lib/metrics](lib/metrics). | ||
`collectDefaultMetrics` takes 1 options object with 3 entries, a timeout for how | ||
often the probe should be fired, an optional prefix for metric names | ||
and a registry to which metrics should be registered. By default probes are | ||
launched every 10 seconds, but this can be modified like this: | ||
`collectDefaultMetrics` optionally accepts a config object with following entries: | ||
```js | ||
const client = require('prom-client'); | ||
- `prefix` an optional prefix for metric names. Default: no prefix. | ||
- `registry` to which metrics should be registered. Default: the global default registry. | ||
- `gcDurationBuckets` with custom buckets for GC duration histogram. Default buckets of GC duration histogram are `[0.001, 0.01, 0.1, 1, 2, 5]` (in seconds). | ||
- `eventLoopMonitoringPrecision` with sampling rate in milliseconds. Must be greater than zero. Default: 10. | ||
const collectDefaultMetrics = client.collectDefaultMetrics; | ||
// Probe every 5th second. | ||
collectDefaultMetrics({ timeout: 5000 }); | ||
``` | ||
To register metrics to another registry, pass it in as `register`: | ||
@@ -77,7 +70,6 @@ | ||
const register = new Registry(); | ||
collectDefaultMetrics({ register }); | ||
``` | ||
To prefix metric names with your own arbitrary string, pass in a `prefix`: | ||
To use custom buckets for GC duration histogram, pass it in as `gcDurationBuckets`: | ||
@@ -89,15 +81,12 @@ ```js | ||
// Probe every 5th second. | ||
collectDefaultMetrics({ prefix: 'my_application_' }); | ||
collectDefaultMetrics({ gcDurationBuckets: [0.1, 0.2, 0.3] }); | ||
``` | ||
To disable metric timestamps set `timestamps` to `false` (You can find the list of metrics that support this feature in `test/defaultMetricsTest.js`): | ||
To prefix metric names with your own arbitrary string, pass in a `prefix`: | ||
```js | ||
const client = require('prom-client'); | ||
const collectDefaultMetrics = client.collectDefaultMetrics; | ||
// Probe every 5th second. | ||
collectDefaultMetrics({ timestamps: false }); | ||
const prefix = 'my_application_'; | ||
collectDefaultMetrics({ prefix }); | ||
``` | ||
@@ -191,3 +180,3 @@ | ||
The defaults buckets are intended to cover usual web/rpc requests, this can | ||
however be overriden. | ||
however be overridden. | ||
@@ -231,3 +220,3 @@ ```js | ||
xhrRequest(function(err, res) { | ||
end(); // Observes the value to xhrRequests duration in seconds | ||
const seconds = end(); // Observes and returns the value to xhrRequests duration in seconds | ||
}); | ||
@@ -243,3 +232,3 @@ ``` | ||
The default percentiles are: 0.01, 0.05, 0.5, 0.9, 0.95, 0.99, 0.999. But they | ||
can be overriden like this: | ||
can be overridden like this: | ||
@@ -346,18 +335,2 @@ ```js | ||
### Timestamps | ||
Counter and gauge metrics can take a timestamp argument after the value | ||
argument. This argument must be a Date or a number (milliseconds since Unix | ||
epoch, i.e. 1970-01-01 00:00:00 UTC, excluding leap seconds). | ||
```js | ||
gauge.set(100, 1485531442231); // Set gauge value and timestamp as milliseconds since Unix epoch | ||
gauge.set(100, Date.now()); // Set gauge value and timestamp as milliseconds since Unix epoch | ||
gauge.set(100, new Date()); // Set gauge value and timestamp as Date | ||
gauge.set({ method: 'GET', statusCode: '200' }, 100, new Date()); // Set gauge value and timestamp with labels | ||
gauge.labels('GET', '200').set(100, new Date()); // Same as above | ||
counter.inc(1, new Date()); // Increment counter with timestamp | ||
``` | ||
### Multiple registries | ||
@@ -412,5 +385,2 @@ | ||
`register.metrics()` takes an optional object with a `timestamps` field. Setting | ||
this to false will strip timestamps from the string. | ||
#### Getting a single metric for Prometheus displaying | ||
@@ -467,5 +437,2 @@ | ||
Note that timestamps will be stripped before the metrics are pushed, since | ||
pushgateway >= 0.4 does not accept timestamps. | ||
```js | ||
@@ -490,3 +457,3 @@ const client = require('prom-client'); | ||
### Utilites | ||
### Utilities | ||
@@ -493,0 +460,0 @@ For convenience, there are 2 bucket generator functions - linear and |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
34
111968
2653
475
6