Skip to content

Commit

Permalink
feat(metrics): added /metrics endpoint. Fixes MEMB-604
Browse files Browse the repository at this point in the history
  • Loading branch information
serge1peshcoff committed Sep 18, 2019
1 parent 0d2347e commit acfcfd9
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 0 deletions.
48 changes: 48 additions & 0 deletions lib/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,54 @@ exports.flattenObject = (obj, prefix = '') => {
}, {});
};

// A helper to count objects by a set of fields.
exports.countByFields = (array, keys) => {
const reduceFunc = (acc, val) => {
// finding element with such keys values
const accElement = acc.find((elementInAcc) => {
for (const key of keys) {
if (elementInAcc[key] !== val[key]) {
return false;
}
}

return true;
});

// if found, increment value, if not, adding another element
// to result array.
if (accElement) {
accElement.value += 1;
} else {
const elt = { value: 1 };
for (const key of keys) {
elt[key] = val[key];
}
acc.push(elt);
}

return acc;
};

return array.reduce(reduceFunc, []);
};

// A helper to add data to gauge Prometheus metric.
exports.addGaugeData = (gauge, array) => {
// reset gauge...
gauge.reset();

// and set it with values
for (const element of array) {
const {
value,
...data
} = element;

gauge.set(data, value);
}
};

// A helper uset to pretty-format values.
exports.beautify = (value) => {
// If it's boolean, display it as Yes/No instead of true/false
Expand Down
52 changes: 52 additions & 0 deletions lib/metrics.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const {
Gauge,
register
} = require('prom-client');

const {
Event,
Application
} = require('../models');
const helpers = require('./helpers');

const createGauge = (name, help, labels = []) => new Gauge({
name,
help,
labelNames: labels
});

const gaugesList = {
eventsTotal: createGauge('events_total', 'Total amount of events'),
eventsByType: createGauge('events_by_type', 'Amount of events by type', ['type']),
eventsByTypeAndStatus: createGauge('events_by_type_and_status', 'Amount of events by type and status', ['type', 'status']),
applicationsTotal: createGauge('applications_total', 'Total amount of applications'),
applicationsByEvent: createGauge('applications_by_event', 'Amount of applications by event', ['event_name']),
applicationsByEventAndStatus: createGauge('applications_by_event_and_status', 'Amount of applications by event and status', ['event_name', 'status']),
applicationsByEventAndBody: createGauge('applications_by_event_and_body', 'Amount of applications by event and body', ['event_name', 'body_name']),
};

exports.getMetrics = async (req, res) => {
let [
events,
applications,
] = await Promise.all([
Event.findAll(),
Application.findAll({ include: [Event] }),
]);

events = events.map((event) => event.toJSON());
applications = applications.map((application) => Object.assign(application.toJSON(), { event_name: application.event.name }));

// setting gauges with real data
helpers.addGaugeData(gaugesList.eventsTotal, helpers.countByFields(events, []));
helpers.addGaugeData(gaugesList.eventsByType, helpers.countByFields(events, ['type']));
helpers.addGaugeData(gaugesList.eventsByTypeAndStatus, helpers.countByFields(events, ['type', 'status']));

helpers.addGaugeData(gaugesList.applicationsTotal, helpers.countByFields(applications, []));
helpers.addGaugeData(gaugesList.applicationsByEvent, helpers.countByFields(applications, ['event_name']));
helpers.addGaugeData(gaugesList.applicationsByEventAndBody, helpers.countByFields(applications, ['event_name', 'body_name']));
helpers.addGaugeData(gaugesList.applicationsByEventAndStatus, helpers.countByFields(applications, ['event_name', 'status']));

res.set('Content-Type', register.contentType);
res.end(register.metrics());
};
2 changes: 2 additions & 0 deletions lib/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const applications = require('./applications'); // API middlewares for applicati
const imageserv = require('./imageserv');
const log = require('./logger');
const middlewares = require('./middlewares');
const metrics = require('./metrics');
const config = require('../config');

const EventsRouter = router({ mergeParams: true });
Expand Down Expand Up @@ -39,6 +40,7 @@ process.on('unhandledRejection', (err) => {
ImagesRouter.use(express.static(config.media_dir)); // Serving images.

GeneralRouter.get('/healthcheck', middlewares.healthcheck);
GeneralRouter.get('/metrics', metrics.getMetrics);
GeneralRouter.use(middlewares.authenticateUser);

GeneralRouter.get('/', events.listEvents);
Expand Down
21 changes: 21 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"node-schedule": "^1.3.2",
"node-xlsx": "^0.15.0",
"pg": "^7.12.1",
"prom-client": "^11.5.3",
"read-chunk": "^3.2.0",
"request": "^2.88.0",
"request-promise-native": "^1.0.7",
Expand Down
32 changes: 32 additions & 0 deletions test/api/metrics.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const { startServer, stopServer } = require('../../lib/server.js');
const { request } = require('../scripts/helpers');
const generator = require('../scripts/generator');
const mock = require('../scripts/mock-core-registry');

describe('Metrics requests', () => {
beforeEach(async () => {
mock.mockAll();
await startServer();
});

afterEach(async () => {
await stopServer();
mock.cleanAll();
});

test('should return data correctly', async () => {
const event = await generator.createEvent({});
await generator.createApplication({ user_id: 1, status: 'accepted' }, event);
await generator.createApplication({ user_id: 2, status: 'accepted' }, event);
await generator.createApplication({ user_id: 3, status: 'pending' }, event);


const res = await request({
uri: '/metrics',
method: 'GET',
json: false
});

expect(res.statusCode).toEqual(200);
});
});

0 comments on commit acfcfd9

Please sign in to comment.