From 971a768c4e167d8cf0030958eefd73ed939fe8a4 Mon Sep 17 00:00:00 2001 From: Eric Mc Sween Date: Fri, 11 Sep 2020 14:18:22 -0400 Subject: [PATCH] Install prettier and eslint --- libraries/metrics/.eslintrc | 55 +++ libraries/metrics/.prettierrc | 4 + libraries/metrics/event_loop.js | 52 +-- libraries/metrics/http.js | 146 ++++---- libraries/metrics/index.js | 354 ++++++++++-------- libraries/metrics/memory.js | 166 ++++---- libraries/metrics/mongodb.js | 282 ++++++++------ libraries/metrics/open_sockets.js | 76 ++-- libraries/metrics/package.json | 16 +- libraries/metrics/prom_wrapper.js | 248 ++++++------ libraries/metrics/test/unit/js/event_loop.js | 71 ++-- .../test/unit/js/timeAsyncMethodTests.js | 318 +++++++++------- libraries/metrics/timeAsyncMethod.js | 114 +++--- libraries/metrics/uv_threadpool_size.js | 4 +- 14 files changed, 1102 insertions(+), 804 deletions(-) create mode 100644 libraries/metrics/.eslintrc create mode 100644 libraries/metrics/.prettierrc diff --git a/libraries/metrics/.eslintrc b/libraries/metrics/.eslintrc new file mode 100644 index 0000000000..dec00724f4 --- /dev/null +++ b/libraries/metrics/.eslintrc @@ -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" + } + } + ] +} diff --git a/libraries/metrics/.prettierrc b/libraries/metrics/.prettierrc new file mode 100644 index 0000000000..b2095be81e --- /dev/null +++ b/libraries/metrics/.prettierrc @@ -0,0 +1,4 @@ +{ + "semi": false, + "singleQuote": true +} diff --git a/libraries/metrics/event_loop.js b/libraries/metrics/event_loop.js index 5c4159ecb4..37271321b4 100644 --- a/libraries/metrics/event_loop.js +++ b/libraries/metrics/event_loop.js @@ -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)) + } +} diff --git a/libraries/metrics/http.js b/libraries/metrics/http.js index 4c9497c6d3..8d0f9caab1 100644 --- a/libraries/metrics/http.js +++ b/libraries/metrics/http.js @@ -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; -} \ No newline at end of file + return typeof value !== 'undefined' && value !== null + ? transform(value) + : undefined +} diff --git a/libraries/metrics/index.js b/libraries/metrics/index.js index 25a739d6f7..178eb01868 100644 --- a/libraries/metrics/index.js +++ b/libraries/metrics/index.js @@ -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()) + } +} diff --git a/libraries/metrics/memory.js b/libraries/metrics/memory.js index 2bcb7917d0..b4fa7210a4 100644 --- a/libraries/metrics/memory.js +++ b/libraries/metrics/memory.js @@ -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) + } + } +} diff --git a/libraries/metrics/mongodb.js b/libraries/metrics/mongodb.js index 230a8a4628..89f1d0155f 100644 --- a/libraries/metrics/mongodb.js +++ b/libraries/metrics/mongodb.js @@ -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' + ) + } +} diff --git a/libraries/metrics/open_sockets.js b/libraries/metrics/open_sockets.js index fc7a11ec45..01b7e8f874 100644 --- a/libraries/metrics/open_sockets.js +++ b/libraries/metrics/open_sockets.js @@ -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 + })() + } +} diff --git a/libraries/metrics/package.json b/libraries/metrics/package.json index 5e76416759..2d509e0776 100644 --- a/libraries/metrics/package.json +++ b/libraries/metrics/package.json @@ -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" } } diff --git a/libraries/metrics/prom_wrapper.js b/libraries/metrics/prom_wrapper.js index 64870ef746..64f267aedb 100644 --- a/libraries/metrics/prom_wrapper.js +++ b/libraries/metrics/prom_wrapper.js @@ -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 diff --git a/libraries/metrics/test/unit/js/event_loop.js b/libraries/metrics/test/unit/js/event_loop.js index 1791c0e216..5af886df99 100644 --- a/libraries/metrics/test/unit/js/event_loop.js +++ b/libraries/metrics/test/unit/js/event_loop.js @@ -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') + }) + }) +}) diff --git a/libraries/metrics/test/unit/js/timeAsyncMethodTests.js b/libraries/metrics/test/unit/js/timeAsyncMethodTests.js index de252acae4..5023d5a87f 100644 --- a/libraries/metrics/test/unit/js/timeAsyncMethodTests.js +++ b/libraries/metrics/test/unit/js/timeAsyncMethodTests.js @@ -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) + }) + }) +}) diff --git a/libraries/metrics/timeAsyncMethod.js b/libraries/metrics/timeAsyncMethod.js index b096cc6851..65553922c8 100644 --- a/libraries/metrics/timeAsyncMethod.js +++ b/libraries/metrics/timeAsyncMethod.js @@ -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) + }) + }) +} diff --git a/libraries/metrics/uv_threadpool_size.js b/libraries/metrics/uv_threadpool_size.js index bc8204e0b1..b2a8425281 100644 --- a/libraries/metrics/uv_threadpool_size.js +++ b/libraries/metrics/uv_threadpool_size.js @@ -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}`) }