Skip to content

@metrics/client

A streaming metric producer. Allows producing counters, gauges, time series in a way that is independent of your metrics system so that you can produce metrics and let consumers decide how to consume them.

Additionally, you can pipe together different metrics streams before finally consuming them all in a single location.

Usage

Terminal window
npm install @metrics/client

First, instantiate a new client:

const Metrics = require('@metrics/client');
const client = new Metrics();

Next, use the client for instrumentation:

const counter = client.counter({
name: 'unique_metric_name',
description: 'Description of metric being collected',
});
counter.inc();

The client supports 4 types of metric creation use cases.

  • Counters are supported via the client.counter method
  • Gauges are supported via the client.gauge method
  • Histograms are supported via the client.histogram method
  • Summaries are supported via the client.summary method

Lastly, the metrics stream needs to be piped to a consumer:

client.pipe(consumer);

If you’re writing a library, you skip this step. Instead, expose the client so your users can pipe the metrics to their chosen consumer.

API

constructor(options)

Creates a new instance of the metrics client.

The Metrics instance inherit from Transform Stream. Due to this the instance also take all config parameters which the Transform Stream does. Please see the documentation of Transform Streams for further documentation.

options

namedescriptiontypedefault
idA optional unique identifier of the instance of the Object.stringhash

Example

const client = new Metrics(options);

return: Duplex Stream

instance methods

.counter(options)

Creates an instance of a Counter class which can be used to populate the metrics stream with counter metrics.

options

namedescriptiontypedefaultrequired
nameMetric name. valid characters: a-z,A-Z,0-9,_stringnulltrue
descriptionMetric descriptionstringnulltrue
labelsAvailable to be used to hold label data.objectnullfalse

return: Counter

Example

const client = new Metrics(options);
const counter = client.counter(options);
counter.inc(value|options, options)

Method that when called will populate the metrics stream with a counter increment.

namedescriptiontypedefaultrequired
valueValue to increment the counter byinteger1false
optionsObject that can be used to specify labelsobject{}false

Example

const counter = client.counter(options);
counter.inc(); // increment by 1
counter.inc(10); // increment by 10
counter.inc({ labels: { url: 'http://finn.no' } }); // increment by 1, specify labels
counter.inc(5, { labels: { url: 'http://finn.no' } }); // increment by 5, specify labels

.gauge(options)

Creates an instance of a Gauge class which can be used to populate the metrics stream with gauge metrics.

options

namedescriptiontypedefaultrequired
nameMetric name. valid characters: a-z,A-Z,0-9,_stringnulltrue
descriptionMetric descriptionstringnulltrue
labelsAvailable to be used to hold label data.objectnullfalse

Example

const client = new Metrics(options);
const gauge = client.gauge(options);
gauge.set(value, options)

Method that when called will populate the metrics stream with a gauge value.

namedescriptiontypedefaultrequired
valueValue to set the gauge tointegernulltrue
optionsObject that can be used to specify labelsobject{}false

Example

const gauge = client.gauge(options);
gauge.set(10); // set to 10
gauge.set(5, { labels: { url: 'http://finn.no' } }); // set to 5, specify labels

.histogram(options)

Creates an instance of a Histogram class which can be used to populate the metrics stream with histogram metrics.

options

namedescriptiontypedefaultrequired
nameMetric name. valid characters: a-z,A-Z,0-9,_stringnulltrue
descriptionMetric descriptionstringnulltrue
bucketsSet custom bucketsnumber[]nullfalse
labelsAvailable to be used to hold label data.objectnullfalse

Example

const client = new Metrics(options);
const histogram = client.histogram(options);
histogram.observe(value, options)

Method that when called will populate the metrics stream with a histogram value.

namedescriptiontypedefaultrequired
valueValue to set the gauge tointegernulltrue
optionsObject that can be used to specify labels and bucketsobject{}false

Example

const histogram = client.histogram(options);
histogram.observe(0.001); // observe value 0.001
histogram.observe(5, { labels: { url: 'http://finn.no' } }); // observe value 5, specify labels
histogram.observe(
0.01,
{ buckets: [0.0001, 0.001, 0.01, 0.1, 0.5, 1, 10, 100] }, // observe 0.01, set buckets
);
histogram.timer(options)

Method that when called will return an end function for use in measuring the time between 2 points

namedescriptiontypedefaultrequired
optionsObject that can be used to specify labels and bucketsobject{}false

Examples

const histogram = client.histogram(options);
const end = histogram.timer(); // start timer
// stuff happens
end();
const end = histogram.timer({ labels: { url: 'http://finn.no' } }); // start timer, set labels
// stuff happens
end();
const end = histogram.timer(); // start timer
// stuff happens
end({ labels: { url: 'http://finn.no' } }); // set labels in end function
const end = histogram.timer({
buckets: [0.0001, 0.001, 0.01, 0.1, 0.5, 1, 10, 100],
}); // start timer, set buckets
// stuff happens
end();

.summary(options)

Creates an instance of a Summary class which can be used to populate the metrics stream with summary metrics.

options

namedescriptiontypedefaultrequired
nameMetric name. valid characters: a-z,A-Z,0-9,_stringnulltrue
descriptionMetric descriptionstringnulltrue
quantilesSet custom quantilesnumber[]nullfalse
labelsAvailable to be used to hold label data.objectnullfalse

Example

const client = new Metrics(options);
const summary = client.summary(options);
summary.observe(value, options)

Method that when called will populate the metrics stream with a summary value.

namedescriptiontypedefaultrequired
valueValue to set the summary tointegernulltrue
optionsObject that can be used to specify labels and quantilesobject{}false

Example

