From d48df84d742f8e56ba5cbb96df711aaa1da5ad51 Mon Sep 17 00:00:00 2001 From: Andrei Dobre Date: Fri, 12 Nov 2021 20:11:57 +0200 Subject: [PATCH 1/6] Add support for OpenMetrics format - Added support for OpenMetrics format, including exemplars - Added support for exemplars with OpenTelemetry tracing data on default metrics - Added the option of passing params as one object to observe() and inc() methods --- README.md | 47 ++++++- example/exemplars.js | 74 ++++++++++ index.d.ts | 24 +++- index.js | 2 + lib/cluster.js | 19 ++- lib/counter.js | 81 ++++++++++- lib/exemplar.js | 37 +++++ lib/gauge.js | 8 +- lib/histogram.js | 90 ++++++++++-- lib/metric.js | 1 + lib/metrics/gc.js | 2 +- lib/metrics/processCpuTotal.js | 41 +++++- lib/registry.js | 110 ++++++++++++--- lib/summary.js | 5 +- lib/util.js | 4 + package.json | 1 + test/__snapshots__/counterTest.js.snap | 16 ++- test/__snapshots__/gaugeTest.js.snap | 4 +- test/__snapshots__/histogramTest.js.snap | 16 ++- test/__snapshots__/registerTest.js.snap | 55 +++++++- test/__snapshots__/summaryTest.js.snap | 12 +- test/clusterTest.js | 15 +- test/counterTest.js | 14 +- test/defaultMetricsTest.js | 14 +- test/exemplarsTest.js | 108 +++++++++++++++ test/gaugeTest.js | 14 +- test/histogramTest.js | 14 +- test/metrics/eventLoopLagTest.js | 13 +- test/metrics/gcTest.js | 13 +- test/metrics/heapSizeAndUsedTest.js | 11 +- test/metrics/heapSpacesSizeAndUsedTest.js | 10 +- test/metrics/maxFileDescriptorsTest.js | 16 ++- test/metrics/processHandlesTest.js | 13 +- .../metrics/processOpenFileDescriptorsTest.js | 24 ++-- test/metrics/processRequestsTest.js | 13 +- test/metrics/processStartTimeTest.js | 13 +- test/metrics/versionTest.js | 14 +- test/pushgatewayTest.js | 14 +- test/pushgatewayWithPathTest.js | 14 +- test/registerTest.js | 131 +++++++++++++----- test/summaryTest.js | 18 ++- 41 files changed, 997 insertions(+), 148 deletions(-) create mode 100644 example/exemplars.js create mode 100644 lib/exemplar.js create mode 100644 test/exemplarsTest.js diff --git a/README.md b/README.md index 4f4faa30..c3647a09 100644 --- a/README.md +++ b/README.md @@ -390,6 +390,47 @@ Default labels will be overridden if there is a name conflict. `register.clear()` will clear default labels. +### Exemplars + +The exemplars defined in the OpenMetrics specification can be enabled on Counter +and Histogram metric types. The default metrics have support for OpenTelemetry, +they will populate the exemplars with the labels `{traceId, spanId}` and their +corresponding values. + +The format for `inc()` and `observe()` calls are different if exemplars are +enabled. They get a single object with the format +`{labels, value, exemplarLabels}`. + +When using exemplars, the registry used for metrics should be set to OpenMetrics +type (including the global or default registry). + +### Registy type + +The library supports both the old Prometheus format and the OpenMetrics format. +The format can be set per registry. For default metrics: + +```js +const Prometheus = require('prom-client'); +Prometheus.register.setContentType( + Prometheus.Registry.OPENMETRICS_CONTENT_TYPE, +); +``` + +Currently available registry types are defined by the content types: + +**PROMETHEUS_CONTENT_TYPE** - version 0.0.4 of the original Prometheus metrics, +this is currently the default registry type. + +**OPENMETRICS_CONTENT_TYPE** - defaults to version 1.0.0 of the +[OpenMetrics standard](https://github.com/OpenObservability/OpenMetrics/blob/d99b705f611b75fec8f450b05e344e02eea6921d/specification/OpenMetrics.md). + +The HTTP Content-Type string for each registry type is exposed both at module +level (`prometheusContentType` and `openMetricsContentType`) and as static +properties on the `Registry` object. + +The `contentType` constant exposed by the module returns the default content +type when creating a new registry, currently defaults to Prometheus type. + ### Multiple registries By default, metrics are automatically registered to the global registry (located @@ -404,6 +445,9 @@ Registry has a `merge` function that enables you to expose multiple registries on the same endpoint. If the same metric name exists in both registries, an error will be thrown. +Merging registries of different types is undefined. The user needs to make sure +all used registries have the same type (Prometheus or OpenMetrics versions). + ```js const client = require('prom-client'); const registry = new client.Registry(); @@ -554,9 +598,6 @@ new client.Histogram({ }); ``` -The content-type prometheus expects is also exported as a constant, both on the -`register` and from the main file of this project, called `contentType`. - ### Garbage Collection Metrics To avoid native dependencies in this module, GC statistics for bytes reclaimed diff --git a/example/exemplars.js b/example/exemplars.js new file mode 100644 index 00000000..1d59e8aa --- /dev/null +++ b/example/exemplars.js @@ -0,0 +1,74 @@ +'use strict'; + +const { register, Registry, Counter, Histogram } = require('..'); + +async function makeCounters() { + const c = new Counter({ + name: 'test_counter_exemplar', + help: 'Example of a counter with exemplar', + labelNames: ['code'], + enableExemplars: true, + }); + + const exemplarLabels = { traceId: '888', spanId: 'jjj' }; + + c.inc({ + labels: { code: 300 }, + value: 1, + exemplarLabels, + }); + c.inc({ + labels: { code: 200 }, + exemplarLabels, + }); + + c.inc({ exemplarLabels }); + c.inc(); +} + +async function makeHistograms() { + const h = new Histogram({ + name: 'test_histogram_exemplar', + help: 'Example of a histogram with exemplar', + labelNames: ['code'], + enableExemplars: true, + }); + + const exemplarLabels = { traceId: '111', spanId: 'zzz' }; + + h.observe({ + labels: { code: '200' }, + value: 1, + exemplarLabels, + }); + + h.observe({ + labels: { code: '200' }, + value: 3, + exemplarLabels, + }); + + h.observe({ + labels: { code: '200' }, + value: 0.3, + exemplarLabels, + }); + + h.observe({ + labels: { code: '200' }, + value: 300, + exemplarLabels, + }); +} + +async function main() { + // should only use exemplars with OpenMetrics registry types + register.setContentType(Registry.OPENMETRICS_CONTENT_TYPE); + + makeCounters(); + makeHistograms(); + + console.log(await register.metrics()); +} + +main(); diff --git a/index.d.ts b/index.d.ts index a99bcd0c..45fc4038 100644 --- a/index.d.ts +++ b/index.d.ts @@ -3,6 +3,10 @@ /** * Container for all registered metrics + * @property {string} PROMETHEUS_CONTENT_TYPE - Content-Type of Prometheus + * registry type + * @property {string} OPENMETRICS_CONTENT_TYPE - Content-Type of OpenMetrics + * registry type. */ export class Registry { /** @@ -66,6 +70,13 @@ export class Registry { */ contentType: string; + /** + * Set the content type of a registry. Used to change between Prometheus and + * OpenMetrics versions. + * @param contentType The type of the registry + */ + setContentType(contentType: string): void; + /** * Merge registers * @param registers The registers you want to merge together @@ -80,10 +91,21 @@ export type Collector = () => void; export const register: Registry; /** - * The Content-Type of the metrics for use in the response headers. + * HTTP Content-Type for metrics response headers, defaults to Prometheus text + * format. */ export const contentType: string; +/** + * HTTP Prometheus Content-Type for metrics response headers. + */ +export const prometheusContentType: string; + +/** + * HTTP OpenMetrics Content-Type for metrics response headers. + */ +export const openMetricsContentType: string; + export class AggregatorRegistry extends Registry { /** * Gets aggregated metrics for all workers. diff --git a/index.js b/index.js index 3199417f..fdc39f0e 100644 --- a/index.js +++ b/index.js @@ -8,6 +8,8 @@ exports.register = require('./lib/registry').globalRegistry; exports.Registry = require('./lib/registry'); exports.contentType = require('./lib/registry').globalRegistry.contentType; +exports.prometheusContentType = require('./lib/registry').PROMETHEUS_CONTENT_TYPE; +exports.openMetricsContentType = require('./lib/registry').OPENMETRICS_CONTENT_TYPE; exports.validateMetricName = require('./lib/validation').validateMetricName; exports.Counter = require('./lib/counter'); diff --git a/lib/cluster.js b/lib/cluster.js index cb564ede..04add12d 100644 --- a/lib/cluster.js +++ b/lib/cluster.js @@ -28,8 +28,8 @@ let listenersAdded = false; const requests = new Map(); // Pending requests for workers' local metrics. class AggregatorRegistry extends Registry { - constructor() { - super(); + constructor(regContentType = Registry.PROMETHEUS_CONTENT_TYPE) { + super(regContentType); addListeners(); } @@ -84,6 +84,10 @@ class AggregatorRegistry extends Registry { }); } + get contentType() { + return super.contentType; + } + /** * Creates a new Registry instance from an array of metrics that were * created by `registry.getMetricsAsJSON()`. Metrics are aggregated using @@ -91,12 +95,21 @@ class AggregatorRegistry extends Registry { * `aggregator` is undefined. * @param {Array} metricsArr Array of metrics, each of which created by * `registry.getMetricsAsJSON()`. + * @param {string} registryType content type of the new registry. Defaults + * to PROMETHEUS_CONTENT_TYPE. * @return {Registry} aggregated registry. */ - static aggregate(metricsArr) { + static aggregate( + metricsArr, + registryType = Registry.PROMETHEUS_CONTENT_TYPE, + ) { const aggregatedRegistry = new Registry(); const metricsByName = new Grouper(); + if (registryType === Registry.OPENMETRICS_CONTENT_TYPE) { + aggregatedRegistry.setContentType(registryType); + } + // Gather by name metricsArr.forEach(metrics => { metrics.forEach(metric => { diff --git a/lib/counter.js b/lib/counter.js index f711a3b9..965aecfd 100644 --- a/lib/counter.js +++ b/lib/counter.js @@ -4,20 +4,53 @@ 'use strict'; const util = require('util'); -const type = 'counter'; -const { hashObject, isObject, getLabels, removeLabels } = require('./util'); +const { + hashObject, + isObject, + getLabels, + removeLabels, + nowTimestamp, +} = require('./util'); const { validateLabel } = require('./validation'); const { Metric } = require('./metric'); +const Exemplar = require('./exemplar'); class Counter extends Metric { + constructor(config) { + super(config); + this.type = 'counter'; + this.defaultLabels = {}; + this.defaultValue = 1; + this.defaultExemplarLabelSet = {}; + if (config.enableExemplars) { + this.enableExemplars = true; + this.inc = this.incWithExemplar; + } else { + this.inc = this.incWithoutExemplar; + } + } + + enableExemplars(enable = false) { + if (enable) { + this.enableExemplars = true; + } + if (this.enableExemplars) { + this.inc = this.incWithExemplar; + } else { + this.inc = this.incWithoutExemplar; + } + this.reset(); + } + /** * Increment counter * @param {object} labels - What label you want to be incremented * @param {Number} value - Value to increment, if omitted increment with 1 - * @returns {void} + * @returns {object} results - object with information about the inc operation + * @returns {string} results.labelHash - hash representation of the labels */ - inc(labels, value) { - let hash; + incWithoutExemplar(labels, value) { + let hash = ''; if (isObject(labels)) { hash = hashObject(labels); validateLabel(this.labelNames, labels); @@ -36,6 +69,41 @@ class Counter extends Metric { if (value === null || value === undefined) value = 1; setValue(this.hashMap, value, labels, hash); + + return { labelHash: hash }; + } + + /** + * Increment counter with exemplar, same as inc but accepts labels for an + * exemplar. + * If no label is provided the current exemplar labels are kept unchanged + * (defaults to empty set). + * + * @param {object} incOpts - Object with options about what metric to increase + * @param {object} incOpts.labels - What label you want to be incremented, + * defaults to null (metric with no labels) + * @param {Number} incOpts.value - Value to increment, defaults to 1 + * @param {object} incOpts.exemplarLabels - Key-value labels for the + * exemplar, defaults to empty set {} + * @returns {void} + */ + incWithExemplar({ + labels = this.defaultLabels, + value = this.defaultValue, + exemplarLabels = this.defaultExemplarLabelSet, + } = {}) { + const res = this.incWithoutExemplar(labels, value); + this.updateExemplar(exemplarLabels, value, res.labelHash); + } + + updateExemplar(exemplarLabels, value, hash) { + if (!isObject(this.hashMap[hash].exemplar)) { + this.hashMap[hash].exemplar = new Exemplar(); + } + this.hashMap[hash].exemplar.validateExemplarLabelSet(exemplarLabels); + this.hashMap[hash].exemplar.labelSet = exemplarLabels; + this.hashMap[hash].exemplar.value = value ? value : 1; + this.hashMap[hash].exemplar.timestamp = nowTimestamp(); } /** @@ -54,10 +122,11 @@ class Counter extends Metric { const v = this.collect(); if (v instanceof Promise) await v; } + return { help: this.help, name: this.name, - type, + type: this.type, values: Object.values(this.hashMap), aggregator: this.aggregator, }; diff --git a/lib/exemplar.js b/lib/exemplar.js new file mode 100644 index 00000000..4a090f7e --- /dev/null +++ b/lib/exemplar.js @@ -0,0 +1,37 @@ +'use strict'; + +/** + * Class representing an OpenMetrics exemplar. + * + * @property {object} labelSet + * @property {number} value + * @property {number} [timestamp] + * */ +class Exemplar { + constructor(labelSet = {}, value = null) { + this.labelSet = labelSet; + this.value = value; + } + + /** + * Validation for the label set format. + * https://github.com/OpenObservability/OpenMetrics/blob/d99b705f611b75fec8f450b05e344e02eea6921d/specification/OpenMetrics.md#exemplars + * + * @param {object} labelSet - Exemplar labels. + * @throws {RangeError} + * @return {void} + */ + validateExemplarLabelSet(labelSet) { + let res = ''; + for (const [labelName, labelValue] of Object.entries(labelSet)) { + res += `${labelName}${labelValue}`; + } + if (res.length > 128) { + throw new RangeError( + 'Label set size must be smaller than 128 UTF-8 chars', + ); + } + } +} + +module.exports = Exemplar; diff --git a/lib/gauge.js b/lib/gauge.js index 1540abb4..77327b6b 100644 --- a/lib/gauge.js +++ b/lib/gauge.js @@ -4,7 +4,6 @@ 'use strict'; const util = require('util'); -const type = 'gauge'; const { setValue, @@ -18,6 +17,11 @@ const { validateLabel } = require('./validation'); const { Metric } = require('./metric'); class Gauge extends Metric { + constructor(config) { + super(config); + this.type = 'gauge'; + } + /** * Set a gauge to a value * @param {object} labels - Object with labels and their values @@ -109,7 +113,7 @@ class Gauge extends Metric { return { help: this.help, name: this.name, - type, + type: this.type, values: Object.values(this.hashMap), aggregator: this.aggregator, }; diff --git a/lib/histogram.js b/lib/histogram.js index ee11352d..6bc22eed 100644 --- a/lib/histogram.js +++ b/lib/histogram.js @@ -4,10 +4,16 @@ 'use strict'; const util = require('util'); -const type = 'histogram'; -const { getLabels, hashObject, isObject, removeLabels } = require('./util'); +const { + getLabels, + hashObject, + isObject, + removeLabels, + nowTimestamp, +} = require('./util'); const { validateLabel } = require('./validation'); const { Metric } = require('./metric'); +const Exemplar = require('./exemplar'); class Histogram extends Metric { constructor(config) { @@ -15,6 +21,16 @@ class Histogram extends Metric { buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10], }); + this.type = 'histogram'; + this.defaultLabels = {}; + this.defaultExemplarLabelSet = {}; + + if (config.enableExemplars) { + this.observe = this.observeWithExemplar; + } else { + this.observe = this.observeWithoutExemplar; + } + for (const label of this.labelNames) { if (label === 'le') { throw new Error('le is a reserved label keyword'); @@ -27,7 +43,13 @@ class Histogram extends Metric { return acc; }, {}); + this.bucketExemplars = this.upperBounds.reduce((acc, upperBound) => { + acc[upperBound] = null; + return acc; + }, {}); + Object.freeze(this.bucketValues); + Object.freeze(this.bucketExemplars); Object.freeze(this.upperBounds); if (this.labelNames.length === 0) { @@ -35,21 +57,57 @@ class Histogram extends Metric { [hashObject({})]: createBaseValues( {}, Object.assign({}, this.bucketValues), + Object.assign({}, this.bucketExemplars), ), }; } } + enableExemplars(enable = false) { + if (enable) { + this.enableExemplars = true; + } + if (this.enableExemplars) { + this.observe = this.observeWithExemplar; + } else { + this.observe = this.observeWithoutExemplar; + } + this.reset(); + } + /** * Observe a value in histogram * @param {object} labels - Object with labels where key is the label key and value is label value. Can only be one level deep * @param {Number} value - Value to observe in the histogram * @returns {void} */ - observe(labels, value) { + observeWithoutExemplar(labels, value) { observe.call(this, labels === 0 ? 0 : labels || {})(value); } + observeWithExemplar({ + labels = this.defaultLabels, + value, + exemplarLabels = this.defaultExemplarLabelSet, + } = {}) { + observe.call(this, labels === 0 ? 0 : labels || {})(value); + this.updateExemplar(labels, value, exemplarLabels); + } + + updateExemplar(labels, value, exemplarLabels) { + const hash = hashObject(labels); + const b = findBound(this.upperBounds, value); + if (!isObject(this.hashMap[hash].bucketExemplars[b])) { + this.hashMap[hash].bucketExemplars[b] = new Exemplar(); + } + this.hashMap[hash].bucketExemplars[b].validateExemplarLabelSet( + exemplarLabels, + ); + this.hashMap[hash].bucketExemplars[b].labelSet = exemplarLabels; + this.hashMap[hash].bucketExemplars[b].value = value; + this.hashMap[hash].bucketExemplars[b].timestamp = nowTimestamp(); + } + async get() { if (this.collect) { const v = this.collect(); @@ -63,7 +121,7 @@ class Histogram extends Metric { return { name: this.name, help: this.help, - type, + type: this.type, values, aggregator: this.aggregator, }; @@ -83,6 +141,7 @@ class Histogram extends Metric { this.hashMap[hash] = createBaseValues( labels, Object.assign({}, this.bucketValues), + Object.assign({}, this.bucketExemplars), ); } @@ -129,11 +188,12 @@ function startTimer(startLabels) { }; } -function setValuePair(labels, value, metricName) { +function setValuePair(labels, value, metricName, exemplar) { return { labels, value, metricName, + exemplar, }; } @@ -164,6 +224,7 @@ function observe(labels) { valueFromMap = createBaseValues( labelValuePair.labels, Object.assign({}, this.bucketValues), + Object.assign({}, this.bucketExemplars), ); } @@ -180,10 +241,11 @@ function observe(labels) { }; } -function createBaseValues(labels, bucketValues) { +function createBaseValues(labels, bucketValues, bucketExemplars) { return { labels, bucketValues, + bucketExemplars, sum: 0, count: 0, }; @@ -213,7 +275,14 @@ function extractBucketValuesForExport(histogram) { for (const labelName of bucketLabelNames) { lbls[labelName] = bucketData.labels[labelName]; } - buckets.push(setValuePair(lbls, acc, `${histogram.name}_bucket`)); + buckets.push( + setValuePair( + lbls, + acc, + `${histogram.name}_bucket`, + bucketData.bucketExemplars[upperBound], + ), + ); } return { buckets, data: bucketData }; }; @@ -228,7 +297,12 @@ function addSumAndCountForExport(histogram) { infLabel[label] = d.data.labels[label]; } acc.push( - setValuePair(infLabel, d.data.count, `${histogram.name}_bucket`), + setValuePair( + infLabel, + d.data.count, + `${histogram.name}_bucket`, + d.data.bucketExemplars['-1'], + ), setValuePair(d.data.labels, d.data.sum, `${histogram.name}_sum`), setValuePair(d.data.labels, d.data.count, `${histogram.name}_count`), ); diff --git a/lib/metric.js b/lib/metric.js index 95b04321..6fd5b648 100644 --- a/lib/metric.js +++ b/lib/metric.js @@ -18,6 +18,7 @@ class Metric { labelNames: [], registers: [globalRegistry], aggregator: 'sum', + enableExemplars: false, }, defaults, config, diff --git a/lib/metrics/gc.js b/lib/metrics/gc.js index 0d2e2839..f26818ab 100644 --- a/lib/metrics/gc.js +++ b/lib/metrics/gc.js @@ -37,6 +37,7 @@ module.exports = (registry, config = {}) => { name: namePrefix + NODEJS_GC_DURATION_SECONDS, help: 'Garbage collection duration by kind, one of major, minor, incremental or weakcb.', labelNames: ['kind', ...labelNames], + enableExemplars: false, buckets, registers: registry ? [registry] : undefined, }); @@ -47,7 +48,6 @@ module.exports = (registry, config = {}) => { // Node >= 16 uses entry.detail.kind // See: https://nodejs.org/docs/latest-v16.x/api/deprecations.html#deprecations_dep0152_extension_performanceentry_properties const kind = entry.detail ? kinds[entry.detail.kind] : kinds[entry.kind]; - // Convert duration from milliseconds to seconds gcHistogram.observe(Object.assign({ kind }, labels), entry.duration / 1000); }); diff --git a/lib/metrics/processCpuTotal.js b/lib/metrics/processCpuTotal.js index ef0aa959..d0213094 100644 --- a/lib/metrics/processCpuTotal.js +++ b/lib/metrics/processCpuTotal.js @@ -1,6 +1,8 @@ 'use strict'; +const OtelApi = require('@opentelemetry/api'); const Counter = require('../counter'); + const PROCESS_CPU_USER_SECONDS = 'process_cpu_user_seconds_total'; const PROCESS_CPU_SYSTEM_SECONDS = 'process_cpu_system_seconds_total'; const PROCESS_CPU_SECONDS = 'process_cpu_seconds_total'; @@ -9,6 +11,7 @@ module.exports = (registry, config = {}) => { const registers = registry ? [registry] : undefined; const namePrefix = config.prefix ? config.prefix : ''; const labels = config.labels ? config.labels : {}; + const exemplars = config.enableExemplars ? config.enableExemplars : false; const labelNames = Object.keys(labels); let lastCpuUsage = process.cpuUsage(); @@ -16,6 +19,7 @@ module.exports = (registry, config = {}) => { const cpuUserUsageCounter = new Counter({ name: namePrefix + PROCESS_CPU_USER_SECONDS, help: 'Total user CPU time spent in seconds.', + enableExemplars: exemplars, registers, labelNames, // Use this one metric's `collect` to set all metrics' values. @@ -27,20 +31,51 @@ module.exports = (registry, config = {}) => { lastCpuUsage = cpuUsage; - cpuUserUsageCounter.inc(labels, userUsageMicros / 1e6); - cpuSystemUsageCounter.inc(labels, systemUsageMicros / 1e6); - cpuUsageCounter.inc(labels, (userUsageMicros + systemUsageMicros) / 1e6); + if (this.enableExemplars) { + let exemplarLabels = {}; + const currentSpan = OtelApi.trace.getSpan(OtelApi.context.active()); + if (currentSpan) { + exemplarLabels = { + traceId: currentSpan.spanContext().traceId, + spanId: currentSpan.spanContext().spanId, + }; + } + cpuUserUsageCounter.inc({ + labels, + value: userUsageMicros / 1e6, + exemplarLabels, + }); + cpuSystemUsageCounter.inc({ + labels, + value: systemUsageMicros / 1e6, + exemplarLabels, + }); + cpuUsageCounter.inc({ + labels, + value: (userUsageMicros + systemUsageMicros) / 1e6, + exemplarLabels, + }); + } else { + cpuUserUsageCounter.inc(labels, userUsageMicros / 1e6); + cpuSystemUsageCounter.inc(labels, systemUsageMicros / 1e6); + cpuUsageCounter.inc( + labels, + (userUsageMicros + systemUsageMicros) / 1e6, + ); + } }, }); const cpuSystemUsageCounter = new Counter({ name: namePrefix + PROCESS_CPU_SYSTEM_SECONDS, help: 'Total system CPU time spent in seconds.', + enableExemplars: exemplars, registers, labelNames, }); const cpuUsageCounter = new Counter({ name: namePrefix + PROCESS_CPU_SECONDS, help: 'Total user and system CPU time spent in seconds.', + enableExemplars: exemplars, registers, labelNames, }); diff --git a/lib/registry.js b/lib/registry.js index 77670f43..bd80ee22 100644 --- a/lib/registry.js +++ b/lib/registry.js @@ -10,27 +10,38 @@ function escapeLabelValue(str) { } return escapeString(str).replace(/"/g, '\\"'); } +function standardizeCounterName(name) { + if (name.endsWith('_total')) { + return name.replace('_total', ''); + } + return name; +} class Registry { - constructor() { + static get PROMETHEUS_CONTENT_TYPE() { + return 'text/plain; version=0.0.4; charset=utf-8'; + } + + static get OPENMETRICS_CONTENT_TYPE() { + return 'application/openmetrics-text; version=1.0.0; charset=utf-8'; + } + + constructor(regContentType = Registry.PROMETHEUS_CONTENT_TYPE) { this._metrics = {}; this._collectors = []; this._defaultLabels = {}; + this._contentType = regContentType; } getMetricsAsArray() { return Object.values(this._metrics); } - async getMetricAsPrometheusString(metric) { - const item = await metric.get(); - const name = escapeString(item.name); - const help = `# HELP ${name} ${escapeString(item.help)}`; - const type = `# TYPE ${name} ${item.type}`; + getLabelSetAsString(metric) { const defaultLabelNames = Object.keys(this._defaultLabels); - let values = ''; - for (const val of item.values || []) { + + for (const val of metric.values || []) { val.labels = val.labels || {}; if (defaultLabelNames.length > 0) { @@ -43,7 +54,13 @@ class Registry { } } - let metricName = val.metricName || item.name; + let metricName = val.metricName || metric.name; + if ( + this.contentType === Registry.OPENMETRICS_CONTENT_TYPE && + metric.type === 'counter' + ) { + metricName = `${metricName}_total`; + } const keys = Object.keys(val.labels); const size = keys.length; @@ -56,10 +73,44 @@ class Registry { labels += `${keys[i]}="${escapeLabelValue(val.labels[keys[i]])}"`; metricName += `{${labels}}`; } - - values += `${metricName} ${getValueAsString(val.value)}\n`; + values += `${metricName} ${getValueAsString(val.value)}`; + if (val.exemplar) { + const exemplarKeys = Object.keys(val.exemplar.labelSet); + const exemplarSize = exemplarKeys.length; + if (exemplarSize > 0) { + let labels = ''; + let i = 0; + for (; i < exemplarSize - 1; i++) { + labels += `${exemplarKeys[i]}="${escapeLabelValue( + val.exemplar.labelSet[exemplarKeys[i]], + )}",`; + } + labels += `${exemplarKeys[i]}="${escapeLabelValue( + val.exemplar.labelSet[exemplarKeys[i]], + )}"`; + values += ` # {${labels}} ${getValueAsString(val.exemplar.value)} ${ + val.exemplar.timestamp + }`; + } else { + values += ` # {} ${getValueAsString(val.exemplar.value)} ${ + val.exemplar.timestamp + }`; + } + } + values += '\n'; } + return values; + } + + async getMetricsAsString(metrics) { + const metric = await metrics.get(); + + const name = escapeString(metric.name); + const help = `# HELP ${name} ${escapeString(metric.help)}`; + const type = `# TYPE ${name} ${metric.type}`; + const values = this.getLabelSetAsString(metric); + return `${help}\n${type}\n${values}`.trim(); } @@ -67,12 +118,22 @@ class Registry { const promises = []; for (const metric of this.getMetricsAsArray()) { - promises.push(this.getMetricAsPrometheusString(metric)); + if ( + this.contentType === Registry.OPENMETRICS_CONTENT_TYPE && + metric.type === 'counter' + ) { + metric.name = standardizeCounterName(metric.name); + } + promises.push(this.getMetricsAsString(metric)); } const resolves = await Promise.all(promises); - return `${resolves.join('\n\n')}\n`; + if (this.contentType === Registry.OPENMETRICS_CONTENT_TYPE) { + return `${resolves.join('\n')}\n# EOF\n`; + } else { + return `${resolves.join('\n\n')}\n`; + } } registerMetric(metric) { @@ -126,7 +187,7 @@ class Registry { } getSingleMetricAsString(name) { - return this.getMetricAsPrometheusString(this._metrics[name]); + return this.getMetricsAsString(this._metrics[name]); } getSingleMetric(name) { @@ -144,11 +205,28 @@ class Registry { } get contentType() { - return 'text/plain; version=0.0.4; charset=utf-8'; + return this._contentType; + } + + setContentType(metricsContentType) { + if (metricsContentType === Registry.OPENMETRICS_CONTENT_TYPE) { + this._contentType = Registry.OPENMETRICS_CONTENT_TYPE; + } else { + this._contentType = Registry.PROMETHEUS_CONTENT_TYPE; + } } + /* Merge behaviour between registries of different types is undefined. The + * user should only provide an array or registries of the same type. */ static merge(registers) { - const mergedRegistry = new Registry(); + let regType = Registry.PROMETHEUS_CONTENT_TYPE; + for (const reg of registers) { + if (reg.type && reg.type !== regType) { + regType = reg.type; + break; + } + } + const mergedRegistry = new Registry(regType); const metricsToMerge = registers.reduce( (acc, reg) => acc.concat(reg.getMetricsAsArray()), diff --git a/lib/summary.js b/lib/summary.js index 9ca3e096..6405d94d 100644 --- a/lib/summary.js +++ b/lib/summary.js @@ -4,7 +4,6 @@ 'use strict'; const util = require('util'); -const type = 'summary'; const { getLabels, hashObject, removeLabels } = require('./util'); const { validateLabel } = require('./validation'); const { Metric } = require('./metric'); @@ -20,6 +19,8 @@ class Summary extends Metric { hashMap: {}, }); + this.type = 'summary'; + for (const label of this.labelNames) { if (label === 'quantile') throw new Error('quantile is a reserved label keyword'); @@ -65,7 +66,7 @@ class Summary extends Metric { return { name: this.name, help: this.help, - type, + type: this.type, values, aggregator: this.aggregator, }; diff --git a/lib/util.js b/lib/util.js index dd626998..c9e3cb3b 100644 --- a/lib/util.js +++ b/lib/util.js @@ -87,6 +87,10 @@ exports.isObject = function isObject(obj) { return obj === Object(obj); }; +exports.nowTimestamp = function nowTimestamp() { + return Date.now() / 1000; +}; + class Grouper extends Map { /** * Adds the `value` to the `key`'s array of values. diff --git a/package.json b/package.json index 34cf6c5e..b4692b8b 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "typescript": "^4.0.2" }, "dependencies": { + "@opentelemetry/api": "^1.0.2", "tdigest": "^0.1.1" }, "types": "./index.d.ts", diff --git a/test/__snapshots__/counterTest.js.snap b/test/__snapshots__/counterTest.js.snap index e68024c3..691f46c6 100644 --- a/test/__snapshots__/counterTest.js.snap +++ b/test/__snapshots__/counterTest.js.snap @@ -1,9 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`counter remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; +exports[`counter with $tag registry remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; -exports[`counter with params as object labels should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; +exports[`counter with $tag registry remove should throw error if label lengths does not match 2`] = `"Invalid number of arguments"`; -exports[`counter with params as object should not be possible to decrease a counter 1`] = `"It is not possible to decrease a counter"`; +exports[`counter with $tag registry with params as object labels should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; -exports[`counter with params as object should throw an error when the value is not a number 1`] = `"Value is not a valid number: 3ms"`; +exports[`counter with $tag registry with params as object labels should throw error if label lengths does not match 2`] = `"Invalid number of arguments"`; + +exports[`counter with $tag registry with params as object should not be possible to decrease a counter 1`] = `"It is not possible to decrease a counter"`; + +exports[`counter with $tag registry with params as object should not be possible to decrease a counter 2`] = `"It is not possible to decrease a counter"`; + +exports[`counter with $tag registry with params as object should throw an error when the value is not a number 1`] = `"Value is not a valid number: 3ms"`; + +exports[`counter with $tag registry with params as object should throw an error when the value is not a number 2`] = `"Value is not a valid number: 3ms"`; diff --git a/test/__snapshots__/gaugeTest.js.snap b/test/__snapshots__/gaugeTest.js.snap index ed9dd322..3b4948dc 100644 --- a/test/__snapshots__/gaugeTest.js.snap +++ b/test/__snapshots__/gaugeTest.js.snap @@ -1,3 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`gauge global registry with parameters as object should not allow non numbers 1`] = `"Value is not a valid number: asd"`; +exports[`gauge with $tag registry global registry with parameters as object should not allow non numbers 1`] = `"Value is not a valid number: asd"`; + +exports[`gauge with $tag registry global registry with parameters as object should not allow non numbers 2`] = `"Value is not a valid number: asd"`; diff --git a/test/__snapshots__/histogramTest.js.snap b/test/__snapshots__/histogramTest.js.snap index ddcc1499..2620b492 100644 --- a/test/__snapshots__/histogramTest.js.snap +++ b/test/__snapshots__/histogramTest.js.snap @@ -1,9 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`histogram with object as params with global registry labels should not allow different number of labels 1`] = `"Invalid number of arguments"`; +exports[`histogram with $tag registry with object as params with global registry labels should not allow different number of labels 1`] = `"Invalid number of arguments"`; -exports[`histogram with object as params with global registry remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; +exports[`histogram with $tag registry with object as params with global registry labels should not allow different number of labels 2`] = `"Invalid number of arguments"`; -exports[`histogram with object as params with global registry should not allow le as a custom label 1`] = `"le is a reserved label keyword"`; +exports[`histogram with $tag registry with object as params with global registry remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; -exports[`histogram with object as params with global registry should not allow non numbers 1`] = `"Value is not a valid number: asd"`; +exports[`histogram with $tag registry with object as params with global registry remove should throw error if label lengths does not match 2`] = `"Invalid number of arguments"`; + +exports[`histogram with $tag registry with object as params with global registry should not allow le as a custom label 1`] = `"le is a reserved label keyword"`; + +exports[`histogram with $tag registry with object as params with global registry should not allow le as a custom label 2`] = `"le is a reserved label keyword"`; + +exports[`histogram with $tag registry with object as params with global registry should not allow non numbers 1`] = `"Value is not a valid number: asd"`; + +exports[`histogram with $tag registry with object as params with global registry should not allow non numbers 2`] = `"Value is not a valid number: asd"`; diff --git a/test/__snapshots__/registerTest.js.snap b/test/__snapshots__/registerTest.js.snap index 320e5fb7..6b5fce26 100644 --- a/test/__snapshots__/registerTest.js.snap +++ b/test/__snapshots__/registerTest.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`register should not output all initialized metrics at value 0 if labels 1`] = ` +exports[`$tag register should not output all initialized metrics at value 0 if labels 1`] = ` "# HELP counter help # TYPE counter counter @@ -15,7 +15,20 @@ exports[`register should not output all initialized metrics at value 0 if labels " `; -exports[`register should output all initialized metrics at value 0 1`] = ` +exports[`$tag register should not output all initialized metrics at value 0 if labels 2`] = ` +"# HELP counter help +# TYPE counter counter +# HELP gauge help +# TYPE gauge gauge +# HELP histogram help +# TYPE histogram histogram +# HELP summary help +# TYPE summary summary +# EOF +" +`; + +exports[`$tag register should output all initialized metrics at value 0 1`] = ` "# HELP counter help # TYPE counter counter counter 0 @@ -54,3 +67,41 @@ summary_sum 0 summary_count 0 " `; + +exports[`$tag register should output all initialized metrics at value 0 2`] = ` +"# HELP counter help +# TYPE counter counter +counter_total 0 +# HELP gauge help +# TYPE gauge gauge +gauge 0 +# HELP histogram help +# TYPE histogram histogram +histogram_bucket{le=\\"0.005\\"} 0 +histogram_bucket{le=\\"0.01\\"} 0 +histogram_bucket{le=\\"0.025\\"} 0 +histogram_bucket{le=\\"0.05\\"} 0 +histogram_bucket{le=\\"0.1\\"} 0 +histogram_bucket{le=\\"0.25\\"} 0 +histogram_bucket{le=\\"0.5\\"} 0 +histogram_bucket{le=\\"1\\"} 0 +histogram_bucket{le=\\"2.5\\"} 0 +histogram_bucket{le=\\"5\\"} 0 +histogram_bucket{le=\\"10\\"} 0 +histogram_bucket{le=\\"+Inf\\"} 0 +histogram_sum 0 +histogram_count 0 +# HELP summary help +# TYPE summary summary +summary{quantile=\\"0.01\\"} 0 +summary{quantile=\\"0.05\\"} 0 +summary{quantile=\\"0.5\\"} 0 +summary{quantile=\\"0.9\\"} 0 +summary{quantile=\\"0.95\\"} 0 +summary{quantile=\\"0.99\\"} 0 +summary{quantile=\\"0.999\\"} 0 +summary_sum 0 +summary_count 0 +# EOF +" +`; diff --git a/test/__snapshots__/summaryTest.js.snap b/test/__snapshots__/summaryTest.js.snap index f9f18ce0..d64fc320 100644 --- a/test/__snapshots__/summaryTest.js.snap +++ b/test/__snapshots__/summaryTest.js.snap @@ -1,7 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`summary global registry with param as object labels should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; +exports[`summary with $tag registry global registry with param as object labels should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; -exports[`summary global registry with param as object remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; +exports[`summary with $tag registry global registry with param as object labels should throw error if label lengths does not match 2`] = `"Invalid number of arguments"`; -exports[`summary global registry with param as object should validate labels when observing 1`] = `"Added label \\"baz\\" is not included in initial labelset: [ 'foo' ]"`; +exports[`summary with $tag registry global registry with param as object remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; + +exports[`summary with $tag registry global registry with param as object remove should throw error if label lengths does not match 2`] = `"Invalid number of arguments"`; + +exports[`summary with $tag registry global registry with param as object should validate labels when observing 1`] = `"Added label \\"baz\\" is not included in initial labelset: [ 'foo' ]"`; + +exports[`summary with $tag registry global registry with param as object should validate labels when observing 2`] = `"Added label \\"baz\\" is not included in initial labelset: [ 'foo' ]"`; diff --git a/test/clusterTest.js b/test/clusterTest.js index 8569ea99..d79dffa7 100644 --- a/test/clusterTest.js +++ b/test/clusterTest.js @@ -2,8 +2,16 @@ const cluster = require('cluster'); const process = require('process'); +const Registry = require('../lib/cluster'); + +describe.each([ + { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, + { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, +])('$tag AggregatorRegistry', ({ tag, regType }) => { + beforeEach(() => { + Registry.globalRegistry.setContentType(regType); + }); -describe('AggregatorRegistry', () => { it('requiring the cluster should not add any listeners on the cluster module', () => { const originalListenerCount = cluster.listenerCount('message'); @@ -35,14 +43,13 @@ describe('AggregatorRegistry', () => { describe('aggregatorRegistry.clusterMetrics()', () => { it('works properly if there are no cluster workers', async () => { const AggregatorRegistry = require('../lib/cluster'); - const ar = new AggregatorRegistry(); + const ar = new AggregatorRegistry(regType); const metrics = await ar.clusterMetrics(); expect(metrics).toEqual(''); }); }); describe('AggregatorRegistry.aggregate()', () => { - const Registry = require('../lib/cluster'); // These mimic the output of `getMetricsAsJSON`. const metricsArr1 = [ { @@ -159,7 +166,7 @@ describe('AggregatorRegistry', () => { }, ]; - const aggregated = Registry.aggregate([metricsArr1, metricsArr2]); + const aggregated = Registry.aggregate([metricsArr1, metricsArr2], regType); it('defaults to summation, preserves histogram bins', async () => { const histogram = aggregated.getSingleMetric('test_histogram').get(); diff --git a/test/counterTest.js b/test/counterTest.js index ff16e512..c9355553 100644 --- a/test/counterTest.js +++ b/test/counterTest.js @@ -1,11 +1,19 @@ 'use strict'; -describe('counter', () => { +const Registry = require('../index').Registry; + +describe.each([ + { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, + { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, +])('counter with $tag registry', ({ tag, regType }) => { const Counter = require('../index').Counter; - const Registry = require('../index').Registry; const globalRegistry = require('../index').register; let instance; + beforeEach(() => { + globalRegistry.setContentType(regType); + }); + describe('with params as object', () => { beforeEach(() => { instance = new Counter({ name: 'gauge_test', help: 'test' }); @@ -168,7 +176,7 @@ describe('counter', () => { describe('registry instance', () => { let registryInstance; beforeEach(() => { - registryInstance = new Registry(); + registryInstance = new Registry(regType); instance = new Counter({ name: 'gauge_test', help: 'test', diff --git a/test/defaultMetricsTest.js b/test/defaultMetricsTest.js index 15fe7095..2a90a5a1 100644 --- a/test/defaultMetricsTest.js +++ b/test/defaultMetricsTest.js @@ -1,8 +1,12 @@ 'use strict'; -describe('collectDefaultMetrics', () => { +const Registry = require('../index').Registry; + +describe.each([ + { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, + { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, +])('collectDefaultMetrics with $tag registry', ({ tag, regType }) => { const register = require('../index').register; - const Registry = require('../index').Registry; const collectDefaultMetrics = require('../index').collectDefaultMetrics; let cpuUsage; @@ -34,6 +38,10 @@ describe('collectDefaultMetrics', () => { } }); + beforeEach(() => { + register.setContentType(regType); + }); + afterEach(() => { register.clear(); }); @@ -95,7 +103,7 @@ describe('collectDefaultMetrics', () => { describe('custom registry', () => { it('should allow to register metrics to custom registry', async () => { - const registry = new Registry(); + const registry = new Registry(regType); expect(await register.getMetricsAsJSON()).toHaveLength(0); expect(await registry.getMetricsAsJSON()).toHaveLength(0); diff --git a/test/exemplarsTest.js b/test/exemplarsTest.js new file mode 100644 index 00000000..bb1c8cb0 --- /dev/null +++ b/test/exemplarsTest.js @@ -0,0 +1,108 @@ +'use strict'; + +const Registry = require('../index').Registry; + +describe.each([ + { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, +])('exemplars with $tag registry', ({ tag, regType }) => { + // create counter with exemplars + // create histogram with exemplars + // check if exemplar is populated by openTElemetry <- this should be tested in the metrics directory + // + const globalRegistry = require('../index').register; + const Counter = require('../index').Counter; + const Histogram = require('../index').Histogram; + + beforeEach(() => { + globalRegistry.setContentType(regType); + }); + + it('should make counter with exemplar', async () => { + const counterInstance = new Counter({ + name: 'counter_exemplar_test', + help: 'help', + labelNames: ['method', 'code'], + enableExemplars: true, + }); + counterInstance.inc({ + value: 2, + labels: { method: 'get', code: '200' }, + exemplarLabels: { traceId: 'trace_id_test', spanId: 'span_id_test' }, + }); + const vals = await counterInstance.get(); + expect(vals.values[0].value).toEqual(2); + expect(vals.values[0].exemplar.value).toEqual(2); + expect(vals.values[0].exemplar.labelSet.traceId).toEqual('trace_id_test'); + }); + + it('should make histogram with exemplars on multiple buckets', async () => { + const histogramInstance = new Histogram({ + name: 'histogram_exemplar_test', + help: 'test', + labelNames: ['method', 'code'], + enableExemplars: true, + }); + + histogramInstance.observe({ + value: 0.007, + labels: { method: 'get', code: '200' }, + exemplarLabels: { traceId: 'trace_id_test_1', spanId: 'span_id_test_1' }, + }); + histogramInstance.observe({ + value: 0.4, + labels: { method: 'get', code: '200' }, + exemplarLabels: { traceId: 'trace_id_test_2', spanId: 'span_id_test_2' }, + }); + histogramInstance.observe({ + value: 11, + labels: { method: 'get', code: '200' }, + exemplarLabels: { traceId: 'trace_id_test_3', spanId: 'span_id_test_3' }, + }); + + const vals = (await histogramInstance.get()).values; + + expect(getValuesByLabel(0.005, vals)[0].value).toEqual(0); + expect(getValuesByLabel(0.005, vals)[0].exemplar).toEqual(null); + + expect(getValuesByLabel(0.5, vals)[0].value).toEqual(2); + expect(getValuesByLabel(0.5, vals)[0].exemplar.labelSet.traceId).toEqual( + 'trace_id_test_2', + ); + expect(getValuesByLabel(0.5, vals)[0].exemplar.value).toEqual(0.4); + + expect(getValuesByLabel(10, vals)[0].value).toEqual(2); + expect(getValuesByLabel(10, vals)[0].exemplar).toEqual(null); + + expect(getValuesByLabel('+Inf', vals)[0].value).toEqual(3); + expect(getValuesByLabel('+Inf', vals)[0].exemplar.labelSet.traceId).toEqual( + 'trace_id_test_3', + ); + expect(getValuesByLabel('+Inf', vals)[0].exemplar.value).toEqual(11); + }); + + it('should throw error if exemplar is too long', async () => { + const histogramInstance = new Histogram({ + name: 'histogram_too_long_exemplar_test', + help: 'test', + labelNames: ['method', 'code'], + enableExemplars: true, + }); + + expect(() => { + histogramInstance.observe({ + value: 0.007, + labels: { method: 'get', code: '200' }, + exemplarLabels: { traceId: 'j'.repeat(100), spanId: 'j'.repeat(100) }, + }); + }).toThrowError('Label set size must be smaller than 128 UTF-8 chars'); + }); + + function getValuesByLabel(label, values, key) { + return values.reduce((acc, val) => { + if (val.labels && val.labels[key || 'le'] === label) { + acc.push(val); + } + return acc; + }, []); + } +}); diff --git a/test/gaugeTest.js b/test/gaugeTest.js index 3657b950..df3f92e8 100644 --- a/test/gaugeTest.js +++ b/test/gaugeTest.js @@ -1,11 +1,19 @@ 'use strict'; -describe('gauge', () => { +const Registry = require('../index').Registry; + +describe.each([ + { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, + { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, +])('gauge with $tag registry', ({ tag, regType }) => { const Gauge = require('../index').Gauge; - const Registry = require('../index').Registry; const globalRegistry = require('../index').register; let instance; + beforeEach(() => { + globalRegistry.setContentType(regType); + }); + describe('global registry', () => { afterEach(() => { globalRegistry.clear(); @@ -208,7 +216,7 @@ describe('gauge', () => { describe('registry instance', () => { let registryInstance; beforeEach(() => { - registryInstance = new Registry(); + registryInstance = new Registry(regType); instance = new Gauge({ name: 'gauge_test', help: 'help', diff --git a/test/histogramTest.js b/test/histogramTest.js index 06978ad5..026a0610 100644 --- a/test/histogramTest.js +++ b/test/histogramTest.js @@ -1,11 +1,19 @@ 'use strict'; -describe('histogram', () => { +const Registry = require('../index').Registry; + +describe.each([ + { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, + { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, +])('histogram with $tag registry', ({ tag, regType }) => { const Histogram = require('../index').Histogram; - const Registry = require('../index').Registry; const globalRegistry = require('../index').register; let instance; + beforeEach(() => { + globalRegistry.setContentType(regType); + }); + afterEach(() => { instance = null; globalRegistry.clear(); @@ -435,7 +443,7 @@ describe('histogram', () => { describe('registry instance', () => { let registryInstance; beforeEach(() => { - registryInstance = new Registry(); + registryInstance = new Registry(regType); instance = new Histogram({ name: 'test_histogram', help: 'test', diff --git a/test/metrics/eventLoopLagTest.js b/test/metrics/eventLoopLagTest.js index ae20e570..ec882426 100644 --- a/test/metrics/eventLoopLagTest.js +++ b/test/metrics/eventLoopLagTest.js @@ -1,6 +1,11 @@ 'use strict'; -describe('eventLoopLag', () => { +const Registry = require('../../index').Registry; + +describe.each([ + { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, + { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, +])('eventLoopLag with $tag registry', ({ tag, regType }) => { const register = require('../../index').register; const eventLoopLag = require('../../lib/metrics/eventLoopLag'); @@ -8,11 +13,15 @@ describe('eventLoopLag', () => { register.clear(); }); + beforeEach(() => { + register.setContentType(regType); + }); + afterEach(() => { register.clear(); }); - it('should add metric to the registry', async done => { + it('should add metric to the $tag registry', async done => { expect(await register.getMetricsAsJSON()).toHaveLength(0); eventLoopLag(); diff --git a/test/metrics/gcTest.js b/test/metrics/gcTest.js index e9bbee8f..f816a08f 100644 --- a/test/metrics/gcTest.js +++ b/test/metrics/gcTest.js @@ -1,6 +1,11 @@ 'use strict'; -describe('gc', () => { +const Registry = require('../../index').Registry; + +describe.each([ + { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, + { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, +])('gc with $tag registry', ({ tag, regType }) => { const register = require('../../index').register; const processHandles = require('../../lib/metrics/gc'); @@ -8,11 +13,15 @@ describe('gc', () => { register.clear(); }); + beforeEach(() => { + register.setContentType(regType); + }); + afterEach(() => { register.clear(); }); - it('should add metric to the registry', async () => { + it('should add metric to the $tag registry', async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); processHandles(); diff --git a/test/metrics/heapSizeAndUsedTest.js b/test/metrics/heapSizeAndUsedTest.js index 6ac37bdc..d981edf2 100644 --- a/test/metrics/heapSizeAndUsedTest.js +++ b/test/metrics/heapSizeAndUsedTest.js @@ -1,10 +1,19 @@ 'use strict'; -describe('heapSizeAndUsed', () => { +const Registry = require('../../index').Registry; + +describe.each([ + { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, + { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, +])('heapSizeAndUsed with $tag registry', ({ tag, regType }) => { const heapSizeAndUsed = require('../../lib/metrics/heapSizeAndUsed'); const globalRegistry = require('../../lib/registry').globalRegistry; const memoryUsedFn = process.memoryUsage; + beforeEach(() => { + globalRegistry.setContentType(regType); + }); + afterEach(() => { process.memoryUsage = memoryUsedFn; globalRegistry.clear(); diff --git a/test/metrics/heapSpacesSizeAndUsedTest.js b/test/metrics/heapSpacesSizeAndUsedTest.js index be984563..71bedff8 100644 --- a/test/metrics/heapSpacesSizeAndUsedTest.js +++ b/test/metrics/heapSpacesSizeAndUsedTest.js @@ -1,5 +1,7 @@ 'use strict'; +const Registry = require('../../index').Registry; + jest.mock('v8', () => { return { getHeapSpaceStatistics() { @@ -44,19 +46,23 @@ jest.mock('v8', () => { }; }); -describe('heapSpacesSizeAndUsed', () => { +describe.each([ + { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, + { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, +])('heapSpacesSizeAndUsed with $tag registry', ({ tag, regType }) => { let heapSpacesSizeAndUsed; const globalRegistry = require('../../lib/registry').globalRegistry; beforeEach(() => { heapSpacesSizeAndUsed = require('../../lib/metrics/heapSpacesSizeAndUsed'); + globalRegistry.setContentType(regType); }); afterEach(() => { globalRegistry.clear(); }); - it('should set total heap spaces size gauges with values from v8', async () => { + it('should set total heap spaces size gauges with values from v8 with $tag registry', async () => { expect(await globalRegistry.getMetricsAsJSON()).toHaveLength(0); heapSpacesSizeAndUsed(); diff --git a/test/metrics/maxFileDescriptorsTest.js b/test/metrics/maxFileDescriptorsTest.js index f89aa45b..7a6301b3 100644 --- a/test/metrics/maxFileDescriptorsTest.js +++ b/test/metrics/maxFileDescriptorsTest.js @@ -1,8 +1,12 @@ 'use strict'; const exec = require('child_process').execSync; +const Registry = require('../../index').Registry; -describe('processMaxFileDescriptors', () => { +describe.each([ + { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, + { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, +])('processMaxFileDescriptors with $tag registry', ({ tag, regType }) => { const register = require('../../index').register; const processMaxFileDescriptors = require('../../lib/metrics/processMaxFileDescriptors'); @@ -10,12 +14,16 @@ describe('processMaxFileDescriptors', () => { register.clear(); }); + beforeEach(() => { + register.setContentType(regType); + }); + afterEach(() => { register.clear(); }); if (process.platform !== 'linux') { - it('should not add metric to the registry', async () => { + it('should not add metric to the $tag registry', async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); processMaxFileDescriptors(); @@ -23,7 +31,7 @@ describe('processMaxFileDescriptors', () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); }); } else { - it('should add metric to the registry', async () => { + it('should add metric to the $tag registry', async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); processMaxFileDescriptors(); @@ -39,7 +47,7 @@ describe('processMaxFileDescriptors', () => { expect(metrics[0].values).toHaveLength(1); }); - it('should have a reasonable metric value', async () => { + it('should have a reasonable metric value with $tag registry', async () => { const maxFiles = Number(exec('ulimit -Hn', { encoding: 'utf8' })); expect(await register.getMetricsAsJSON()).toHaveLength(0); diff --git a/test/metrics/processHandlesTest.js b/test/metrics/processHandlesTest.js index 614b34d8..eb7cf201 100644 --- a/test/metrics/processHandlesTest.js +++ b/test/metrics/processHandlesTest.js @@ -1,6 +1,11 @@ 'use strict'; -describe('processHandles', () => { +const Registry = require('../../index').Registry; + +describe.each([ + { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, + { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, +])('processHandles with $tag registry', ({ tag, regType }) => { const register = require('../../index').register; const processHandles = require('../../lib/metrics/processHandles'); @@ -8,11 +13,15 @@ describe('processHandles', () => { register.clear(); }); + beforeEach(() => { + register.setContentType(regType); + }); + afterEach(() => { register.clear(); }); - it('should add metric to the registry', async () => { + it('should add metric to the $tag registry', async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); processHandles(); diff --git a/test/metrics/processOpenFileDescriptorsTest.js b/test/metrics/processOpenFileDescriptorsTest.js index 1309d555..fc6d4724 100644 --- a/test/metrics/processOpenFileDescriptorsTest.js +++ b/test/metrics/processOpenFileDescriptorsTest.js @@ -1,24 +1,32 @@ 'use strict'; -describe('processOpenFileDescriptors', () => { +const Registry = require('../../index').Registry; + +jest.mock( + 'process', + () => Object.assign({}, jest.requireActual('process'), { platform: 'linux' }), // This metric only works on Linux +); + +describe.each([ + { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, + { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, +])('processOpenFileDescriptors with $tag registry', ({ tag, regType }) => { const register = require('../../index').register; const processOpenFileDescriptors = require('../../lib/metrics/processOpenFileDescriptors'); - jest.mock( - 'process', - () => - Object.assign({}, jest.requireActual('process'), { platform: 'linux' }), // This metric only works on Linux - ); - beforeAll(() => { register.clear(); }); + beforeEach(() => { + register.setContentType(regType); + }); + afterEach(() => { register.clear(); }); - it('should add metric to the registry', async () => { + it('should add metric to the $tag registry', async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); processOpenFileDescriptors(); diff --git a/test/metrics/processRequestsTest.js b/test/metrics/processRequestsTest.js index 7e9afe0e..f2344f05 100644 --- a/test/metrics/processRequestsTest.js +++ b/test/metrics/processRequestsTest.js @@ -1,6 +1,11 @@ 'use strict'; -describe('processRequests', () => { +const Registry = require('../../index').Registry; + +describe.each([ + { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, + { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, +])('processRequests with $tag registry', ({ tag, regType }) => { const register = require('../../index').register; const processRequests = require('../../lib/metrics/processRequests'); @@ -8,11 +13,15 @@ describe('processRequests', () => { register.clear(); }); + beforeEach(() => { + register.setContentType(regType); + }); + afterEach(() => { register.clear(); }); - it('should add metric to the registry', async () => { + it('should add metric to the $tag registry', async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); processRequests(); diff --git a/test/metrics/processStartTimeTest.js b/test/metrics/processStartTimeTest.js index e51fa887..2066c494 100644 --- a/test/metrics/processStartTimeTest.js +++ b/test/metrics/processStartTimeTest.js @@ -1,6 +1,11 @@ 'use strict'; -describe('processStartTime', () => { +const Registry = require('../../index').Registry; + +describe.each([ + { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, + { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, +])('processStartTime with $tag registry', ({ tag, regType }) => { const register = require('../../index').register; const op = require('../../lib/metrics/processStartTime'); @@ -8,11 +13,15 @@ describe('processStartTime', () => { register.clear(); }); + beforeEach(() => { + register.setContentType(regType); + }); + afterEach(() => { register.clear(); }); - it('should add metric to the registry', async () => { + it('should add metric to the $tag registry', async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); op(); diff --git a/test/metrics/versionTest.js b/test/metrics/versionTest.js index 42abcc03..3b31daac 100644 --- a/test/metrics/versionTest.js +++ b/test/metrics/versionTest.js @@ -1,5 +1,6 @@ 'use strict'; +const Registry = require('../../index').Registry; const nodeVersion = process.version; const versionSegments = nodeVersion.slice(1).split('.').map(Number); @@ -15,7 +16,10 @@ function expectVersionMetrics(metrics) { expect(metrics[0].values[0].labels.patch).toEqual(versionSegments[2]); } -describe('version', () => { +describe.each([ + { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, + { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, +])('version with $tag registry', ({ tag, regType }) => { const register = require('../../index').register; const version = require('../../lib/metrics/version'); @@ -23,11 +27,15 @@ describe('version', () => { register.clear(); }); + beforeEach(() => { + register.setContentType(regType); + }); + afterEach(() => { register.clear(); }); - it('should add metric to the registry', async () => { + it('should add metric to the $tag registry', async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); expect(typeof versionSegments[0]).toEqual('number'); expect(typeof versionSegments[1]).toEqual('number'); @@ -39,7 +47,7 @@ describe('version', () => { expectVersionMetrics(metrics); }); - it('should still be present after resetting the registry #238', async () => { + it('should still be present after resetting the $tag registry #238', async () => { const collector = version(); expectVersionMetrics(await register.getMetricsAsJSON()); register.resetMetrics(); diff --git a/test/pushgatewayTest.js b/test/pushgatewayTest.js index 6e84f8a2..5f80a3af 100644 --- a/test/pushgatewayTest.js +++ b/test/pushgatewayTest.js @@ -3,13 +3,21 @@ const nock = require('nock'); const { gzipSync } = require('zlib'); -describe('pushgateway', () => { +const Registry = require('../index').Registry; + +describe.each([ + { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, + // { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, +])('pushgateway with $tag registry', ({ tag, regType }) => { const Pushgateway = require('../index').Pushgateway; const register = require('../index').register; - const Registry = require('../index').Registry; let instance; let registry = undefined; + beforeEach(() => { + register.setContentType(regType); + }); + const tests = function () { describe('pushAdd', () => { it('should push metrics', () => { @@ -229,7 +237,7 @@ describe('pushgateway', () => { }); beforeEach(() => { - registry = new Registry(); + registry = new Registry(regType); instance = new Pushgateway('http://192.168.99.100:9091', null, registry); const promeClient = require('../index'); const cnt = new promeClient.Counter({ diff --git a/test/pushgatewayWithPathTest.js b/test/pushgatewayWithPathTest.js index 36a9df73..1385c243 100644 --- a/test/pushgatewayWithPathTest.js +++ b/test/pushgatewayWithPathTest.js @@ -16,13 +16,21 @@ jest.mock('http', () => { }; }); -describe('pushgateway with path', () => { +const Registry = require('../index').Registry; + +describe.each([ + { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, + { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, +])('pushgateway with path and $tag registry', ({ tag, regType }) => { const Pushgateway = require('../index').Pushgateway; const register = require('../index').register; - const Registry = require('../index').Registry; let instance; let registry = undefined; + beforeEach(() => { + register.setContentType(regType); + }); + const tests = function () { describe('pushAdd', () => { it('should push metrics', () => { @@ -164,7 +172,7 @@ describe('pushgateway with path', () => { mockHttp.mockClear(); }); beforeEach(() => { - registry = new Registry(); + registry = new Registry(regType); instance = new Pushgateway(pushGatewayFullURL, null, registry); const promClient = require('../index'); const cnt = new promClient.Counter({ diff --git a/test/registerTest.js b/test/registerTest.js index 81d38c00..9bc7263e 100644 --- a/test/registerTest.js +++ b/test/registerTest.js @@ -1,6 +1,11 @@ 'use strict'; -describe('register', () => { +const Registry = require('../index').Registry; + +describe.each([ + { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, + { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, +])('$tag register', ({ tag, regType }) => { const register = require('../index').register; const Counter = require('../index').Counter; const Gauge = require('../index').Gauge; @@ -8,6 +13,7 @@ describe('register', () => { const Summary = require('../index').Summary; beforeEach(() => { + register.setContentType(regType); register.clear(); }); @@ -25,10 +31,22 @@ describe('register', () => { expect(output[1]).toEqual('# TYPE test_metric counter'); }); it('with first value of the metric as third item', () => { - expect(output[2]).toEqual('test_metric{label="hello",code="303"} 12'); + if (register.contentType === Registry.OPENMETRICS_CONTENT_TYPE) { + expect(output[2]).toEqual( + 'test_metric_total{label="hello",code="303"} 12', + ); + } else { + expect(output[2]).toEqual('test_metric{label="hello",code="303"} 12'); + } }); it('with second value of the metric as fourth item', () => { - expect(output[3]).toEqual('test_metric{label="bye",code="404"} 34'); + if (register.contentType === Registry.OPENMETRICS_CONTENT_TYPE) { + expect(output[3]).toEqual( + 'test_metric_total{label="bye",code="404"} 34', + ); + } else { + expect(output[3]).toEqual('test_metric{label="bye",code="404"} 34'); + } }); }); @@ -58,7 +76,11 @@ describe('register', () => { }, }); const lines = (await register.metrics()).split('\n'); - expect(lines).toHaveLength(4); + if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { + expect(lines).toHaveLength(5); + } else { + expect(lines).toHaveLength(4); + } expect(lines[2]).toEqual('test_metric Nan'); }); @@ -78,7 +100,11 @@ describe('register', () => { }, }); const lines = (await register.metrics()).split('\n'); - expect(lines).toHaveLength(4); + if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { + expect(lines).toHaveLength(5); + } else { + expect(lines).toHaveLength(4); + } expect(lines[2]).toEqual('test_metric +Inf'); }); @@ -98,7 +124,11 @@ describe('register', () => { }, }); const lines = (await register.metrics()).split('\n'); - expect(lines).toHaveLength(4); + if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { + expect(lines).toHaveLength(5); + } else { + expect(lines).toHaveLength(4); + } expect(lines[2]).toEqual('test_metric -Inf'); }); @@ -117,7 +147,11 @@ describe('register', () => { }; }, }); - expect((await register.metrics()).split('\n')).toHaveLength(4); + if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { + expect((await register.metrics()).split('\n')).toHaveLength(5); + } else { + expect((await register.metrics()).split('\n')).toHaveLength(4); + } }); it('should handle a metric with default labels', async () => { @@ -134,7 +168,11 @@ describe('register', () => { }); const output = (await register.metrics()).split('\n')[2]; - expect(output).toEqual('test_metric{testLabel="testValue"} 1'); + if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { + expect(output).toEqual('test_metric_total{testLabel="testValue"} 1'); + } else { + expect(output).toEqual('test_metric{testLabel="testValue"} 1'); + } }); it('labeled metrics should take precidence over defaulted', async () => { @@ -158,9 +196,15 @@ describe('register', () => { }, }); - expect((await register.metrics()).split('\n')[2]).toEqual( - 'test_metric{testLabel="overlapped",anotherLabel="value123"} 1', - ); + if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { + expect((await register.metrics()).split('\n')[2]).toEqual( + 'test_metric_total{testLabel="overlapped",anotherLabel="value123"} 1', + ); + } else { + expect((await register.metrics()).split('\n')[2]).toEqual( + 'test_metric{testLabel="overlapped",anotherLabel="value123"} 1', + ); + } }); it('should output all initialized metrics at value 0', async () => { @@ -281,14 +325,20 @@ describe('register', () => { expect(output).toEqual(metric); }); - it('should allow gettings metrics', async () => { + it('should allow getting metrics', async () => { const metric = getMetric(); register.registerMetric(metric); const metrics = await register.metrics(); - expect(metrics.split('\n')[3]).toEqual( - 'test_metric{label="bye",code="404"} 34', - ); + if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { + expect(metrics.split('\n')[3]).toEqual( + 'test_metric_total{label="bye",code="404"} 34', + ); + } else { + expect(metrics.split('\n')[3]).toEqual( + 'test_metric{label="bye",code="404"} 34', + ); + } }); describe('resetting', () => { @@ -344,7 +394,7 @@ describe('register', () => { describe('mutation tests', () => { describe('registry.metrics()', () => { it('should not throw with default labels (counter)', async () => { - const r = new Registry(); + const r = new Registry(regType); r.setDefaultLabels({ env: 'development', }); @@ -362,21 +412,33 @@ describe('register', () => { const metrics = await r.metrics(); const lines = metrics.split('\n'); - expect(lines).toContain( - 'my_counter{type="myType",env="development"} 1', - ); + if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { + expect(lines).toContain( + 'my_counter_total{type="myType",env="development"} 1', + ); + } else { + expect(lines).toContain( + 'my_counter{type="myType",env="development"} 1', + ); + } myCounter.inc(); const metrics2 = await r.metrics(); const lines2 = metrics2.split('\n'); - expect(lines2).toContain( - 'my_counter{type="myType",env="development"} 2', - ); + if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { + expect(lines2).toContain( + 'my_counter_total{type="myType",env="development"} 2', + ); + } else { + expect(lines2).toContain( + 'my_counter{type="myType",env="development"} 2', + ); + } }); it('should not throw with default labels (gauge)', async () => { - const r = new Registry(); + const r = new Registry(regType); r.setDefaultLabels({ env: 'development', }); @@ -408,7 +470,7 @@ describe('register', () => { }); it('should not throw with default labels (histogram)', async () => { - const r = new Registry(); + const r = new Registry(regType); r.setDefaultLabels({ env: 'development', }); @@ -442,7 +504,7 @@ describe('register', () => { describe('registry.getMetricsAsJSON()', () => { it('should not throw with default labels (counter)', async () => { - const r = new Registry(); + const r = new Registry(regType); r.setDefaultLabels({ env: 'development', }); @@ -490,7 +552,7 @@ describe('register', () => { }); it('should not throw with default labels (gauge)', async () => { - const r = new Registry(); + const r = new Registry(regType); r.setDefaultLabels({ env: 'development', }); @@ -538,7 +600,7 @@ describe('register', () => { }); it('should not throw with default labels (histogram)', async () => { - const r = new Registry(); + const r = new Registry(regType); r.setDefaultLabels({ env: 'development', }); @@ -555,8 +617,9 @@ describe('register', () => { myHist.observe(1); const metrics = await r.getMetricsAsJSON(); - // NOTE: at this test we don't need to check exacte JSON schema + // NOTE: at this test we don't need to check exact JSON schema expect(metrics[0].values).toContainEqual({ + exemplar: null, labels: { env: 'development', le: 1, type: 'myType' }, metricName: 'my_histogram_bucket', value: 1, @@ -565,8 +628,9 @@ describe('register', () => { myHist.observe(1); const metrics2 = await r.getMetricsAsJSON(); - // NOTE: at this test we don't need to check exacte JSON schema + // NOTE: at this test we don't need to check exact JSON schema expect(metrics2[0].values).toContainEqual({ + exemplar: null, labels: { env: 'development', le: 1, type: 'myType' }, metricName: 'my_histogram_bucket', value: 2, @@ -582,8 +646,8 @@ describe('register', () => { let registryTwo; beforeEach(() => { - registryOne = new Registry(); - registryTwo = new Registry(); + registryOne = new Registry(regType); + registryTwo = new Registry(regType); }); it('should merge all provided registers', async () => { @@ -609,11 +673,6 @@ describe('register', () => { }); }); - it('should have the same contentType as the module', () => { - const moduleWideContentType = require('../').contentType; - expect(register.contentType).toEqual(moduleWideContentType); - }); - function getMetric(name) { name = name || 'test_metric'; return { diff --git a/test/summaryTest.js b/test/summaryTest.js index 10ad4b0a..3c3f8bf3 100644 --- a/test/summaryTest.js +++ b/test/summaryTest.js @@ -1,11 +1,23 @@ 'use strict'; -describe('summary', () => { +const Registry = require('../index').Registry; + +describe.each([ + { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, + { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, +])('summary with $tag registry', ({ tag, regType }) => { const Summary = require('../index').Summary; - const Registry = require('../index').Registry; const globalRegistry = require('../index').register; let instance; + beforeEach(() => { + globalRegistry.setContentType(regType); + }); + + afterEach(() => { + globalRegistry.clear(); + }); + describe('global registry', () => { afterEach(() => { globalRegistry.clear(); @@ -464,7 +476,7 @@ describe('summary', () => { describe('registry instance', () => { let registryInstance; beforeEach(() => { - registryInstance = new Registry(); + registryInstance = new Registry(regType); instance = new Summary({ name: 'summary_test', help: 'test', From 36e3877d492134ca0a52858f43a98fb71f4e174e Mon Sep 17 00:00:00 2001 From: Andrei Dobre Date: Thu, 12 May 2022 12:26:55 +0200 Subject: [PATCH 2/6] Enforce stronger typing on registries --- README.md | 2 +- example/exemplars.js | 17 +- example/exemplars.ts | 66 + index.d.ts | 60 +- index.js | 6 +- lib/cluster.js | 4 +- lib/counter.js | 12 - lib/histogram.js | 12 - lib/metric.js | 14 +- lib/registry.js | 32 +- test/__snapshots__/counterTest.js.snap | 16 +- test/__snapshots__/gaugeTest.js.snap | 4 +- test/__snapshots__/histogramTest.js.snap | 16 +- test/__snapshots__/registerTest.js.snap | 48 +- test/__snapshots__/summaryTest.js.snap | 12 +- test/clusterTest.js | 6 +- test/counterTest.js | 6 +- test/defaultMetricsTest.js | 6 +- test/exemplarsTest.js | 201 +-- test/gaugeTest.js | 6 +- test/histogramTest.js | 6 +- test/metrics/eventLoopLagTest.js | 8 +- test/metrics/gcTest.js | 8 +- test/metrics/heapSizeAndUsedTest.js | 6 +- test/metrics/heapSpacesSizeAndUsedTest.js | 8 +- test/metrics/maxFileDescriptorsTest.js | 12 +- test/metrics/processHandlesTest.js | 8 +- .../metrics/processOpenFileDescriptorsTest.js | 8 +- test/metrics/processRequestsTest.js | 8 +- test/metrics/processStartTimeTest.js | 8 +- test/metrics/versionTest.js | 10 +- test/pushgatewayTest.js | 53 +- test/pushgatewayWithPathTest.js | 6 +- test/registerTest.js | 1221 +++++++++-------- test/summaryTest.js | 6 +- 35 files changed, 1037 insertions(+), 885 deletions(-) create mode 100644 example/exemplars.ts diff --git a/README.md b/README.md index c3647a09..4b909e43 100644 --- a/README.md +++ b/README.md @@ -402,7 +402,7 @@ enabled. They get a single object with the format `{labels, value, exemplarLabels}`. When using exemplars, the registry used for metrics should be set to OpenMetrics -type (including the global or default registry). +type (including the global or default registry if no registries are specified). ### Registy type diff --git a/example/exemplars.js b/example/exemplars.js index 1d59e8aa..29f779e7 100644 --- a/example/exemplars.js +++ b/example/exemplars.js @@ -62,13 +62,28 @@ async function makeHistograms() { } async function main() { - // should only use exemplars with OpenMetrics registry types + // exemplars will be shown only by OpenMetrics registry types register.setContentType(Registry.OPENMETRICS_CONTENT_TYPE); makeCounters(); makeHistograms(); console.log(await register.metrics()); + console.log('---'); + + // if you dont want to set the default registry to OpenMetrics type then you need to create a new registry and assign it to the metric + + register.setContentType(Registry.PROMETHEUS_CONTENT_TYPE); + const omReg = new Registry(Registry.OPENMETRICS_CONTENT_TYPE); + const c = new Counter({ + name: 'counter_with_exemplar', + help: 'Example of a counter', + labelNames: ['code'], + registers: [omReg], + enableExemplars: true, + }); + c.inc({ labels: { code: '200' }, exemplarLabels: { traceId: 'traceA' } }); + console.log(await omReg.metrics()); } main(); diff --git a/example/exemplars.ts b/example/exemplars.ts new file mode 100644 index 00000000..6301d122 --- /dev/null +++ b/example/exemplars.ts @@ -0,0 +1,66 @@ +import * as prom from '../index'; + +async function prometheusRegistry() { + let reg = new prom.Registry(); + + let counter = new prom.Counter({ + name: 'test_counter', + help: 'counter help message', + registers: [reg], + labelNames: ['code'], + }); + + let hist = new prom.Histogram({ + name: 'test_histogram', + help: 'histogram help message', + registers: [reg], + labelNames: ['code'], + }); + + counter.inc({ code: '300' }, 2); + hist.observe({ code: '200' }, 1); + + console.log(await reg.metrics()); +} + +async function openMetricsRegistry() { + let reg = new prom.Registry(); + reg.setContentType(prom.openMetricsContentType); + + let counter = new prom.Counter({ + name: 'test_counter', + help: 'counter help message', + registers: [reg], + labelNames: ['code'], + enableExemplars: true, + }); + + let hist = new prom.Histogram({ + name: 'test_histogram', + help: 'histogram help message', + registers: [reg], + labelNames: ['code'], + enableExemplars: true, + }); + + counter.inc(>{ + value: 2, + labels: { code: '300' }, + exemplarLabels: { traceID: 'traceA' }, + }); + + hist.observe(>{ + value: 1, + labels: { code: '200' }, + exemplarLabels: { traceID: 'traceA' }, + }); + + console.log(await reg.metrics()); +} + +async function main() { + prometheusRegistry(); + openMetricsRegistry(); +} + +main(); diff --git a/index.d.ts b/index.d.ts index 45fc4038..82dde536 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,14 +1,27 @@ // Type definitions for prom-client // Definitions by: Simon Nyberg http://twitter.com/siimon_nyberg +export type Charset = 'utf-8'; + +export type PrometheusMIME = 'text/plain'; +export type PrometheusMetricsVersion = '0.0.4'; + +export type OpenMetricsMIME = 'application/openmetrics-text'; +export type OpenMetricsVersion = '1.0.0'; + +export type PrometheusContentType = + `${OpenMetricsMIME}; version=${OpenMetricsVersion}; charset=${Charset}`; +export type OpenMetricsContentType = + `${PrometheusMIME}; version=${PrometheusMetricsVersion}; charset=${Charset}`; + +export type RegistryContentType = + | PrometheusContentType + | OpenMetricsContentType; + /** * Container for all registered metrics - * @property {string} PROMETHEUS_CONTENT_TYPE - Content-Type of Prometheus - * registry type - * @property {string} OPENMETRICS_CONTENT_TYPE - Content-Type of OpenMetrics - * registry type. */ -export class Registry { +export class Registry { /** * Get string representation for all metrics */ @@ -68,14 +81,14 @@ export class Registry { /** * Gets the Content-Type of the metrics for use in the response headers. */ - contentType: string; + contentType: RegistryContentType; /** * Set the content type of a registry. Used to change between Prometheus and * OpenMetrics versions. * @param contentType The type of the registry */ - setContentType(contentType: string): void; + setContentType(contentType: RegistryContentType): void; /** * Merge registers @@ -94,17 +107,17 @@ export const register: Registry; * HTTP Content-Type for metrics response headers, defaults to Prometheus text * format. */ -export const contentType: string; +export const contentType: RegistryContentType; /** * HTTP Prometheus Content-Type for metrics response headers. */ -export const prometheusContentType: string; +export const prometheusContentType: PrometheusContentType; /** * HTTP OpenMetrics Content-Type for metrics response headers. */ -export const openMetricsContentType: string; +export const openMetricsContentType: OpenMetricsContentType; export class AggregatorRegistry extends Registry { /** @@ -172,9 +185,10 @@ interface MetricConfiguration { name: string; help: string; labelNames?: T[] | readonly T[]; - registers?: Registry[]; + registers?: Registry[]; aggregator?: Aggregator; collect?: CollectFunction; + enableExemplars?: boolean; } export interface CounterConfiguration @@ -182,6 +196,18 @@ export interface CounterConfiguration collect?: CollectFunction>; } +export interface IncreaseDataWithExemplar { + value?: number; + labels?: LabelValues; + exemplarLabels?: LabelValues; +} + +export interface ObserveDataWithExemplar { + value: number; + labels?: LabelValues; + exemplarLabels?: LabelValues; +} + /** * A counter is a cumulative metric that represents a single numerical value that only ever goes up */ @@ -204,6 +230,12 @@ export class Counter { */ inc(value?: number): void; + /** + * Increment with exemplars + * @param incData Object with labels, value and exemplars for an increase + */ + inc(incData: IncreaseDataWithExemplar): void; + /** * Return the child for given labels * @param values Label values @@ -408,6 +440,12 @@ export class Histogram { */ observe(labels: LabelValues, value: number): void; + /** + * Observe with exemplars + * @param observeData Object with labels, value and exemplars for an observation + */ + observe(observeData: ObserveDataWithExemplar): void; + /** * Start a timer. Calling the returned function will observe the duration in * seconds in the histogram. diff --git a/index.js b/index.js index fdc39f0e..7f6e167a 100644 --- a/index.js +++ b/index.js @@ -8,8 +8,10 @@ exports.register = require('./lib/registry').globalRegistry; exports.Registry = require('./lib/registry'); exports.contentType = require('./lib/registry').globalRegistry.contentType; -exports.prometheusContentType = require('./lib/registry').PROMETHEUS_CONTENT_TYPE; -exports.openMetricsContentType = require('./lib/registry').OPENMETRICS_CONTENT_TYPE; +exports.prometheusContentType = + require('./lib/registry').PROMETHEUS_CONTENT_TYPE; +exports.openMetricsContentType = + require('./lib/registry').OPENMETRICS_CONTENT_TYPE; exports.validateMetricName = require('./lib/validation').validateMetricName; exports.Counter = require('./lib/counter'); diff --git a/lib/cluster.js b/lib/cluster.js index 04add12d..5cb707ed 100644 --- a/lib/cluster.js +++ b/lib/cluster.js @@ -106,9 +106,7 @@ class AggregatorRegistry extends Registry { const aggregatedRegistry = new Registry(); const metricsByName = new Grouper(); - if (registryType === Registry.OPENMETRICS_CONTENT_TYPE) { - aggregatedRegistry.setContentType(registryType); - } + aggregatedRegistry.setContentType(registryType); // Gather by name metricsArr.forEach(metrics => { diff --git a/lib/counter.js b/lib/counter.js index 965aecfd..4204bf5b 100644 --- a/lib/counter.js +++ b/lib/counter.js @@ -30,18 +30,6 @@ class Counter extends Metric { } } - enableExemplars(enable = false) { - if (enable) { - this.enableExemplars = true; - } - if (this.enableExemplars) { - this.inc = this.incWithExemplar; - } else { - this.inc = this.incWithoutExemplar; - } - this.reset(); - } - /** * Increment counter * @param {object} labels - What label you want to be incremented diff --git a/lib/histogram.js b/lib/histogram.js index 6bc22eed..66ba4fc8 100644 --- a/lib/histogram.js +++ b/lib/histogram.js @@ -63,18 +63,6 @@ class Histogram extends Metric { } } - enableExemplars(enable = false) { - if (enable) { - this.enableExemplars = true; - } - if (this.enableExemplars) { - this.observe = this.observeWithExemplar; - } else { - this.observe = this.observeWithoutExemplar; - } - this.reset(); - } - /** * Observe a value in histogram * @param {object} labels - Object with labels where key is the label key and value is label value. Can only be one level deep diff --git a/lib/metric.js b/lib/metric.js index 6fd5b648..2e0f4191 100644 --- a/lib/metric.js +++ b/lib/metric.js @@ -1,6 +1,6 @@ 'use strict'; -const { globalRegistry } = require('./registry'); +const Registry = require('./registry'); const { isObject } = require('./util'); const { validateMetricName, validateLabelName } = require('./validation'); @@ -16,7 +16,7 @@ class Metric { this, { labelNames: [], - registers: [globalRegistry], + registers: [Registry.globalRegistry], aggregator: 'sum', enableExemplars: false, }, @@ -25,7 +25,7 @@ class Metric { ); if (!this.registers) { // in case config.registers is `undefined` - this.registers = [globalRegistry]; + this.registers = [Registry.globalRegistry]; } if (!this.help) { throw new Error('Missing mandatory help parameter'); @@ -46,6 +46,14 @@ class Metric { this.reset(); for (const register of this.registers) { + if ( + this.enableExemplars && + register.contentType === Registry.PROMETHEUS_CONTENT_TYPE + ) { + throw new TypeError( + 'Exemplars are supported only on OpenMetrics registries', + ); + } register.registerMetric(this); } } diff --git a/lib/registry.js b/lib/registry.js index bd80ee22..19f86dfb 100644 --- a/lib/registry.js +++ b/lib/registry.js @@ -1,4 +1,5 @@ 'use strict'; + const { getValueAsString } = require('./util'); function escapeString(str) { @@ -30,6 +31,12 @@ class Registry { this._metrics = {}; this._collectors = []; this._defaultLabels = {}; + if ( + regContentType !== Registry.PROMETHEUS_CONTENT_TYPE && + regContentType !== Registry.OPENMETRICS_CONTENT_TYPE + ) { + throw new TypeError('Content type unsupported'); + } this._contentType = regContentType; } @@ -74,7 +81,10 @@ class Registry { metricName += `{${labels}}`; } values += `${metricName} ${getValueAsString(val.value)}`; - if (val.exemplar) { + if ( + val.exemplar && + this.contentType === Registry.OPENMETRICS_CONTENT_TYPE + ) { const exemplarKeys = Object.keys(val.exemplar.labelSet); const exemplarSize = exemplarKeys.length; if (exemplarSize > 0) { @@ -209,21 +219,23 @@ class Registry { } setContentType(metricsContentType) { - if (metricsContentType === Registry.OPENMETRICS_CONTENT_TYPE) { - this._contentType = Registry.OPENMETRICS_CONTENT_TYPE; + if ( + metricsContentType === Registry.OPENMETRICS_CONTENT_TYPE || + metricsContentType === Registry.PROMETHEUS_CONTENT_TYPE + ) { + this._contentType = metricsContentType; } else { - this._contentType = Registry.PROMETHEUS_CONTENT_TYPE; + throw new Error('Content type unsupported'); } } - /* Merge behaviour between registries of different types is undefined. The - * user should only provide an array or registries of the same type. */ static merge(registers) { - let regType = Registry.PROMETHEUS_CONTENT_TYPE; + const regType = registers[0].contentType; for (const reg of registers) { - if (reg.type && reg.type !== regType) { - regType = reg.type; - break; + if (reg.contentType !== regType) { + throw new Error( + 'Registers can only be merged if they have the same content type', + ); } } const mergedRegistry = new Registry(regType); diff --git a/test/__snapshots__/counterTest.js.snap b/test/__snapshots__/counterTest.js.snap index 691f46c6..1b59e42f 100644 --- a/test/__snapshots__/counterTest.js.snap +++ b/test/__snapshots__/counterTest.js.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`counter with $tag registry remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; +exports[`counter with OpenMetrics registry remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; -exports[`counter with $tag registry remove should throw error if label lengths does not match 2`] = `"Invalid number of arguments"`; +exports[`counter with OpenMetrics registry with params as object labels should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; -exports[`counter with $tag registry with params as object labels should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; +exports[`counter with OpenMetrics registry with params as object should not be possible to decrease a counter 1`] = `"It is not possible to decrease a counter"`; -exports[`counter with $tag registry with params as object labels should throw error if label lengths does not match 2`] = `"Invalid number of arguments"`; +exports[`counter with OpenMetrics registry with params as object should throw an error when the value is not a number 1`] = `"Value is not a valid number: 3ms"`; -exports[`counter with $tag registry with params as object should not be possible to decrease a counter 1`] = `"It is not possible to decrease a counter"`; +exports[`counter with Prometheus registry remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; -exports[`counter with $tag registry with params as object should not be possible to decrease a counter 2`] = `"It is not possible to decrease a counter"`; +exports[`counter with Prometheus registry with params as object labels should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; -exports[`counter with $tag registry with params as object should throw an error when the value is not a number 1`] = `"Value is not a valid number: 3ms"`; +exports[`counter with Prometheus registry with params as object should not be possible to decrease a counter 1`] = `"It is not possible to decrease a counter"`; -exports[`counter with $tag registry with params as object should throw an error when the value is not a number 2`] = `"Value is not a valid number: 3ms"`; +exports[`counter with Prometheus registry with params as object should throw an error when the value is not a number 1`] = `"Value is not a valid number: 3ms"`; diff --git a/test/__snapshots__/gaugeTest.js.snap b/test/__snapshots__/gaugeTest.js.snap index 3b4948dc..e0ebcdfd 100644 --- a/test/__snapshots__/gaugeTest.js.snap +++ b/test/__snapshots__/gaugeTest.js.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`gauge with $tag registry global registry with parameters as object should not allow non numbers 1`] = `"Value is not a valid number: asd"`; +exports[`gauge with OpenMetrics registry global registry with parameters as object should not allow non numbers 1`] = `"Value is not a valid number: asd"`; -exports[`gauge with $tag registry global registry with parameters as object should not allow non numbers 2`] = `"Value is not a valid number: asd"`; +exports[`gauge with Prometheus registry global registry with parameters as object should not allow non numbers 1`] = `"Value is not a valid number: asd"`; diff --git a/test/__snapshots__/histogramTest.js.snap b/test/__snapshots__/histogramTest.js.snap index 2620b492..075148fd 100644 --- a/test/__snapshots__/histogramTest.js.snap +++ b/test/__snapshots__/histogramTest.js.snap @@ -1,17 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`histogram with $tag registry with object as params with global registry labels should not allow different number of labels 1`] = `"Invalid number of arguments"`; +exports[`histogram with OpenMetrics registry with object as params with global registry labels should not allow different number of labels 1`] = `"Invalid number of arguments"`; -exports[`histogram with $tag registry with object as params with global registry labels should not allow different number of labels 2`] = `"Invalid number of arguments"`; +exports[`histogram with OpenMetrics registry with object as params with global registry remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; -exports[`histogram with $tag registry with object as params with global registry remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; +exports[`histogram with OpenMetrics registry with object as params with global registry should not allow le as a custom label 1`] = `"le is a reserved label keyword"`; -exports[`histogram with $tag registry with object as params with global registry remove should throw error if label lengths does not match 2`] = `"Invalid number of arguments"`; +exports[`histogram with OpenMetrics registry with object as params with global registry should not allow non numbers 1`] = `"Value is not a valid number: asd"`; -exports[`histogram with $tag registry with object as params with global registry should not allow le as a custom label 1`] = `"le is a reserved label keyword"`; +exports[`histogram with Prometheus registry with object as params with global registry labels should not allow different number of labels 1`] = `"Invalid number of arguments"`; -exports[`histogram with $tag registry with object as params with global registry should not allow le as a custom label 2`] = `"le is a reserved label keyword"`; +exports[`histogram with Prometheus registry with object as params with global registry remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; -exports[`histogram with $tag registry with object as params with global registry should not allow non numbers 1`] = `"Value is not a valid number: asd"`; +exports[`histogram with Prometheus registry with object as params with global registry should not allow le as a custom label 1`] = `"le is a reserved label keyword"`; -exports[`histogram with $tag registry with object as params with global registry should not allow non numbers 2`] = `"Value is not a valid number: asd"`; +exports[`histogram with Prometheus registry with object as params with global registry should not allow non numbers 1`] = `"Value is not a valid number: asd"`; diff --git a/test/__snapshots__/registerTest.js.snap b/test/__snapshots__/registerTest.js.snap index 6b5fce26..6088a851 100644 --- a/test/__snapshots__/registerTest.js.snap +++ b/test/__snapshots__/registerTest.js.snap @@ -1,21 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`$tag register should not output all initialized metrics at value 0 if labels 1`] = ` -"# HELP counter help -# TYPE counter counter - -# HELP gauge help -# TYPE gauge gauge - -# HELP histogram help -# TYPE histogram histogram - -# HELP summary help -# TYPE summary summary -" -`; - -exports[`$tag register should not output all initialized metrics at value 0 if labels 2`] = ` +exports[`Register with OpenMetrics type should not output all initialized metrics at value 0 if labels 1`] = ` "# HELP counter help # TYPE counter counter # HELP gauge help @@ -28,15 +13,13 @@ exports[`$tag register should not output all initialized metrics at value 0 if l " `; -exports[`$tag register should output all initialized metrics at value 0 1`] = ` +exports[`Register with OpenMetrics type should output all initialized metrics at value 0 1`] = ` "# HELP counter help # TYPE counter counter -counter 0 - +counter_total 0 # HELP gauge help # TYPE gauge gauge gauge 0 - # HELP histogram help # TYPE histogram histogram histogram_bucket{le=\\"0.005\\"} 0 @@ -53,7 +36,6 @@ histogram_bucket{le=\\"10\\"} 0 histogram_bucket{le=\\"+Inf\\"} 0 histogram_sum 0 histogram_count 0 - # HELP summary help # TYPE summary summary summary{quantile=\\"0.01\\"} 0 @@ -65,16 +47,34 @@ summary{quantile=\\"0.99\\"} 0 summary{quantile=\\"0.999\\"} 0 summary_sum 0 summary_count 0 +# EOF " `; -exports[`$tag register should output all initialized metrics at value 0 2`] = ` +exports[`Register with Prometheus type should not output all initialized metrics at value 0 if labels 1`] = ` "# HELP counter help # TYPE counter counter -counter_total 0 + +# HELP gauge help +# TYPE gauge gauge + +# HELP histogram help +# TYPE histogram histogram + +# HELP summary help +# TYPE summary summary +" +`; + +exports[`Register with Prometheus type should output all initialized metrics at value 0 1`] = ` +"# HELP counter help +# TYPE counter counter +counter 0 + # HELP gauge help # TYPE gauge gauge gauge 0 + # HELP histogram help # TYPE histogram histogram histogram_bucket{le=\\"0.005\\"} 0 @@ -91,6 +91,7 @@ histogram_bucket{le=\\"10\\"} 0 histogram_bucket{le=\\"+Inf\\"} 0 histogram_sum 0 histogram_count 0 + # HELP summary help # TYPE summary summary summary{quantile=\\"0.01\\"} 0 @@ -102,6 +103,5 @@ summary{quantile=\\"0.99\\"} 0 summary{quantile=\\"0.999\\"} 0 summary_sum 0 summary_count 0 -# EOF " `; diff --git a/test/__snapshots__/summaryTest.js.snap b/test/__snapshots__/summaryTest.js.snap index d64fc320..29729118 100644 --- a/test/__snapshots__/summaryTest.js.snap +++ b/test/__snapshots__/summaryTest.js.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`summary with $tag registry global registry with param as object labels should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; +exports[`summary with OpenMetrics registry global registry with param as object labels should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; -exports[`summary with $tag registry global registry with param as object labels should throw error if label lengths does not match 2`] = `"Invalid number of arguments"`; +exports[`summary with OpenMetrics registry global registry with param as object remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; -exports[`summary with $tag registry global registry with param as object remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; +exports[`summary with OpenMetrics registry global registry with param as object should validate labels when observing 1`] = `"Added label \\"baz\\" is not included in initial labelset: [ 'foo' ]"`; -exports[`summary with $tag registry global registry with param as object remove should throw error if label lengths does not match 2`] = `"Invalid number of arguments"`; +exports[`summary with Prometheus registry global registry with param as object labels should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; -exports[`summary with $tag registry global registry with param as object should validate labels when observing 1`] = `"Added label \\"baz\\" is not included in initial labelset: [ 'foo' ]"`; +exports[`summary with Prometheus registry global registry with param as object remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; -exports[`summary with $tag registry global registry with param as object should validate labels when observing 2`] = `"Added label \\"baz\\" is not included in initial labelset: [ 'foo' ]"`; +exports[`summary with Prometheus registry global registry with param as object should validate labels when observing 1`] = `"Added label \\"baz\\" is not included in initial labelset: [ 'foo' ]"`; diff --git a/test/clusterTest.js b/test/clusterTest.js index d79dffa7..d1a58314 100644 --- a/test/clusterTest.js +++ b/test/clusterTest.js @@ -5,9 +5,9 @@ const process = require('process'); const Registry = require('../lib/cluster'); describe.each([ - { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, - { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, -])('$tag AggregatorRegistry', ({ tag, regType }) => { + ['Prometheus', Registry.PROMETHEUS_CONTENT_TYPE], + ['OpenMetrics', Registry.OPENMETRICS_CONTENT_TYPE], +])('%s AggregatorRegistry', (tag, regType) => { beforeEach(() => { Registry.globalRegistry.setContentType(regType); }); diff --git a/test/counterTest.js b/test/counterTest.js index c9355553..0ee0fc2d 100644 --- a/test/counterTest.js +++ b/test/counterTest.js @@ -3,9 +3,9 @@ const Registry = require('../index').Registry; describe.each([ - { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, - { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, -])('counter with $tag registry', ({ tag, regType }) => { + ['Prometheus', Registry.PROMETHEUS_CONTENT_TYPE], + ['OpenMetrics', Registry.OPENMETRICS_CONTENT_TYPE], +])('counter with %s registry', (tag, regType) => { const Counter = require('../index').Counter; const globalRegistry = require('../index').register; let instance; diff --git a/test/defaultMetricsTest.js b/test/defaultMetricsTest.js index 2a90a5a1..da01265a 100644 --- a/test/defaultMetricsTest.js +++ b/test/defaultMetricsTest.js @@ -3,9 +3,9 @@ const Registry = require('../index').Registry; describe.each([ - { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, - { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, -])('collectDefaultMetrics with $tag registry', ({ tag, regType }) => { + ['Prometheus', Registry.PROMETHEUS_CONTENT_TYPE], + ['OpenMetrics', Registry.OPENMETRICS_CONTENT_TYPE], +])('collectDefaultMetrics with %s registry', (tag, regType) => { const register = require('../index').register; const collectDefaultMetrics = require('../index').collectDefaultMetrics; let cpuUsage; diff --git a/test/exemplarsTest.js b/test/exemplarsTest.js index bb1c8cb0..ef5c2254 100644 --- a/test/exemplarsTest.js +++ b/test/exemplarsTest.js @@ -1,108 +1,131 @@ 'use strict'; const Registry = require('../index').Registry; +const globalRegistry = require('../index').register; +const Histogram = require('../index').Histogram; +const Counter = require('../index').Counter; -describe.each([ - { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, -])('exemplars with $tag registry', ({ tag, regType }) => { - // create counter with exemplars - // create histogram with exemplars - // check if exemplar is populated by openTElemetry <- this should be tested in the metrics directory - // - const globalRegistry = require('../index').register; - const Counter = require('../index').Counter; - const Histogram = require('../index').Histogram; - - beforeEach(() => { - globalRegistry.setContentType(regType); +describe('Exemplars', () => { + it('should throw when using with Prometheus registry', async () => { + globalRegistry.setContentType(Registry.PROMETHEUS_CONTENT_TYPE); + expect(() => { + const counterInstance = new Counter({ + name: 'counter_exemplar_test', + help: 'help', + labelNames: ['method', 'code'], + enableExemplars: true, + }); + }).toThrowError('Exemplars are supported only on OpenMetrics registries'); }); + describe.each([['OpenMetrics', Registry.OPENMETRICS_CONTENT_TYPE]])( + 'with %s registry', + (tag, regType) => { + beforeEach(() => { + globalRegistry.setContentType(regType); + }); - it('should make counter with exemplar', async () => { - const counterInstance = new Counter({ - name: 'counter_exemplar_test', - help: 'help', - labelNames: ['method', 'code'], - enableExemplars: true, - }); - counterInstance.inc({ - value: 2, - labels: { method: 'get', code: '200' }, - exemplarLabels: { traceId: 'trace_id_test', spanId: 'span_id_test' }, - }); - const vals = await counterInstance.get(); - expect(vals.values[0].value).toEqual(2); - expect(vals.values[0].exemplar.value).toEqual(2); - expect(vals.values[0].exemplar.labelSet.traceId).toEqual('trace_id_test'); - }); + it('should make counter with exemplar', async () => { + const counterInstance = new Counter({ + name: 'counter_exemplar_test', + help: 'help', + labelNames: ['method', 'code'], + enableExemplars: true, + }); + counterInstance.inc({ + value: 2, + labels: { method: 'get', code: '200' }, + exemplarLabels: { traceId: 'trace_id_test', spanId: 'span_id_test' }, + }); + const vals = await counterInstance.get(); + expect(vals.values[0].value).toEqual(2); + expect(vals.values[0].exemplar.value).toEqual(2); + expect(vals.values[0].exemplar.labelSet.traceId).toEqual( + 'trace_id_test', + ); + }); - it('should make histogram with exemplars on multiple buckets', async () => { - const histogramInstance = new Histogram({ - name: 'histogram_exemplar_test', - help: 'test', - labelNames: ['method', 'code'], - enableExemplars: true, - }); + it('should make histogram with exemplars on multiple buckets', async () => { + const histogramInstance = new Histogram({ + name: 'histogram_exemplar_test', + help: 'test', + labelNames: ['method', 'code'], + enableExemplars: true, + }); - histogramInstance.observe({ - value: 0.007, - labels: { method: 'get', code: '200' }, - exemplarLabels: { traceId: 'trace_id_test_1', spanId: 'span_id_test_1' }, - }); - histogramInstance.observe({ - value: 0.4, - labels: { method: 'get', code: '200' }, - exemplarLabels: { traceId: 'trace_id_test_2', spanId: 'span_id_test_2' }, - }); - histogramInstance.observe({ - value: 11, - labels: { method: 'get', code: '200' }, - exemplarLabels: { traceId: 'trace_id_test_3', spanId: 'span_id_test_3' }, - }); + histogramInstance.observe({ + value: 0.007, + labels: { method: 'get', code: '200' }, + exemplarLabels: { + traceId: 'trace_id_test_1', + spanId: 'span_id_test_1', + }, + }); + histogramInstance.observe({ + value: 0.4, + labels: { method: 'get', code: '200' }, + exemplarLabels: { + traceId: 'trace_id_test_2', + spanId: 'span_id_test_2', + }, + }); + histogramInstance.observe({ + value: 11, + labels: { method: 'get', code: '200' }, + exemplarLabels: { + traceId: 'trace_id_test_3', + spanId: 'span_id_test_3', + }, + }); - const vals = (await histogramInstance.get()).values; + const vals = (await histogramInstance.get()).values; - expect(getValuesByLabel(0.005, vals)[0].value).toEqual(0); - expect(getValuesByLabel(0.005, vals)[0].exemplar).toEqual(null); + expect(getValuesByLabel(0.005, vals)[0].value).toEqual(0); + expect(getValuesByLabel(0.005, vals)[0].exemplar).toEqual(null); - expect(getValuesByLabel(0.5, vals)[0].value).toEqual(2); - expect(getValuesByLabel(0.5, vals)[0].exemplar.labelSet.traceId).toEqual( - 'trace_id_test_2', - ); - expect(getValuesByLabel(0.5, vals)[0].exemplar.value).toEqual(0.4); + expect(getValuesByLabel(0.5, vals)[0].value).toEqual(2); + expect( + getValuesByLabel(0.5, vals)[0].exemplar.labelSet.traceId, + ).toEqual('trace_id_test_2'); + expect(getValuesByLabel(0.5, vals)[0].exemplar.value).toEqual(0.4); - expect(getValuesByLabel(10, vals)[0].value).toEqual(2); - expect(getValuesByLabel(10, vals)[0].exemplar).toEqual(null); + expect(getValuesByLabel(10, vals)[0].value).toEqual(2); + expect(getValuesByLabel(10, vals)[0].exemplar).toEqual(null); - expect(getValuesByLabel('+Inf', vals)[0].value).toEqual(3); - expect(getValuesByLabel('+Inf', vals)[0].exemplar.labelSet.traceId).toEqual( - 'trace_id_test_3', - ); - expect(getValuesByLabel('+Inf', vals)[0].exemplar.value).toEqual(11); - }); + expect(getValuesByLabel('+Inf', vals)[0].value).toEqual(3); + expect( + getValuesByLabel('+Inf', vals)[0].exemplar.labelSet.traceId, + ).toEqual('trace_id_test_3'); + expect(getValuesByLabel('+Inf', vals)[0].exemplar.value).toEqual(11); + }); - it('should throw error if exemplar is too long', async () => { - const histogramInstance = new Histogram({ - name: 'histogram_too_long_exemplar_test', - help: 'test', - labelNames: ['method', 'code'], - enableExemplars: true, - }); + it('should throw if exemplar is too long', async () => { + const histogramInstance = new Histogram({ + name: 'histogram_too_long_exemplar_test', + help: 'test', + labelNames: ['method', 'code'], + enableExemplars: true, + }); - expect(() => { - histogramInstance.observe({ - value: 0.007, - labels: { method: 'get', code: '200' }, - exemplarLabels: { traceId: 'j'.repeat(100), spanId: 'j'.repeat(100) }, + expect(() => { + histogramInstance.observe({ + value: 0.007, + labels: { method: 'get', code: '200' }, + exemplarLabels: { + traceId: 'j'.repeat(100), + spanId: 'j'.repeat(100), + }, + }); + }).toThrowError('Label set size must be smaller than 128 UTF-8 chars'); }); - }).toThrowError('Label set size must be smaller than 128 UTF-8 chars'); - }); - function getValuesByLabel(label, values, key) { - return values.reduce((acc, val) => { - if (val.labels && val.labels[key || 'le'] === label) { - acc.push(val); + function getValuesByLabel(label, values, key) { + return values.reduce((acc, val) => { + if (val.labels && val.labels[key || 'le'] === label) { + acc.push(val); + } + return acc; + }, []); } - return acc; - }, []); - } + }, + ); }); diff --git a/test/gaugeTest.js b/test/gaugeTest.js index df3f92e8..c7d9ec59 100644 --- a/test/gaugeTest.js +++ b/test/gaugeTest.js @@ -3,9 +3,9 @@ const Registry = require('../index').Registry; describe.each([ - { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, - { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, -])('gauge with $tag registry', ({ tag, regType }) => { + ['Prometheus', Registry.PROMETHEUS_CONTENT_TYPE], + ['OpenMetrics', Registry.OPENMETRICS_CONTENT_TYPE], +])('gauge with %s registry', (tag, regType) => { const Gauge = require('../index').Gauge; const globalRegistry = require('../index').register; let instance; diff --git a/test/histogramTest.js b/test/histogramTest.js index 026a0610..5ec89f97 100644 --- a/test/histogramTest.js +++ b/test/histogramTest.js @@ -3,9 +3,9 @@ const Registry = require('../index').Registry; describe.each([ - { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, - { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, -])('histogram with $tag registry', ({ tag, regType }) => { + ['Prometheus', Registry.PROMETHEUS_CONTENT_TYPE], + ['OpenMetrics', Registry.OPENMETRICS_CONTENT_TYPE], +])('histogram with %s registry', (tag, regType) => { const Histogram = require('../index').Histogram; const globalRegistry = require('../index').register; let instance; diff --git a/test/metrics/eventLoopLagTest.js b/test/metrics/eventLoopLagTest.js index ec882426..a8a0fa3c 100644 --- a/test/metrics/eventLoopLagTest.js +++ b/test/metrics/eventLoopLagTest.js @@ -3,9 +3,9 @@ const Registry = require('../../index').Registry; describe.each([ - { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, - { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, -])('eventLoopLag with $tag registry', ({ tag, regType }) => { + ['Prometheus', Registry.PROMETHEUS_CONTENT_TYPE], + ['OpenMetrics', Registry.OPENMETRICS_CONTENT_TYPE], +])('eventLoopLag with %s registry', (tag, regType) => { const register = require('../../index').register; const eventLoopLag = require('../../lib/metrics/eventLoopLag'); @@ -21,7 +21,7 @@ describe.each([ register.clear(); }); - it('should add metric to the $tag registry', async done => { + it(`should add metric to the ${tag} registry`, async done => { expect(await register.getMetricsAsJSON()).toHaveLength(0); eventLoopLag(); diff --git a/test/metrics/gcTest.js b/test/metrics/gcTest.js index f816a08f..81253cfb 100644 --- a/test/metrics/gcTest.js +++ b/test/metrics/gcTest.js @@ -3,9 +3,9 @@ const Registry = require('../../index').Registry; describe.each([ - { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, - { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, -])('gc with $tag registry', ({ tag, regType }) => { + ['Prometheus', Registry.PROMETHEUS_CONTENT_TYPE], + ['OpenMetrics', Registry.OPENMETRICS_CONTENT_TYPE], +])('gc with %s registry', (tag, regType) => { const register = require('../../index').register; const processHandles = require('../../lib/metrics/gc'); @@ -21,7 +21,7 @@ describe.each([ register.clear(); }); - it('should add metric to the $tag registry', async () => { + it(`should add metric to the ${tag} registry`, async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); processHandles(); diff --git a/test/metrics/heapSizeAndUsedTest.js b/test/metrics/heapSizeAndUsedTest.js index d981edf2..3c917ed6 100644 --- a/test/metrics/heapSizeAndUsedTest.js +++ b/test/metrics/heapSizeAndUsedTest.js @@ -3,9 +3,9 @@ const Registry = require('../../index').Registry; describe.each([ - { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, - { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, -])('heapSizeAndUsed with $tag registry', ({ tag, regType }) => { + ['Prometheus', Registry.PROMETHEUS_CONTENT_TYPE], + ['OpenMetrics', Registry.OPENMETRICS_CONTENT_TYPE], +])('heapSizeAndUsed with %s registry', (tag, regType) => { const heapSizeAndUsed = require('../../lib/metrics/heapSizeAndUsed'); const globalRegistry = require('../../lib/registry').globalRegistry; const memoryUsedFn = process.memoryUsage; diff --git a/test/metrics/heapSpacesSizeAndUsedTest.js b/test/metrics/heapSpacesSizeAndUsedTest.js index 71bedff8..c4c06ce7 100644 --- a/test/metrics/heapSpacesSizeAndUsedTest.js +++ b/test/metrics/heapSpacesSizeAndUsedTest.js @@ -47,9 +47,9 @@ jest.mock('v8', () => { }); describe.each([ - { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, - { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, -])('heapSpacesSizeAndUsed with $tag registry', ({ tag, regType }) => { + ['Prometheus', Registry.PROMETHEUS_CONTENT_TYPE], + ['OpenMetrics', Registry.OPENMETRICS_CONTENT_TYPE], +])('heapSpacesSizeAndUsed with %s registry', (tag, regType) => { let heapSpacesSizeAndUsed; const globalRegistry = require('../../lib/registry').globalRegistry; @@ -62,7 +62,7 @@ describe.each([ globalRegistry.clear(); }); - it('should set total heap spaces size gauges with values from v8 with $tag registry', async () => { + it(`should set total heap spaces size gauges with values from v8 with ${tag} registry`, async () => { expect(await globalRegistry.getMetricsAsJSON()).toHaveLength(0); heapSpacesSizeAndUsed(); diff --git a/test/metrics/maxFileDescriptorsTest.js b/test/metrics/maxFileDescriptorsTest.js index 7a6301b3..3921b871 100644 --- a/test/metrics/maxFileDescriptorsTest.js +++ b/test/metrics/maxFileDescriptorsTest.js @@ -4,9 +4,9 @@ const exec = require('child_process').execSync; const Registry = require('../../index').Registry; describe.each([ - { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, - { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, -])('processMaxFileDescriptors with $tag registry', ({ tag, regType }) => { + ['Prometheus', Registry.PROMETHEUS_CONTENT_TYPE], + ['OpenMetrics', Registry.OPENMETRICS_CONTENT_TYPE], +])('processMaxFileDescriptors with %s registry', (tag, regType) => { const register = require('../../index').register; const processMaxFileDescriptors = require('../../lib/metrics/processMaxFileDescriptors'); @@ -23,7 +23,7 @@ describe.each([ }); if (process.platform !== 'linux') { - it('should not add metric to the $tag registry', async () => { + it(`should not add metric to the ${tag} registry`, async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); processMaxFileDescriptors(); @@ -31,7 +31,7 @@ describe.each([ expect(await register.getMetricsAsJSON()).toHaveLength(0); }); } else { - it('should add metric to the $tag registry', async () => { + it(`should add metric to the ${tag} registry`, async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); processMaxFileDescriptors(); @@ -47,7 +47,7 @@ describe.each([ expect(metrics[0].values).toHaveLength(1); }); - it('should have a reasonable metric value with $tag registry', async () => { + it(`should have a reasonable metric value with ${tag} registry`, async () => { const maxFiles = Number(exec('ulimit -Hn', { encoding: 'utf8' })); expect(await register.getMetricsAsJSON()).toHaveLength(0); diff --git a/test/metrics/processHandlesTest.js b/test/metrics/processHandlesTest.js index eb7cf201..dd03404c 100644 --- a/test/metrics/processHandlesTest.js +++ b/test/metrics/processHandlesTest.js @@ -3,9 +3,9 @@ const Registry = require('../../index').Registry; describe.each([ - { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, - { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, -])('processHandles with $tag registry', ({ tag, regType }) => { + ['Prometheus', Registry.PROMETHEUS_CONTENT_TYPE], + ['OpenMetrics', Registry.OPENMETRICS_CONTENT_TYPE], +])('processHandles with %s registry', (tag, regType) => { const register = require('../../index').register; const processHandles = require('../../lib/metrics/processHandles'); @@ -21,7 +21,7 @@ describe.each([ register.clear(); }); - it('should add metric to the $tag registry', async () => { + it(`should add metric to the ${tag} registry`, async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); processHandles(); diff --git a/test/metrics/processOpenFileDescriptorsTest.js b/test/metrics/processOpenFileDescriptorsTest.js index fc6d4724..7cc6c50b 100644 --- a/test/metrics/processOpenFileDescriptorsTest.js +++ b/test/metrics/processOpenFileDescriptorsTest.js @@ -8,9 +8,9 @@ jest.mock( ); describe.each([ - { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, - { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, -])('processOpenFileDescriptors with $tag registry', ({ tag, regType }) => { + ['Prometheus', Registry.PROMETHEUS_CONTENT_TYPE], + ['OpenMetrics', Registry.OPENMETRICS_CONTENT_TYPE], +])('processOpenFileDescriptors with %s registry', (tag, regType) => { const register = require('../../index').register; const processOpenFileDescriptors = require('../../lib/metrics/processOpenFileDescriptors'); @@ -26,7 +26,7 @@ describe.each([ register.clear(); }); - it('should add metric to the $tag registry', async () => { + it(`should add metric to the ${tag} registry`, async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); processOpenFileDescriptors(); diff --git a/test/metrics/processRequestsTest.js b/test/metrics/processRequestsTest.js index f2344f05..c28a51d2 100644 --- a/test/metrics/processRequestsTest.js +++ b/test/metrics/processRequestsTest.js @@ -3,9 +3,9 @@ const Registry = require('../../index').Registry; describe.each([ - { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, - { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, -])('processRequests with $tag registry', ({ tag, regType }) => { + ['Prometheus', Registry.PROMETHEUS_CONTENT_TYPE], + ['OpenMetrics', Registry.OPENMETRICS_CONTENT_TYPE], +])('processRequests with %s registry', (tag, regType) => { const register = require('../../index').register; const processRequests = require('../../lib/metrics/processRequests'); @@ -21,7 +21,7 @@ describe.each([ register.clear(); }); - it('should add metric to the $tag registry', async () => { + it(`should add metric to the ${tag} registry`, async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); processRequests(); diff --git a/test/metrics/processStartTimeTest.js b/test/metrics/processStartTimeTest.js index 2066c494..bee5cdab 100644 --- a/test/metrics/processStartTimeTest.js +++ b/test/metrics/processStartTimeTest.js @@ -3,9 +3,9 @@ const Registry = require('../../index').Registry; describe.each([ - { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, - { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, -])('processStartTime with $tag registry', ({ tag, regType }) => { + ['Prometheus', Registry.PROMETHEUS_CONTENT_TYPE], + ['OpenMetrics', Registry.OPENMETRICS_CONTENT_TYPE], +])('processStartTime with %s registry', (tag, regType) => { const register = require('../../index').register; const op = require('../../lib/metrics/processStartTime'); @@ -21,7 +21,7 @@ describe.each([ register.clear(); }); - it('should add metric to the $tag registry', async () => { + it(`should add metric to the ${tag} registry`, async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); op(); diff --git a/test/metrics/versionTest.js b/test/metrics/versionTest.js index 3b31daac..0a28fb0e 100644 --- a/test/metrics/versionTest.js +++ b/test/metrics/versionTest.js @@ -17,9 +17,9 @@ function expectVersionMetrics(metrics) { } describe.each([ - { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, - { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, -])('version with $tag registry', ({ tag, regType }) => { + ['Prometheus', Registry.PROMETHEUS_CONTENT_TYPE], + ['OpenMetrics', Registry.OPENMETRICS_CONTENT_TYPE], +])('version with %s registry', (tag, regType) => { const register = require('../../index').register; const version = require('../../lib/metrics/version'); @@ -35,7 +35,7 @@ describe.each([ register.clear(); }); - it('should add metric to the $tag registry', async () => { + it(`should add metric to the ${tag} registry`, async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); expect(typeof versionSegments[0]).toEqual('number'); expect(typeof versionSegments[1]).toEqual('number'); @@ -47,7 +47,7 @@ describe.each([ expectVersionMetrics(metrics); }); - it('should still be present after resetting the $tag registry #238', async () => { + it(`should still be present after resetting the ${tag} registry #238`, async () => { const collector = version(); expectVersionMetrics(await register.getMetricsAsJSON()); register.resetMetrics(); diff --git a/test/pushgatewayTest.js b/test/pushgatewayTest.js index 5f80a3af..bcc6b06c 100644 --- a/test/pushgatewayTest.js +++ b/test/pushgatewayTest.js @@ -6,9 +6,9 @@ const { gzipSync } = require('zlib'); const Registry = require('../index').Registry; describe.each([ - { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, - // { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, -])('pushgateway with $tag registry', ({ tag, regType }) => { + ['Prometheus', Registry.PROMETHEUS_CONTENT_TYPE], + ['OpenMetrics', Registry.OPENMETRICS_CONTENT_TYPE], +])('pushgateway with %s registry', (tag, regType) => { const Pushgateway = require('../index').Pushgateway; const register = require('../index').register; let instance; @@ -19,13 +19,17 @@ describe.each([ }); const tests = function () { + let body; + if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { + body = '# HELP test test\n# TYPE test counter\ntest_total 100\n# EOF\n'; + } else { + body = '# HELP test test\n# TYPE test counter\ntest 100\n'; + } + describe('pushAdd', () => { it('should push metrics', () => { const mockHttp = nock('http://192.168.99.100:9091') - .post( - '/metrics/job/testJob', - '# HELP test test\n# TYPE test counter\ntest 100\n', - ) + .post('/metrics/job/testJob', body) .reply(200); return instance.pushAdd({ jobName: 'testJob' }).then(() => { @@ -35,10 +39,7 @@ describe.each([ it('should use groupings', () => { const mockHttp = nock('http://192.168.99.100:9091') - .post( - '/metrics/job/testJob/key/value', - '# HELP test test\n# TYPE test counter\ntest 100\n', - ) + .post('/metrics/job/testJob/key/value', body) .reply(200); return instance @@ -53,10 +54,7 @@ describe.each([ it('should escape groupings', () => { const mockHttp = nock('http://192.168.99.100:9091') - .post( - '/metrics/job/testJob/key/va%26lue', - '# HELP test test\n# TYPE test counter\ntest 100\n', - ) + .post('/metrics/job/testJob/key/va%26lue', body) .reply(200); return instance @@ -73,10 +71,7 @@ describe.each([ describe('push', () => { it('should push with PUT', () => { const mockHttp = nock('http://192.168.99.100:9091') - .put( - '/metrics/job/testJob', - '# HELP test test\n# TYPE test counter\ntest 100\n', - ) + .put('/metrics/job/testJob', body) .reply(200); return instance.push({ jobName: 'testJob' }).then(() => { @@ -86,10 +81,7 @@ describe.each([ it('should uri encode url', () => { const mockHttp = nock('http://192.168.99.100:9091') - .put( - '/metrics/job/test%26Job', - '# HELP test test\n# TYPE test counter\ntest 100\n', - ) + .put('/metrics/job/test%26Job', body) .reply(200); return instance.push({ jobName: 'test&Job' }).then(() => { @@ -125,10 +117,7 @@ describe.each([ it('pushAdd should send POST request with basic auth data', () => { const mockHttp = nock(`http://${auth}@192.168.99.100:9091`) - .post( - '/metrics/job/testJob', - '# HELP test test\n# TYPE test counter\ntest 100\n', - ) + .post('/metrics/job/testJob', body) .reply(200); return instance.pushAdd({ jobName: 'testJob' }).then(() => { @@ -138,10 +127,7 @@ describe.each([ it('push should send PUT request with basic auth data', () => { const mockHttp = nock(`http://${auth}@192.168.99.100:9091`) - .put( - '/metrics/job/testJob', - '# HELP test test\n# TYPE test counter\ntest 100\n', - ) + .put('/metrics/job/testJob', body) .reply(200); return instance.push({ jobName: 'testJob' }).then(() => { @@ -166,10 +152,7 @@ describe.each([ 'unit-test': '1', }, }) - .put( - '/metrics/job/testJob', - '# HELP test test\n# TYPE test counter\ntest 100\n', - ) + .put('/metrics/job/testJob', body) .reply(200); instance = new Pushgateway( diff --git a/test/pushgatewayWithPathTest.js b/test/pushgatewayWithPathTest.js index 1385c243..df274eb4 100644 --- a/test/pushgatewayWithPathTest.js +++ b/test/pushgatewayWithPathTest.js @@ -19,9 +19,9 @@ jest.mock('http', () => { const Registry = require('../index').Registry; describe.each([ - { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, - { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, -])('pushgateway with path and $tag registry', ({ tag, regType }) => { + ['Prometheus', Registry.PROMETHEUS_CONTENT_TYPE], + ['OpenMetrics', Registry.OPENMETRICS_CONTENT_TYPE], +])('pushgateway with path and %s registry', (tag, regType) => { const Pushgateway = require('../index').Pushgateway; const register = require('../index').register; let instance; diff --git a/test/registerTest.js b/test/registerTest.js index 9bc7263e..83c5b183 100644 --- a/test/registerTest.js +++ b/test/registerTest.js @@ -1,705 +1,736 @@ 'use strict'; const Registry = require('../index').Registry; +const register = require('../index').register; -describe.each([ - { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, - { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, -])('$tag register', ({ tag, regType }) => { - const register = require('../index').register; - const Counter = require('../index').Counter; - const Gauge = require('../index').Gauge; - const Histogram = require('../index').Histogram; - const Summary = require('../index').Summary; - - beforeEach(() => { - register.setContentType(regType); - register.clear(); +describe('Register', () => { + it('should throw if set to an unsupported type', () => { + expect(() => { + register.setContentType( + 'application/openmetrics-text; version=42.0.0; charset=utf-8', + ); + }).toThrowError('Content type unsupported'); }); - describe('should output a counter metric', () => { - let output; - beforeEach(async () => { - register.registerMetric(getMetric()); - output = (await register.metrics()).split('\n'); + it('should throw if created with an unsupported type', () => { + expect(() => { + new Registry( + 'application/openmetrics-text; version=42.0.0; charset=utf-8', + ); + }).toThrowError('Content type unsupported'); + }); + + describe.each([ + ['Prometheus', Registry.PROMETHEUS_CONTENT_TYPE], + ['OpenMetrics', Registry.OPENMETRICS_CONTENT_TYPE], + ])('with %s type', (tag, regType) => { + const Counter = require('../index').Counter; + const Gauge = require('../index').Gauge; + const Histogram = require('../index').Histogram; + const Summary = require('../index').Summary; + + beforeEach(() => { + register.setContentType(regType); + register.clear(); }); - it('with help as first item', () => { - expect(output[0]).toEqual('# HELP test_metric A test metric'); + describe('should output a counter metric', () => { + let output; + beforeEach(async () => { + register.registerMetric(getMetric()); + output = (await register.metrics()).split('\n'); + }); + + it('with help as first item', () => { + expect(output[0]).toEqual('# HELP test_metric A test metric'); + }); + it('with type as second item', () => { + expect(output[1]).toEqual('# TYPE test_metric counter'); + }); + it('with first value of the metric as third item', () => { + if (register.contentType === Registry.OPENMETRICS_CONTENT_TYPE) { + expect(output[2]).toEqual( + 'test_metric_total{label="hello",code="303"} 12', + ); + } else { + expect(output[2]).toEqual('test_metric{label="hello",code="303"} 12'); + } + }); + it('with second value of the metric as fourth item', () => { + if (register.contentType === Registry.OPENMETRICS_CONTENT_TYPE) { + expect(output[3]).toEqual( + 'test_metric_total{label="bye",code="404"} 34', + ); + } else { + expect(output[3]).toEqual('test_metric{label="bye",code="404"} 34'); + } + }); }); - it('with type as second item', () => { - expect(output[1]).toEqual('# TYPE test_metric counter'); + + it('should throw on more than one metric', () => { + register.registerMetric(getMetric()); + + expect(() => { + register.registerMetric(getMetric()); + }).toThrowError( + 'A metric with the name test_metric has already been registered.', + ); }); - it('with first value of the metric as third item', () => { - if (register.contentType === Registry.OPENMETRICS_CONTENT_TYPE) { - expect(output[2]).toEqual( - 'test_metric_total{label="hello",code="303"} 12', - ); + + it('should handle and output a metric with a NaN value', async () => { + register.registerMetric({ + async get() { + return { + name: 'test_metric', + type: 'gauge', + help: 'A test metric', + values: [ + { + value: NaN, + }, + ], + }; + }, + }); + const lines = (await register.metrics()).split('\n'); + if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { + expect(lines).toHaveLength(5); } else { - expect(output[2]).toEqual('test_metric{label="hello",code="303"} 12'); + expect(lines).toHaveLength(4); } + expect(lines[2]).toEqual('test_metric Nan'); }); - it('with second value of the metric as fourth item', () => { - if (register.contentType === Registry.OPENMETRICS_CONTENT_TYPE) { - expect(output[3]).toEqual( - 'test_metric_total{label="bye",code="404"} 34', - ); + + it('should handle and output a metric with an +Infinity value', async () => { + register.registerMetric({ + async get() { + return { + name: 'test_metric', + type: 'gauge', + help: 'A test metric', + values: [ + { + value: Infinity, + }, + ], + }; + }, + }); + const lines = (await register.metrics()).split('\n'); + if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { + expect(lines).toHaveLength(5); } else { - expect(output[3]).toEqual('test_metric{label="bye",code="404"} 34'); + expect(lines).toHaveLength(4); } + expect(lines[2]).toEqual('test_metric +Inf'); }); - }); - - it('should throw on more than one metric', () => { - register.registerMetric(getMetric()); - - expect(() => { - register.registerMetric(getMetric()); - }).toThrowError( - 'A metric with the name test_metric has already been registered.', - ); - }); - - it('should handle and output a metric with a NaN value', async () => { - register.registerMetric({ - async get() { - return { - name: 'test_metric', - type: 'gauge', - help: 'A test metric', - values: [ - { - value: NaN, - }, - ], - }; - }, - }); - const lines = (await register.metrics()).split('\n'); - if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { - expect(lines).toHaveLength(5); - } else { - expect(lines).toHaveLength(4); - } - expect(lines[2]).toEqual('test_metric Nan'); - }); - it('should handle and output a metric with an +Infinity value', async () => { - register.registerMetric({ - async get() { - return { - name: 'test_metric', - type: 'gauge', - help: 'A test metric', - values: [ - { - value: Infinity, - }, - ], - }; - }, + it('should handle and output a metric with an -Infinity value', async () => { + register.registerMetric({ + async get() { + return { + name: 'test_metric', + type: 'gauge', + help: 'A test metric', + values: [ + { + value: -Infinity, + }, + ], + }; + }, + }); + const lines = (await register.metrics()).split('\n'); + if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { + expect(lines).toHaveLength(5); + } else { + expect(lines).toHaveLength(4); + } + expect(lines[2]).toEqual('test_metric -Inf'); }); - const lines = (await register.metrics()).split('\n'); - if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { - expect(lines).toHaveLength(5); - } else { - expect(lines).toHaveLength(4); - } - expect(lines[2]).toEqual('test_metric +Inf'); - }); - it('should handle and output a metric with an -Infinity value', async () => { - register.registerMetric({ - async get() { - return { - name: 'test_metric', - type: 'gauge', - help: 'A test metric', - values: [ - { - value: -Infinity, - }, - ], - }; - }, + it('should handle a metric without labels', async () => { + register.registerMetric({ + async get() { + return { + name: 'test_metric', + type: 'counter', + help: 'A test metric', + values: [ + { + value: 1, + }, + ], + }; + }, + }); + if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { + expect((await register.metrics()).split('\n')).toHaveLength(5); + } else { + expect((await register.metrics()).split('\n')).toHaveLength(4); + } }); - const lines = (await register.metrics()).split('\n'); - if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { - expect(lines).toHaveLength(5); - } else { - expect(lines).toHaveLength(4); - } - expect(lines[2]).toEqual('test_metric -Inf'); - }); - it('should handle a metric without labels', async () => { - register.registerMetric({ - async get() { - return { - name: 'test_metric', - type: 'counter', - help: 'A test metric', - values: [ - { - value: 1, - }, - ], - }; - }, - }); - if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { - expect((await register.metrics()).split('\n')).toHaveLength(5); - } else { - expect((await register.metrics()).split('\n')).toHaveLength(4); - } - }); + it('should handle a metric with default labels', async () => { + register.setDefaultLabels({ testLabel: 'testValue' }); + register.registerMetric({ + async get() { + return { + name: 'test_metric', + type: 'counter', + help: 'A test metric', + values: [{ value: 1 }], + }; + }, + }); - it('should handle a metric with default labels', async () => { - register.setDefaultLabels({ testLabel: 'testValue' }); - register.registerMetric({ - async get() { - return { - name: 'test_metric', - type: 'counter', - help: 'A test metric', - values: [{ value: 1 }], - }; - }, + const output = (await register.metrics()).split('\n')[2]; + if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { + expect(output).toEqual('test_metric_total{testLabel="testValue"} 1'); + } else { + expect(output).toEqual('test_metric{testLabel="testValue"} 1'); + } }); - const output = (await register.metrics()).split('\n')[2]; - if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { - expect(output).toEqual('test_metric_total{testLabel="testValue"} 1'); - } else { - expect(output).toEqual('test_metric{testLabel="testValue"} 1'); - } - }); - - it('labeled metrics should take precidence over defaulted', async () => { - register.setDefaultLabels({ testLabel: 'testValue' }); - register.registerMetric({ - async get() { - return { - name: 'test_metric', - type: 'counter', - help: 'A test metric', - values: [ - { - value: 1, - labels: { - testLabel: 'overlapped', - anotherLabel: 'value123', + it('labeled metrics should take precidence over defaulted', async () => { + register.setDefaultLabels({ testLabel: 'testValue' }); + register.registerMetric({ + async get() { + return { + name: 'test_metric', + type: 'counter', + help: 'A test metric', + values: [ + { + value: 1, + labels: { + testLabel: 'overlapped', + anotherLabel: 'value123', + }, }, - }, - ], - }; - }, + ], + }; + }, + }); + + if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { + expect((await register.metrics()).split('\n')[2]).toEqual( + 'test_metric_total{testLabel="overlapped",anotherLabel="value123"} 1', + ); + } else { + expect((await register.metrics()).split('\n')[2]).toEqual( + 'test_metric{testLabel="overlapped",anotherLabel="value123"} 1', + ); + } }); - if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { - expect((await register.metrics()).split('\n')[2]).toEqual( - 'test_metric_total{testLabel="overlapped",anotherLabel="value123"} 1', - ); - } else { - expect((await register.metrics()).split('\n')[2]).toEqual( - 'test_metric{testLabel="overlapped",anotherLabel="value123"} 1', - ); - } - }); + it('should output all initialized metrics at value 0', async () => { + new Counter({ name: 'counter', help: 'help' }); + new Gauge({ name: 'gauge', help: 'help' }); + new Histogram({ name: 'histogram', help: 'help' }); + new Summary({ name: 'summary', help: 'help' }); - it('should output all initialized metrics at value 0', async () => { - new Counter({ name: 'counter', help: 'help' }); - new Gauge({ name: 'gauge', help: 'help' }); - new Histogram({ name: 'histogram', help: 'help' }); - new Summary({ name: 'summary', help: 'help' }); + expect(await register.metrics()).toMatchSnapshot(); + }); - expect(await register.metrics()).toMatchSnapshot(); - }); + it('should not output all initialized metrics at value 0 if labels', async () => { + new Counter({ name: 'counter', help: 'help', labelNames: ['label'] }); + new Gauge({ name: 'gauge', help: 'help', labelNames: ['label'] }); + new Histogram({ name: 'histogram', help: 'help', labelNames: ['label'] }); + new Summary({ name: 'summary', help: 'help', labelNames: ['label'] }); - it('should not output all initialized metrics at value 0 if labels', async () => { - new Counter({ name: 'counter', help: 'help', labelNames: ['label'] }); - new Gauge({ name: 'gauge', help: 'help', labelNames: ['label'] }); - new Histogram({ name: 'histogram', help: 'help', labelNames: ['label'] }); - new Summary({ name: 'summary', help: 'help', labelNames: ['label'] }); + expect(await register.metrics()).toMatchSnapshot(); + }); - expect(await register.metrics()).toMatchSnapshot(); - }); + describe('should escape', () => { + let escapedResult; + beforeEach(async () => { + register.registerMetric({ + async get() { + return { + name: 'test_"_\\_\n_metric', + help: 'help_help', + type: 'counter', + }; + }, + }); + escapedResult = await register.metrics(); + }); + it('backslash to \\\\', () => { + expect(escapedResult).toMatch(/\\\\/); + }); + it('newline to \\\\n', () => { + expect(escapedResult).toMatch(/\n/); + }); + }); - describe('should escape', () => { - let escapedResult; - beforeEach(async () => { + it('should escape " in label values', async () => { register.registerMetric({ async get() { return { - name: 'test_"_\\_\n_metric', - help: 'help_help', + name: 'test_metric', type: 'counter', + help: 'A test metric', + values: [ + { + value: 12, + labels: { + label: 'hello', + code: '3"03', + }, + }, + ], }; }, }); - escapedResult = await register.metrics(); - }); - it('backslash to \\\\', () => { - expect(escapedResult).toMatch(/\\\\/); - }); - it('newline to \\\\n', () => { - expect(escapedResult).toMatch(/\n/); + const escapedResult = await register.metrics(); + expect(escapedResult).toMatch(/\\"/); }); - }); - - it('should escape " in label values', async () => { - register.registerMetric({ - async get() { - return { - name: 'test_metric', - type: 'counter', - help: 'A test metric', - values: [ - { - value: 12, - labels: { - label: 'hello', - code: '3"03', - }, - }, - ], - }; - }, - }); - const escapedResult = await register.metrics(); - expect(escapedResult).toMatch(/\\"/); - }); - describe('should output metrics as JSON', () => { - it('should output metrics as JSON', async () => { - register.registerMetric(getMetric()); - const output = await register.getMetricsAsJSON(); + describe('should output metrics as JSON', () => { + it('should output metrics as JSON', async () => { + register.registerMetric(getMetric()); + const output = await register.getMetricsAsJSON(); - expect(output.length).toEqual(1); - expect(output[0].name).toEqual('test_metric'); - expect(output[0].type).toEqual('counter'); - expect(output[0].help).toEqual('A test metric'); - expect(output[0].values.length).toEqual(2); - }); - - it('should add default labels to JSON', async () => { - register.registerMetric(getMetric()); - register.setDefaultLabels({ - defaultRegistryLabel: 'testValue', + expect(output.length).toEqual(1); + expect(output[0].name).toEqual('test_metric'); + expect(output[0].type).toEqual('counter'); + expect(output[0].help).toEqual('A test metric'); + expect(output[0].values.length).toEqual(2); }); - const output = await register.getMetricsAsJSON(); - expect(output.length).toEqual(1); - expect(output[0].name).toEqual('test_metric'); - expect(output[0].type).toEqual('counter'); - expect(output[0].help).toEqual('A test metric'); - expect(output[0].values.length).toEqual(2); - expect(output[0].values[0].labels).toEqual({ - code: '303', - label: 'hello', - defaultRegistryLabel: 'testValue', + it('should add default labels to JSON', async () => { + register.registerMetric(getMetric()); + register.setDefaultLabels({ + defaultRegistryLabel: 'testValue', + }); + const output = await register.getMetricsAsJSON(); + + expect(output.length).toEqual(1); + expect(output[0].name).toEqual('test_metric'); + expect(output[0].type).toEqual('counter'); + expect(output[0].help).toEqual('A test metric'); + expect(output[0].values.length).toEqual(2); + expect(output[0].values[0].labels).toEqual({ + code: '303', + label: 'hello', + defaultRegistryLabel: 'testValue', + }); }); }); - }); - it('should allow removing single metrics', async () => { - register.registerMetric(getMetric()); - register.registerMetric(getMetric('some other name')); + it('should allow removing single metrics', async () => { + register.registerMetric(getMetric()); + register.registerMetric(getMetric('some other name')); - let output = await register.getMetricsAsJSON(); - expect(output.length).toEqual(2); + let output = await register.getMetricsAsJSON(); + expect(output.length).toEqual(2); - register.removeSingleMetric('test_metric'); + register.removeSingleMetric('test_metric'); - output = await register.getMetricsAsJSON(); + output = await register.getMetricsAsJSON(); - expect(output.length).toEqual(1); - expect(output[0].name).toEqual('some other name'); - }); + expect(output.length).toEqual(1); + expect(output[0].name).toEqual('some other name'); + }); - it('should allow getting single metrics', () => { - const metric = getMetric(); - register.registerMetric(metric); + it('should allow getting single metrics', () => { + const metric = getMetric(); + register.registerMetric(metric); - const output = register.getSingleMetric('test_metric'); - expect(output).toEqual(metric); - }); + const output = register.getSingleMetric('test_metric'); + expect(output).toEqual(metric); + }); - it('should allow getting metrics', async () => { - const metric = getMetric(); - register.registerMetric(metric); - const metrics = await register.metrics(); + it('should allow getting metrics', async () => { + const metric = getMetric(); + register.registerMetric(metric); + const metrics = await register.metrics(); - if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { - expect(metrics.split('\n')[3]).toEqual( - 'test_metric_total{label="bye",code="404"} 34', - ); - } else { - expect(metrics.split('\n')[3]).toEqual( - 'test_metric{label="bye",code="404"} 34', - ); - } - }); + if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { + expect(metrics.split('\n')[3]).toEqual( + 'test_metric_total{label="bye",code="404"} 34', + ); + } else { + expect(metrics.split('\n')[3]).toEqual( + 'test_metric{label="bye",code="404"} 34', + ); + } + }); - describe('resetting', () => { - it('should allow resetting all metrics', async () => { - const counter = new Counter({ - name: 'test_counter', - help: 'test metric', - labelNames: ['serial', 'active'], - }); - const gauge = new Gauge({ - name: 'test_gauge', - help: 'Another test metric', - labelNames: ['level'], - }); - const histo = new Histogram({ - name: 'test_histo', - help: 'test', - }); - const summ = new Summary({ - name: 'test_summ', - help: 'test', - percentiles: [0.5], - }); - register.registerMetric(counter); - register.registerMetric(gauge); - register.registerMetric(histo); - register.registerMetric(summ); + describe('resetting', () => { + it('should allow resetting all metrics', async () => { + const counter = new Counter({ + name: 'test_counter', + help: 'test metric', + labelNames: ['serial', 'active'], + }); + const gauge = new Gauge({ + name: 'test_gauge', + help: 'Another test metric', + labelNames: ['level'], + }); + const histo = new Histogram({ + name: 'test_histo', + help: 'test', + }); + const summ = new Summary({ + name: 'test_summ', + help: 'test', + percentiles: [0.5], + }); + register.registerMetric(counter); + register.registerMetric(gauge); + register.registerMetric(histo); + register.registerMetric(summ); - counter.inc({ serial: '12345', active: 'yes' }, 12); - gauge.set({ level: 'low' }, -12); - histo.observe(1); - summ.observe(100); + counter.inc({ serial: '12345', active: 'yes' }, 12); + gauge.set({ level: 'low' }, -12); + histo.observe(1); + summ.observe(100); - register.resetMetrics(); + register.resetMetrics(); - const same_counter = register.getSingleMetric('test_counter'); - expect((await same_counter.get()).values).toEqual([]); + const same_counter = register.getSingleMetric('test_counter'); + expect((await same_counter.get()).values).toEqual([]); - const same_gauge = register.getSingleMetric('test_gauge'); - expect((await same_gauge.get()).values).toEqual([]); + const same_gauge = register.getSingleMetric('test_gauge'); + expect((await same_gauge.get()).values).toEqual([]); - const same_histo = register.getSingleMetric('test_histo'); - expect((await same_histo.get()).values).toEqual([]); + const same_histo = register.getSingleMetric('test_histo'); + expect((await same_histo.get()).values).toEqual([]); - const same_summ = register.getSingleMetric('test_summ'); - expect((await same_summ.get()).values[0].value).toEqual(0); + const same_summ = register.getSingleMetric('test_summ'); + expect((await same_summ.get()).values[0].value).toEqual(0); + }); }); - }); - - describe('Registry with default labels', () => { - const Registry = require('../lib/registry'); - describe('mutation tests', () => { - describe('registry.metrics()', () => { - it('should not throw with default labels (counter)', async () => { - const r = new Registry(regType); - r.setDefaultLabels({ - env: 'development', + describe('Registry with default labels', () => { + const Registry = require('../lib/registry'); + + describe('mutation tests', () => { + describe('registry.metrics()', () => { + it('should not throw with default labels (counter)', async () => { + const r = new Registry(regType); + r.setDefaultLabels({ + env: 'development', + }); + + const counter = new Counter({ + name: 'my_counter', + help: 'my counter', + registers: [r], + labelNames: ['type'], + }); + + const myCounter = counter.labels('myType'); + + myCounter.inc(); + + const metrics = await r.metrics(); + const lines = metrics.split('\n'); + if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { + expect(lines).toContain( + 'my_counter_total{type="myType",env="development"} 1', + ); + } else { + expect(lines).toContain( + 'my_counter{type="myType",env="development"} 1', + ); + } + + myCounter.inc(); + + const metrics2 = await r.metrics(); + const lines2 = metrics2.split('\n'); + if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { + expect(lines2).toContain( + 'my_counter_total{type="myType",env="development"} 2', + ); + } else { + expect(lines2).toContain( + 'my_counter{type="myType",env="development"} 2', + ); + } }); - const counter = new Counter({ - name: 'my_counter', - help: 'my counter', - registers: [r], - labelNames: ['type'], - }); + it('should not throw with default labels (gauge)', async () => { + const r = new Registry(regType); + r.setDefaultLabels({ + env: 'development', + }); - const myCounter = counter.labels('myType'); + const gauge = new Gauge({ + name: 'my_gauge', + help: 'my gauge', + registers: [r], + labelNames: ['type'], + }); - myCounter.inc(); + const myGauge = gauge.labels('myType'); - const metrics = await r.metrics(); - const lines = metrics.split('\n'); - if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { - expect(lines).toContain( - 'my_counter_total{type="myType",env="development"} 1', - ); - } else { + myGauge.inc(1); + + const metrics = await r.metrics(); + const lines = metrics.split('\n'); expect(lines).toContain( - 'my_counter{type="myType",env="development"} 1', + 'my_gauge{type="myType",env="development"} 1', ); - } - myCounter.inc(); + myGauge.inc(2); - const metrics2 = await r.metrics(); - const lines2 = metrics2.split('\n'); - if (regType === Registry.OPENMETRICS_CONTENT_TYPE) { + const metrics2 = await r.metrics(); + const lines2 = metrics2.split('\n'); expect(lines2).toContain( - 'my_counter_total{type="myType",env="development"} 2', + 'my_gauge{type="myType",env="development"} 3', ); - } else { - expect(lines2).toContain( - 'my_counter{type="myType",env="development"} 2', - ); - } - }); - - it('should not throw with default labels (gauge)', async () => { - const r = new Registry(regType); - r.setDefaultLabels({ - env: 'development', }); - const gauge = new Gauge({ - name: 'my_gauge', - help: 'my gauge', - registers: [r], - labelNames: ['type'], - }); + it('should not throw with default labels (histogram)', async () => { + const r = new Registry(regType); + r.setDefaultLabels({ + env: 'development', + }); - const myGauge = gauge.labels('myType'); + const hist = new Histogram({ + name: 'my_histogram', + help: 'my histogram', + registers: [r], + labelNames: ['type'], + }); - myGauge.inc(1); + const myHist = hist.labels('myType'); - const metrics = await r.metrics(); - const lines = metrics.split('\n'); - expect(lines).toContain( - 'my_gauge{type="myType",env="development"} 1', - ); + myHist.observe(1); - myGauge.inc(2); + const metrics = await r.metrics(); + const lines = metrics.split('\n'); + expect(lines).toContain( + 'my_histogram_bucket{le="1",type="myType",env="development"} 1', + ); - const metrics2 = await r.metrics(); - const lines2 = metrics2.split('\n'); - expect(lines2).toContain( - 'my_gauge{type="myType",env="development"} 3', - ); - }); + myHist.observe(1); - it('should not throw with default labels (histogram)', async () => { - const r = new Registry(regType); - r.setDefaultLabels({ - env: 'development', + const metrics2 = await r.metrics(); + const lines2 = metrics2.split('\n'); + expect(lines2).toContain( + 'my_histogram_bucket{le="1",type="myType",env="development"} 2', + ); }); + }); - const hist = new Histogram({ - name: 'my_histogram', - help: 'my histogram', - registers: [r], - labelNames: ['type'], + describe('registry.getMetricsAsJSON()', () => { + it('should not throw with default labels (counter)', async () => { + const r = new Registry(regType); + r.setDefaultLabels({ + env: 'development', + }); + + const counter = new Counter({ + name: 'my_counter', + help: 'my counter', + registers: [r], + labelNames: ['type'], + }); + + const myCounter = counter.labels('myType'); + + myCounter.inc(); + + const metrics = await r.getMetricsAsJSON(); + expect(metrics).toContainEqual({ + aggregator: 'sum', + help: 'my counter', + name: 'my_counter', + type: 'counter', + values: [ + { + labels: { env: 'development', type: 'myType' }, + value: 1, + }, + ], + }); + + myCounter.inc(); + + const metrics2 = await r.getMetricsAsJSON(); + expect(metrics2).toContainEqual({ + aggregator: 'sum', + help: 'my counter', + name: 'my_counter', + type: 'counter', + values: [ + { + labels: { env: 'development', type: 'myType' }, + value: 2, + }, + ], + }); }); - const myHist = hist.labels('myType'); - - myHist.observe(1); - - const metrics = await r.metrics(); - const lines = metrics.split('\n'); - expect(lines).toContain( - 'my_histogram_bucket{le="1",type="myType",env="development"} 1', - ); - - myHist.observe(1); + it('should not throw with default labels (gauge)', async () => { + const r = new Registry(regType); + r.setDefaultLabels({ + env: 'development', + }); + + const gauge = new Gauge({ + name: 'my_gauge', + help: 'my gauge', + registers: [r], + labelNames: ['type'], + }); + + const myGauge = gauge.labels('myType'); + + myGauge.inc(1); + + const metrics = await r.getMetricsAsJSON(); + expect(metrics).toContainEqual({ + aggregator: 'sum', + help: 'my gauge', + name: 'my_gauge', + type: 'gauge', + values: [ + { + labels: { env: 'development', type: 'myType' }, + value: 1, + }, + ], + }); + + myGauge.inc(2); + + const metrics2 = await r.getMetricsAsJSON(); + expect(metrics2).toContainEqual({ + aggregator: 'sum', + help: 'my gauge', + name: 'my_gauge', + type: 'gauge', + values: [ + { + labels: { env: 'development', type: 'myType' }, + value: 3, + }, + ], + }); + }); - const metrics2 = await r.metrics(); - const lines2 = metrics2.split('\n'); - expect(lines2).toContain( - 'my_histogram_bucket{le="1",type="myType",env="development"} 2', - ); + it('should not throw with default labels (histogram)', async () => { + const r = new Registry(regType); + r.setDefaultLabels({ + env: 'development', + }); + + const hist = new Histogram({ + name: 'my_histogram', + help: 'my histogram', + registers: [r], + labelNames: ['type'], + }); + + const myHist = hist.labels('myType'); + + myHist.observe(1); + + const metrics = await r.getMetricsAsJSON(); + // NOTE: at this test we don't need to check exact JSON schema + expect(metrics[0].values).toContainEqual({ + exemplar: null, + labels: { env: 'development', le: 1, type: 'myType' }, + metricName: 'my_histogram_bucket', + value: 1, + }); + + myHist.observe(1); + + const metrics2 = await r.getMetricsAsJSON(); + // NOTE: at this test we don't need to check exact JSON schema + expect(metrics2[0].values).toContainEqual({ + exemplar: null, + labels: { env: 'development', le: 1, type: 'myType' }, + metricName: 'my_histogram_bucket', + value: 2, + }); + }); }); }); + }); - describe('registry.getMetricsAsJSON()', () => { - it('should not throw with default labels (counter)', async () => { - const r = new Registry(regType); - r.setDefaultLabels({ - env: 'development', - }); - - const counter = new Counter({ - name: 'my_counter', - help: 'my counter', - registers: [r], - labelNames: ['type'], - }); + describe('merging', () => { + const Registry = require('../lib/registry'); + let registryOne; + let registryTwo; - const myCounter = counter.labels('myType'); + beforeEach(() => { + registryOne = new Registry(regType); + registryTwo = new Registry(regType); + }); - myCounter.inc(); + it('should merge all provided registers', async () => { + registryOne.registerMetric(getMetric('one')); + registryTwo.registerMetric(getMetric('two')); - const metrics = await r.getMetricsAsJSON(); - expect(metrics).toContainEqual({ - aggregator: 'sum', - help: 'my counter', - name: 'my_counter', - type: 'counter', - values: [ - { - labels: { env: 'development', type: 'myType' }, - value: 1, - }, - ], - }); + const merged = await Registry.merge([ + registryOne, + registryTwo, + ]).getMetricsAsJSON(); + expect(merged).toHaveLength(2); + }); - myCounter.inc(); + it('should throw if same name exists on both registers', () => { + registryOne.registerMetric(getMetric()); + registryTwo.registerMetric(getMetric()); - const metrics2 = await r.getMetricsAsJSON(); - expect(metrics2).toContainEqual({ - aggregator: 'sum', - help: 'my counter', - name: 'my_counter', - type: 'counter', - values: [ - { - labels: { env: 'development', type: 'myType' }, - value: 2, - }, - ], - }); - }); + const fn = function () { + Registry.merge([registryOne, registryTwo]); + }; - it('should not throw with default labels (gauge)', async () => { - const r = new Registry(regType); - r.setDefaultLabels({ - env: 'development', - }); + expect(fn).toThrowError(Error); + }); - const gauge = new Gauge({ - name: 'my_gauge', - help: 'my gauge', - registers: [r], - labelNames: ['type'], - }); + it('should throw if merging different types of registers', () => { + registryOne.setContentType(Registry.PROMETHEUS_CONTENT_TYPE); + registryTwo.setContentType(Registry.OPENMETRICS_CONTENT_TYPE); - const myGauge = gauge.labels('myType'); + const fn = function () { + Registry.merge([registryOne, registryTwo]); + }; - myGauge.inc(1); + expect(fn).toThrowError( + 'Registers can only be merged if they have the same content type', + ); + }); + }); - const metrics = await r.getMetricsAsJSON(); - expect(metrics).toContainEqual({ - aggregator: 'sum', - help: 'my gauge', - name: 'my_gauge', - type: 'gauge', + function getMetric(name) { + name = name || 'test_metric'; + return { + name, + async get() { + return { + name, + type: 'counter', + help: 'A test metric', values: [ { - labels: { env: 'development', type: 'myType' }, - value: 1, + value: 12, + labels: { + label: 'hello', + code: '303', + }, }, - ], - }); - - myGauge.inc(2); - - const metrics2 = await r.getMetricsAsJSON(); - expect(metrics2).toContainEqual({ - aggregator: 'sum', - help: 'my gauge', - name: 'my_gauge', - type: 'gauge', - values: [ { - labels: { env: 'development', type: 'myType' }, - value: 3, + value: 34, + labels: { + label: 'bye', + code: '404', + }, }, ], - }); - }); - - it('should not throw with default labels (histogram)', async () => { - const r = new Registry(regType); - r.setDefaultLabels({ - env: 'development', - }); - - const hist = new Histogram({ - name: 'my_histogram', - help: 'my histogram', - registers: [r], - labelNames: ['type'], - }); - - const myHist = hist.labels('myType'); - - myHist.observe(1); - - const metrics = await r.getMetricsAsJSON(); - // NOTE: at this test we don't need to check exact JSON schema - expect(metrics[0].values).toContainEqual({ - exemplar: null, - labels: { env: 'development', le: 1, type: 'myType' }, - metricName: 'my_histogram_bucket', - value: 1, - }); - - myHist.observe(1); - - const metrics2 = await r.getMetricsAsJSON(); - // NOTE: at this test we don't need to check exact JSON schema - expect(metrics2[0].values).toContainEqual({ - exemplar: null, - labels: { env: 'development', le: 1, type: 'myType' }, - metricName: 'my_histogram_bucket', - value: 2, - }); - }); - }); - }); - }); - - describe('merging', () => { - const Registry = require('../lib/registry'); - let registryOne; - let registryTwo; - - beforeEach(() => { - registryOne = new Registry(regType); - registryTwo = new Registry(regType); - }); - - it('should merge all provided registers', async () => { - registryOne.registerMetric(getMetric('one')); - registryTwo.registerMetric(getMetric('two')); - - const merged = await Registry.merge([ - registryOne, - registryTwo, - ]).getMetricsAsJSON(); - expect(merged).toHaveLength(2); - }); - - it('should throw if same name exists on both registers', () => { - registryOne.registerMetric(getMetric()); - registryTwo.registerMetric(getMetric()); - - const fn = function () { - Registry.merge([registryOne, registryTwo]); + }; + }, }; - - expect(fn).toThrowError(Error); - }); + } }); - - function getMetric(name) { - name = name || 'test_metric'; - return { - name, - async get() { - return { - name, - type: 'counter', - help: 'A test metric', - values: [ - { - value: 12, - labels: { - label: 'hello', - code: '303', - }, - }, - { - value: 34, - labels: { - label: 'bye', - code: '404', - }, - }, - ], - }; - }, - }; - } }); diff --git a/test/summaryTest.js b/test/summaryTest.js index 3c3f8bf3..79c56462 100644 --- a/test/summaryTest.js +++ b/test/summaryTest.js @@ -3,9 +3,9 @@ const Registry = require('../index').Registry; describe.each([ - { tag: 'Prometheus', regType: Registry.PROMETHEUS_CONTENT_TYPE }, - { tag: 'OpenMetrics', regType: Registry.OPENMETRICS_CONTENT_TYPE }, -])('summary with $tag registry', ({ tag, regType }) => { + ['Prometheus', Registry.PROMETHEUS_CONTENT_TYPE], + ['OpenMetrics', Registry.OPENMETRICS_CONTENT_TYPE], +])('summary with %s registry', (tag, regType) => { const Summary = require('../index').Summary; const globalRegistry = require('../index').register; let instance; From 17830bc72d0174a4cea6ae83694748ed99c241c9 Mon Sep 17 00:00:00 2001 From: Karl O'Dwyer Date: Fri, 3 Mar 2023 14:37:16 +0000 Subject: [PATCH 3/6] Fix tests and address comments on PR: #482 --- example/exemplars.ts | 66 -------------------------------------------- 1 file changed, 66 deletions(-) delete mode 100644 example/exemplars.ts diff --git a/example/exemplars.ts b/example/exemplars.ts deleted file mode 100644 index 6301d122..00000000 --- a/example/exemplars.ts +++ /dev/null @@ -1,66 +0,0 @@ -import * as prom from '../index'; - -async function prometheusRegistry() { - let reg = new prom.Registry(); - - let counter = new prom.Counter({ - name: 'test_counter', - help: 'counter help message', - registers: [reg], - labelNames: ['code'], - }); - - let hist = new prom.Histogram({ - name: 'test_histogram', - help: 'histogram help message', - registers: [reg], - labelNames: ['code'], - }); - - counter.inc({ code: '300' }, 2); - hist.observe({ code: '200' }, 1); - - console.log(await reg.metrics()); -} - -async function openMetricsRegistry() { - let reg = new prom.Registry(); - reg.setContentType(prom.openMetricsContentType); - - let counter = new prom.Counter({ - name: 'test_counter', - help: 'counter help message', - registers: [reg], - labelNames: ['code'], - enableExemplars: true, - }); - - let hist = new prom.Histogram({ - name: 'test_histogram', - help: 'histogram help message', - registers: [reg], - labelNames: ['code'], - enableExemplars: true, - }); - - counter.inc(>{ - value: 2, - labels: { code: '300' }, - exemplarLabels: { traceID: 'traceA' }, - }); - - hist.observe(>{ - value: 1, - labels: { code: '200' }, - exemplarLabels: { traceID: 'traceA' }, - }); - - console.log(await reg.metrics()); -} - -async function main() { - prometheusRegistry(); - openMetricsRegistry(); -} - -main(); From ef89282fee80c2057672422c74d575cff237dc2f Mon Sep 17 00:00:00 2001 From: Karl O'Dwyer Date: Fri, 3 Mar 2023 14:38:14 +0000 Subject: [PATCH 4/6] Address comments and fix tests on PR: #482 --- index.d.ts | 5 +- lib/registry.js | 4 +- package.json | 2 +- test/__snapshots__/registerTest.js.snap | 76 ++++++++++++------------- test/__snapshots__/summaryTest.js.snap | 4 +- test/metrics/eventLoopLagTest.js | 4 +- test/pushgatewayTest.js | 5 +- test/registerTest.js | 15 +++-- 8 files changed, 56 insertions(+), 59 deletions(-) diff --git a/index.d.ts b/index.d.ts index 82dde536..0e4d0ae5 100644 --- a/index.d.ts +++ b/index.d.ts @@ -185,7 +185,10 @@ interface MetricConfiguration { name: string; help: string; labelNames?: T[] | readonly T[]; - registers?: Registry[]; + registers?: ( + | Registry + | Registry + )[]; aggregator?: Aggregator; collect?: CollectFunction; enableExemplars?: boolean; diff --git a/lib/registry.js b/lib/registry.js index 19f86dfb..2b151809 100644 --- a/lib/registry.js +++ b/lib/registry.js @@ -35,7 +35,7 @@ class Registry { regContentType !== Registry.PROMETHEUS_CONTENT_TYPE && regContentType !== Registry.OPENMETRICS_CONTENT_TYPE ) { - throw new TypeError('Content type unsupported'); + throw new TypeError(`Content type ${regContentType} is unsupported`); } this._contentType = regContentType; } @@ -225,7 +225,7 @@ class Registry { ) { this._contentType = metricsContentType; } else { - throw new Error('Content type unsupported'); + throw new Error(`Content type ${metricsContentType} is unsupported`); } } diff --git a/package.json b/package.json index b4692b8b..5386c330 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "typescript": "^4.0.2" }, "dependencies": { - "@opentelemetry/api": "^1.0.2", + "@opentelemetry/api": "^1.4.0", "tdigest": "^0.1.1" }, "types": "./index.d.ts", diff --git a/test/__snapshots__/registerTest.js.snap b/test/__snapshots__/registerTest.js.snap index 6088a851..000b1446 100644 --- a/test/__snapshots__/registerTest.js.snap +++ b/test/__snapshots__/registerTest.js.snap @@ -22,29 +22,29 @@ counter_total 0 gauge 0 # HELP histogram help # TYPE histogram histogram -histogram_bucket{le=\\"0.005\\"} 0 -histogram_bucket{le=\\"0.01\\"} 0 -histogram_bucket{le=\\"0.025\\"} 0 -histogram_bucket{le=\\"0.05\\"} 0 -histogram_bucket{le=\\"0.1\\"} 0 -histogram_bucket{le=\\"0.25\\"} 0 -histogram_bucket{le=\\"0.5\\"} 0 -histogram_bucket{le=\\"1\\"} 0 -histogram_bucket{le=\\"2.5\\"} 0 -histogram_bucket{le=\\"5\\"} 0 -histogram_bucket{le=\\"10\\"} 0 -histogram_bucket{le=\\"+Inf\\"} 0 +histogram_bucket{le="0.005"} 0 +histogram_bucket{le="0.01"} 0 +histogram_bucket{le="0.025"} 0 +histogram_bucket{le="0.05"} 0 +histogram_bucket{le="0.1"} 0 +histogram_bucket{le="0.25"} 0 +histogram_bucket{le="0.5"} 0 +histogram_bucket{le="1"} 0 +histogram_bucket{le="2.5"} 0 +histogram_bucket{le="5"} 0 +histogram_bucket{le="10"} 0 +histogram_bucket{le="+Inf"} 0 histogram_sum 0 histogram_count 0 # HELP summary help # TYPE summary summary -summary{quantile=\\"0.01\\"} 0 -summary{quantile=\\"0.05\\"} 0 -summary{quantile=\\"0.5\\"} 0 -summary{quantile=\\"0.9\\"} 0 -summary{quantile=\\"0.95\\"} 0 -summary{quantile=\\"0.99\\"} 0 -summary{quantile=\\"0.999\\"} 0 +summary{quantile="0.01"} 0 +summary{quantile="0.05"} 0 +summary{quantile="0.5"} 0 +summary{quantile="0.9"} 0 +summary{quantile="0.95"} 0 +summary{quantile="0.99"} 0 +summary{quantile="0.999"} 0 summary_sum 0 summary_count 0 # EOF @@ -77,30 +77,30 @@ gauge 0 # HELP histogram help # TYPE histogram histogram -histogram_bucket{le=\\"0.005\\"} 0 -histogram_bucket{le=\\"0.01\\"} 0 -histogram_bucket{le=\\"0.025\\"} 0 -histogram_bucket{le=\\"0.05\\"} 0 -histogram_bucket{le=\\"0.1\\"} 0 -histogram_bucket{le=\\"0.25\\"} 0 -histogram_bucket{le=\\"0.5\\"} 0 -histogram_bucket{le=\\"1\\"} 0 -histogram_bucket{le=\\"2.5\\"} 0 -histogram_bucket{le=\\"5\\"} 0 -histogram_bucket{le=\\"10\\"} 0 -histogram_bucket{le=\\"+Inf\\"} 0 +histogram_bucket{le="0.005"} 0 +histogram_bucket{le="0.01"} 0 +histogram_bucket{le="0.025"} 0 +histogram_bucket{le="0.05"} 0 +histogram_bucket{le="0.1"} 0 +histogram_bucket{le="0.25"} 0 +histogram_bucket{le="0.5"} 0 +histogram_bucket{le="1"} 0 +histogram_bucket{le="2.5"} 0 +histogram_bucket{le="5"} 0 +histogram_bucket{le="10"} 0 +histogram_bucket{le="+Inf"} 0 histogram_sum 0 histogram_count 0 # HELP summary help # TYPE summary summary -summary{quantile=\\"0.01\\"} 0 -summary{quantile=\\"0.05\\"} 0 -summary{quantile=\\"0.5\\"} 0 -summary{quantile=\\"0.9\\"} 0 -summary{quantile=\\"0.95\\"} 0 -summary{quantile=\\"0.99\\"} 0 -summary{quantile=\\"0.999\\"} 0 +summary{quantile="0.01"} 0 +summary{quantile="0.05"} 0 +summary{quantile="0.5"} 0 +summary{quantile="0.9"} 0 +summary{quantile="0.95"} 0 +summary{quantile="0.99"} 0 +summary{quantile="0.999"} 0 summary_sum 0 summary_count 0 " diff --git a/test/__snapshots__/summaryTest.js.snap b/test/__snapshots__/summaryTest.js.snap index 29729118..3ff9ef50 100644 --- a/test/__snapshots__/summaryTest.js.snap +++ b/test/__snapshots__/summaryTest.js.snap @@ -4,10 +4,10 @@ exports[`summary with OpenMetrics registry global registry with param as object exports[`summary with OpenMetrics registry global registry with param as object remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; -exports[`summary with OpenMetrics registry global registry with param as object should validate labels when observing 1`] = `"Added label \\"baz\\" is not included in initial labelset: [ 'foo' ]"`; +exports[`summary with OpenMetrics registry global registry with param as object should validate labels when observing 1`] = `"Added label "baz" is not included in initial labelset: [ 'foo' ]"`; exports[`summary with Prometheus registry global registry with param as object labels should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; exports[`summary with Prometheus registry global registry with param as object remove should throw error if label lengths does not match 1`] = `"Invalid number of arguments"`; -exports[`summary with Prometheus registry global registry with param as object should validate labels when observing 1`] = `"Added label \\"baz\\" is not included in initial labelset: [ 'foo' ]"`; +exports[`summary with Prometheus registry global registry with param as object should validate labels when observing 1`] = `"Added label "baz" is not included in initial labelset: [ 'foo' ]"`; diff --git a/test/metrics/eventLoopLagTest.js b/test/metrics/eventLoopLagTest.js index a8a0fa3c..7c347bb0 100644 --- a/test/metrics/eventLoopLagTest.js +++ b/test/metrics/eventLoopLagTest.js @@ -21,7 +21,7 @@ describe.each([ register.clear(); }); - it(`should add metric to the ${tag} registry`, async done => { + it(`should add metric to the ${tag} registry`, async () => { expect(await register.getMetricsAsJSON()).toHaveLength(0); eventLoopLag(); @@ -71,8 +71,6 @@ describe.each([ ); expect(metrics[7].type).toEqual('gauge'); expect(metrics[7].name).toEqual('nodejs_eventloop_lag_p99_seconds'); - - done(); }); }); diff --git a/test/pushgatewayTest.js b/test/pushgatewayTest.js index bcc6b06c..f61f480e 100644 --- a/test/pushgatewayTest.js +++ b/test/pushgatewayTest.js @@ -176,10 +176,7 @@ describe.each([ 'Content-Encoding': 'gzip', }, }) - .post( - '/metrics/job/testJob', - gzipSync('# HELP test test\n# TYPE test counter\ntest 100\n'), - ) + .post('/metrics/job/testJob', gzipSync(body)) .reply(200); instance = new Pushgateway( diff --git a/test/registerTest.js b/test/registerTest.js index 83c5b183..1e4623cf 100644 --- a/test/registerTest.js +++ b/test/registerTest.js @@ -4,20 +4,19 @@ const Registry = require('../index').Registry; const register = require('../index').register; describe('Register', () => { + const contentTypeTestStr = + 'application/openmetrics-text; version=42.0.0; charset=utf-8'; + const expectedContentTypeErrStr = `Content type ${contentTypeTestStr} is unsupported`; it('should throw if set to an unsupported type', () => { expect(() => { - register.setContentType( - 'application/openmetrics-text; version=42.0.0; charset=utf-8', - ); - }).toThrowError('Content type unsupported'); + register.setContentType(contentTypeTestStr); + }).toThrowError(expectedContentTypeErrStr); }); it('should throw if created with an unsupported type', () => { expect(() => { - new Registry( - 'application/openmetrics-text; version=42.0.0; charset=utf-8', - ); - }).toThrowError('Content type unsupported'); + new Registry(contentTypeTestStr); + }).toThrowError(expectedContentTypeErrStr); }); describe.each([ From 5aec2da210a8be022a5838758036fa15c0ccd7f0 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Sat, 4 Mar 2023 11:19:35 +0100 Subject: [PATCH 5/6] fix prettier error --- test/metrics/eventLoopLagTest.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/metrics/eventLoopLagTest.js b/test/metrics/eventLoopLagTest.js index 147a0e1f..7c347bb0 100644 --- a/test/metrics/eventLoopLagTest.js +++ b/test/metrics/eventLoopLagTest.js @@ -22,7 +22,6 @@ describe.each([ }); it(`should add metric to the ${tag} registry`, async () => { - expect(await register.getMetricsAsJSON()).toHaveLength(0); eventLoopLag(); From fb8237fb50eb1320af932aca7aabb244377247ce Mon Sep 17 00:00:00 2001 From: Karl O'Dwyer Date: Mon, 6 Mar 2023 14:14:20 +0000 Subject: [PATCH 6/6] Updated changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0a6f05c..f177c0f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ project adheres to [Semantic Versioning](http://semver.org/). ### Added +- Support for OpenMetrics and Exemplars + ## [14.1.1] - 2022-12-31 ### Changed