artillery-plugin-publish-metrics
Version:
Publish metrics from your Artillery.io tests to external monitoring & observability systems
191 lines (164 loc) • 5.29 kB
JavaScript
const http = require('http');
const https = require('https');
const fs = require('fs');
const PromClient = require('prom-client');
const uuid = require('uuid');
const debug = require('debug')('plugin:publish-metrics:prometheus');
const { sleep } = require('./util');
const COUNTERS_STATS = 'counters', // counters stats
RATES_STATS = 'rates', // rates stats
SUMMARIES_STATS = 'summaries'; // summaries stats includes errors
class PrometheusReporter {
constructor(config, events) {
if (!config.pushgateway) {
throw new Error(
'Prometheus reporter: pushgateway must be provided. More info in the docs (https://docs.art/reference/extensions/publish-metrics#prometheus-pushgateway)'
);
}
this.hasPendingRequest = false;
this.workerID = process.env.WORKER_ID || uuid.v4();
this.config = Object.assign(
{
tags: [],
prefix: 'artillery'
},
config
);
this.prometheusOpts = {
pushgatewayUrl: config.pushgateway,
ca: config.ca
};
debug('setting default labels');
PromClient.register.setDefaultLabels(this.tagsToLabels(this.config.tags));
this.registerMetrics(this.config.prefix);
debug(
'creating pushgateway client using url: %s',
this.prometheusOpts.pushgatewayUrl
);
const httpModule = isHttps(this.prometheusOpts.pushgatewayUrl)
? https
: http;
this.pushgateway = new PromClient.Pushgateway(
this.prometheusOpts.pushgatewayUrl,
{
timeout: 5000, //Set the request timeout to 5000ms
ca: this.prometheusOpts.ca
? fs.readFileSync(this.prometheusOpts.ca)
: null,
agent: new httpModule.Agent({
keepAlive: true,
keepAliveMsec: 10000,
maxSockets: 10
})
}
);
debug('configure sending metrics to pushgateway');
this.sendMetrics(config, events);
debug('init done');
}
registerMetrics(prefix) {
this.countersStats =
PromClient.register.getSingleMetric(`${prefix}_${COUNTERS_STATS}`) ||
new PromClient.Counter({
name: `${prefix}_${COUNTERS_STATS}`,
help: 'counter based stats e.g.: core_vusers_created_total, engine.http.requests',
labelNames: ['metric']
});
this.ratesStats =
PromClient.register.getSingleMetric(`${prefix}_${RATES_STATS}`) ||
new PromClient.Gauge({
name: `${prefix}_${RATES_STATS}`,
help: 'rates based stats e.g.: engine_http_request_rate',
labelNames: ['metric']
});
this.summariesStats =
PromClient.register.getSingleMetric(`${prefix}_${SUMMARIES_STATS}`) ||
new PromClient.Gauge({
name: `${prefix}_${SUMMARIES_STATS}`,
help: 'summaries based stats e.g.: engine_http_response_time_min, engine_http_response_time_p999',
labelNames: ['metric']
});
debug('setupMeasurements');
}
tagsToLabels(tags) {
let labels = {};
tags.forEach((tag) => {
let parts = tag.split(':');
labels[parts[0]] = parts[1];
});
return labels;
}
toPrometheusKey(candidate) {
return candidate.replace(/\s|\./g, '_').toLowerCase();
}
sendMetrics(config, events) {
let that = this;
events.on('stats', (stats) => {
debug('On stats event: %O', stats);
if (stats[COUNTERS_STATS]) {
for (const cKey in stats[COUNTERS_STATS]) {
const transformed = that.toPrometheusKey(cKey);
this.countersStats.inc(
{ metric: transformed },
stats[COUNTERS_STATS][cKey]
);
}
}
if (stats[RATES_STATS]) {
for (const rKey in stats[RATES_STATS]) {
const transformed = that.toPrometheusKey(rKey);
this.ratesStats.set(
{ metric: transformed },
stats[RATES_STATS][rKey]
);
}
}
if (stats[SUMMARIES_STATS]) {
for (const sKey in stats[SUMMARIES_STATS]) {
let readings = stats[SUMMARIES_STATS][sKey];
for (const readingKey in readings) {
const transformed = `${that.toPrometheusKey(sKey)}_${readingKey}`;
this.summariesStats.set(
{ metric: transformed },
readings[readingKey]
);
}
}
}
// noinspection JSCheckFunctionSignatures
this.hasPendingRequest = true;
this.pushgateway
.pushAdd({ jobName: this.workerID.toString() })
.then(() => {
debug('metrics pushed successfully');
})
.catch((err) => {
console.log('Error pushing metrics to push gateway', err);
})
.finally(() => {
this.hasPendingRequest = false;
});
});
}
async waitingForRequest() {
do {
debug('Waiting for pending request ...');
await sleep(500);
} while (this.hasPendingRequest);
debug('Pending requests done');
return true;
}
cleanup(done) {
debug('cleaning up');
return this.waitingForRequest().then(done);
}
}
function createPrometheusReporter(config, events, script) {
return new PrometheusReporter(config, events, script);
}
function isHttps(href) {
return href.search(/^https/) !== -1;
}
module.exports = {
createPrometheusReporter
};