mirror of
https://github.com/Unitech/pm2.git
synced 2025-12-08 20:35:53 +00:00
235 lines
6.2 KiB
JavaScript
235 lines
6.2 KiB
JavaScript
var path = require('path');
|
|
|
|
// EWMA = ExponentiallyWeightedMovingAverage from
|
|
// https://github.com/felixge/node-measured/blob/master/lib/util/ExponentiallyMovingWeightedAverage.js
|
|
// used to compute the nbr of time per minute that a variance is hit by a new trace
|
|
function EWMA () {
|
|
this._timePeriod = 60000
|
|
this._tickInterval = 5000
|
|
this._alpha = 1 - Math.exp(-this._tickInterval / this._timePeriod)
|
|
this._count = 0
|
|
this._rate = 0
|
|
|
|
var self = this
|
|
this._interval = setInterval(function () {
|
|
self.tick()
|
|
}, this._tickInterval)
|
|
this._interval.unref()
|
|
}
|
|
|
|
EWMA.prototype.update = function (n) {
|
|
this._count += n || 1
|
|
}
|
|
|
|
EWMA.prototype.tick = function () {
|
|
var instantRate = this._count / this._tickInterval
|
|
this._count = 0
|
|
|
|
this._rate += (this._alpha * (instantRate - this._rate))
|
|
}
|
|
|
|
EWMA.prototype.rate = function (timeUnit) {
|
|
return (this._rate || 0) * timeUnit
|
|
}
|
|
|
|
var moment = require('moment');
|
|
|
|
/**
|
|
* Simple cache implementation
|
|
*
|
|
* @param {Object} opts cache options
|
|
* @param {Integer} opts.ttl time to live of all the keys
|
|
* @param {Function} opts.miss function called when a key isn't found in the cache
|
|
*/
|
|
function Cache (opts) {
|
|
this._cache = {};
|
|
this._miss = opts.miss;
|
|
this._ttl_time = opts.ttl;
|
|
this._ttl = {};
|
|
|
|
if (opts.ttl) {
|
|
setInterval(this._worker.bind(this), 1000);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Task running to check TTL and potentially remove older key
|
|
*/
|
|
Cache.prototype._worker = function () {
|
|
var keys = Object.keys(this._ttl);
|
|
for (var i = 0; i < keys.length; i++) {
|
|
var key = keys[i];
|
|
var value = this._ttl[key];
|
|
if (moment().isAfter(value)) {
|
|
delete this._cache[key];
|
|
delete this._ttl[key];
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Empty the cache
|
|
*/
|
|
Cache.prototype.reset = function () {
|
|
this._cache = null;
|
|
this._cache = {};
|
|
this._ttl = null;
|
|
this._ttl = {};
|
|
};
|
|
|
|
/**
|
|
* Get a value from the cache
|
|
*
|
|
* @param {String} key
|
|
*/
|
|
Cache.prototype.get = function (key) {
|
|
if (!key) return null;
|
|
var value = this._cache[key];
|
|
if (value) return value;
|
|
|
|
value = this._miss(key);
|
|
|
|
if (value) {
|
|
this.set(key, value);
|
|
}
|
|
return value;
|
|
};
|
|
|
|
/**
|
|
* Set a value in the cache
|
|
*
|
|
* @param {String} key
|
|
* @param {Mixed} value
|
|
*/
|
|
Cache.prototype.set = function (key, value) {
|
|
if (!key || !value) return false;
|
|
this._cache[key] = value;
|
|
if (this._ttl_time) {
|
|
this._ttl[key] = moment().add(this._ttl_time, 'seconds');
|
|
}
|
|
return true;
|
|
};
|
|
|
|
/**
|
|
* StackTraceParser is used to parse callsite from stacktrace
|
|
* and get from FS the context of the error (if available)
|
|
*
|
|
* @param {Cache} cache cache implementation used to query file from FS and get context
|
|
*/
|
|
function StackTraceParser (opts) {
|
|
this._cache = opts.cache;
|
|
this._context_size = opts.context;
|
|
}
|
|
|
|
StackTraceParser.prototype.attachContext = function (error) {
|
|
var self = this;
|
|
if (!error) return error;
|
|
|
|
// if pmx attached callsites we can parse them to retrieve the context
|
|
if (typeof (error.stackframes) === 'object') {
|
|
var result = self.parse(error.stackframes);
|
|
// no need to send it since there is already the stacktrace
|
|
delete error.stackframes;
|
|
delete error.__error_callsites;
|
|
|
|
if (result) {
|
|
error.callsite = result.callsite;
|
|
error.context = result.context;
|
|
}
|
|
}
|
|
// if the stack is here we can parse it directly from the stack string
|
|
// only if the context has been retrieved from elsewhere
|
|
if (typeof error.stack === 'string' && !error.callsite) {
|
|
var siteRegex = /(\/[^\\\n]*)/g;
|
|
var tmp;
|
|
var stack = [];
|
|
|
|
// find matching groups
|
|
while ((tmp = siteRegex.exec(error.stack))) {
|
|
stack.push(tmp[1]);
|
|
}
|
|
|
|
// parse each callsite to match the format used by the stackParser
|
|
stack = stack.map(function (callsite) {
|
|
// remove the trailing ) if present
|
|
if (callsite[callsite.length - 1] === ')') {
|
|
callsite = callsite.substr(0, callsite.length - 1);
|
|
}
|
|
var location = callsite.split(':');
|
|
|
|
return location.length < 3 ? callsite : {
|
|
file_name: location[0],
|
|
line_number: parseInt(location[1])
|
|
};
|
|
});
|
|
|
|
var finalCallsite = self.parse(stack);
|
|
if (finalCallsite) {
|
|
error.callsite = finalCallsite.callsite;
|
|
error.context = finalCallsite.context;
|
|
}
|
|
}
|
|
return error;
|
|
};
|
|
|
|
/**
|
|
* Parse the stacktrace and return callsite + context if available
|
|
*/
|
|
StackTraceParser.prototype.parse = function (stack) {
|
|
var self = this;
|
|
if (!stack || stack.length === 0) return false;
|
|
|
|
for (var i = 0, len = stack.length; i < len; i++) {
|
|
var callsite = stack[i];
|
|
|
|
// avoid null values
|
|
if (typeof callsite !== 'object') continue;
|
|
if (!callsite.file_name || !callsite.line_number) continue;
|
|
|
|
var type = path.isAbsolute(callsite.file_name) || callsite.file_name[0] === '.' ? 'user' : 'core';
|
|
|
|
// only use the callsite if its inside user space
|
|
if (!callsite || type === 'core' || callsite.file_name.indexOf('node_modules') > -1 ||
|
|
callsite.file_name.indexOf('vxx') > -1) {
|
|
continue;
|
|
}
|
|
|
|
// get the whole context (all lines) and cache them if necessary
|
|
var context = self._cache.get(callsite.file_name);
|
|
var source = [];
|
|
if (context && context.length > 0) {
|
|
// get line before the call
|
|
var preLine = callsite.line_number - self._context_size - 1;
|
|
var pre = context.slice(preLine > 0 ? preLine : 0, callsite.line_number - 1);
|
|
if (pre && pre.length > 0) {
|
|
pre.forEach(function (line) {
|
|
source.push(line.replace(/\t/g, ' '));
|
|
});
|
|
}
|
|
// get the line where the call has been made
|
|
if (context[callsite.line_number - 1]) {
|
|
source.push(context[callsite.line_number - 1].replace(/\t/g, ' ').replace(' ', '>>'));
|
|
}
|
|
// and get the line after the call
|
|
var postLine = callsite.line_number + self._context_size;
|
|
var post = context.slice(callsite.line_number, postLine);
|
|
if (post && post.length > 0) {
|
|
post.forEach(function (line) {
|
|
source.push(line.replace(/\t/g, ' '));
|
|
});
|
|
}
|
|
}
|
|
return {
|
|
context: source.length > 0 ? source.join('\n') : 'cannot retrieve source context',
|
|
callsite: [ callsite.file_name, callsite.line_number ].join(':')
|
|
};
|
|
}
|
|
return false;
|
|
};
|
|
|
|
module.exports = {
|
|
EWMA: EWMA,
|
|
Cache: Cache,
|
|
StackTraceParser: StackTraceParser
|
|
};
|