Przeglądaj źródła

Install prettier and eslint

Eric Mc Sween 6 lat temu
rodzic
commit
971a768c4e

+ 55 - 0
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"
+      }
+    }
+  ]
+}

+ 4 - 0
libraries/metrics/.prettierrc

@@ -0,0 +1,4 @@
+{
+  "semi": false,
+  "singleQuote": true
+}

+ 28 - 24
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))
+  }
+}

+ 83 - 63
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;
-}
+  return typeof value !== 'undefined' && value !== null
+    ? transform(value)
+    : undefined
+}

+ 226 - 184
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");
-
-const ExpressCompression = require('compression');
-const prom = require('./prom_wrapper');
-
-const {
-    collectDefaultMetrics
-} = prom;
-
-let appname = "unknown";
-const hostname = require('os').hostname();
-
-const destructors = [];
-
-require("./uv_threadpool_size");
-
-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;
-		}
-
-		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);
-		}
-
-		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']
-				}
-			});
-		}
-
-		return Metrics.inc("process_startup");
-	},
-
-	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());
-		});
-	},
-
-	buildPromKey(key){
-		if (key == null) { key = ""; }
-		return key.replace(/[^a-zA-Z0-9]/g, "_");
-	},
-
-	sanitizeValue(value) {
-		return parseFloat(value);
-	},
-
-	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);
-		}
-	},
-
-	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);
-		}
-	},
-
-	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;
-		}
-
-		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);
-		}
-	},
-
-	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"),
-
-	timeAsyncMethod: require('./timeAsyncMethod'),
-
-	close() {
-		return Array.from(destructors).map((func) =>
-			func());
-	}
-});
+let Metrics
+console.log('using prometheus')
+
+const ExpressCompression = require('compression')
+const prom = require('./prom_wrapper')
+
+const { collectDefaultMetrics } = prom
+
+let appname = 'unknown'
+const hostname = require('os').hostname()
+
+const destructors = []
+
+require('./uv_threadpool_size')
+
+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
+    }
+
+    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)
+    }
+
+    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
+        }
+      })
+    }
+
+    return Metrics.inc('process_startup')
+  },
+
+  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())
+      }
+    )
+  },
+
+  buildPromKey(key) {
+    if (key == null) {
+      key = ''
+    }
+    return key.replace(/[^a-zA-Z0-9]/g, '_')
+  },
+
+  sanitizeValue(value) {
+    return parseFloat(value)
+  },
+
+  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)
+    }
+  },
+
+  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)
+    }
+  },
+
+  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
+    }
+
+    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)
+    }
+  },
+
+  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'),
+
+  timeAsyncMethod: require('./timeAsyncMethod'),
+
+  close() {
+    return Array.from(destructors).map(func => func())
+  }
+}

+ 88 - 78
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)
+    }
+  }
+}

+ 193 - 121
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) {
-
-		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 v2_path = mongodb_require_path.replace(/\/mongodb$/, '/mongodb-core');
-			mongodbCore = require(v2_path);
-		} catch (error1) {}
-
-		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 mongo_driver_v1_wrapper = function(db_command, options, callback) {
-				let query;
-				if (typeof callback === 'undefined') {
-					callback = options;
-					options = {};
-				}
-
-				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);
-				}
-
-				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, 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 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);
-				}
-
-				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();
-					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] = 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(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.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");
-	}
-};
+  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) {}
+
+    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) {}
+
+    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 mongoDriverV1Wrapper = function(dbCommand, 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)
+        }
+
+        if (dbCommand.query != null) {
+          query = Object.keys(dbCommand.query)
+            .sort()
+            .join('_')
+        }
+
+        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 mongoDriverV2Wrapper = 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)
+        }
+
+        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)
+        })
+      }
+
+      if (arglen === 3) {
+        return (base[method] = mongoDriverV1Wrapper)
+      } else if (arglen === 4) {
+        return (base[method] = mongoDriverV2Wrapper)
+      }
+    }
+
+    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.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'
+    )
+  }
+}

+ 41 - 35
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
+    })()
+  }
+}

+ 15 - 1
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"
   }
 }

+ 142 - 124
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 ''; }
-
-	keys = keys.sort();
-
-	let hash = '';
-	for (let key of Array.from(keys)) {
-		if (hash.length) { hash += ","; }
-		hash += `${key}:${opts[key]}`;
-	}
-
-	return hash;
-};
+  let keys = Object.keys(opts)
+  if (keys.length === 0) {
+    return ''
+  }
+
+  keys = keys.sort()
+
+  let hash = ''
+  for (const key of Array.from(keys)) {
+    if (hash.length) {
+      hash += ','
+    }
+    hash += `${key}:${opts[key]}`
+  }
+
+  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,
-
-	metric(type, name) {
-		return metrics.get(name) || new MetricWrapper(type, name);
-	},
+  ttlInMinutes: 0,
+  registry,
 
-	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']
-				});
-		} })();
-	}
-
-	inc(opts, value) {
-		return this._execMethod('inc', opts, value);
-	}
-
-	observe(opts, value) {
-		return this._execMethod('observe', 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) || []));
-			}
-		});
-
-		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);
-	}
+  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)
+  }
+
+  observe(opts, value) {
+    return this._execMethod('observe', 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) || [])
+        )
+      }
+    })
+
+    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)
+  }
 }
 
-
 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

+ 34 - 37
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')
+    })
+  })
+})

+ 193 - 155
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
-		}
-	}
-		);
-
-		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, 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 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) => {
-			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) {
-				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();
-			});
-		});
-
-		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()};});
-
-		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() {});
-
-		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);
-		});
-	});
-});
-
-
+  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)
+      }
+    })
+  })
+
+  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 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 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()
+    })
+  })
+
+  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)
+      })
+    })
+
+    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()
+      })
+    })
+  })
+
+  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()
+      })
+    })
+  })
+
+  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 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)
+    })
+  })
+})

+ 66 - 48
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)
+    })
+  })
+}

+ 2 - 2
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}`)
 }