diff --git a/libraries/metrics/http.coffee b/libraries/metrics/http.coffee index c175f26a14..f5e3719d53 100644 --- a/libraries/metrics/http.coffee +++ b/libraries/metrics/http.coffee @@ -10,9 +10,7 @@ module.exports.monitor = (logger) -> responseTime = new Date() - startTime if req.route?.path? routePath = req.route.path.toString().replace(/\//g, '_').replace(/\:/g, '').slice(1) - key = "http-requests.#{routePath}.#{req.method}.#{res.statusCode}" - - Metrics.timing(key, responseTime) + Metrics.timing("http_request", responseTime, null, {method:req.method, status_code: res.statusCode, path:routePath}) logger.log req: url: req.originalUrl || req.url diff --git a/libraries/metrics/metrics.coffee b/libraries/metrics/metrics.coffee index 7d07d6aed3..9f8df741dd 100644 --- a/libraries/metrics/metrics.coffee +++ b/libraries/metrics/metrics.coffee @@ -1,6 +1,3 @@ -StatsD = require('lynx') -statsd = new StatsD(process.env["STATSD_HOST"] or "localhost", 8125, {on_error:->}) - traceAgent = require('@google-cloud/trace-agent') debugAgent = require('@google-cloud/debug-agent') @@ -8,14 +5,13 @@ prom = require('prom-client') Register = require('prom-client').register collectDefaultMetrics = prom.collectDefaultMetrics -name = "unknown" +appname = "unknown" hostname = require('os').hostname() buildKey = (key)-> "#{name}.#{hostname}.#{key}" buildGlobalKey = (key)-> "#{name}.global.#{key}" -counters = {} -gauges = {} +promMetrics = {} destructors = [] @@ -23,17 +19,17 @@ require "./uv_threadpool_size" module.exports = Metrics = initialize: (_name) -> - name = _name + appname = _name + collectDefaultMetrics({ timeout: 5000, prefix: Metrics.buildPromKey()}) traceAgent.start() debugAgent.start({ serviceContext: { allowExpressions: true, - service: name, + service: appname, version: '0.0.1' } }) - - collectDefaultMetrics({ timeout: 5000, prefix: name + "_" }) + Metrics.inc("process_startup") registerDestructor: (func) -> destructors.push func @@ -44,63 +40,90 @@ module.exports = Metrics = res.end(Register.metrics()) ) - sanitizeKey: (key) -> + buildPromKey: (key = "")-> key.replace /[^a-zA-Z0-9]/g, "_" sanitizeValue: (value) -> parseFloat(value) set : (key, value, sampleRate = 1)-> - statsd.set buildKey(key), value, sampleRate + console.log("counts are not currently supported") - inc : (key, sampleRate = 1)-> - statsd.increment buildKey(key), sampleRate - key = this.sanitizeKey(key) - if !counters[key] - counters[key] = new prom.Counter({ - name: key, + inc : (key, sampleRate = 1, opts = {})-> + key = Metrics.buildPromKey(key) + if !promMetrics[key]? + promMetrics[key] = new prom.Counter({ + name: key, help: key, - labelNames: ['name','host'] + labelNames: ['app','host','status','method'] }) - counters[key].inc({name: name, host: hostname}) + opts.app = appname + opts.host = hostname + promMetrics[key].inc(opts) + if process.env['DEBUG_METRICS'] + console.log("doing inc", key, opts) count : (key, count, sampleRate = 1)-> - statsd.count buildKey(key), count, sampleRate + key = Metrics.buildPromKey(key) + if !promMetrics[key]? + promMetrics[key] = new prom.Counter({ + name: key, + help: key, + labelNames: ['app','host'] + }) + promMetrics[key].inc({app: appname, host: hostname}, count) + if process.env['DEBUG_METRICS'] + console.log("doing count/inc", key, opts) - timing: (key, timeSpan, sampleRate)-> - statsd.timing(buildKey(key), timeSpan, sampleRate) + timing: (key, timeSpan, sampleRate, opts = {})-> + key = Metrics.buildPromKey("timer_" + key) + if !promMetrics[key]? + promMetrics[key] = new prom.Summary({ + name: key, + help: key, + maxAgeSeconds: 600, + ageBuckets: 10, + labelNames: ['app', 'path', 'status_code', 'method', 'collection', 'query'] + }) + opts.app = appname + promMetrics[key].observe(opts, timeSpan) + if process.env['DEBUG_METRICS'] + console.log("doing timing", key, opts) Timer : class - constructor :(key, sampleRate = 1)-> + constructor :(key, sampleRate = 1, opts)-> this.start = new Date() + key = Metrics.buildPromKey(key) this.key = key this.sampleRate = sampleRate + this.opts = opts + done:-> timeSpan = new Date - this.start - statsd.timing(buildKey(this.key), timeSpan, this.sampleRate) + Metrics.timing(this.key, timeSpan, this.sampleRate, this.opts) return timeSpan gauge : (key, value, sampleRate = 1)-> - statsd.gauge buildKey(key), value, sampleRate - key = this.sanitizeKey(key) - if !gauges[key] - gauges[key] = new prom.Gauge({ - name: key, + key = Metrics.buildPromKey(key) + if !promMetrics[key]? + promMetrics[key] = new prom.Gauge({ + name: key, help: key, - labelNames: ['name','host'] + labelNames: ['app','host'] }) - gauges[key].set({name: name, host: hostname},this.sanitizeValue(value)) - + promMetrics[key].set({app: appname, host: hostname}, this.sanitizeValue(value)) + if process.env['DEBUG_METRICS'] + console.log("doing gauge", key, opts) + globalGauge: (key, value, sampleRate = 1)-> - statsd.gauge buildGlobalKey(key), value, sampleRate - key = this.sanitizeKey(key) - if !gauges[key] - gauges[key] = new prom.Gauge({ - name: key, + key = Metrics.buildPromKey(key) + if !promMetrics[key]? + promMetrics[key] = new prom.Gauge({ + name: key, help: key, - labelNames: ['name','host'] + labelNames: ['app','host'] }) - gauges[key].set({name: name},this.sanitizeValue(value)) + promMetrics[key].set({app: appname},this.sanitizeValue(value)) mongodb: require "./mongodb" http: require "./http" @@ -113,4 +136,3 @@ module.exports = Metrics = close: () -> for func in destructors func() - statsd.close() diff --git a/libraries/metrics/mongodb.coffee b/libraries/metrics/mongodb.coffee index ce1ca71385..1f54cdde9a 100644 --- a/libraries/metrics/mongodb.coffee +++ b/libraries/metrics/mongodb.coffee @@ -34,7 +34,7 @@ module.exports = query = Object.keys(db_command.query).sort().join("_") key += "." + query - timer = new Metrics.Timer(key) + timer = new Metrics.Timer("mongo", {collection: collection, query:query}) start = new Date() _method.call this, db_command, options, () -> timer.done() diff --git a/libraries/metrics/package.json b/libraries/metrics/package.json index 450b588158..c56b67ab3d 100644 --- a/libraries/metrics/package.json +++ b/libraries/metrics/package.json @@ -1,6 +1,6 @@ { "name": "metrics-sharelatex", - "version": "1.9.0", + "version": "2.0.7", "description": "A drop-in metrics and monitoring module for node.js apps", "repository": { "type": "git", @@ -10,7 +10,9 @@ "coffee-script": "1.6.0", "lynx": "~0.1.1", "prom-client": "^11.1.3", - "underscore": "~1.6.0" + "underscore": "~1.6.0", + "@google-cloud/debug-agent": "^3.0.0", + "@google-cloud/trace-agent": "^3.2.0" }, "devDependencies": { "bunyan": "^1.0.0", @@ -22,8 +24,6 @@ "grunt-execute": "^0.2.2", "grunt-mocha-test": "^0.11.0", "sandboxed-module": "", - "sinon": "", - "@google-cloud/debug-agent": "^3.0.0", - "@google-cloud/trace-agent": "^3.2.0" + "sinon": "" } } diff --git a/libraries/metrics/timeAsyncMethod.coffee b/libraries/metrics/timeAsyncMethod.coffee index 27e21e6e09..8d6099d826 100644 --- a/libraries/metrics/timeAsyncMethod.coffee +++ b/libraries/metrics/timeAsyncMethod.coffee @@ -5,9 +5,17 @@ module.exports = (obj, methodName, prefix, logger) -> if typeof obj[methodName] != 'function' throw new Error("[Metrics] expected object property '#{methodName}' to be a function") - realMethod = obj[methodName] key = "#{prefix}.#{methodName}" + realMethod = obj[methodName] + + splitPrefix = prefix.split(".") + startPrefix = splitPrefix[0] + + if splitPrefix[1]? + modifedMethodName = "#{splitPrefix[1]}_#{methodName}" + else + modifedMethodName = methodName obj[methodName] = (originalArgs...) -> [firstArgs..., callback] = originalArgs @@ -17,15 +25,15 @@ module.exports = (obj, methodName, prefix, logger) -> logger.log "[Metrics] expected wrapped method '#{methodName}' to be invoked with a callback" return realMethod.apply this, originalArgs - timer = new metrics.Timer(key) + timer = new metrics.Timer(startPrefix, 1, {method: modifedMethodName}) realMethod.call this, firstArgs..., (callbackArgs...) -> elapsedTime = timer.done() possibleError = callbackArgs[0] if possibleError? - metrics.inc "#{key}.failure" + metrics.inc "#{startPrefix}_result", 1, {status:"failed", method: modifedMethodName} else - metrics.inc "#{key}.success" + metrics.inc "#{startPrefix}_result", 1, {status:"success", method: modifedMethodName} if logger? loggableArgs = {} try