Просмотр исходного кода

Merge pull request #11679 from overleaf/em-upgrade-prom-client

Upgrade prom-client library in metrics

GitOrigin-RevId: 4ff60d94b17e7f236d4b40103365ed6fe1515148
Eric Mc Sween 3 лет назад
Родитель
Сommit
5683cebf71
4 измененных файлов с 130 добавлено и 137 удалено
  1. 49 39
      libraries/metrics/index.js
  2. 1 1
      libraries/metrics/package.json
  3. 71 88
      libraries/metrics/prom_wrapper.js
  4. 9 9
      package-lock.json

+ 49 - 39
libraries/metrics/index.js

@@ -24,11 +24,16 @@ function configure(opts = {}) {
   }
 }
 
+let initialized = false
+
 /**
  * Configure the metrics module and start the default metrics collectors and
  * profiling agents.
  */
 function initialize(appName, opts = {}) {
+  if (initialized) {
+    return
+  }
   appName = appName || DEFAULT_APP_NAME
   if (tracing.tracingEnabled()) {
     tracing.initialize(appName)
@@ -72,6 +77,7 @@ function initialize(appName, opts = {}) {
   }
 
   inc('process_startup')
+  initialized = true
 }
 
 function registerDestructor(func) {
@@ -84,9 +90,16 @@ function injectMetricsRoute(app) {
     ExpressCompression({
       level: parseInt(process.env.METRICS_COMPRESSION_LEVEL || '1', 10),
     }),
-    function (req, res) {
+    function (req, res, next) {
       res.set('Content-Type', promWrapper.registry.contentType)
-      res.end(promWrapper.registry.metrics())
+      promWrapper.registry
+        .metrics()
+        .then(metrics => {
+          res.end(metrics)
+        })
+        .catch(err => {
+          next(err)
+        })
     }
   )
 }
@@ -103,70 +116,70 @@ function set(key, value, sampleRate = 1) {
   console.log('counts are not currently supported')
 }
 
-function inc(key, sampleRate = 1, opts = {}) {
+function inc(key, sampleRate = 1, labels = {}) {
   if (arguments.length === 2 && typeof sampleRate === 'object') {
-    opts = sampleRate
+    labels = sampleRate
   }
 
   key = buildPromKey(key)
-  promWrapper.metric('counter', key).inc(opts)
+  promWrapper.metric('counter', key, labels).inc(labels)
   if (process.env.DEBUG_METRICS) {
-    console.log('doing inc', key, opts)
+    console.log('doing inc', key, labels)
   }
 }
 
-function count(key, count, sampleRate = 1, opts = {}) {
+function count(key, count, sampleRate = 1, labels = {}) {
   if (arguments.length === 3 && typeof sampleRate === 'object') {
-    opts = sampleRate
+    labels = sampleRate
   }
 
   key = buildPromKey(key)
-  promWrapper.metric('counter', key).inc(opts, count)
+  promWrapper.metric('counter', key, labels).inc(labels, count)
   if (process.env.DEBUG_METRICS) {
-    console.log('doing count/inc', key, opts)
+    console.log('doing count/inc', key, labels)
   }
 }
 
-function summary(key, value, opts = {}) {
+function summary(key, value, labels = {}) {
   key = buildPromKey(key)
-  promWrapper.metric('summary', key).observe(opts, value)
+  promWrapper.metric('summary', key, labels).observe(labels, value)
   if (process.env.DEBUG_METRICS) {
-    console.log('doing summary', key, value, opts)
+    console.log('doing summary', key, value, labels)
   }
 }
 
-function timing(key, timeSpan, sampleRate = 1, opts = {}) {
+function timing(key, timeSpan, sampleRate = 1, labels = {}) {
   if (arguments.length === 3 && typeof sampleRate === 'object') {
-    opts = sampleRate
+    labels = sampleRate
   }
 
   key = buildPromKey('timer_' + key)
-  promWrapper.metric('summary', key).observe(opts, timeSpan)
+  promWrapper.metric('summary', key, labels).observe(labels, timeSpan)
   if (process.env.DEBUG_METRICS) {
-    console.log('doing timing', key, opts)
+    console.log('doing timing', key, labels)
   }
 }
 
-function histogram(key, value, buckets, opts = {}) {
+function histogram(key, value, buckets, labels = {}) {
   key = buildPromKey('histogram_' + key)
-  promWrapper.metric('histogram', key, buckets).observe(opts, value)
+  promWrapper.metric('histogram', key, labels, buckets).observe(labels, value)
   if (process.env.DEBUG_METRICS) {
-    console.log('doing histogram', key, buckets, opts)
+    console.log('doing histogram', key, buckets, labels)
   }
 }
 
 class Timer {
-  constructor(key, sampleRate = 1, opts = {}, buckets) {
+  constructor(key, sampleRate = 1, labels = {}, buckets) {
     if (typeof sampleRate === 'object') {
-      // called with (key, opts, buckets)
+      // called with (key, labels, buckets)
       if (arguments.length === 3) {
-        buckets = opts
-        opts = sampleRate
+        buckets = labels
+        labels = sampleRate
       }
 
-      // called with (key, opts)
+      // called with (key, labels)
       if (arguments.length === 2) {
-        opts = sampleRate
+        labels = sampleRate
       }
 
       sampleRate = 1 // default value to pass to timing function
@@ -176,40 +189,37 @@ class Timer {
     key = buildPromKey(key)
     this.key = key
     this.sampleRate = sampleRate
-    this.opts = opts
+    this.labels = labels
     this.buckets = buckets
   }
 
   done() {
     const timeSpan = new Date() - this.start
     if (this.buckets) {
-      histogram(this.key, timeSpan, this.buckets, this.opts)
+      histogram(this.key, timeSpan, this.buckets, this.labels)
     } else {
-      timing(this.key, timeSpan, this.sampleRate, this.opts)
+      timing(this.key, timeSpan, this.sampleRate, this.labels)
     }
     return timeSpan
   }
 }
 
-function gauge(key, value, sampleRate = 1, opts = {}) {
+function gauge(key, value, sampleRate = 1, labels = {}) {
   if (arguments.length === 3 && typeof sampleRate === 'object') {
-    opts = sampleRate
+    labels = sampleRate
   }
 
   key = buildPromKey(key)
-  promWrapper
-    .metric('gauge', key)
-    .set({ status: opts.status }, sanitizeValue(value))
+  promWrapper.metric('gauge', key, labels).set(labels, sanitizeValue(value))
   if (process.env.DEBUG_METRICS) {
-    console.log('doing gauge', key, opts)
+    console.log('doing gauge', key, labels)
   }
 }
 
-function globalGauge(key, value, sampleRate = 1, opts = {}) {
+function globalGauge(key, value, sampleRate = 1, labels = {}) {
   key = buildPromKey(key)
-  promWrapper
-    .metric('gauge', key)
-    .set({ host: 'global', status: opts.status }, sanitizeValue(value))
+  labels = { host: 'global', ...labels }
+  promWrapper.metric('gauge', key, labels).set(labels, sanitizeValue(value))
 }
 
 function close() {

+ 1 - 1
libraries/metrics/package.json

@@ -20,7 +20,7 @@
     "@opentelemetry/sdk-node": "^0.28.0",
     "@opentelemetry/semantic-conventions": "^1.2.0",
     "compression": "^1.7.4",
-    "prom-client": "^11.1.3",
+    "prom-client": "^14.1.1",
     "yn": "^3.1.1"
   },
   "devDependencies": {

+ 71 - 88
libraries/metrics/prom_wrapper.js

@@ -1,16 +1,10 @@
-/*
- * decaffeinate suggestions:
- * DS101: Remove unnecessary use of Array.from
- * DS102: Remove unnecessary code created because of implicit returns
- * DS205: Consider reworking code to avoid use of IIFEs
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
+const logger = require('@overleaf/logger')
 const prom = require('prom-client')
 const registry = require('prom-client').register
 const metrics = new Map()
 
-const optsKey = function (opts) {
-  let keys = Object.keys(opts)
+const labelsKey = function (labels) {
+  let keys = Object.keys(labels)
   if (keys.length === 0) {
     return ''
   }
@@ -18,31 +12,20 @@ const optsKey = function (opts) {
   keys = keys.sort()
 
   let hash = ''
-  for (const key of Array.from(keys)) {
+  for (const key of keys) {
     if (hash.length) {
       hash += ','
     }
-    hash += `${key}:${opts[key]}`
+    hash += `${key}:${labels[key]}`
   }
 
   return hash
 }
 
-const extendOpts = function (opts, labelNames) {
-  // Make a clone in order to be able to re-use opts for other kinds of metrics.
-  opts = Object.assign({}, opts)
-  for (const label of Array.from(labelNames)) {
-    if (!opts[label]) {
-      opts[label] = ''
-    }
-  }
-  return opts
-}
-
-const optsAsArgs = function (opts, labelNames) {
+const labelsAsArgs = function (labels, labelNames) {
   const args = []
-  for (const label of Array.from(labelNames)) {
-    args.push(opts[label] || '')
+  for (const label of labelNames) {
+    args.push(labels[label] || '')
   }
   return args
 }
@@ -51,74 +34,68 @@ const PromWrapper = {
   ttlInMinutes: 0,
   registry,
 
-  metric(type, name, buckets) {
-    return metrics.get(name) || new MetricWrapper(type, name, buckets)
+  metric(type, name, labels, buckets) {
+    return metrics.get(name) || new MetricWrapper(type, name, labels, buckets)
   },
 
   collectDefaultMetrics: prom.collectDefaultMetrics,
 }
 
 class MetricWrapper {
-  constructor(type, name, buckets) {
+  constructor(type, name, labels, buckets) {
     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: ['status', 'method', 'path'],
-          })
-        case 'histogram':
-          return new prom.Histogram({
-            name,
-            help: name,
-            labelNames: [
-              'path',
-              'status_code',
-              'method',
-              'collection',
-              'query',
-            ],
-            buckets,
-          })
-        case 'summary':
-          return new prom.Summary({
-            name,
-            help: name,
-            maxAgeSeconds: 60,
-            ageBuckets: 10,
-            labelNames: [
-              'path',
-              'status_code',
-              'method',
-              'collection',
-              'query',
-            ],
-          })
-        case 'gauge':
-          return new prom.Gauge({
-            name,
-            help: name,
-            labelNames: ['host', 'status'],
-          })
-      }
-    })()
+
+    const labelNames = labels ? Object.keys(labels) : []
+    switch (type) {
+      case 'counter':
+        this.metric = new prom.Counter({
+          name,
+          help: name,
+          labelNames,
+        })
+        break
+      case 'histogram':
+        this.metric = new prom.Histogram({
+          name,
+          help: name,
+          labelNames,
+          buckets,
+        })
+        break
+      case 'summary':
+        this.metric = new prom.Summary({
+          name,
+          help: name,
+          maxAgeSeconds: 60,
+          ageBuckets: 10,
+          labelNames,
+        })
+        break
+      case 'gauge':
+        this.metric = new prom.Gauge({
+          name,
+          help: name,
+          labelNames,
+        })
+        break
+      default:
+        throw new Error(`Unknown metric type: ${type}`)
+    }
   }
 
-  inc(opts, value) {
-    return this._execMethod('inc', opts, value)
+  inc(labels, value) {
+    this._execMethod('inc', labels, value)
   }
 
-  observe(opts, value) {
-    return this._execMethod('observe', opts, value)
+  observe(labels, value) {
+    this._execMethod('observe', labels, value)
   }
 
-  set(opts, value) {
-    return this._execMethod('set', opts, value)
+  set(labels, value) {
+    this._execMethod('set', labels, value)
   }
 
   sweep() {
@@ -130,12 +107,12 @@ class MetricWrapper {
           console.log(
             'Sweeping stale metric instance',
             this.name,
-            { opts: instance.opts },
+            { labels: instance.labels },
             key
           )
         }
-        return this.metric.remove(
-          ...Array.from(optsAsArgs(instance.opts, this.metric.labelNames) || [])
+        this.metric.remove(
+          ...labelsAsArgs(instance.labels, this.metric.labelNames)
         )
       }
     })
@@ -146,18 +123,24 @@ class MetricWrapper {
         console.log('Sweeping stale metric', this.name, thresh, this.lastAccess)
       }
       metrics.delete(this.name)
-      return registry.removeSingleMetric(this.name)
+      registry.removeSingleMetric(this.name)
     }
   }
 
-  _execMethod(method, opts, value) {
-    opts = extendOpts(opts, this.metric.labelNames)
-    const key = optsKey(opts)
+  _execMethod(method, labels, value) {
+    const key = labelsKey(labels)
     if (key !== '') {
-      this.instances.set(key, { time: new Date(), opts })
+      this.instances.set(key, { time: new Date(), labels })
     }
     this.lastAccess = new Date()
-    return this.metric[method](opts, value)
+    try {
+      this.metric[method](labels, value)
+    } catch (err) {
+      logger.warn(
+        { err, metric: this.metric.name, labels },
+        'failed to record metric'
+      )
+    }
   }
 }
 
@@ -182,8 +165,8 @@ PromWrapper.setupSweeping = function () {
       // eslint-disable-next-line no-console
       console.log('Sweeping metrics')
     }
-    return metrics.forEach((metric, key) => {
-      return metric.sweep()
+    metrics.forEach((metric, key) => {
+      metric.sweep()
     })
   }, 60000)
 

+ 9 - 9
package-lock.json

@@ -136,7 +136,7 @@
         "@opentelemetry/sdk-node": "^0.28.0",
         "@opentelemetry/semantic-conventions": "^1.2.0",
         "compression": "^1.7.4",
-        "prom-client": "^11.1.3",
+        "prom-client": "^14.1.1",
         "yn": "^3.1.1"
       },
       "devDependencies": {
@@ -25779,14 +25779,14 @@
       "integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q=="
     },
     "node_modules/prom-client": {
-      "version": "11.5.3",
-      "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-11.5.3.tgz",
-      "integrity": "sha512-iz22FmTbtkyL2vt0MdDFY+kWof+S9UB/NACxSn2aJcewtw+EERsen0urSkZ2WrHseNdydsvcxCTAnPcSMZZv4Q==",
+      "version": "14.1.1",
+      "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-14.1.1.tgz",
+      "integrity": "sha512-hFU32q7UZQ59bVJQGUtm3I2PrJ3gWvoCkilX9sF165ks1qflhugVCeK+S1JjJYHvyt3o5kj68+q3bchormjnzw==",
       "dependencies": {
         "tdigest": "^0.1.1"
       },
       "engines": {
-        "node": ">=6.1"
+        "node": ">=10"
       }
     },
     "node_modules/promise": {
@@ -43170,7 +43170,7 @@
         "chai": "^4.3.6",
         "compression": "^1.7.4",
         "mocha": "^10.2.0",
-        "prom-client": "^11.1.3",
+        "prom-client": "^14.1.1",
         "sandboxed-module": "^2.0.4",
         "sinon": "^9.2.4",
         "yn": "^3.1.1"
@@ -60770,9 +60770,9 @@
       "integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q=="
     },
     "prom-client": {
-      "version": "11.5.3",
-      "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-11.5.3.tgz",
-      "integrity": "sha512-iz22FmTbtkyL2vt0MdDFY+kWof+S9UB/NACxSn2aJcewtw+EERsen0urSkZ2WrHseNdydsvcxCTAnPcSMZZv4Q==",
+      "version": "14.1.1",
+      "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-14.1.1.tgz",
+      "integrity": "sha512-hFU32q7UZQ59bVJQGUtm3I2PrJ3gWvoCkilX9sF165ks1qflhugVCeK+S1JjJYHvyt3o5kj68+q3bchormjnzw==",
       "requires": {
         "tdigest": "^0.1.1"
       }