193 lines
5.1 KiB
JavaScript
193 lines
5.1 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
/* eslint no-unused-vars: 0 */
|
||
|
|
||
|
/**
|
||
|
* Parsed response
|
||
|
* @typedef {object} PingResponse
|
||
|
* @param {string} inputHost - The input IP address or HOST
|
||
|
* @param {string} host - The input IP address or HOST
|
||
|
* @param {string} numeric_host - Target IP address
|
||
|
* @param {boolean} alive - True for existed host
|
||
|
* @param {string} output - Raw stdout from system ping
|
||
|
* @param {number} time - Time (float) in ms for first successful ping response
|
||
|
* @param {string} min - Minimum time for collection records
|
||
|
* @param {string} max - Maximum time for collection records
|
||
|
* @param {string} avg - Average time for collection records
|
||
|
* @param {number} packetLoss - Packet Losses in percent (number)
|
||
|
* @param {string} stddev - Standard deviation time for collected records
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* @constructor
|
||
|
* @param {string} addr - Hostname or ip addres
|
||
|
* @param {PingConfig} config - Config object in probe()
|
||
|
*/
|
||
|
function parser(addr, config) {
|
||
|
// Initial state is 0
|
||
|
this._state = 0;
|
||
|
|
||
|
// Initial cache value
|
||
|
this._response = {
|
||
|
inputHost: addr,
|
||
|
host: 'unknown',
|
||
|
alive: false,
|
||
|
output: 'unknown',
|
||
|
time: 'unknown',
|
||
|
times: [],
|
||
|
min: 'unknown',
|
||
|
max: 'unknown',
|
||
|
avg: 'unknown',
|
||
|
stddev: 'unknown',
|
||
|
packetLoss: 'unknown',
|
||
|
};
|
||
|
|
||
|
// Initial times storage for ping time
|
||
|
this._times = [];
|
||
|
|
||
|
// Initial lines storage for ping output
|
||
|
this._lines = [];
|
||
|
|
||
|
// strip string regexp
|
||
|
this._stripRegex = /[ ]*\r?\n?$/g;
|
||
|
|
||
|
// Ping Config
|
||
|
this._pingConfig = config || {};
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Enum for parser states
|
||
|
* @readonly
|
||
|
* @enum {number}
|
||
|
*/
|
||
|
parser.prototype.STATES = {
|
||
|
INIT: 0,
|
||
|
HEADER: 1,
|
||
|
BODY: 2,
|
||
|
FOOTER: 3,
|
||
|
END: 4,
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Change state of this parser
|
||
|
* @param {number} state - parser.STATES
|
||
|
* @return {this} - This instance
|
||
|
*/
|
||
|
parser.prototype._changeState = function (state) {
|
||
|
// var states = Object.values(this.STATES); // If minimum engine version can be raised to >=7.0.0 in package.json
|
||
|
var states = Object.keys(this.STATES).map(function (key) { return this.STATES[key]; }, this);
|
||
|
if (states.indexOf(state) < 0) {
|
||
|
throw new Error('Unknown state');
|
||
|
}
|
||
|
|
||
|
this._state = state;
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Process output's header
|
||
|
* @param {string} line - A line from system ping
|
||
|
*/
|
||
|
parser.prototype._processHeader = function (line) {
|
||
|
throw new Error('Subclass should implement this method');
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Process output's body
|
||
|
* @param {string} line - A line from system ping
|
||
|
*/
|
||
|
parser.prototype._processBody = function (line) {
|
||
|
throw new Error('Subclass should implement this method');
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Process output's footer
|
||
|
* @param {string} line - A line from system ping
|
||
|
*/
|
||
|
parser.prototype._processFooter = function (line) {
|
||
|
throw new Error('Subclass should implement this method');
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Process a line from system ping
|
||
|
* @param {string} line - A line from system ping
|
||
|
* @return {this} - This instance
|
||
|
*/
|
||
|
parser.prototype.eat = function (line) {
|
||
|
var headerStates = [this.STATES.INIT, this.STATES.HEADER];
|
||
|
|
||
|
// Store lines
|
||
|
this._lines.push(line);
|
||
|
|
||
|
// Strip all space \r\n at the end
|
||
|
var _line = line.replace(this._stripRegex, '');
|
||
|
|
||
|
if (_line.length === 0) {
|
||
|
// Do nothing if this is an empty line
|
||
|
} else if (headerStates.indexOf(this._state) >= 0) {
|
||
|
this._processHeader(_line);
|
||
|
} else if (this._state === this.STATES.BODY) {
|
||
|
this._processBody(_line);
|
||
|
} else if (this._state === this.STATES.FOOTER) {
|
||
|
this._processFooter(_line);
|
||
|
} else if (this._state === this.STATES.END) {
|
||
|
// Do nothing
|
||
|
} else {
|
||
|
throw new Error('Unknown state');
|
||
|
}
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Get results after parsing certain lines from system ping
|
||
|
* @return {PingResponse} - Response from parsing ping output
|
||
|
*/
|
||
|
parser.prototype.getResult = function () {
|
||
|
var ret = Object.assign({}, this._response);
|
||
|
|
||
|
// Concat output
|
||
|
ret.output = this._lines.join('\n');
|
||
|
|
||
|
// Determine alive
|
||
|
ret.alive = this._times.length > 0;
|
||
|
|
||
|
// Update time at first successful line
|
||
|
if (ret.alive) {
|
||
|
this._response.time = this._times[0];
|
||
|
ret.time = this._response.time;
|
||
|
this._response.times = this._times;
|
||
|
ret.times = this._response.times;
|
||
|
}
|
||
|
|
||
|
// Get stddev
|
||
|
if (ret.stddev === 'unknown' && ret.alive) {
|
||
|
var numberOfSamples = this._times.length;
|
||
|
|
||
|
var sumOfAllSquareDifferences = this._times.reduce(
|
||
|
function (memory, time) {
|
||
|
var differenceFromMean = time - ret.avg;
|
||
|
var squaredDifference = differenceFromMean * differenceFromMean;
|
||
|
return memory + squaredDifference;
|
||
|
},
|
||
|
0
|
||
|
);
|
||
|
var variances = sumOfAllSquareDifferences / numberOfSamples;
|
||
|
|
||
|
ret.stddev = Math.round(Math.sqrt(variances) * 1000) / 1000;
|
||
|
}
|
||
|
|
||
|
// Fix min, avg, max, stddev up to 3 decimal points
|
||
|
['min', 'avg', 'max', 'stddev', 'packetLoss'].forEach(function (key) {
|
||
|
var v = ret[key];
|
||
|
if (typeof v === 'number') {
|
||
|
ret[key] = v.toFixed(3);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return ret;
|
||
|
};
|
||
|
|
||
|
module.exports = parser;
|