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 Nov 1, 2022
1 parent 97fc813 commit bd807c6
Show file tree
Hide file tree
Showing 35 changed files with 1,037 additions and 885 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({ labels: { code: '200' }, exemplarLabels: { traceId: 'traceA' } });
console.log(await omReg.metrics());
}

main();
66 changes: 66 additions & 0 deletions example/exemplars.ts
Original file line number Diff line number Diff line change
@@ -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<prom.OpenMetricsContentType>();
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(<prom.IncreaseDataWithExemplar<string>>{
value: 2,
labels: { code: '300' },
exemplarLabels: { traceID: 'traceA' },
});

hist.observe(<prom.ObserveDataWithExemplar<string>>{
value: 1,
labels: { code: '200' },
exemplarLabels: { traceID: 'traceA' },
});

console.log(await reg.metrics());
}

async function main() {
prometheusRegistry();
openMetricsRegistry();
}

main();
60 changes: 49 additions & 11 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -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<RegistryContentType = PrometheusContentType> {
/**
* Get string representation for all metrics
*/
Expand Down Expand Up @@ -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
Expand All @@ -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 {
/**
Expand Down Expand Up @@ -172,16 +185,29 @@ interface MetricConfiguration<T extends string> {
name: string;
help: string;
labelNames?: T[] | readonly T[];
registers?: Registry[];
registers?: Registry<PrometheusContentType | OpenMetricsContentType>[];
aggregator?: Aggregator;
collect?: CollectFunction<any>;
enableExemplars?: boolean;
}

export interface CounterConfiguration<T extends string>
extends MetricConfiguration<T> {
collect?: CollectFunction<Counter<T>>;
}

export interface IncreaseDataWithExemplar<T extends string> {
value?: number;
labels?: LabelValues<T>;
exemplarLabels?: LabelValues<T>;
}

export interface ObserveDataWithExemplar<T extends string> {
value: number;
labels?: LabelValues<T>;
exemplarLabels?: LabelValues<T>;
}

/**
* A counter is a cumulative metric that represents a single numerical value that only ever goes up
*/
Expand All @@ -204,6 +230,12 @@ export class Counter<T extends string = string> {
*/
inc(value?: number): void;

/**
* Increment with exemplars
* @param incData Object with labels, value and exemplars for an increase
*/
inc(incData: IncreaseDataWithExemplar<T>): void;

/**
* Return the child for given labels
* @param values Label values
Expand Down Expand Up @@ -408,6 +440,12 @@ export class Histogram<T extends string = string> {
*/
observe(labels: LabelValues<T>, value: number): void;

/**
* Observe with exemplars
* @param observeData Object with labels, value and exemplars for an observation
*/
observe(observeData: ObserveDataWithExemplar<T>): void;

/**
* Start a timer. Calling the returned function will observe the duration in
* seconds in the histogram.
Expand Down
6 changes: 4 additions & 2 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 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

0 comments on commit bd807c6

Please sign in to comment.