statsd
Version:
Network daemon for the collection and aggregation of realtime application metrics
164 lines (137 loc) • 6.01 kB
JavaScript
/*jshint node:true, laxcomma:true */
const process_metrics = function (metrics, calculatedTimerMetrics, flushInterval, ts, flushCallback) {
const starttime = Date.now();
let key;
let counter_rates = {};
let timer_data = {};
let statsd_metrics = {};
const counters = metrics.counters;
const timers = metrics.timers;
const timer_counters = metrics.timer_counters;
const pctThreshold = metrics.pctThreshold;
const histogram = metrics.histogram;
for (key in counters) {
const value = counters[key];
// calculate "per second" rate
counter_rates[key] = value / (flushInterval / 1000);
}
for (key in timers) {
const current_timer_data = {};
if (timers[key].length > 0) {
timer_data[key] = {};
const values = timers[key].sort(function (a,b) { return a-b; });
const count = values.length;
const min = values[0];
const max = values[count - 1];
const cumulativeValues = [min];
const cumulSumSquaresValues = [min * min];
for (let i = 1; i < count; i++) {
cumulativeValues.push(values[i] + cumulativeValues[i-1]);
cumulSumSquaresValues.push((values[i] * values[i]) +
cumulSumSquaresValues[i - 1]);
}
let sum = min;
let sumSquares = min * min;
let mean = min;
let thresholdBoundary = max;
let key2;
for (key2 in pctThreshold) {
const pct = pctThreshold[key2];
let numInThreshold = count;
if (count > 1) {
numInThreshold = Math.round(Math.abs(pct) / 100 * count);
if (numInThreshold === 0) {
continue;
}
if (pct > 0) {
thresholdBoundary = values[numInThreshold - 1];
sum = cumulativeValues[numInThreshold - 1];
sumSquares = cumulSumSquaresValues[numInThreshold - 1];
} else {
thresholdBoundary = values[count - numInThreshold];
sum = cumulativeValues[count - 1] - cumulativeValues[count - numInThreshold - 1];
sumSquares = cumulSumSquaresValues[count - 1] -
cumulSumSquaresValues[count - numInThreshold - 1];
}
mean = sum / numInThreshold;
}
let clean_pct = '' + pct;
clean_pct = clean_pct.replace('.', '_').replace('-', 'top');
current_timer_data["count_" + clean_pct] = numInThreshold;
current_timer_data["mean_" + clean_pct] = mean;
current_timer_data[(pct > 0 ? "upper_" : "lower_") + clean_pct] = thresholdBoundary;
current_timer_data["sum_" + clean_pct] = sum;
current_timer_data["sum_squares_" + clean_pct] = sumSquares;
}
sum = cumulativeValues[count-1];
sumSquares = cumulSumSquaresValues[count-1];
mean = sum / count;
let sumOfDiffs = 0;
for (let i = 0; i < count; i++) {
sumOfDiffs += (values[i] - mean) * (values[i] - mean);
}
const mid = Math.floor(count/2);
const median = (count % 2) ? values[mid] : (values[mid-1] + values[mid])/2;
const stddev = Math.sqrt(sumOfDiffs / count);
current_timer_data["std"] = stddev;
current_timer_data["upper"] = max;
current_timer_data["lower"] = min;
current_timer_data["count"] = timer_counters[key];
current_timer_data["count_ps"] = timer_counters[key] / (flushInterval / 1000);
current_timer_data["sum"] = sum;
current_timer_data["sum_squares"] = sumSquares;
current_timer_data["mean"] = mean;
current_timer_data["median"] = median;
// note: values bigger than the upper limit of the last bin are ignored, by design
conf = histogram || [];
bins = [];
for (let i = 0; i < conf.length; i++) {
if (key.indexOf(conf[i].metric) > -1) {
bins = conf[i].bins;
break;
}
}
if(bins.length) {
current_timer_data['histogram'] = {};
}
// the outer loop iterates bins, the inner loop iterates timer values;
// within each run of the inner loop we should only consider the timer value range that's within the scope of the current bin
// so we leverage the fact that the values are already sorted to end up with only full 1 iteration of the entire values range
let i = 0;
for (let bin_i = 0; bin_i < bins.length; bin_i++) {
let freq = 0;
for (; i < count && (bins[bin_i] == 'inf' || values[i] < bins[bin_i]); i++) {
freq += 1;
}
bin_name = 'bin_' + bins[bin_i].toString().replace('.', '_');
current_timer_data['histogram'][bin_name] = freq;
}
} else {
current_timer_data["count"] = current_timer_data["count_ps"] = 0;
}
timer_data[key] = filter_timer_metrics(current_timer_data, calculatedTimerMetrics);
}
statsd_metrics["processing_time"] = (Date.now() - starttime);
//add processed metrics to the metrics_hash
metrics.counter_rates = counter_rates;
metrics.timer_data = timer_data;
metrics.statsd_metrics = statsd_metrics;
flushCallback(metrics);
};
var filter_timer_metrics = function (currentTimerMetrics, calculatedTimerMetrics = []) {
if (!Array.isArray(calculatedTimerMetrics) || calculatedTimerMetrics.length == 0) {
return currentTimerMetrics;
} else {
return Object.keys(currentTimerMetrics)
.filter((key) => {
// Generalizes filtering percent metrics by cleaning key from <metric>_<number> to <metric>_percent
let cleaned_key = key.replace(/_(top)?\d+$/, "_percent")
return calculatedTimerMetrics.includes(cleaned_key);
})
.reduce((obj, key) => {
obj[key] = currentTimerMetrics[key];
return obj;
}, {});
}
}
exports.process_metrics = process_metrics;