Skip to content

Commit

Permalink
feat: add prometheusRegistry option (#332)
Browse files Browse the repository at this point in the history
Added a option prometheusRegistry that allows to pass a existing
prom-client registry.
  • Loading branch information
SerayaEryn authored and lance committed Jun 18, 2019
1 parent 454e4e4 commit 0056cdc
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 19 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,27 @@ app.use('/metrics', (req, res) => {
});
```

The `prometheusRegistry` option allows to provide a existing
[prom-client](https://github.com/siimon/prom-client) registry.
The metrics about the circuit will be added to the provided registry instead
of the global registry.
The [default metrics](https://github.com/siimon/prom-client#default-metrics)
will not be added to the provided registry.

```js
const opossum = require('opossum');
const { Registry } = require('prom-client');

// Create a registry
const prometheusRegistry = new Registry();

// create a circuit
const circuit = opossum(functionThatMightFail, {
usePrometheus: true,
prometheusRegistry
});
```

#### Hystrix

**NOTE: Hystrix metrics are deprecated**
Expand Down
5 changes: 4 additions & 1 deletion lib/circuit.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,10 @@ class CircuitBreaker extends EventEmitter {

// Add Prometheus metrics if not running in a web env
if (PrometheusMetrics && options.usePrometheus) {
this[PROMETHEUS_METRICS] = new PrometheusMetrics(this);
this[PROMETHEUS_METRICS] = new PrometheusMetrics(
this,
options.prometheusRegistry
);
}
}

Expand Down
16 changes: 10 additions & 6 deletions lib/prometheus-metrics.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,23 @@ function normalizePrefix(prefixName) {
}

class PrometheusMetrics {
constructor (circuit) {
constructor (circuit, registry) {
this.circuit = circuit;
this._registry = registry || client.register
this._client = client;
this.counters = [];
const prefix = normalizePrefix(this.circuit.name);

this.interval = this._client
.collectDefaultMetrics({ prefix, timeout: 5000 });
if (!registry) {
this.interval = this._client
.collectDefaultMetrics({ prefix, timeout: 5000 });
}

for (let eventName of this.circuit.eventNames()) {
const counter = new this._client.Counter({
name: `${prefix}${eventName}`,
help: `A count of the ${circuit.name} circuit's ${eventName} event`
help: `A count of the ${circuit.name} circuit's ${eventName} event`,
registers: [this._registry]
});
this.circuit.on(eventName, _ => {
counter.inc();
Expand All @@ -37,11 +41,11 @@ class PrometheusMetrics {

clear () {
clearInterval(this.interval);
this._client.register.clear();
this._registry.clear();
}

get metrics () {
return this._client.register.metrics();
return this._registry.metrics();
}

get client () {
Expand Down
72 changes: 60 additions & 12 deletions test/prometheus-test.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,69 @@
'use strict';

const test = require('tape');
const cb = require('../');
const circuitBreaker = require('../');
const { passFail } = require('./common');
const client = require('prom-client');

const { Registry } = client;

test('Factory metrics func does not fail if no circuits yet', t => {
t.plan(1);
t.equal(cb.metrics(), undefined);
t.equal(circuitBreaker.metrics(), undefined);
t.end();
});

test('A circuit provides prometheus metrics when not in a web env', t => {
t.plan(1);
const circuit = cb(passFail, {usePrometheus: true});
const circuit = circuitBreaker(passFail, {usePrometheus: true});
t.ok(process.env.WEB ? circuit.metrics : !!circuit.metrics);
circuit.metrics.clear();
t.end();
});

test('Does not load Prometheus when the option is not provided', t => {
t.plan(1);
const circuit = cb(passFail);
const circuit = circuitBreaker(passFail);
t.ok(!circuit.metrics);
circuit.shutdown();
t.end();
});

test('The factory function provides access to metrics for all circuits', t => {
t.plan(4);
const c1 = cb(passFail, { usePrometheus: true, name: 'fred' });
const c2 = cb(passFail, { usePrometheus: true, name: 'bob' });
const c1 = circuitBreaker(passFail, { usePrometheus: true, name: 'fred' });
const c2 = circuitBreaker(passFail, { usePrometheus: true, name: 'bob' });
t.equal(c1.name, 'fred');
t.equal(c2.name, 'bob');
t.ok(/circuit_fred_/.test(circuitBreaker.metrics()));
t.ok(/circuit_bob_/.test(circuitBreaker.metrics()));
t.end();
});

test('The factory function uses a custom prom-client registry', t => {
t.plan(4);
const registry = new Registry();
const c1 = circuitBreaker(passFail, {
usePrometheus: true,
name: 'fred',
prometheusRegistry: registry
});
const c2 = circuitBreaker(passFail, {
usePrometheus: true,
name: 'bob',
prometheusRegistry: registry
});
t.equal(c1.name, 'fred');
t.equal(c2.name, 'bob');
t.ok(/circuit_fred_/.test(cb.metrics()));
t.ok(/circuit_bob_/.test(cb.metrics()));
t.ok(/circuit_fred_/.test(registry.metrics()));
t.ok(/circuit_bob_/.test(registry.metrics()));
t.end();
});

// All of the additional tests only make sense when running in a Node.js context
if (!process.env.WEB) {
test('Circuit fire/success/failure are counted', t => {
const circuit = cb(passFail, {usePrometheus: true});
const circuit = circuitBreaker(passFail, {usePrometheus: true});
const fire = /circuit_passFail_fire 2/;
const success = /circuit_passFail_success 1/;
const failure = /circuit_passFail_failure 1/;
Expand All @@ -59,7 +82,7 @@ if (!process.env.WEB) {
});

test('Metrics are enabled for all circuit events', t => {
const circuit = cb(passFail, {usePrometheus: true});
const circuit = circuitBreaker(passFail, {usePrometheus: true});
const metrics = circuit.metrics.metrics;
t.plan(circuit.eventNames().length);
for (let name of circuit.eventNames()) {
Expand All @@ -71,7 +94,7 @@ if (!process.env.WEB) {
});

test('Default prometheus metrics are enabled', t => {
const circuit = cb(passFail, {usePrometheus: true});
const circuit = circuitBreaker(passFail, {usePrometheus: true});
const metrics = circuit.metrics.metrics;
const names = [
'process_cpu_seconds_total',
Expand All @@ -91,8 +114,33 @@ if (!process.env.WEB) {
t.end();
});

test('Should not add default metrics to custom registry', t => {
const registry = new Registry();
const circuit = circuitBreaker(passFail, {
usePrometheus: true,
prometheusRegistry: registry
});
const metrics = circuit.metrics.metrics;
const names = [
'process_cpu_seconds_total',
'process_open_fds',
'process_max_fds',
'process_virtual_memory_bytes',
'process_resident_memory_bytes',
'process_heap_bytes',
'process_start_time_seconds'
];
t.plan(names.length);
for (let name of names) {
const match = new RegExp(`circuit_passFail_${name}`);
t.notOk(match.test(metrics), name);
}
circuit.metrics.clear();
t.end();
});

test('Node.js specific metrics are enabled', t => {
const circuit = cb(passFail, {usePrometheus: true});
const circuit = circuitBreaker(passFail, {usePrometheus: true});
const metrics = circuit.metrics.metrics;
const names = [
'nodejs_eventloop_lag',
Expand Down

0 comments on commit 0056cdc

Please sign in to comment.