const summary = client.summary(options);
summary.observe(0.001); // observe value 0.001
summary.observe(5, { labels: { url: 'http://finn.no' } }); // observe value 5, specify labels
summary.observe(
0.01,
{ quantiles: [0.001, 0.01, 0.5, 0.9, 0.99] }, // observe 0.01, use meta to specify quantile meta
);
summary.timer(options)

Method that when called will return an end function for use in measuring the time between 2 points

namedescriptiontypedefaultrequired
optionsObject that can be used to specify labels and quantilesobject{}false

Examples

const summary = client.summary(options);
const end = summary.timer(); // start timer
// stuff happens
end();
const end = summary.timer({ labels: { url: 'http://finn.no' } }); // start timer, set labels
// stuff happens
end();
const end = summary.timer(); // start timer
// stuff happens
end({ labels: { url: 'http://finn.no' } }); // set labels in end function
const end = summary.timer({ quantiles: [0.001, 0.01, 0.5, 0.9, 0.99] }); // start timer, set meta
// stuff happens
end();

.metric(options)

Collects a generic metric. As a minimum, a name and description for the metric must be provided.

options

namedescriptiontypedefaultrequired
nameMetric name. valid characters: a-z,A-Z,0-9,_stringnulltrue
descriptionMetric descriptionstringnulltrue
valueArbitrary value for the metric (used for gauges)string|numbernullfalse
metaAvailable to be used to hold any misc data.objectnullfalse
labelsAvailable to be used to hold label data.array[object]nullfalse

return: void

client.metric({
name: '',
description: '',
});

meta

meta can be used to hold any additional information you might wish to pass on to a consumer. It should be an object of keys and values.

client.metric({
name: 'my_metric',
description: 'My HTTP timing metric',
meta: {
quantiles: [0.01, 0.1, 0.5, 0.9, 0.99],
},
});

labels

labels can be used to pass on specific label metadata to a consumer. Examples of labels are the URL or method when timing HTTP requests.

Labels should be defined as an array of objects where each object has a name and value property. The name property describes the labels name and the value property describes the label’s actual value.

client.metric({
name: 'my_metric',
description: 'My HTTP timing metric',
labels: [
{ name: 'url', value: 'http://finn.no' },
{ name: 'method', value: 'get' },
],
});

.timer(options)

Starts a metric timer and returns and end function to be called when the measurement should be considered finished.

options

namedescriptiontypedefaultrequired
nameMetric name. valid characters: a-z,A-Z,0-9,_stringnulltrue
descriptionMetric descriptionstringnulltrue
valueArbitrary value for the metric (used for gauges)string|numbernullfalse
metaAvailable to be used to hold any misc dataobjectnullfalse

return: function Returns an end function (see below) to be used to indicate that the timer measurement is finished.

Example

const end = client.timer(options);
.end(options)

Stops a previously started timer, merges timers options with end options and and sets the measured time value.

options

namedescriptiontypedefaultrequired
nameMetric name. valid characters: a-z,A-Z,0-9,_stringnulltrue
descriptionMetric descriptionstringnulltrue
valueArbitrary value for the metric (used for gauges)string|numbernullfalse
metaAvailable to be used to hold any misc dataobjectnullfalse

return: void

Example

const end = client.timer(options);
// ... thing to be measured
end(options);

instance events

drop

Emitted when the client starts dropping metrics. Will emit the dropped metric.

Example

const client = new Metrics();
client.on('drop', (metric) => {
console.log('dropped metric', metric);
});

Examples

Counter

const counter = client.counter({
name: 'my_counter',
description: 'Counter description',
});
counter.inc();

Gauge

const gauge = client.gauge({
name: 'my_gauge',
description: 'Gauge description',
});
gauge.set(10);

Summary

const summary = client.summary({
name: 'my_summary',
description: 'Summary description',
});
summary.observe(0.123);

Summary using a timer

const summary = client.summary({
name: 'my_summary',
description: 'Summary description',
});
const end = summary.timer();
// ... time something
end();

Histogram

const histogram = client.histogram({
name: 'my_histogram',
description: 'Histogram description',
});
histogram.observe(0.123);

Histogram using a timer

const histogram = client.histogram({
name: 'my_histogram',
description: 'Histogram description',
});
const end = histogram.timer();
// ... time something
end();

Composing metric streams

One of the goals of @metrics/client is to allow any number of modules to produce their own metrics, not know about where they might be consumed.

This can be achieved by including and instantiating a @metrics/client client in each module, using it to create metrics and then exposing the client for consumption elsewhere.

Example

// module-1
const Metrics = require('@metrics/client');
const client = new Metrics();
const counter = client.counter({
name: 'my_counter',
description: 'Counter description',
});
counter.inc();
module.exports.metrics = client;
// module-2
const Metrics = require('@metrics/client');
const client = new Metrics();
const counter = client.counter({
name: 'my_counter',
description: 'Counter description',
});
counter.inc();
module.exports.metrics = client;
// consuming module
const module1 = require('module-1');
const module2 = require('module-2');
const consumer = require('some-consumer');
module1.pipe(consumer);
module2.pipe(consumer);

Metrics consumption

In order to consume metrics produced by @metrics/client you need to listen for data and use your favourite metrics client to convert our data format into something usable by your system of choice.

Example: Prometheus using prom-client

const { Counter } = require('prom-client');
const { Writable } = require('stream');
class Consumer extends Writable {
constructor() {
super({ objectMode: true });
this.counter = new Counter({
name: 'my_metric_counter',
help: 'Counts http request type things',
labelNames: ['url', 'method'],
});
}
_write(metric, enc, cb) {
let url;
let method;
metric.labels.forEach((obj) => {
if (obj.name === 'url') {
url = obj.value;
}
if (obj.name === 'method') {
method = obj.value;
}
});
this.counter.labels(url, method).inc(1);
cb();
}
}