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 18, 2022
1 parent 2ffc25f commit 5e10b87
Show file tree
Hide file tree
Showing 38 changed files with 898 additions and 886 deletions.
2 changes: 1 addition & 1 deletion example/exemplars.js
Expand Up @@ -62,7 +62,7 @@ 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();
Expand Down
29 changes: 23 additions & 6 deletions index.d.ts
@@ -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
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
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
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
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
8 changes: 8 additions & 0 deletions lib/metric.js
Expand Up @@ -46,6 +46,14 @@ class Metric {
this.reset();

for (const register of this.registers) {
if (
this.enableExemplars &&
register.contentType === register.PROMETHEUS_CONTENT_TYPE
) {
throw new Error(
'Exemplars are supported only on OpenMetrics registries',
);
}
register.registerMetric(this);
}
}
Expand Down
3 changes: 1 addition & 2 deletions lib/metrics/gc.js
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
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
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
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
21 changes: 12 additions & 9 deletions lib/registry.js
@@ -1,4 +1,5 @@
'use strict';

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

function escapeString(str) {
Expand Down Expand Up @@ -209,21 +210,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
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
@@ -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
@@ -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
@@ -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 5e10b87

Please sign in to comment.