lib/metrics/reporter.js (90 lines of code) (raw):
/*
* Copyright Elasticsearch B.V. and other contributors where applicable.
* Licensed under the BSD 2-Clause License; you may not use this file except in
* compliance with the BSD 2-Clause License.
*/
'use strict';
const afterAll = require('after-all-results');
const { Reporter } = require('measured-reporting');
const ObjectIdentityMap = require('object-identity-map');
class MetricsReporter extends Reporter {
constructor(agent, options = {}) {
super(options);
this.enabled = options.enabled;
this._agent = agent;
if (!this.enabled) {
this.shutdown();
}
}
_reportMetrics(metrics) {
if (!this.enabled) return;
const baseDimensions = {
timestamp: Date.now() * 1000,
tags: this._getDimensions(metrics),
};
const next = afterAll(() => {
const seen = new ObjectIdentityMap();
for (const metric of metrics) {
// Due to limitations in measured-reporting, metrics dropped
// due to `metricsLimit` leave empty slots in the list.
if (!metric) continue;
if (this._agent._isMetricNameDisabled(metric.name)) {
continue;
}
if (this.isStaleMetric(metric)) {
this.removeMetricFromRegistry(metric, this._registry);
continue;
}
const data = seen.ensure(metric.dimensions, () => {
const metricData = unflattenBreakdown(metric.dimensions);
const merged = Object.assign(
{ samples: {} },
baseDimensions,
metricData,
);
Object.assign(merged.tags, baseDimensions.tags, metricData.tags);
return merged;
});
data.samples[metric.name] = {
value: metric.metricImpl.toJSON(),
};
if (metric.metricImpl.constructor.name === 'Counter') {
metric.metricImpl.reset();
}
}
if (this._agent._apmClient) {
for (const metric of seen.values()) {
this._agent._apmClient.sendMetricSet(metric);
}
}
});
for (const collector of this._registry.collectors) {
collector.collect(next());
}
}
isStaleMetric(metric) {
// if a metric is a counting metric and that count is
// zero, then the metric is considered stale
if (metric.metricImpl && metric.metricImpl._count === 0) {
return true;
}
return false;
}
removeMetricFromRegistry(metric) {
if (!this._registry || !this._registry._metrics) {
return;
}
const key = this._registry._generateStorageKey(
metric.name,
metric.dimensions,
);
return this._registry._metrics.delete(key);
}
}
module.exports = MetricsReporter;
function unflattenBreakdown(source) {
const target = {
tags: {},
};
for (const [key, value] of Object.entries(source)) {
if (key.includes('::')) {
const [parent, child] = key.split('::');
if (!target[parent]) target[parent] = {};
target[parent][child] = value;
} else {
target.tags[key] = value;
}
}
return target;
}