Skip to content

Commit

Permalink
Enforce stronger typing on registries
Browse files Browse the repository at this point in the history
  • Loading branch information
voltbit committed May 19, 2022
1 parent 2ffc25f commit d24e4fc
Show file tree
Hide file tree
Showing 39 changed files with 952 additions and 890 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
17 changes: 16 additions & 1 deletion example/exemplars.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
console.log(await omReg.metrics());
}

main();
29 changes: 23 additions & 6 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
// 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<RegistryContentType = PrometheusContentType> {
/**
* Get string representation for all metrics
*/
Expand Down Expand Up @@ -68,14 +85,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
Expand All @@ -94,17 +111,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 {
/**
Expand Down
9 changes: 6 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -19,7 +21,8 @@ exports.Summary = require('./lib/summary');
exports.Pushgateway = require('./lib/pushgateway');

exports.linearBuckets = require('./lib/bucketGenerators').linearBuckets;
exports.exponentialBuckets = require('./lib/bucketGenerators').exponentialBuckets;
exports.exponentialBuckets =
require('./lib/bucketGenerators').exponentialBuckets;

exports.collectDefaultMetrics = require('./lib/defaultMetrics');

Expand Down
4 changes: 1 addition & 3 deletions lib/cluster.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down
12 changes: 0 additions & 12 deletions lib/counter.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 0 additions & 12 deletions lib/histogram.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 11 additions & 3 deletions lib/metric.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

const { globalRegistry } = require('./registry');
const Registry = require('./registry');
const { isObject } = require('./util');
const { validateMetricName, validateLabelName } = require('./validation');

Expand All @@ -16,7 +16,7 @@ class Metric {
this,
{
labelNames: [],
registers: [globalRegistry],
registers: [Registry.globalRegistry],
aggregator: 'sum',
enableExemplars: false,
},
Expand All @@ -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');
Expand All @@ -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);
}
}
Expand Down
3 changes: 1 addition & 2 deletions lib/metrics/gc.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ module.exports = (registry, config = {}) => {
: DEFAULT_GC_DURATION_BUCKETS;
const gcHistogram = new Histogram({
name: namePrefix + NODEJS_GC_DURATION_SECONDS,
help:
'Garbage collection duration by kind, one of major, minor, incremental or weakcb.',
help: 'Garbage collection duration by kind, one of major, minor, incremental or weakcb.',
labelNames: ['kind', ...labelNames],
enableExemplars: false,
buckets,
Expand Down
3 changes: 1 addition & 2 deletions lib/metrics/processHandles.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ module.exports = (registry, config = {}) => {

new Gauge({
name: namePrefix + NODEJS_ACTIVE_HANDLES,
help:
'Number of active libuv handles grouped by handle type. Every handle type is C++ class name.',
help: 'Number of active libuv handles grouped by handle type. Every handle type is C++ class name.',
labelNames: ['type', ...labelNames],
registers,
collect() {
Expand Down
3 changes: 1 addition & 2 deletions lib/metrics/processRequests.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ module.exports = (registry, config = {}) => {

new Gauge({
name: namePrefix + NODEJS_ACTIVE_REQUESTS,
help:
'Number of active libuv requests grouped by request type. Every request type is C++ class name.',
help: 'Number of active libuv requests grouped by request type. Every request type is C++ class name.',
labelNames: ['type', ...labelNames],
registers: registry ? [registry] : undefined,
collect() {
Expand Down
3 changes: 1 addition & 2 deletions lib/metrics/processResources.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ module.exports = (registry, config = {}) => {

new Gauge({
name: namePrefix + NODEJS_ACTIVE_RESOURCES,
help:
'Number of active resources that are currently keeping the event loop alive, grouped by async resource type.',
help: 'Number of active resources that are currently keeping the event loop alive, grouped by async resource type.',
labelNames: ['type', ...labelNames],
registers: registry ? [registry] : undefined,
collect() {
Expand Down
32 changes: 22 additions & 10 deletions lib/registry.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use strict';

const { getValueAsString } = require('./util');

function escapeString(str) {
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"jest": "^26.0.1",
"lint-staged": "^10.0.4",
"nock": "^13.0.5",
"prettier": "2.0.5",
"prettier": "2.6.2",
"typescript": "^4.0.2"
},
"dependencies": {
Expand Down
16 changes: 8 additions & 8 deletions test/__snapshots__/counterTest.js.snap
Original file line number Diff line number Diff line change
@@ -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"`;
4 changes: 2 additions & 2 deletions test/__snapshots__/gaugeTest.js.snap
Original file line number Diff line number Diff line change
@@ -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"`;
16 changes: 8 additions & 8 deletions test/__snapshots__/histogramTest.js.snap
Original file line number Diff line number Diff line change
@@ -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"`;

0 comments on commit d24e4fc

Please sign in to comment.