mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Install prettier and eslint
This commit is contained in:
parent
c530422f1e
commit
971a768c4e
14 changed files with 1102 additions and 804 deletions
55
libraries/metrics/.eslintrc
Normal file
55
libraries/metrics/.eslintrc
Normal file
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"extends": [
|
||||
"standard",
|
||||
"prettier",
|
||||
"prettier/standard"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018
|
||||
},
|
||||
"plugins": [
|
||||
"mocha",
|
||||
"chai-expect",
|
||||
"chai-friendly"
|
||||
],
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"rules": {
|
||||
// Swap the no-unused-expressions rule with a more chai-friendly one
|
||||
"no-unused-expressions": 0,
|
||||
"chai-friendly/no-unused-expressions": "error"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
// Test specific rules
|
||||
"files": ["test/**/*.js"],
|
||||
"env": {
|
||||
"mocha": true
|
||||
},
|
||||
"globals": {
|
||||
"expect": true
|
||||
},
|
||||
"rules": {
|
||||
// mocha-specific rules
|
||||
"mocha/handle-done-callback": "error",
|
||||
"mocha/no-exclusive-tests": "error",
|
||||
"mocha/no-global-tests": "error",
|
||||
"mocha/no-identical-title": "error",
|
||||
"mocha/no-nested-tests": "error",
|
||||
"mocha/no-pending-tests": "error",
|
||||
"mocha/no-skipped-tests": "error",
|
||||
"mocha/no-mocha-arrows": "error",
|
||||
|
||||
// chai-specific rules
|
||||
"chai-expect/missing-assertion": "error",
|
||||
"chai-expect/terminating-properties": "error",
|
||||
|
||||
// prefer-arrow-callback applies to all callbacks, not just ones in mocha tests.
|
||||
// we don't enforce this at the top-level - just in tests to manage `this` scope
|
||||
// based on mocha's context mechanism
|
||||
"mocha/prefer-arrow-callback": "error"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
4
libraries/metrics/.prettierrc
Normal file
4
libraries/metrics/.prettierrc
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
|
@ -4,27 +4,31 @@
|
|||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
let EventLoopMonitor;
|
||||
module.exports = (EventLoopMonitor = {
|
||||
monitor(logger, interval, log_threshold) {
|
||||
if (interval == null) { interval = 1000; }
|
||||
if (log_threshold == null) { log_threshold = 100; }
|
||||
const Metrics = require("./index");
|
||||
// check for logger on startup to avoid exceptions later if undefined
|
||||
if ((logger == null)) { throw new Error("logger is undefined"); }
|
||||
// monitor delay in setInterval to detect event loop blocking
|
||||
let previous = Date.now();
|
||||
const intervalId = setInterval(function() {
|
||||
const now = Date.now();
|
||||
const offset = now - previous - interval;
|
||||
if (offset > log_threshold) {
|
||||
logger.warn({offset}, "slow event loop");
|
||||
}
|
||||
previous = now;
|
||||
return Metrics.timing("event-loop-millsec", offset);
|
||||
}
|
||||
, interval);
|
||||
|
||||
return Metrics.registerDestructor(() => clearInterval(intervalId));
|
||||
}
|
||||
});
|
||||
module.exports = {
|
||||
monitor(logger, interval, logThreshold) {
|
||||
if (interval == null) {
|
||||
interval = 1000
|
||||
}
|
||||
if (logThreshold == null) {
|
||||
logThreshold = 100
|
||||
}
|
||||
const Metrics = require('./index')
|
||||
// check for logger on startup to avoid exceptions later if undefined
|
||||
if (logger == null) {
|
||||
throw new Error('logger is undefined')
|
||||
}
|
||||
// monitor delay in setInterval to detect event loop blocking
|
||||
let previous = Date.now()
|
||||
const intervalId = setInterval(function() {
|
||||
const now = Date.now()
|
||||
const offset = now - previous - interval
|
||||
if (offset > logThreshold) {
|
||||
logger.warn({ offset }, 'slow event loop')
|
||||
}
|
||||
previous = now
|
||||
return Metrics.timing('event-loop-millsec', offset)
|
||||
}, interval)
|
||||
|
||||
return Metrics.registerDestructor(() => clearInterval(intervalId))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,73 +5,93 @@
|
|||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const os = require("os");
|
||||
const yn = require("yn");
|
||||
const yn = require('yn')
|
||||
|
||||
const STACKDRIVER_LOGGING = yn(process.env['STACKDRIVER_LOGGING']);
|
||||
const STACKDRIVER_LOGGING = yn(process.env.STACKDRIVER_LOGGING)
|
||||
|
||||
module.exports.monitor = logger => (function(req, res, next) {
|
||||
const Metrics = require("./index");
|
||||
const startTime = process.hrtime();
|
||||
const {
|
||||
end
|
||||
} = res;
|
||||
module.exports.monitor = logger =>
|
||||
function(req, res, next) {
|
||||
const Metrics = require('./index')
|
||||
const startTime = process.hrtime()
|
||||
const { end } = res
|
||||
res.end = function() {
|
||||
let info;
|
||||
end.apply(this, arguments);
|
||||
const responseTime = process.hrtime(startTime);
|
||||
const responseTimeMs = Math.round((responseTime[0] * 1000) + (responseTime[1] / 1000000));
|
||||
const requestSize = parseInt(req.headers["content-length"], 10);
|
||||
if ((req.route != null ? req.route.path : undefined) != null) {
|
||||
const routePath = req.route.path.toString().replace(/\//g, '_').replace(/\:/g, '').slice(1);
|
||||
Metrics.timing("http_request", responseTimeMs, null, {method:req.method, status_code: res.statusCode, path:routePath});
|
||||
if (requestSize) {
|
||||
Metrics.summary("http_request_size_bytes", requestSize, {method:req.method, status_code: res.statusCode, path:routePath});
|
||||
}
|
||||
let info
|
||||
end.apply(this, arguments)
|
||||
const responseTime = process.hrtime(startTime)
|
||||
const responseTimeMs = Math.round(
|
||||
responseTime[0] * 1000 + responseTime[1] / 1000000
|
||||
)
|
||||
const requestSize = parseInt(req.headers['content-length'], 10)
|
||||
if ((req.route != null ? req.route.path : undefined) != null) {
|
||||
const routePath = req.route.path
|
||||
.toString()
|
||||
.replace(/\//g, '_')
|
||||
.replace(/:/g, '')
|
||||
.slice(1)
|
||||
Metrics.timing('http_request', responseTimeMs, null, {
|
||||
method: req.method,
|
||||
status_code: res.statusCode,
|
||||
path: routePath
|
||||
})
|
||||
if (requestSize) {
|
||||
Metrics.summary('http_request_size_bytes', requestSize, {
|
||||
method: req.method,
|
||||
status_code: res.statusCode,
|
||||
path: routePath
|
||||
})
|
||||
}
|
||||
const remoteIp = req.ip || __guard__(req.socket != null ? req.socket.socket : undefined, x => x.remoteAddress) || (req.socket != null ? req.socket.remoteAddress : undefined);
|
||||
const reqUrl = req.originalUrl || req.url;
|
||||
const referrer = req.headers['referer'] || req.headers['referrer'];
|
||||
if (STACKDRIVER_LOGGING) {
|
||||
info = {
|
||||
httpRequest: {
|
||||
requestMethod: req.method,
|
||||
requestUrl: reqUrl,
|
||||
requestSize,
|
||||
status: res.statusCode,
|
||||
responseSize: res.getHeader("content-length"),
|
||||
userAgent: req.headers["user-agent"],
|
||||
remoteIp,
|
||||
referer: referrer,
|
||||
latency: {
|
||||
seconds: responseTime[0],
|
||||
nanos: responseTime[1]
|
||||
},
|
||||
protocol: req.protocol
|
||||
}
|
||||
};
|
||||
} else {
|
||||
info = {
|
||||
req: {
|
||||
url: reqUrl,
|
||||
method: req.method,
|
||||
referrer,
|
||||
"remote-addr": remoteIp,
|
||||
"user-agent": req.headers["user-agent"],
|
||||
"content-length": req.headers["content-length"]
|
||||
},
|
||||
res: {
|
||||
"content-length": res.getHeader("content-length"),
|
||||
statusCode: res.statusCode
|
||||
},
|
||||
"response-time": responseTimeMs
|
||||
};
|
||||
}
|
||||
const remoteIp =
|
||||
req.ip ||
|
||||
__guard__(
|
||||
req.socket != null ? req.socket.socket : undefined,
|
||||
x => x.remoteAddress
|
||||
) ||
|
||||
(req.socket != null ? req.socket.remoteAddress : undefined)
|
||||
const reqUrl = req.originalUrl || req.url
|
||||
const referrer = req.headers.referer || req.headers.referrer
|
||||
if (STACKDRIVER_LOGGING) {
|
||||
info = {
|
||||
httpRequest: {
|
||||
requestMethod: req.method,
|
||||
requestUrl: reqUrl,
|
||||
requestSize,
|
||||
status: res.statusCode,
|
||||
responseSize: res.getHeader('content-length'),
|
||||
userAgent: req.headers['user-agent'],
|
||||
remoteIp,
|
||||
referer: referrer,
|
||||
latency: {
|
||||
seconds: responseTime[0],
|
||||
nanos: responseTime[1]
|
||||
},
|
||||
protocol: req.protocol
|
||||
}
|
||||
}
|
||||
return logger.info(info, "%s %s", req.method, reqUrl);
|
||||
};
|
||||
return next();
|
||||
});
|
||||
} else {
|
||||
info = {
|
||||
req: {
|
||||
url: reqUrl,
|
||||
method: req.method,
|
||||
referrer,
|
||||
'remote-addr': remoteIp,
|
||||
'user-agent': req.headers['user-agent'],
|
||||
'content-length': req.headers['content-length']
|
||||
},
|
||||
res: {
|
||||
'content-length': res.getHeader('content-length'),
|
||||
statusCode: res.statusCode
|
||||
},
|
||||
'response-time': responseTimeMs
|
||||
}
|
||||
}
|
||||
return logger.info(info, '%s %s', req.method, reqUrl)
|
||||
}
|
||||
return next()
|
||||
}
|
||||
|
||||
function __guard__(value, transform) {
|
||||
return (typeof value !== 'undefined' && value !== null) ? transform(value) : undefined;
|
||||
}
|
||||
return typeof value !== 'undefined' && value !== null
|
||||
? transform(value)
|
||||
: undefined
|
||||
}
|
||||
|
|
|
@ -5,187 +5,229 @@
|
|||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
let Metrics;
|
||||
console.log("using prometheus");
|
||||
let Metrics
|
||||
console.log('using prometheus')
|
||||
|
||||
const ExpressCompression = require('compression');
|
||||
const prom = require('./prom_wrapper');
|
||||
const ExpressCompression = require('compression')
|
||||
const prom = require('./prom_wrapper')
|
||||
|
||||
const {
|
||||
collectDefaultMetrics
|
||||
} = prom;
|
||||
const { collectDefaultMetrics } = prom
|
||||
|
||||
let appname = "unknown";
|
||||
const hostname = require('os').hostname();
|
||||
let appname = 'unknown'
|
||||
const hostname = require('os').hostname()
|
||||
|
||||
const destructors = [];
|
||||
const destructors = []
|
||||
|
||||
require("./uv_threadpool_size");
|
||||
require('./uv_threadpool_size')
|
||||
|
||||
module.exports = (Metrics = {
|
||||
register: prom.registry,
|
||||
module.exports = Metrics = {
|
||||
register: prom.registry,
|
||||
|
||||
initialize(_name, opts) {
|
||||
if (opts == null) { opts = {}; }
|
||||
appname = _name;
|
||||
collectDefaultMetrics({ timeout: 5000, prefix: Metrics.buildPromKey()});
|
||||
if (opts.ttlInMinutes) {
|
||||
prom.ttlInMinutes = opts.ttlInMinutes;
|
||||
}
|
||||
initialize(_name, opts) {
|
||||
if (opts == null) {
|
||||
opts = {}
|
||||
}
|
||||
appname = _name
|
||||
collectDefaultMetrics({ timeout: 5000, prefix: Metrics.buildPromKey() })
|
||||
if (opts.ttlInMinutes) {
|
||||
prom.ttlInMinutes = opts.ttlInMinutes
|
||||
}
|
||||
|
||||
console.log(`ENABLE_TRACE_AGENT set to ${process.env['ENABLE_TRACE_AGENT']}`);
|
||||
if (process.env['ENABLE_TRACE_AGENT'] === "true") {
|
||||
console.log("starting google trace agent");
|
||||
const traceAgent = require('@google-cloud/trace-agent');
|
||||
console.log(
|
||||
`ENABLE_TRACE_AGENT set to ${process.env.ENABLE_TRACE_AGENT}`
|
||||
)
|
||||
if (process.env.ENABLE_TRACE_AGENT === 'true') {
|
||||
console.log('starting google trace agent')
|
||||
const traceAgent = require('@google-cloud/trace-agent')
|
||||
|
||||
const traceOpts =
|
||||
{ignoreUrls: [/^\/status/, /^\/health_check/]};
|
||||
traceAgent.start(traceOpts);
|
||||
}
|
||||
const traceOpts = { ignoreUrls: [/^\/status/, /^\/health_check/] }
|
||||
traceAgent.start(traceOpts)
|
||||
}
|
||||
|
||||
console.log(`ENABLE_DEBUG_AGENT set to ${process.env['ENABLE_DEBUG_AGENT']}`);
|
||||
if (process.env['ENABLE_DEBUG_AGENT'] === "true") {
|
||||
console.log("starting google debug agent");
|
||||
const debugAgent = require('@google-cloud/debug-agent');
|
||||
debugAgent.start({
|
||||
allowExpressions: true,
|
||||
serviceContext: {
|
||||
service: appname,
|
||||
version: process.env['BUILD_VERSION']
|
||||
}
|
||||
});
|
||||
}
|
||||
console.log(
|
||||
`ENABLE_DEBUG_AGENT set to ${process.env.ENABLE_DEBUG_AGENT}`
|
||||
)
|
||||
if (process.env.ENABLE_DEBUG_AGENT === 'true') {
|
||||
console.log('starting google debug agent')
|
||||
const debugAgent = require('@google-cloud/debug-agent')
|
||||
debugAgent.start({
|
||||
allowExpressions: true,
|
||||
serviceContext: {
|
||||
service: appname,
|
||||
version: process.env.BUILD_VERSION
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
console.log(`ENABLE_PROFILE_AGENT set to ${process.env['ENABLE_PROFILE_AGENT']}`);
|
||||
if (process.env['ENABLE_PROFILE_AGENT'] === "true") {
|
||||
console.log("starting google profile agent");
|
||||
const profiler = require('@google-cloud/profiler');
|
||||
profiler.start({
|
||||
serviceContext: {
|
||||
service: appname,
|
||||
version: process.env['BUILD_VERSION']
|
||||
}
|
||||
});
|
||||
}
|
||||
console.log(
|
||||
`ENABLE_PROFILE_AGENT set to ${process.env.ENABLE_PROFILE_AGENT}`
|
||||
)
|
||||
if (process.env.ENABLE_PROFILE_AGENT === 'true') {
|
||||
console.log('starting google profile agent')
|
||||
const profiler = require('@google-cloud/profiler')
|
||||
profiler.start({
|
||||
serviceContext: {
|
||||
service: appname,
|
||||
version: process.env.BUILD_VERSION
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return Metrics.inc("process_startup");
|
||||
},
|
||||
return Metrics.inc('process_startup')
|
||||
},
|
||||
|
||||
registerDestructor(func) {
|
||||
return destructors.push(func);
|
||||
},
|
||||
registerDestructor(func) {
|
||||
return destructors.push(func)
|
||||
},
|
||||
|
||||
injectMetricsRoute(app) {
|
||||
return app.get('/metrics', ExpressCompression({
|
||||
level: parseInt(process.env.METRICS_COMPRESSION_LEVEL || '1', 10)
|
||||
}), function (req, res) {
|
||||
res.set('Content-Type', prom.registry.contentType);
|
||||
return res.end(prom.registry.metrics());
|
||||
});
|
||||
},
|
||||
injectMetricsRoute(app) {
|
||||
return app.get(
|
||||
'/metrics',
|
||||
ExpressCompression({
|
||||
level: parseInt(process.env.METRICS_COMPRESSION_LEVEL || '1', 10)
|
||||
}),
|
||||
function(req, res) {
|
||||
res.set('Content-Type', prom.registry.contentType)
|
||||
return res.end(prom.registry.metrics())
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
buildPromKey(key){
|
||||
if (key == null) { key = ""; }
|
||||
return key.replace(/[^a-zA-Z0-9]/g, "_");
|
||||
},
|
||||
buildPromKey(key) {
|
||||
if (key == null) {
|
||||
key = ''
|
||||
}
|
||||
return key.replace(/[^a-zA-Z0-9]/g, '_')
|
||||
},
|
||||
|
||||
sanitizeValue(value) {
|
||||
return parseFloat(value);
|
||||
},
|
||||
sanitizeValue(value) {
|
||||
return parseFloat(value)
|
||||
},
|
||||
|
||||
set(key, value, sampleRate){
|
||||
if (sampleRate == null) { sampleRate = 1; }
|
||||
return console.log("counts are not currently supported");
|
||||
},
|
||||
set(key, value, sampleRate) {
|
||||
if (sampleRate == null) {
|
||||
sampleRate = 1
|
||||
}
|
||||
return console.log('counts are not currently supported')
|
||||
},
|
||||
|
||||
inc(key, sampleRate, opts){
|
||||
if (sampleRate == null) { sampleRate = 1; }
|
||||
if (opts == null) { opts = {}; }
|
||||
key = Metrics.buildPromKey(key);
|
||||
opts.app = appname;
|
||||
opts.host = hostname;
|
||||
prom.metric('counter', key).inc(opts);
|
||||
if (process.env['DEBUG_METRICS']) {
|
||||
return console.log("doing inc", key, opts);
|
||||
}
|
||||
},
|
||||
inc(key, sampleRate, opts) {
|
||||
if (sampleRate == null) {
|
||||
sampleRate = 1
|
||||
}
|
||||
if (opts == null) {
|
||||
opts = {}
|
||||
}
|
||||
key = Metrics.buildPromKey(key)
|
||||
opts.app = appname
|
||||
opts.host = hostname
|
||||
prom.metric('counter', key).inc(opts)
|
||||
if (process.env.DEBUG_METRICS) {
|
||||
return console.log('doing inc', key, opts)
|
||||
}
|
||||
},
|
||||
|
||||
count(key, count, sampleRate, opts){
|
||||
if (sampleRate == null) { sampleRate = 1; }
|
||||
if (opts == null) { opts = {}; }
|
||||
key = Metrics.buildPromKey(key);
|
||||
opts.app = appname;
|
||||
opts.host = hostname;
|
||||
prom.metric('counter', key).inc(opts, count);
|
||||
if (process.env['DEBUG_METRICS']) {
|
||||
return console.log("doing count/inc", key, opts);
|
||||
}
|
||||
},
|
||||
count(key, count, sampleRate, opts) {
|
||||
if (sampleRate == null) {
|
||||
sampleRate = 1
|
||||
}
|
||||
if (opts == null) {
|
||||
opts = {}
|
||||
}
|
||||
key = Metrics.buildPromKey(key)
|
||||
opts.app = appname
|
||||
opts.host = hostname
|
||||
prom.metric('counter', key).inc(opts, count)
|
||||
if (process.env.DEBUG_METRICS) {
|
||||
return console.log('doing count/inc', key, opts)
|
||||
}
|
||||
},
|
||||
|
||||
summary(key, value, opts){
|
||||
if (opts == null) { opts = {}; }
|
||||
key = Metrics.buildPromKey(key);
|
||||
opts.app = appname;
|
||||
opts.host = hostname;
|
||||
prom.metric('summary', key).observe(opts, value);
|
||||
if (process.env['DEBUG_METRICS']) {
|
||||
return console.log("doing summary", key, value, opts);
|
||||
}
|
||||
},
|
||||
summary(key, value, opts) {
|
||||
if (opts == null) {
|
||||
opts = {}
|
||||
}
|
||||
key = Metrics.buildPromKey(key)
|
||||
opts.app = appname
|
||||
opts.host = hostname
|
||||
prom.metric('summary', key).observe(opts, value)
|
||||
if (process.env.DEBUG_METRICS) {
|
||||
return console.log('doing summary', key, value, opts)
|
||||
}
|
||||
},
|
||||
|
||||
timing(key, timeSpan, sampleRate, opts){
|
||||
if (opts == null) { opts = {}; }
|
||||
key = Metrics.buildPromKey("timer_" + key);
|
||||
opts.app = appname;
|
||||
opts.host = hostname;
|
||||
prom.metric('summary', key).observe(opts, timeSpan);
|
||||
if (process.env['DEBUG_METRICS']) {
|
||||
return console.log("doing timing", key, opts);
|
||||
}
|
||||
},
|
||||
timing(key, timeSpan, sampleRate, opts) {
|
||||
if (opts == null) {
|
||||
opts = {}
|
||||
}
|
||||
key = Metrics.buildPromKey('timer_' + key)
|
||||
opts.app = appname
|
||||
opts.host = hostname
|
||||
prom.metric('summary', key).observe(opts, timeSpan)
|
||||
if (process.env.DEBUG_METRICS) {
|
||||
return console.log('doing timing', key, opts)
|
||||
}
|
||||
},
|
||||
|
||||
Timer : class {
|
||||
constructor(key, sampleRate, opts){
|
||||
if (sampleRate == null) { sampleRate = 1; }
|
||||
this.start = new Date();
|
||||
key = Metrics.buildPromKey(key);
|
||||
this.key = key;
|
||||
this.sampleRate = sampleRate;
|
||||
this.opts = opts;
|
||||
}
|
||||
Timer: class {
|
||||
constructor(key, sampleRate, opts) {
|
||||
if (sampleRate == null) {
|
||||
sampleRate = 1
|
||||
}
|
||||
this.start = new Date()
|
||||
key = Metrics.buildPromKey(key)
|
||||
this.key = key
|
||||
this.sampleRate = sampleRate
|
||||
this.opts = opts
|
||||
}
|
||||
|
||||
done() {
|
||||
const timeSpan = new Date - this.start;
|
||||
Metrics.timing(this.key, timeSpan, this.sampleRate, this.opts);
|
||||
return timeSpan;
|
||||
}
|
||||
},
|
||||
done() {
|
||||
const timeSpan = new Date() - this.start
|
||||
Metrics.timing(this.key, timeSpan, this.sampleRate, this.opts)
|
||||
return timeSpan
|
||||
}
|
||||
},
|
||||
|
||||
gauge(key, value, sampleRate, opts){
|
||||
if (sampleRate == null) { sampleRate = 1; }
|
||||
key = Metrics.buildPromKey(key);
|
||||
prom.metric('gauge', key).set({app: appname, host: hostname, status: (opts != null ? opts.status : undefined)}, this.sanitizeValue(value));
|
||||
if (process.env['DEBUG_METRICS']) {
|
||||
return console.log("doing gauge", key, opts);
|
||||
}
|
||||
},
|
||||
gauge(key, value, sampleRate, opts) {
|
||||
if (sampleRate == null) {
|
||||
sampleRate = 1
|
||||
}
|
||||
key = Metrics.buildPromKey(key)
|
||||
prom.metric('gauge', key).set(
|
||||
{
|
||||
app: appname,
|
||||
host: hostname,
|
||||
status: opts != null ? opts.status : undefined
|
||||
},
|
||||
this.sanitizeValue(value)
|
||||
)
|
||||
if (process.env.DEBUG_METRICS) {
|
||||
return console.log('doing gauge', key, opts)
|
||||
}
|
||||
},
|
||||
|
||||
globalGauge(key, value, sampleRate, opts){
|
||||
if (sampleRate == null) { sampleRate = 1; }
|
||||
key = Metrics.buildPromKey(key);
|
||||
return prom.metric('gauge', key).set({app: appname, status: (opts != null ? opts.status : undefined)},this.sanitizeValue(value));
|
||||
},
|
||||
globalGauge(key, value, sampleRate, opts) {
|
||||
if (sampleRate == null) {
|
||||
sampleRate = 1
|
||||
}
|
||||
key = Metrics.buildPromKey(key)
|
||||
return prom
|
||||
.metric('gauge', key)
|
||||
.set(
|
||||
{ app: appname, status: opts != null ? opts.status : undefined },
|
||||
this.sanitizeValue(value)
|
||||
)
|
||||
},
|
||||
|
||||
mongodb: require("./mongodb"),
|
||||
http: require("./http"),
|
||||
open_sockets: require("./open_sockets"),
|
||||
event_loop: require("./event_loop"),
|
||||
memory: require("./memory"),
|
||||
mongodb: require('./mongodb'),
|
||||
http: require('./http'),
|
||||
open_sockets: require('./open_sockets'),
|
||||
event_loop: require('./event_loop'),
|
||||
memory: require('./memory'),
|
||||
|
||||
timeAsyncMethod: require('./timeAsyncMethod'),
|
||||
timeAsyncMethod: require('./timeAsyncMethod'),
|
||||
|
||||
close() {
|
||||
return Array.from(destructors).map((func) =>
|
||||
func());
|
||||
}
|
||||
});
|
||||
close() {
|
||||
return Array.from(destructors).map(func => func())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,95 +9,105 @@
|
|||
// adjust the period between gc()'s to reach a target of the gc saving
|
||||
// 4 megabytes each time.
|
||||
|
||||
let MemoryMonitor;
|
||||
const oneMinute = 60 * 1000;
|
||||
const oneMegaByte = 1024 * 1024;
|
||||
let MemoryMonitor
|
||||
const oneMinute = 60 * 1000
|
||||
const oneMegaByte = 1024 * 1024
|
||||
|
||||
let CpuTimeBucket = 100; // current cpu time allowance in milliseconds
|
||||
const CpuTimeBucketMax = 100; // maximum amount of cpu time allowed in bucket
|
||||
const CpuTimeBucketRate = 10; // add this many milliseconds per minute
|
||||
let CpuTimeBucket = 100 // current cpu time allowance in milliseconds
|
||||
const CpuTimeBucketMax = 100 // maximum amount of cpu time allowed in bucket
|
||||
const CpuTimeBucketRate = 10 // add this many milliseconds per minute
|
||||
|
||||
let gcInterval = 1; // how many minutes between gc (parameter is dynamically adjusted)
|
||||
let countSinceLastGc = 0; // how many minutes since last gc
|
||||
const MemoryChunkSize = 4; // how many megabytes we need to free to consider gc worth doing
|
||||
let gcInterval = 1 // how many minutes between gc (parameter is dynamically adjusted)
|
||||
let countSinceLastGc = 0 // how many minutes since last gc
|
||||
const MemoryChunkSize = 4 // how many megabytes we need to free to consider gc worth doing
|
||||
|
||||
const readyToGc = function() {
|
||||
// update allowed cpu time
|
||||
CpuTimeBucket = CpuTimeBucket + CpuTimeBucketRate;
|
||||
CpuTimeBucket = CpuTimeBucket < CpuTimeBucketMax ? CpuTimeBucket : CpuTimeBucketMax;
|
||||
// update counts since last gc
|
||||
countSinceLastGc = countSinceLastGc + 1;
|
||||
// check there is enough time since last gc and we have enough cpu
|
||||
return (countSinceLastGc > gcInterval) && (CpuTimeBucket > 0);
|
||||
};
|
||||
// update allowed cpu time
|
||||
CpuTimeBucket = CpuTimeBucket + CpuTimeBucketRate
|
||||
CpuTimeBucket =
|
||||
CpuTimeBucket < CpuTimeBucketMax ? CpuTimeBucket : CpuTimeBucketMax
|
||||
// update counts since last gc
|
||||
countSinceLastGc = countSinceLastGc + 1
|
||||
// check there is enough time since last gc and we have enough cpu
|
||||
return countSinceLastGc > gcInterval && CpuTimeBucket > 0
|
||||
}
|
||||
|
||||
const executeAndTime = function(fn) {
|
||||
// time the execution of fn() and subtract from cpu allowance
|
||||
const t0 = process.hrtime();
|
||||
fn();
|
||||
const dt = process.hrtime(t0);
|
||||
const timeTaken = (dt[0] + (dt[1]*1e-9)) * 1e3; // in milliseconds
|
||||
CpuTimeBucket -= Math.ceil(timeTaken);
|
||||
return timeTaken;
|
||||
};
|
||||
// time the execution of fn() and subtract from cpu allowance
|
||||
const t0 = process.hrtime()
|
||||
fn()
|
||||
const dt = process.hrtime(t0)
|
||||
const timeTaken = (dt[0] + dt[1] * 1e-9) * 1e3 // in milliseconds
|
||||
CpuTimeBucket -= Math.ceil(timeTaken)
|
||||
return timeTaken
|
||||
}
|
||||
|
||||
const inMegaBytes = function(obj) {
|
||||
// convert process.memoryUsage hash {rss,heapTotal,heapFreed} into megabytes
|
||||
const result = {};
|
||||
for (let k in obj) {
|
||||
const v = obj[k];
|
||||
result[k] = (v / oneMegaByte).toFixed(2);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
// convert process.memoryUsage hash {rss,heapTotal,heapFreed} into megabytes
|
||||
const result = {}
|
||||
for (const k in obj) {
|
||||
const v = obj[k]
|
||||
result[k] = (v / oneMegaByte).toFixed(2)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const updateMemoryStats = function(oldMem, newMem) {
|
||||
countSinceLastGc = 0;
|
||||
const delta = {};
|
||||
for (let k in newMem) {
|
||||
delta[k] = (newMem[k] - oldMem[k]).toFixed(2);
|
||||
}
|
||||
// take the max of all memory measures
|
||||
const savedMemory = Math.max(-delta.rss, -delta.heapTotal, -delta.heapUsed);
|
||||
delta.megabytesFreed = savedMemory;
|
||||
// did it do any good?
|
||||
if (savedMemory < MemoryChunkSize) {
|
||||
gcInterval = gcInterval + 1; // no, so wait longer next time
|
||||
} else {
|
||||
gcInterval = Math.max(gcInterval - 1, 1); // yes, wait less time
|
||||
}
|
||||
return delta;
|
||||
};
|
||||
countSinceLastGc = 0
|
||||
const delta = {}
|
||||
for (const k in newMem) {
|
||||
delta[k] = (newMem[k] - oldMem[k]).toFixed(2)
|
||||
}
|
||||
// take the max of all memory measures
|
||||
const savedMemory = Math.max(-delta.rss, -delta.heapTotal, -delta.heapUsed)
|
||||
delta.megabytesFreed = savedMemory
|
||||
// did it do any good?
|
||||
if (savedMemory < MemoryChunkSize) {
|
||||
gcInterval = gcInterval + 1 // no, so wait longer next time
|
||||
} else {
|
||||
gcInterval = Math.max(gcInterval - 1, 1) // yes, wait less time
|
||||
}
|
||||
return delta
|
||||
}
|
||||
|
||||
module.exports = (MemoryMonitor = {
|
||||
monitor(logger) {
|
||||
const interval = setInterval(() => MemoryMonitor.Check(logger)
|
||||
, oneMinute);
|
||||
const Metrics = require("./index");
|
||||
return Metrics.registerDestructor(() => clearInterval(interval));
|
||||
},
|
||||
module.exports = MemoryMonitor = {
|
||||
monitor(logger) {
|
||||
const interval = setInterval(() => MemoryMonitor.Check(logger), oneMinute)
|
||||
const Metrics = require('./index')
|
||||
return Metrics.registerDestructor(() => clearInterval(interval))
|
||||
},
|
||||
|
||||
Check(logger) {
|
||||
let mem;
|
||||
const Metrics = require("./index");
|
||||
const memBeforeGc = (mem = inMegaBytes(process.memoryUsage()));
|
||||
Metrics.gauge("memory.rss", mem.rss);
|
||||
Metrics.gauge("memory.heaptotal", mem.heapTotal);
|
||||
Metrics.gauge("memory.heapused", mem.heapUsed);
|
||||
Metrics.gauge("memory.gc-interval", gcInterval);
|
||||
//Metrics.gauge("memory.cpu-time-bucket", CpuTimeBucket)
|
||||
Check(logger) {
|
||||
let mem
|
||||
const Metrics = require('./index')
|
||||
const memBeforeGc = (mem = inMegaBytes(process.memoryUsage()))
|
||||
Metrics.gauge('memory.rss', mem.rss)
|
||||
Metrics.gauge('memory.heaptotal', mem.heapTotal)
|
||||
Metrics.gauge('memory.heapused', mem.heapUsed)
|
||||
Metrics.gauge('memory.gc-interval', gcInterval)
|
||||
// Metrics.gauge("memory.cpu-time-bucket", CpuTimeBucket)
|
||||
|
||||
logger.log(mem, "process.memoryUsage()");
|
||||
logger.log(mem, 'process.memoryUsage()')
|
||||
|
||||
if ((global.gc != null) && readyToGc()) {
|
||||
const gcTime = (executeAndTime(global.gc)).toFixed(2);
|
||||
const memAfterGc = inMegaBytes(process.memoryUsage());
|
||||
const deltaMem = updateMemoryStats(memBeforeGc, memAfterGc);
|
||||
logger.log({gcTime, memBeforeGc, memAfterGc, deltaMem, gcInterval, CpuTimeBucket}, "global.gc() forced");
|
||||
//Metrics.timing("memory.gc-time", gcTime)
|
||||
Metrics.gauge("memory.gc-rss-freed", -deltaMem.rss);
|
||||
Metrics.gauge("memory.gc-heaptotal-freed", -deltaMem.heapTotal);
|
||||
return Metrics.gauge("memory.gc-heapused-freed", -deltaMem.heapUsed);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (global.gc != null && readyToGc()) {
|
||||
const gcTime = executeAndTime(global.gc).toFixed(2)
|
||||
const memAfterGc = inMegaBytes(process.memoryUsage())
|
||||
const deltaMem = updateMemoryStats(memBeforeGc, memAfterGc)
|
||||
logger.log(
|
||||
{
|
||||
gcTime,
|
||||
memBeforeGc,
|
||||
memAfterGc,
|
||||
deltaMem,
|
||||
gcInterval,
|
||||
CpuTimeBucket
|
||||
},
|
||||
'global.gc() forced'
|
||||
)
|
||||
// Metrics.timing("memory.gc-time", gcTime)
|
||||
Metrics.gauge('memory.gc-rss-freed', -deltaMem.rss)
|
||||
Metrics.gauge('memory.gc-heaptotal-freed', -deltaMem.heapTotal)
|
||||
return Metrics.gauge('memory.gc-heapused-freed', -deltaMem.heapUsed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,124 +5,196 @@
|
|||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
module.exports = {
|
||||
monitor(mongodb_require_path, logger) {
|
||||
monitor(mongodbRequirePath, logger) {
|
||||
let mongodb, mongodbCore
|
||||
try {
|
||||
// for the v1 driver the methods to wrap are in the mongodb
|
||||
// module in lib/mongodb/db.js
|
||||
mongodb = require(mongodbRequirePath)
|
||||
} catch (error) {}
|
||||
|
||||
let mongodb, mongodbCore;
|
||||
try {
|
||||
// for the v1 driver the methods to wrap are in the mongodb
|
||||
// module in lib/mongodb/db.js
|
||||
mongodb = require(`${mongodb_require_path}`);
|
||||
} catch (error) {}
|
||||
try {
|
||||
// for the v2 driver the relevant methods are in the mongodb-core
|
||||
// module in lib/topologies/{server,replset,mongos}.js
|
||||
const v2Path = mongodbRequirePath.replace(/\/mongodb$/, '/mongodb-core')
|
||||
mongodbCore = require(v2Path)
|
||||
} catch (error1) {}
|
||||
|
||||
try {
|
||||
// for the v2 driver the relevant methods are in the mongodb-core
|
||||
// module in lib/topologies/{server,replset,mongos}.js
|
||||
const v2_path = mongodb_require_path.replace(/\/mongodb$/, '/mongodb-core');
|
||||
mongodbCore = require(v2_path);
|
||||
} catch (error1) {}
|
||||
const Metrics = require('./index')
|
||||
|
||||
const Metrics = require("./index");
|
||||
const monitorMethod = function(base, method, type) {
|
||||
let _method
|
||||
if (base == null) {
|
||||
return
|
||||
}
|
||||
if ((_method = base[method]) == null) {
|
||||
return
|
||||
}
|
||||
const arglen = _method.length
|
||||
|
||||
const monitorMethod = function(base, method, type) {
|
||||
let _method;
|
||||
if (base == null) { return; }
|
||||
if ((_method = base[method]) == null) { return; }
|
||||
const arglen = _method.length;
|
||||
const mongoDriverV1Wrapper = function(dbCommand, options, callback) {
|
||||
let query
|
||||
if (typeof callback === 'undefined') {
|
||||
callback = options
|
||||
options = {}
|
||||
}
|
||||
|
||||
const mongo_driver_v1_wrapper = function(db_command, options, callback) {
|
||||
let query;
|
||||
if (typeof callback === 'undefined') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
const collection = dbCommand.collectionName
|
||||
if (collection.match(/\$cmd$/)) {
|
||||
// Ignore noisy command methods like authenticating, ismaster and ping
|
||||
return _method.call(this, dbCommand, options, callback)
|
||||
}
|
||||
|
||||
const collection = db_command.collectionName;
|
||||
if (collection.match(/\$cmd$/)) {
|
||||
// Ignore noisy command methods like authenticating, ismaster and ping
|
||||
return _method.call(this, db_command, options, callback);
|
||||
}
|
||||
if (dbCommand.query != null) {
|
||||
query = Object.keys(dbCommand.query)
|
||||
.sort()
|
||||
.join('_')
|
||||
}
|
||||
|
||||
let key = `mongo-requests.${collection}.${type}`;
|
||||
if (db_command.query != null) {
|
||||
query = Object.keys(db_command.query).sort().join("_");
|
||||
key += "." + query;
|
||||
}
|
||||
const timer = new Metrics.Timer('mongo', { collection, query })
|
||||
const start = new Date()
|
||||
return _method.call(this, dbCommand, options, function() {
|
||||
timer.done()
|
||||
logger.log(
|
||||
{
|
||||
query: dbCommand.query,
|
||||
query_type: type,
|
||||
collection,
|
||||
'response-time': new Date() - start
|
||||
},
|
||||
'mongo request'
|
||||
)
|
||||
return callback.apply(this, arguments)
|
||||
})
|
||||
}
|
||||
|
||||
const timer = new Metrics.Timer("mongo", {collection, query});
|
||||
const start = new Date();
|
||||
return _method.call(this, db_command, options, function() {
|
||||
timer.done();
|
||||
const time = new Date() - start;
|
||||
logger.log({
|
||||
query: db_command.query,
|
||||
query_type: type,
|
||||
collection,
|
||||
"response-time": new Date() - start
|
||||
},
|
||||
"mongo request");
|
||||
return callback.apply(this, arguments);
|
||||
});
|
||||
};
|
||||
const mongoDriverV2Wrapper = function(ns, ops, options, callback) {
|
||||
let query
|
||||
if (typeof callback === 'undefined') {
|
||||
callback = options
|
||||
options = {}
|
||||
}
|
||||
|
||||
const mongo_driver_v2_wrapper = function(ns, ops, options, callback) {
|
||||
let query;
|
||||
if (typeof callback === 'undefined') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
if (ns.match(/\$cmd$/)) {
|
||||
// Ignore noisy command methods like authenticating, ismaster and ping
|
||||
return _method.call(this, ns, ops, options, callback)
|
||||
}
|
||||
|
||||
if (ns.match(/\$cmd$/)) {
|
||||
// Ignore noisy command methods like authenticating, ismaster and ping
|
||||
return _method.call(this, ns, ops, options, callback);
|
||||
}
|
||||
let key = `mongo-requests.${ns}.${type}`
|
||||
if (ops[0].q != null) {
|
||||
// ops[0].q
|
||||
query = Object.keys(ops[0].q)
|
||||
.sort()
|
||||
.join('_')
|
||||
key += '.' + query
|
||||
}
|
||||
|
||||
let key = `mongo-requests.${ns}.${type}`;
|
||||
if (ops[0].q != null) { // ops[0].q
|
||||
query = Object.keys(ops[0].q).sort().join("_");
|
||||
key += "." + query;
|
||||
}
|
||||
const timer = new Metrics.Timer(key)
|
||||
const start = new Date()
|
||||
return _method.call(this, ns, ops, options, function() {
|
||||
timer.done()
|
||||
logger.log(
|
||||
{
|
||||
query: ops[0].q,
|
||||
query_type: type,
|
||||
collection: ns,
|
||||
'response-time': new Date() - start
|
||||
},
|
||||
'mongo request'
|
||||
)
|
||||
return callback.apply(this, arguments)
|
||||
})
|
||||
}
|
||||
|
||||
const timer = new Metrics.Timer(key);
|
||||
const start = new Date();
|
||||
return _method.call(this, ns, ops, options, function() {
|
||||
timer.done();
|
||||
const time = new Date() - start;
|
||||
logger.log({
|
||||
query: ops[0].q,
|
||||
query_type: type,
|
||||
collection: ns,
|
||||
"response-time": new Date() - start
|
||||
},
|
||||
"mongo request");
|
||||
return callback.apply(this, arguments);
|
||||
});
|
||||
};
|
||||
if (arglen === 3) {
|
||||
return (base[method] = mongoDriverV1Wrapper)
|
||||
} else if (arglen === 4) {
|
||||
return (base[method] = mongoDriverV2Wrapper)
|
||||
}
|
||||
}
|
||||
|
||||
if (arglen === 3) {
|
||||
return base[method] = mongo_driver_v1_wrapper;
|
||||
} else if (arglen === 4) {
|
||||
return base[method] = mongo_driver_v2_wrapper;
|
||||
}
|
||||
};
|
||||
monitorMethod(
|
||||
mongodb != null ? mongodb.Db.prototype : undefined,
|
||||
'_executeQueryCommand',
|
||||
'query'
|
||||
)
|
||||
monitorMethod(
|
||||
mongodb != null ? mongodb.Db.prototype : undefined,
|
||||
'_executeRemoveCommand',
|
||||
'remove'
|
||||
)
|
||||
monitorMethod(
|
||||
mongodb != null ? mongodb.Db.prototype : undefined,
|
||||
'_executeInsertCommand',
|
||||
'insert'
|
||||
)
|
||||
monitorMethod(
|
||||
mongodb != null ? mongodb.Db.prototype : undefined,
|
||||
'_executeUpdateCommand',
|
||||
'update'
|
||||
)
|
||||
|
||||
monitorMethod(mongodb != null ? mongodb.Db.prototype : undefined, "_executeQueryCommand", "query");
|
||||
monitorMethod(mongodb != null ? mongodb.Db.prototype : undefined, "_executeRemoveCommand", "remove");
|
||||
monitorMethod(mongodb != null ? mongodb.Db.prototype : undefined, "_executeInsertCommand", "insert");
|
||||
monitorMethod(mongodb != null ? mongodb.Db.prototype : undefined, "_executeUpdateCommand", "update");
|
||||
monitorMethod(
|
||||
mongodbCore != null ? mongodbCore.Server.prototype : undefined,
|
||||
'command',
|
||||
'command'
|
||||
)
|
||||
monitorMethod(
|
||||
mongodbCore != null ? mongodbCore.Server.prototype : undefined,
|
||||
'remove',
|
||||
'remove'
|
||||
)
|
||||
monitorMethod(
|
||||
mongodbCore != null ? mongodbCore.Server.prototype : undefined,
|
||||
'insert',
|
||||
'insert'
|
||||
)
|
||||
monitorMethod(
|
||||
mongodbCore != null ? mongodbCore.Server.prototype : undefined,
|
||||
'update',
|
||||
'update'
|
||||
)
|
||||
|
||||
monitorMethod(mongodbCore != null ? mongodbCore.Server.prototype : undefined, "command", "command");
|
||||
monitorMethod(mongodbCore != null ? mongodbCore.Server.prototype : undefined, "remove", "remove");
|
||||
monitorMethod(mongodbCore != null ? mongodbCore.Server.prototype : undefined, "insert", "insert");
|
||||
monitorMethod(mongodbCore != null ? mongodbCore.Server.prototype : undefined, "update", "update");
|
||||
monitorMethod(
|
||||
mongodbCore != null ? mongodbCore.ReplSet.prototype : undefined,
|
||||
'command',
|
||||
'command'
|
||||
)
|
||||
monitorMethod(
|
||||
mongodbCore != null ? mongodbCore.ReplSet.prototype : undefined,
|
||||
'remove',
|
||||
'remove'
|
||||
)
|
||||
monitorMethod(
|
||||
mongodbCore != null ? mongodbCore.ReplSet.prototype : undefined,
|
||||
'insert',
|
||||
'insert'
|
||||
)
|
||||
monitorMethod(
|
||||
mongodbCore != null ? mongodbCore.ReplSet.prototype : undefined,
|
||||
'update',
|
||||
'update'
|
||||
)
|
||||
|
||||
monitorMethod(mongodbCore != null ? mongodbCore.ReplSet.prototype : undefined, "command", "command");
|
||||
monitorMethod(mongodbCore != null ? mongodbCore.ReplSet.prototype : undefined, "remove", "remove");
|
||||
monitorMethod(mongodbCore != null ? mongodbCore.ReplSet.prototype : undefined, "insert", "insert");
|
||||
monitorMethod(mongodbCore != null ? mongodbCore.ReplSet.prototype : undefined, "update", "update");
|
||||
|
||||
monitorMethod(mongodbCore != null ? mongodbCore.Mongos.prototype : undefined, "command", "command");
|
||||
monitorMethod(mongodbCore != null ? mongodbCore.Mongos.prototype : undefined, "remove", "remove");
|
||||
monitorMethod(mongodbCore != null ? mongodbCore.Mongos.prototype : undefined, "insert", "insert");
|
||||
return monitorMethod(mongodbCore != null ? mongodbCore.Mongos.prototype : undefined, "update", "update");
|
||||
}
|
||||
};
|
||||
monitorMethod(
|
||||
mongodbCore != null ? mongodbCore.Mongos.prototype : undefined,
|
||||
'command',
|
||||
'command'
|
||||
)
|
||||
monitorMethod(
|
||||
mongodbCore != null ? mongodbCore.Mongos.prototype : undefined,
|
||||
'remove',
|
||||
'remove'
|
||||
)
|
||||
monitorMethod(
|
||||
mongodbCore != null ? mongodbCore.Mongos.prototype : undefined,
|
||||
'insert',
|
||||
'insert'
|
||||
)
|
||||
return monitorMethod(
|
||||
mongodbCore != null ? mongodbCore.Mongos.prototype : undefined,
|
||||
'update',
|
||||
'update'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,44 +5,50 @@
|
|||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
let OpenSocketsMonitor;
|
||||
const URL = require("url");
|
||||
const seconds = 1000;
|
||||
let OpenSocketsMonitor
|
||||
const { URL } = require('url')
|
||||
const seconds = 1000
|
||||
|
||||
// In Node 0.10 the default is 5, which means only 5 open connections at one.
|
||||
// Node 0.12 has a default of Infinity. Make sure we have no limit set,
|
||||
// regardless of Node version.
|
||||
require("http").globalAgent.maxSockets = Infinity;
|
||||
require("https").globalAgent.maxSockets = Infinity;
|
||||
require('http').globalAgent.maxSockets = Infinity
|
||||
require('https').globalAgent.maxSockets = Infinity
|
||||
|
||||
module.exports = (OpenSocketsMonitor = {
|
||||
monitor(logger) {
|
||||
const interval = setInterval(() => OpenSocketsMonitor.gaugeOpenSockets()
|
||||
, 5 * seconds);
|
||||
const Metrics = require("./index");
|
||||
return Metrics.registerDestructor(() => clearInterval(interval));
|
||||
},
|
||||
module.exports = OpenSocketsMonitor = {
|
||||
monitor(logger) {
|
||||
const interval = setInterval(
|
||||
() => OpenSocketsMonitor.gaugeOpenSockets(),
|
||||
5 * seconds
|
||||
)
|
||||
const Metrics = require('./index')
|
||||
return Metrics.registerDestructor(() => clearInterval(interval))
|
||||
},
|
||||
|
||||
gaugeOpenSockets() {
|
||||
let agents, hostname, url;
|
||||
const Metrics = require("./index");
|
||||
const object = require('http').globalAgent.sockets;
|
||||
for (url in object) {
|
||||
agents = object[url];
|
||||
url = URL.parse(`http://${url}`);
|
||||
hostname = url.hostname != null ? url.hostname.replace(/\./g, "_") : undefined;
|
||||
Metrics.gauge(`open_connections.http.${hostname}`, agents.length);
|
||||
}
|
||||
return (() => {
|
||||
const result = [];
|
||||
const object1 = require('https').globalAgent.sockets;
|
||||
for (url in object1) {
|
||||
agents = object1[url];
|
||||
url = URL.parse(`https://${url}`);
|
||||
hostname = url.hostname != null ? url.hostname.replace(/\./g, "_") : undefined;
|
||||
result.push(Metrics.gauge(`open_connections.https.${hostname}`, agents.length));
|
||||
}
|
||||
return result;
|
||||
})();
|
||||
}
|
||||
});
|
||||
gaugeOpenSockets() {
|
||||
let agents, hostname, url
|
||||
const Metrics = require('./index')
|
||||
const object = require('http').globalAgent.sockets
|
||||
for (url in object) {
|
||||
agents = object[url]
|
||||
url = new URL(`http://${url}`)
|
||||
hostname =
|
||||
url.hostname != null ? url.hostname.replace(/\./g, '_') : undefined
|
||||
Metrics.gauge(`open_connections.http.${hostname}`, agents.length)
|
||||
}
|
||||
return (() => {
|
||||
const result = []
|
||||
const object1 = require('https').globalAgent.sockets
|
||||
for (url in object1) {
|
||||
agents = object1[url]
|
||||
url = new URL(`https://${url}`)
|
||||
hostname =
|
||||
url.hostname != null ? url.hostname.replace(/\./g, '_') : undefined
|
||||
result.push(
|
||||
Metrics.gauge(`open_connections.https.${hostname}`, agents.length)
|
||||
)
|
||||
}
|
||||
return result
|
||||
})()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,11 +18,25 @@
|
|||
"devDependencies": {
|
||||
"bunyan": "^1.0.0",
|
||||
"chai": "^4.2.0",
|
||||
"eslint": "^7.8.1",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-config-standard": "^14.1.1",
|
||||
"eslint-plugin-chai-expect": "^2.2.0",
|
||||
"eslint-plugin-chai-friendly": "^0.6.0",
|
||||
"eslint-plugin-import": "^2.22.0",
|
||||
"eslint-plugin-mocha": "^8.0.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.0.1",
|
||||
"mocha": "^8.0.1",
|
||||
"prettier-eslint-cli": "^5.0.0",
|
||||
"sandboxed-module": "^2.0.4",
|
||||
"sinon": "^9.0.2"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "mocha --reporter spec --recursive --exit --grep=$MOCHA_GREP test/unit"
|
||||
"lint": "eslint --max-warnings 0 .",
|
||||
"test": "mocha --reporter spec --recursive --exit --grep=$MOCHA_GREP test/unit",
|
||||
"format": "prettier-eslint $PWD'/**/*.js' --list-different",
|
||||
"format:fix": "prettier-eslint $PWD'/**/*.js' --write"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,144 +5,162 @@
|
|||
* DS205: Consider reworking code to avoid use of IIFEs
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const prom = require('prom-client');
|
||||
const registry = require('prom-client').register;
|
||||
const metrics = new Map();
|
||||
|
||||
const prom = require('prom-client')
|
||||
const registry = require('prom-client').register
|
||||
const metrics = new Map()
|
||||
|
||||
const optsKey = function(opts) {
|
||||
let keys = Object.keys(opts);
|
||||
if (keys.length === 0) { return ''; }
|
||||
let keys = Object.keys(opts)
|
||||
if (keys.length === 0) {
|
||||
return ''
|
||||
}
|
||||
|
||||
keys = keys.sort();
|
||||
keys = keys.sort()
|
||||
|
||||
let hash = '';
|
||||
for (let key of Array.from(keys)) {
|
||||
if (hash.length) { hash += ","; }
|
||||
hash += `${key}:${opts[key]}`;
|
||||
}
|
||||
let hash = ''
|
||||
for (const key of Array.from(keys)) {
|
||||
if (hash.length) {
|
||||
hash += ','
|
||||
}
|
||||
hash += `${key}:${opts[key]}`
|
||||
}
|
||||
|
||||
return hash;
|
||||
};
|
||||
return hash
|
||||
}
|
||||
|
||||
const extendOpts = function(opts, labelNames) {
|
||||
for (let label of Array.from(labelNames)) {
|
||||
if (!opts[label]) { opts[label] = ''; }
|
||||
}
|
||||
return opts;
|
||||
};
|
||||
for (const label of Array.from(labelNames)) {
|
||||
if (!opts[label]) {
|
||||
opts[label] = ''
|
||||
}
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
const optsAsArgs = function(opts, labelNames) {
|
||||
const args = [];
|
||||
for (let label of Array.from(labelNames)) {
|
||||
args.push(opts[label] || '');
|
||||
}
|
||||
return args;
|
||||
};
|
||||
|
||||
const args = []
|
||||
for (const label of Array.from(labelNames)) {
|
||||
args.push(opts[label] || '')
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
const PromWrapper = {
|
||||
ttlInMinutes: 0,
|
||||
registry,
|
||||
ttlInMinutes: 0,
|
||||
registry,
|
||||
|
||||
metric(type, name) {
|
||||
return metrics.get(name) || new MetricWrapper(type, name);
|
||||
},
|
||||
|
||||
collectDefaultMetrics: prom.collectDefaultMetrics
|
||||
};
|
||||
metric(type, name) {
|
||||
return metrics.get(name) || new MetricWrapper(type, name)
|
||||
},
|
||||
|
||||
collectDefaultMetrics: prom.collectDefaultMetrics
|
||||
}
|
||||
|
||||
class MetricWrapper {
|
||||
constructor(type, name) {
|
||||
metrics.set(name, this);
|
||||
this.name = name;
|
||||
this.instances = new Map();
|
||||
this.lastAccess = new Date();
|
||||
this.metric = (() => { switch (type) {
|
||||
case "counter":
|
||||
return new prom.Counter({
|
||||
name,
|
||||
help: name,
|
||||
labelNames: ['app','host','status','method', 'path']
|
||||
});
|
||||
case "summary":
|
||||
return new prom.Summary({
|
||||
name,
|
||||
help: name,
|
||||
maxAgeSeconds: 60,
|
||||
ageBuckets: 10,
|
||||
labelNames: ['app', 'host', 'path', 'status_code', 'method', 'collection', 'query']
|
||||
});
|
||||
case "gauge":
|
||||
return new prom.Gauge({
|
||||
name,
|
||||
help: name,
|
||||
labelNames: ['app','host', 'status']
|
||||
});
|
||||
} })();
|
||||
}
|
||||
constructor(type, name) {
|
||||
metrics.set(name, this)
|
||||
this.name = name
|
||||
this.instances = new Map()
|
||||
this.lastAccess = new Date()
|
||||
this.metric = (() => {
|
||||
switch (type) {
|
||||
case 'counter':
|
||||
return new prom.Counter({
|
||||
name,
|
||||
help: name,
|
||||
labelNames: ['app', 'host', 'status', 'method', 'path']
|
||||
})
|
||||
case 'summary':
|
||||
return new prom.Summary({
|
||||
name,
|
||||
help: name,
|
||||
maxAgeSeconds: 60,
|
||||
ageBuckets: 10,
|
||||
labelNames: [
|
||||
'app',
|
||||
'host',
|
||||
'path',
|
||||
'status_code',
|
||||
'method',
|
||||
'collection',
|
||||
'query'
|
||||
]
|
||||
})
|
||||
case 'gauge':
|
||||
return new prom.Gauge({
|
||||
name,
|
||||
help: name,
|
||||
labelNames: ['app', 'host', 'status']
|
||||
})
|
||||
}
|
||||
})()
|
||||
}
|
||||
|
||||
inc(opts, value) {
|
||||
return this._execMethod('inc', opts, value);
|
||||
}
|
||||
inc(opts, value) {
|
||||
return this._execMethod('inc', opts, value)
|
||||
}
|
||||
|
||||
observe(opts, value) {
|
||||
return this._execMethod('observe', opts, value);
|
||||
}
|
||||
observe(opts, value) {
|
||||
return this._execMethod('observe', opts, value)
|
||||
}
|
||||
|
||||
set(opts, value) {
|
||||
return this._execMethod('set', opts, value);
|
||||
}
|
||||
set(opts, value) {
|
||||
return this._execMethod('set', opts, value)
|
||||
}
|
||||
|
||||
sweep() {
|
||||
const thresh = new Date(Date.now() - (1000 * 60 * PromWrapper.ttlInMinutes));
|
||||
this.instances.forEach((instance, key) => {
|
||||
if (thresh > instance.time) {
|
||||
if (process.env['DEBUG_METRICS']) {
|
||||
console.log("Sweeping stale metric instance", this.name, {opts: instance.opts}, key);
|
||||
}
|
||||
return this.metric.remove(...Array.from(optsAsArgs(instance.opts, this.metric.labelNames) || []));
|
||||
}
|
||||
});
|
||||
sweep() {
|
||||
const thresh = new Date(Date.now() - 1000 * 60 * PromWrapper.ttlInMinutes)
|
||||
this.instances.forEach((instance, key) => {
|
||||
if (thresh > instance.time) {
|
||||
if (process.env.DEBUG_METRICS) {
|
||||
console.log(
|
||||
'Sweeping stale metric instance',
|
||||
this.name,
|
||||
{ opts: instance.opts },
|
||||
key
|
||||
)
|
||||
}
|
||||
return this.metric.remove(
|
||||
...Array.from(optsAsArgs(instance.opts, this.metric.labelNames) || [])
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
if (thresh > this.lastAccess) {
|
||||
if (process.env['DEBUG_METRICS']) {
|
||||
console.log("Sweeping stale metric", this.name, thresh, this.lastAccess);
|
||||
}
|
||||
metrics.delete(this.name);
|
||||
return registry.removeSingleMetric(this.name);
|
||||
}
|
||||
}
|
||||
if (thresh > this.lastAccess) {
|
||||
if (process.env.DEBUG_METRICS) {
|
||||
console.log('Sweeping stale metric', this.name, thresh, this.lastAccess)
|
||||
}
|
||||
metrics.delete(this.name)
|
||||
return registry.removeSingleMetric(this.name)
|
||||
}
|
||||
}
|
||||
|
||||
_execMethod(method, opts, value) {
|
||||
opts = extendOpts(opts, this.metric.labelNames);
|
||||
const key = optsKey(opts);
|
||||
if (key !== '') { this.instances.set(key, { time: new Date(), opts }); }
|
||||
this.lastAccess = new Date();
|
||||
return this.metric[method](opts, value);
|
||||
}
|
||||
_execMethod(method, opts, value) {
|
||||
opts = extendOpts(opts, this.metric.labelNames)
|
||||
const key = optsKey(opts)
|
||||
if (key !== '') {
|
||||
this.instances.set(key, { time: new Date(), opts })
|
||||
}
|
||||
this.lastAccess = new Date()
|
||||
return this.metric[method](opts, value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!PromWrapper.sweepRegistered) {
|
||||
if (process.env['DEBUG_METRICS']) {
|
||||
console.log("Registering sweep method");
|
||||
}
|
||||
PromWrapper.sweepRegistered = true;
|
||||
setInterval(
|
||||
function() {
|
||||
if (PromWrapper.ttlInMinutes) {
|
||||
if (process.env['DEBUG_METRICS']) {
|
||||
console.log("Sweeping metrics");
|
||||
}
|
||||
return metrics.forEach((metric, key) => {
|
||||
return metric.sweep();
|
||||
});
|
||||
}
|
||||
},
|
||||
60000);
|
||||
if (process.env.DEBUG_METRICS) {
|
||||
console.log('Registering sweep method')
|
||||
}
|
||||
PromWrapper.sweepRegistered = true
|
||||
setInterval(function() {
|
||||
if (PromWrapper.ttlInMinutes) {
|
||||
if (process.env.DEBUG_METRICS) {
|
||||
console.log('Sweeping metrics')
|
||||
}
|
||||
return metrics.forEach((metric, key) => {
|
||||
return metric.sweep()
|
||||
})
|
||||
}
|
||||
}, 60000)
|
||||
}
|
||||
|
||||
|
||||
module.exports = PromWrapper;
|
||||
module.exports = PromWrapper
|
||||
|
|
|
@ -3,45 +3,42 @@
|
|||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const chai = require('chai');
|
||||
const should = chai.should();
|
||||
const {
|
||||
expect
|
||||
} = chai;
|
||||
const path = require('path');
|
||||
const modulePath = path.join(__dirname, '../../../event_loop.js');
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const sinon = require("sinon");
|
||||
const chai = require('chai')
|
||||
const { expect } = chai
|
||||
const path = require('path')
|
||||
const modulePath = path.join(__dirname, '../../../event_loop.js')
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const sinon = require('sinon')
|
||||
|
||||
describe('event_loop', function() {
|
||||
before(function() {
|
||||
this.metrics = {
|
||||
timing: sinon.stub(),
|
||||
registerDestructor: sinon.stub()
|
||||
}
|
||||
this.logger = {
|
||||
warn: sinon.stub()
|
||||
}
|
||||
return (this.event_loop = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'./index': this.metrics
|
||||
}
|
||||
}))
|
||||
})
|
||||
|
||||
before(function() {
|
||||
this.metrics = {
|
||||
timing: sinon.stub(),
|
||||
registerDestructor: sinon.stub()
|
||||
};
|
||||
this.logger = {
|
||||
warn: sinon.stub()
|
||||
};
|
||||
return this.event_loop = SandboxedModule.require(modulePath, { requires: {
|
||||
'./index': this.metrics
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
describe('with a logger provided', function() {
|
||||
before(function() {
|
||||
return this.event_loop.monitor(this.logger)
|
||||
})
|
||||
|
||||
describe('with a logger provided', function() {
|
||||
before(function() {
|
||||
return this.event_loop.monitor(this.logger);
|
||||
});
|
||||
|
||||
return it('should register a destructor with metrics', function() {
|
||||
return this.metrics.registerDestructor.called.should.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
return describe('without a logger provided', () => it('should throw an exception', function() {
|
||||
return expect(this.event_loop.monitor).to.throw('logger is undefined');
|
||||
}));
|
||||
});
|
||||
return it('should register a destructor with metrics', function() {
|
||||
return expect(this.metrics.registerDestructor.called).to.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return describe('without a logger provided', function() {
|
||||
return it('should throw an exception', function() {
|
||||
return expect(this.event_loop.monitor).to.throw('logger is undefined')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -4,160 +4,198 @@
|
|||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const chai = require('chai');
|
||||
const should = chai.should();
|
||||
const {
|
||||
expect
|
||||
} = chai;
|
||||
const path = require('path');
|
||||
const modulePath = path.join(__dirname, '../../../timeAsyncMethod.js');
|
||||
const SandboxedModule = require('sandboxed-module');
|
||||
const sinon = require("sinon");
|
||||
|
||||
const chai = require('chai')
|
||||
const { expect } = chai
|
||||
const path = require('path')
|
||||
const modulePath = path.join(__dirname, '../../../timeAsyncMethod.js')
|
||||
const SandboxedModule = require('sandboxed-module')
|
||||
const sinon = require('sinon')
|
||||
|
||||
describe('timeAsyncMethod', function() {
|
||||
beforeEach(function() {
|
||||
this.Timer = { done: sinon.stub() }
|
||||
this.TimerConstructor = sinon.stub().returns(this.Timer)
|
||||
this.metrics = {
|
||||
Timer: this.TimerConstructor,
|
||||
inc: sinon.stub()
|
||||
}
|
||||
this.timeAsyncMethod = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'./index': this.metrics
|
||||
}
|
||||
})
|
||||
|
||||
beforeEach(function() {
|
||||
this.Timer = {done: sinon.stub()};
|
||||
this.TimerConstructor = sinon.stub().returns(this.Timer);
|
||||
this.metrics = {
|
||||
Timer: this.TimerConstructor,
|
||||
inc: sinon.stub()
|
||||
};
|
||||
this.timeAsyncMethod = SandboxedModule.require(modulePath, { requires: {
|
||||
'./index': this.metrics
|
||||
}
|
||||
}
|
||||
);
|
||||
return (this.testObject = {
|
||||
nextNumber(n, callback) {
|
||||
return setTimeout(() => callback(null, n + 1), 100)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return this.testObject = {
|
||||
nextNumber(n, callback) {
|
||||
if (callback == null) { callback = function(err, result){}; }
|
||||
return setTimeout(
|
||||
() => callback(null, n+1)
|
||||
, 100
|
||||
);
|
||||
}
|
||||
};});
|
||||
it('should have the testObject behave correctly before wrapping', function(done) {
|
||||
return this.testObject.nextNumber(2, (err, result) => {
|
||||
expect(err).to.not.exist
|
||||
expect(result).to.equal(3)
|
||||
return done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should have the testObject behave correctly before wrapping', function(done) {
|
||||
return this.testObject.nextNumber(2, function(err, result) {
|
||||
expect(err).to.not.exist;
|
||||
expect(result).to.equal(3);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
it('should wrap method without error', function(done) {
|
||||
this.timeAsyncMethod(
|
||||
this.testObject,
|
||||
'nextNumber',
|
||||
'someContext.TestObject'
|
||||
)
|
||||
return done()
|
||||
})
|
||||
|
||||
it('should wrap method without error', function(done) {
|
||||
this.timeAsyncMethod(this.testObject, 'nextNumber', 'someContext.TestObject');
|
||||
return done();
|
||||
});
|
||||
it('should transparently wrap method invocation in timer', function(done) {
|
||||
this.timeAsyncMethod(
|
||||
this.testObject,
|
||||
'nextNumber',
|
||||
'someContext.TestObject'
|
||||
)
|
||||
return this.testObject.nextNumber(2, (err, result) => {
|
||||
expect(err).to.not.exist
|
||||
expect(result).to.equal(3)
|
||||
expect(this.TimerConstructor.callCount).to.equal(1)
|
||||
expect(this.Timer.done.callCount).to.equal(1)
|
||||
return done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should transparently wrap method invocation in timer', function(done) {
|
||||
this.timeAsyncMethod(this.testObject, 'nextNumber', 'someContext.TestObject');
|
||||
return this.testObject.nextNumber(2, (err, result) => {
|
||||
expect(err).to.not.exist;
|
||||
expect(result).to.equal(3);
|
||||
expect(this.TimerConstructor.callCount).to.equal(1);
|
||||
expect(this.Timer.done.callCount).to.equal(1);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
it('should increment success count', function(done) {
|
||||
this.metrics.inc = sinon.stub()
|
||||
this.timeAsyncMethod(
|
||||
this.testObject,
|
||||
'nextNumber',
|
||||
'someContext.TestObject'
|
||||
)
|
||||
return this.testObject.nextNumber(2, (err, result) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
expect(this.metrics.inc.callCount).to.equal(1)
|
||||
expect(
|
||||
this.metrics.inc.calledWith('someContext_result', 1, {
|
||||
method: 'TestObject_nextNumber',
|
||||
status: 'success'
|
||||
})
|
||||
).to.equal(true)
|
||||
return done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should increment success count', function(done) {
|
||||
this.metrics.inc = sinon.stub();
|
||||
this.timeAsyncMethod(this.testObject, 'nextNumber', 'someContext.TestObject');
|
||||
return this.testObject.nextNumber(2, (err, result) => {
|
||||
expect(this.metrics.inc.callCount).to.equal(1);
|
||||
expect(this.metrics.inc.calledWith('someContext_result', 1, { method: 'TestObject_nextNumber', status: 'success'})).to.equal(true);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
describe('when base method produces an error', function() {
|
||||
beforeEach(function() {
|
||||
this.metrics.inc = sinon.stub()
|
||||
return (this.testObject.nextNumber = function(n, callback) {
|
||||
return setTimeout(() => callback(new Error('woops')), 100)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when base method produces an error', function() {
|
||||
beforeEach(function() {
|
||||
this.metrics.inc = sinon.stub();
|
||||
return this.testObject.nextNumber = function(n, callback) {
|
||||
if (callback == null) { callback = function(err, result){}; }
|
||||
return setTimeout(
|
||||
() => callback(new Error('woops'))
|
||||
, 100
|
||||
);
|
||||
};
|
||||
});
|
||||
it('should propagate the error transparently', function(done) {
|
||||
this.timeAsyncMethod(
|
||||
this.testObject,
|
||||
'nextNumber',
|
||||
'someContext.TestObject'
|
||||
)
|
||||
return this.testObject.nextNumber(2, (err, result) => {
|
||||
expect(err).to.exist
|
||||
expect(err).to.be.instanceof(Error)
|
||||
expect(result).to.not.exist
|
||||
return done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should propagate the error transparently', function(done) {
|
||||
this.timeAsyncMethod(this.testObject, 'nextNumber', 'someContext.TestObject');
|
||||
return this.testObject.nextNumber(2, (err, result) => {
|
||||
expect(err).to.exist;
|
||||
expect(err).to.be.instanceof(Error);
|
||||
expect(result).to.not.exist;
|
||||
return done();
|
||||
});
|
||||
});
|
||||
return it('should increment failure count', function(done) {
|
||||
this.timeAsyncMethod(
|
||||
this.testObject,
|
||||
'nextNumber',
|
||||
'someContext.TestObject'
|
||||
)
|
||||
return this.testObject.nextNumber(2, (err, result) => {
|
||||
expect(err).to.exist
|
||||
expect(this.metrics.inc.callCount).to.equal(1)
|
||||
expect(
|
||||
this.metrics.inc.calledWith('someContext_result', 1, {
|
||||
method: 'TestObject_nextNumber',
|
||||
status: 'failed'
|
||||
})
|
||||
).to.equal(true)
|
||||
return done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return it('should increment failure count', function(done) {
|
||||
this.timeAsyncMethod(this.testObject, 'nextNumber', 'someContext.TestObject');
|
||||
return this.testObject.nextNumber(2, (err, result) => {
|
||||
expect(this.metrics.inc.callCount).to.equal(1);
|
||||
expect(this.metrics.inc.calledWith('someContext_result', 1, { method: 'TestObject_nextNumber', status: 'failed'})).to.equal(true);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('when a logger is supplied', function() {
|
||||
beforeEach(function() {
|
||||
return (this.logger = { log: sinon.stub() })
|
||||
})
|
||||
|
||||
describe('when a logger is supplied', function() {
|
||||
beforeEach(function() {
|
||||
return this.logger = {log: sinon.stub()};});
|
||||
return it('should also call logger.log', function(done) {
|
||||
this.timeAsyncMethod(
|
||||
this.testObject,
|
||||
'nextNumber',
|
||||
'someContext.TestObject',
|
||||
this.logger
|
||||
)
|
||||
return this.testObject.nextNumber(2, (err, result) => {
|
||||
expect(err).to.not.exist
|
||||
expect(result).to.equal(3)
|
||||
expect(this.TimerConstructor.callCount).to.equal(1)
|
||||
expect(this.Timer.done.callCount).to.equal(1)
|
||||
expect(this.logger.log.callCount).to.equal(1)
|
||||
return done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return it('should also call logger.log', function(done) {
|
||||
this.timeAsyncMethod(this.testObject, 'nextNumber', 'someContext.TestObject', this.logger);
|
||||
return this.testObject.nextNumber(2, (err, result) => {
|
||||
expect(err).to.not.exist;
|
||||
expect(result).to.equal(3);
|
||||
expect(this.TimerConstructor.callCount).to.equal(1);
|
||||
expect(this.Timer.done.callCount).to.equal(1);
|
||||
expect(this.logger.log.callCount).to.equal(1);
|
||||
return done();
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('when the wrapper cannot be applied', function() {
|
||||
beforeEach(function() {})
|
||||
|
||||
describe('when the wrapper cannot be applied', function() {
|
||||
beforeEach(function() {});
|
||||
return it('should raise an error', function() {
|
||||
const badWrap = () => {
|
||||
return this.timeAsyncMethod(
|
||||
this.testObject,
|
||||
'DEFINITELY_NOT_A_REAL_METHOD',
|
||||
'someContext.TestObject'
|
||||
)
|
||||
}
|
||||
return expect(badWrap).to.throw(
|
||||
/^.*expected object property 'DEFINITELY_NOT_A_REAL_METHOD' to be a function.*$/
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
return it('should raise an error', function() {
|
||||
const badWrap = () => {
|
||||
return this.timeAsyncMethod(this.testObject, 'DEFINITELY_NOT_A_REAL_METHOD', 'someContext.TestObject');
|
||||
};
|
||||
return expect(badWrap).to.throw(
|
||||
/^.*expected object property 'DEFINITELY_NOT_A_REAL_METHOD' to be a function.*$/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
return describe('when the wrapped function is not using a callback', function() {
|
||||
beforeEach(function() {
|
||||
this.realMethod = sinon.stub().returns(42);
|
||||
return this.testObject.nextNumber = this.realMethod;
|
||||
});
|
||||
|
||||
it('should not throw an error', function() {
|
||||
this.timeAsyncMethod(this.testObject, 'nextNumber', 'someContext.TestObject');
|
||||
const badCall = () => {
|
||||
return this.testObject.nextNumber(2);
|
||||
};
|
||||
return expect(badCall).to.not.throw(Error);
|
||||
});
|
||||
|
||||
return it('should call the underlying method', function() {
|
||||
this.timeAsyncMethod(this.testObject, 'nextNumber', 'someContext.TestObject');
|
||||
const result = this.testObject.nextNumber(12);
|
||||
expect(this.realMethod.callCount).to.equal(1);
|
||||
expect(this.realMethod.calledWith(12)).to.equal(true);
|
||||
return expect(result).to.equal(42);
|
||||
});
|
||||
});
|
||||
});
|
||||
return describe('when the wrapped function is not using a callback', function() {
|
||||
beforeEach(function() {
|
||||
this.realMethod = sinon.stub().returns(42)
|
||||
return (this.testObject.nextNumber = this.realMethod)
|
||||
})
|
||||
|
||||
it('should not throw an error', function() {
|
||||
this.timeAsyncMethod(
|
||||
this.testObject,
|
||||
'nextNumber',
|
||||
'someContext.TestObject'
|
||||
)
|
||||
const badCall = () => {
|
||||
return this.testObject.nextNumber(2)
|
||||
}
|
||||
return expect(badCall).to.not.throw(Error)
|
||||
})
|
||||
|
||||
return it('should call the underlying method', function() {
|
||||
this.timeAsyncMethod(
|
||||
this.testObject,
|
||||
'nextNumber',
|
||||
'someContext.TestObject'
|
||||
)
|
||||
const result = this.testObject.nextNumber(12)
|
||||
expect(this.realMethod.callCount).to.equal(1)
|
||||
expect(this.realMethod.calledWith(12)).to.equal(true)
|
||||
return expect(result).to.equal(42)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -8,59 +8,77 @@
|
|||
*/
|
||||
|
||||
module.exports = function(obj, methodName, prefix, logger) {
|
||||
let modifedMethodName;
|
||||
const metrics = require('./index');
|
||||
let modifedMethodName
|
||||
const metrics = require('./index')
|
||||
|
||||
if (typeof obj[methodName] !== 'function') {
|
||||
throw new Error(`[Metrics] expected object property '${methodName}' to be a function`);
|
||||
}
|
||||
if (typeof obj[methodName] !== 'function') {
|
||||
throw new Error(
|
||||
`[Metrics] expected object property '${methodName}' to be a function`
|
||||
)
|
||||
}
|
||||
|
||||
const key = `${prefix}.${methodName}`;
|
||||
const key = `${prefix}.${methodName}`
|
||||
|
||||
const realMethod = obj[methodName];
|
||||
const realMethod = obj[methodName]
|
||||
|
||||
const splitPrefix = prefix.split(".");
|
||||
const startPrefix = splitPrefix[0];
|
||||
const splitPrefix = prefix.split('.')
|
||||
const startPrefix = splitPrefix[0]
|
||||
|
||||
if (splitPrefix[1] != null) {
|
||||
modifedMethodName = `${splitPrefix[1]}_${methodName}`;
|
||||
} else {
|
||||
modifedMethodName = methodName;
|
||||
}
|
||||
return obj[methodName] = function(...originalArgs) {
|
||||
if (splitPrefix[1] != null) {
|
||||
modifedMethodName = `${splitPrefix[1]}_${methodName}`
|
||||
} else {
|
||||
modifedMethodName = methodName
|
||||
}
|
||||
return (obj[methodName] = function(...originalArgs) {
|
||||
const adjustedLength = Math.max(originalArgs.length, 1)
|
||||
const firstArgs = originalArgs.slice(0, adjustedLength - 1)
|
||||
const callback = originalArgs[adjustedLength - 1]
|
||||
|
||||
const adjustedLength = Math.max(originalArgs.length, 1), firstArgs = originalArgs.slice(0, adjustedLength - 1), callback = originalArgs[adjustedLength - 1];
|
||||
if (callback == null || typeof callback !== 'function') {
|
||||
if (logger != null) {
|
||||
logger.log(
|
||||
`[Metrics] expected wrapped method '${methodName}' to be invoked with a callback`
|
||||
)
|
||||
}
|
||||
return realMethod.apply(this, originalArgs)
|
||||
}
|
||||
|
||||
if ((callback == null) || (typeof callback !== 'function')) {
|
||||
if (logger != null) {
|
||||
logger.log(`[Metrics] expected wrapped method '${methodName}' to be invoked with a callback`);
|
||||
}
|
||||
return realMethod.apply(this, originalArgs);
|
||||
}
|
||||
const timer = new metrics.Timer(startPrefix, 1, {
|
||||
method: modifedMethodName
|
||||
})
|
||||
|
||||
const timer = new metrics.Timer(startPrefix, 1, {method: modifedMethodName});
|
||||
|
||||
return realMethod.call(this, ...Array.from(firstArgs), function(...callbackArgs) {
|
||||
const elapsedTime = timer.done();
|
||||
const possibleError = callbackArgs[0];
|
||||
if (possibleError != null) {
|
||||
metrics.inc(`${startPrefix}_result`, 1, {status:"failed", method: modifedMethodName});
|
||||
} else {
|
||||
metrics.inc(`${startPrefix}_result`, 1, {status:"success", method: modifedMethodName});
|
||||
}
|
||||
if (logger != null) {
|
||||
const loggableArgs = {};
|
||||
try {
|
||||
for (let idx = 0; idx < firstArgs.length; idx++) {
|
||||
const arg = firstArgs[idx];
|
||||
if (arg.toString().match(/^[0-9a-f]{24}$/)) {
|
||||
loggableArgs[`${idx}`] = arg;
|
||||
}
|
||||
}
|
||||
} catch (error) {}
|
||||
logger.log({key, args: loggableArgs, elapsedTime}, "[Metrics] timed async method call");
|
||||
}
|
||||
return callback.apply(this, callbackArgs);
|
||||
});
|
||||
};
|
||||
};
|
||||
return realMethod.call(this, ...Array.from(firstArgs), function(
|
||||
...callbackArgs
|
||||
) {
|
||||
const elapsedTime = timer.done()
|
||||
const possibleError = callbackArgs[0]
|
||||
if (possibleError != null) {
|
||||
metrics.inc(`${startPrefix}_result`, 1, {
|
||||
status: 'failed',
|
||||
method: modifedMethodName
|
||||
})
|
||||
} else {
|
||||
metrics.inc(`${startPrefix}_result`, 1, {
|
||||
status: 'success',
|
||||
method: modifedMethodName
|
||||
})
|
||||
}
|
||||
if (logger != null) {
|
||||
const loggableArgs = {}
|
||||
try {
|
||||
for (let idx = 0; idx < firstArgs.length; idx++) {
|
||||
const arg = firstArgs[idx]
|
||||
if (arg.toString().match(/^[0-9a-f]{24}$/)) {
|
||||
loggableArgs[`${idx}`] = arg
|
||||
}
|
||||
}
|
||||
} catch (error) {}
|
||||
logger.log(
|
||||
{ key, args: loggableArgs, elapsedTime },
|
||||
'[Metrics] timed async method call'
|
||||
)
|
||||
}
|
||||
return callback.apply(this, callbackArgs)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
if (!process.env.UV_THREADPOOL_SIZE) {
|
||||
process.env.UV_THREADPOOL_SIZE=16;
|
||||
console.log(`Set UV_THREADPOOL_SIZE=${process.env.UV_THREADPOOL_SIZE}`);
|
||||
process.env.UV_THREADPOOL_SIZE = 16
|
||||
console.log(`Set UV_THREADPOOL_SIZE=${process.env.UV_THREADPOOL_SIZE}`)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue