presets: add stats.js

This commit is contained in:
Micooz 2017-08-10 14:34:40 +08:00
parent 0f20f51428
commit c80dccd224

203
src/presets/stats.js Normal file

@ -0,0 +1,203 @@
import fs from 'fs';
import {IPreset, PROCESSING_FAILED} from './defs';
const now = () => (new Date()).getTime();
const DEFAULT_SAMPLE_INTERVAL = 30;
const DEFAULT_SAVE_INTERVAL = 60;
/**
* @description
* Perform statistics among traffic via this preset
*
* @params
* interval: The sample interval in seconds.
*
* @examples
* {
* "name": "stats",
* "params": {
* "save_to": "stats.json",
* "sample_interval": 1,
* "save_interval": 10
* }
* }
*/
export default class StatsPreset extends IPreset {
sampleInterval = DEFAULT_SAMPLE_INTERVAL; // seconds
saveInterval = DEFAULT_SAVE_INTERVAL; // seconds
static saveTo = '';
static sampleTimer = null;
static saveTimer = null;
static isHookExit = false;
// statistics
static startedAt = now();
static totalOut = 0;
static totalIn = 0;
static totalOutPackets = 0;
static totalInPackets = 0;
static totalErrors = 0;
static maxConnections = 0;
// calculated
static instantOutSpeed = 0;
static instantInSpeed = 0;
static maxOutSpeed = 0;
static maxInSpeed = 0;
// temporary
static tmpOut = 0;
static tmpIn = 0;
constructor(params) {
super();
const props = {
sample_interval: DEFAULT_SAMPLE_INTERVAL,
save_interval: DEFAULT_SAVE_INTERVAL,
...params
};
// save_to
if (typeof props.save_to !== 'string' || props.save_to.length < 1) {
throw Error('\'save_to\' must be provided as a non-empty string');
}
// sample_interval
if (typeof props.sample_interval === 'undefined') {
throw Error('\'sample_interval\' must be provided as an integer');
}
if (typeof props.sample_interval !== 'number') {
throw Error('\'sample_interval\' must be a number');
}
if (!Number.isSafeInteger(props.sample_interval)) {
throw Error('\'sample_interval\' must be an integer');
}
if (props.sample_interval < 1) {
throw Error('\'sample_interval\' must be greater than 0');
}
// save_interval
if (typeof props.save_interval === 'undefined') {
throw Error('\'save_interval\' must be provided as an integer');
}
if (typeof props.save_interval !== 'number') {
throw Error('\'save_interval\' must be a number');
}
if (!Number.isSafeInteger(props.save_interval)) {
throw Error('\'save_interval\' must be an integer');
}
if (props.save_interval < 1) {
throw Error('\'save_interval\' must be greater than 0');
}
StatsPreset.saveTo = props.save_to;
this.sampleInterval = props.sample_interval;
this.saveInterval = props.save_interval;
// timers
if (StatsPreset.sampleTimer === null) {
StatsPreset.sampleTimer = setInterval(this.sample.bind(this), props.sample_interval * 1e3);
}
if (StatsPreset.saveTimer === null) {
StatsPreset.saveTimer = setInterval(StatsPreset.save, props.save_interval * 1e3);
}
// exit hook
if (!StatsPreset.isHookExit) {
process.on('SIGINT', StatsPreset.save);
StatsPreset.isHookExit = true;
}
StatsPreset.maxConnections += 1;
}
static save() {
const startedAt = StatsPreset.startedAt;
const endAt = now();
const durationMilliSec = endAt - startedAt;
const durationSec = durationMilliSec / 1e3;
const totalPackets = StatsPreset.totalInPackets + StatsPreset.totalOutPackets;
const totalBytes = StatsPreset.totalIn + StatsPreset.totalOut;
const json = {
sample: {
from: startedAt,
to: endAt,
duration: durationMilliSec
},
summary: {
totalErrors: StatsPreset.totalErrors,
totalOut: StatsPreset.totalOut,
totalIn: StatsPreset.totalIn,
totalOutPackets: StatsPreset.totalOutPackets,
totalInPackets: StatsPreset.totalInPackets,
totalBytes: totalBytes,
totalPackets: totalPackets,
maxOutSpeed: StatsPreset.maxOutSpeed,
maxInSpeed: StatsPreset.maxInSpeed,
maxConnections: StatsPreset.maxConnections - 2
},
instant: {
outSpeed: StatsPreset.instantOutSpeed,
inSpeed: StatsPreset.instantInSpeed,
errorRate: totalPackets.length > 0 ? StatsPreset.totalErrors / totalPackets : 0,
outBytesRate: StatsPreset.totalOut / durationSec,
outPacketsRate: StatsPreset.totalOutPackets / durationSec,
inBytesRate: StatsPreset.totalIn / durationSec,
inPacketsRate: StatsPreset.totalInPackets / durationSec,
totalBytesRate: totalBytes / durationSec,
totalPacketsRate: totalPackets / durationSec
},
process: {
upTime: process.uptime(),
cpuUsage: process.cpuUsage(),
memoryUsage: process.memoryUsage()
}
};
fs.writeFileSync(StatsPreset.saveTo, JSON.stringify(json, null, ' '));
}
sample() {
StatsPreset.instantOutSpeed = StatsPreset.tmpOut / this.sampleInterval;
StatsPreset.instantInSpeed = StatsPreset.tmpIn / this.sampleInterval;
StatsPreset.maxOutSpeed = Math.max(StatsPreset.maxOutSpeed, StatsPreset.instantOutSpeed);
StatsPreset.maxInSpeed = Math.max(StatsPreset.maxInSpeed, StatsPreset.instantInSpeed);
StatsPreset.tmpOut = 0;
StatsPreset.tmpIn = 0;
}
onNotified(action) {
if (action.type === PROCESSING_FAILED) {
StatsPreset.totalErrors += 1;
}
}
beforeOut({buffer}) {
StatsPreset.totalOut += buffer.length;
StatsPreset.totalOutPackets += 1;
StatsPreset.tmpOut += buffer.length;
return buffer;
}
beforeIn({buffer}) {
StatsPreset.totalIn += buffer.length;
StatsPreset.totalInPackets += 1;
StatsPreset.tmpIn += buffer.length;
return buffer;
}
}