Skip to content

Instantly share code, notes, and snippets.

@icebob
Created June 15, 2018 09:35
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save icebob/99dc388ee29ae165f879233c2a9faf63 to your computer and use it in GitHub Desktop.
Save icebob/99dc388ee29ae165f879233c2a9faf63 to your computer and use it in GitHub Desktop.
Old $node.stats code as a service
"use strict";
const defaultsDeep = require("lodash/defaultsDeep");
/**
*
*
* @class StatRequestStore
*/
class StatRequestStore {
/**
* Creates an instance of StatRequestStore.
*
* @param {any} name
* @param {any} bucketCount
*
* @memberof StatRequestStore
*/
constructor(name, bucketCount) {
this.name = name;
this.dirty = false;
this.count = 0;
this.errors = {};
this.maxBucketCount = bucketCount;
this.timeBuckets = [];
this.cycle();
this.stat = null;
}
/**
*
*
* @param {any} latency
* @param {any} errCode
*
* @memberof StatRequestStore
*/
append(latency, errCode) {
this.dirty = true;
this.cycleCount++;
this.count++;
if (errCode)
this.errors[errCode] = (this.errors[errCode] || 0) + 1;
if (latency != null)
this.lastTimeBucket.times.push(latency);
}
/**
*
*
*
* @memberof StatRequestStore
*/
cycle() {
this.lastTimeBucket = {
time: Date.now(),
times: [],
rps: null
};
this.timeBuckets.push(this.lastTimeBucket);
if (this.timeBuckets.length > this.maxBucketCount)
this.timeBuckets.shift();
this.firstBucketTime = this.timeBuckets[0].time;
this.dirty = true;
this.calculate();
}
/**
*
*
* @returns
*
* @memberof StatRequestStore
*/
calculateRps() {
const now = Date.now();
let totalCount = 0;
let values = [];
this.timeBuckets.forEach((bucket, i) => {
totalCount = totalCount + bucket.times.length;
if (bucket.rps == null && i < this.timeBuckets.length - 1 ) {
const endTime = this.timeBuckets[i + 1].time; // Next bucket start time
bucket.rps = bucket.times.length / ((endTime - bucket.time) / 1000);
}
if (bucket.rps != null)
values.push(bucket.rps);
});
let current;
if (now - this.firstBucketTime > 0)
current = (totalCount / ((now - this.firstBucketTime) / 1000));
return {
current,
values
};
}
/**
*
*
* @returns
*
* @memberof StatRequestStore
*/
calculate() {
if (this.dirty || !this.stat) {
let stat = {
count: this.count,
errors: Object.assign({}, this.errors),
rps: this.calculateRps()
};
const times = this.timeBuckets.reduce((a, b) => a.concat(b.times), []);
if (times.length > 0) {
// Calculate latencies
times.sort((a, b) => a - b);
stat.latency = {
mean: times.reduce((a, b) => a + b, 0) / times.length,
median: times[Math.ceil(0.5 * times.length) - 1],
"90th": times[Math.ceil(0.9 * times.length) - 1],
"95th": times[Math.ceil(0.95 * times.length) - 1],
"99th": times[Math.ceil(0.99 * times.length) - 1],
"99.5th": times[Math.ceil(0.995 * times.length) - 1]
};
}
this.stat = stat;
this.dirty = false;
} else {
// Calculate req/sec
/* istanbul ignore next */
this.stat.rps = this.calculateRps();
}
return this.stat;
}
/**
*
*
* @returns
*
* @memberof StatRequestStore
*/
snapshot() {
if (!this.stat)
return this.calculate();
return this.stat;
}
}
/**
*
*
* @class RequestStatistics
*/
class RequestStatistics {
/**
* Creates an instance of RequestStatistics.
*
* @param {any} options
*
* @memberof RequestStatistics
*/
constructor(options) {
this.options = defaultsDeep({}, options, {
cycleTime: 5 * 1000,
bucketCount: 12
});
this.total = new StatRequestStore("total", this.options.bucketCount);
this.actions = new Map();
this.cycleTimer = setInterval(() => {
this.cycle();
}, this.options.cycleTime);
this.cycleTimer.unref();
}
/**
*
*
* @param {any} actionName
* @param {any} latency
* @param {any} errCode
*
* @memberof RequestStatistics
*/
append(actionName, latency, errCode) {
this.total.append(latency, errCode);
if (actionName) {
if (!this.actions.has(actionName))
this.actions.set(actionName, new StatRequestStore(actionName, this.options.bucketCount));
this.actions.get(actionName).append(latency, errCode);
}
}
/**
*
*
*
* @memberof RequestStatistics
*/
cycle() {
this.total.cycle();
this.actions.forEach(item => item.cycle());
}
/**
*
*
* @returns
*
* @memberof RequestStatistics
*/
snapshot() {
let snapshot = {
total: this.total.snapshot(),
actions: {}
};
this.actions.forEach((item, name) => snapshot.actions[name] = item.snapshot());
return snapshot;
}
}
module.exports = {
name: "stat",
actions: {
snapshot(ctx) {
return this.snapshot(ctx.params);
}
},
events: {
"metrics.trace.span.finish"(payload) {
if (payload.error)
this.requests.append(payload.action.name, payload.duration, payload.error.code || 500);
else
this.requests.append(payload.action.name, payload.duration);
}
},
methods: {
snapshot(opts) {
return {
requests: this.requests.snapshot(opts)
};
}
},
created() {
this.requests = new RequestStatistics(defaultsDeep({}, this.broker.options));
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment