dl
Version:
DreamLab Libs
270 lines (203 loc) • 7.61 kB
JavaScript
var core = require('core');
var AbstractReporter = require('./AbstractReporter.js').AbstractReporter;
var CounterMetric = require('./metrics/CounterMetric.js').CounterMetric;
var GaugeMetric = require('./metrics/GaugeMetric.js').GaugeMetric;
var HistogramMetric = require('./metrics/HistogramMetric.js').HistogramMetric;
var OpalLoader = require('../opal/OpalLoader.js').OpalLoader;
var OpalRequest = require('../opal/OpalRequest.js').OpalRequest;
var Types = core.common.Types;
var SECOND = 1000;
/*
* How it works ?
* - start collecting metrics at 0:45 and collect for one minute (collect 0:45 - 1:45)
* - every second try to send outdated and current metrics - we have 15 seconds to do it on time
* - for current timestamp do not send metrics often than once every 15 seconds
*/
var MaasReporter = function () {
AbstractReporter.apply(this, arguments);
this._metrics = {};
this._metricsCount = {};
this._endpoint = MaasReporter.DEFAULTS.ENDPOINT;
this._maxSendAtOnce = MaasReporter.DEFAULTS.MAX_SEND_AT_ONCE;
this._maxMetricsCount = MaasReporter.DEFAULTS.MAX_METRICS;
this._maxRetries = MaasReporter.DEFAULTS.MAX_RETRIES;
this._sendInterval = MaasReporter.DEFAULTS.SEND_INTERVAL;
this._lastSend = {};
this._started = false;
};
MaasReporter.prototype = Object.create(AbstractReporter.prototype);
MaasReporter.prototype._send = function () {
var currentMinute = this._getMinute();
var allowedMinute = this._getMinute(Date.now());
if (!this._lastSend[currentMinute]) {
this._lastSend[currentMinute] = Date.now();
}
var minutes = Object.keys(this._metrics);
for (var i = 0, il = minutes.length; i < il; i++) {
var minute = parseInt(minutes[i]);
if (minute === currentMinute && ((Date.now() - this._lastSend[minute]) < this._sendInterval)) {
continue;
}
if (minute === allowedMinute) {
this._reportStats('maas.send.in-time');
} else {
this._reportStats('maas.send.late');
}
var metrics = this._metrics[minute];
var keysToSend = Object.keys(metrics);
while (keysToSend.length > 0) {
var keys = keysToSend.splice(0, this._maxSendAtOnce);
var metricsToSend = {};
for (var j = 0, jl = keys.length; j < jl; j++) {
var dump = metrics[keys[j]].dump();
for (var k = 0, jk = dump.length; k < jk; k++) {
metricsToSend[dump[k].key] = dump[k].value;
}
}
this._sendMetrics(metricsToSend);
}
this._lastSend[minute] = Date.now();
delete this._metrics[minute];
delete this._metricsCount[minute];
}
};
MaasReporter.prototype._sendMetrics = function (metrics, retry) {
var that = this;
retry = retry || 0;
if (retry > this._maxRetries) {
var metricsCount = Object.keys(metrics).length;
console.error('MaasReporter: send retry limit reached - drop', metricsCount, 'metrics');
this._reportStats('maas.metrics.dropped', metricsCount);
return;
}
var req = new OpalRequest({
'url': this._endpoint,
'params': {
'monitoring_key': this._config.key,
'metrics': metrics
},
'method': 'submit'
});
var loader = new OpalLoader(req);
loader.addEventListener(OpalLoader.Event.JSON_RESPONSE, function (e) {
if (e.data.getBody().isError()) {
console.error('MaasReporter: failed to report metrics to MaaS (%s): %s', retry, e.data.getBody().getError());
that._reportStats('maas.send.error');
that._sendMetrics(metrics, retry + 1);
}
}, this);
loader.addEventListener(OpalLoader.Event.HTTP_RESPONSE, function (e) {
console.error('MaasReporter: failed to report metrics to MaaS (%s), sc: %s', retry, e.data.getStatusCode());
that._reportStats('maas.send.error');
that._sendMetrics(metrics, retry + 1);
}, this);
loader.addEventListener(OpalLoader.Event.ERROR, function (e) {
console.error('MaasReporter: failed to report metrics to MaaS (%s), code: %s, err: %s', retry, e.code, e.message);
that._reportStats('maas.send.error');
that._sendMetrics(metrics, retry + 1);
}, this);
loader.setLogStatus(false);
loader.setTimeout(4 * SECOND);
loader.load();
};
MaasReporter.prototype._getMinute = function (timestamp) {
if (!timestamp) {
timestamp = Date.now() + 15 * SECOND;
}
var date = new Date(timestamp);
date.setMilliseconds(0);
date.setSeconds(0);
return date.getMinutes();
};
MaasReporter.prototype._getMetric = function (name) {
var minute = this._getMinute();
if (!this._metrics.hasOwnProperty(minute)) {
return null;
}
if (!this._metrics[minute].hasOwnProperty(name)) {
return null;
}
return this._metrics[minute][name];
};
MaasReporter.prototype._createMetric = function (name, value) {
var minute = this._getMinute();
if (!this._metrics.hasOwnProperty(minute)) {
this._metrics[minute] = {};
}
if (!this._metricsCount.hasOwnProperty(minute)) {
this._metricsCount[minute] = 0;
}
if (!this._started) {
this._start();
}
var metrics = this._metrics[minute];
if (!metrics.hasOwnProperty(name)) {
if (this._metricsCount[minute] >= this._maxMetricsCount) {
this._reportStats('maas.metrics.dropped');
return;
}
this._metricsCount[minute]++;
this._reportStats('maas.metrics.collected');
}
metrics[name] = value;
};
MaasReporter.prototype._start = function () {
var that = this;
setInterval(function () {
that._send();
}, SECOND);
this._started = true;
};
MaasReporter.prototype.setConfig = function () {
AbstractReporter.prototype.setConfig.apply(this, arguments);
if (this._config.hasOwnProperty('endpoint')) {
this._endpoint = this._config['endpoint'];
}
if (this._config.hasOwnProperty('maxSendAtOnce')) {
this._maxSendAtOnce = this._config['maxSendAtOnce'];
}
if (this._config.hasOwnProperty('maxMetrics')) {
this._maxMetricsCount = this._config['maxMetrics'];
}
if (this._config.hasOwnProperty('sendInterval')) {
this._sendInterval = this._config['sendInterval'];
}
if (this._config.hasOwnProperty('maxRetries')) {
this._maxRetries = this._config['maxRetries'];
}
return this;
};
MaasReporter.prototype.gauge = function (name, value, buckets) {
if (!Types.isNumber(value)) {
this._reportStats('maas.metrics.incorrect');
return this;
}
var metric = this._getMetric(name);
if (!metric) {
if (buckets) {
this._createMetric(name, new HistogramMetric(name, buckets).update(value));
} else {
this._createMetric(name, new GaugeMetric(name).update(value));
}
return this;
}
metric.update(value);
return this;
};
MaasReporter.prototype.counter = function (name) {
var metric = this._getMetric(name);
if (!metric) {
this._createMetric(name, new CounterMetric(name).update());
return this;
}
metric.update();
return this;
};
MaasReporter.DEFAULTS = {
ENDPOINT: 'llogd2-receiver.monitoring.onetapi.pl',
SEND_INTERVAL: 15 * SECOND,
MAX_SEND_AT_ONCE: 100,
MAX_METRICS: 10000,
MAX_RETRIES: 3
};
exports.MaasReporter = MaasReporter;