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

Upgrade prom-client library in metrics

GitOrigin-RevId: 4ff60d94b17e7f236d4b40103365ed6fe1515148
This commit is contained in:
Eric Mc Sween 2023-02-09 07:24:35 -05:00 committed by Copybot
parent b0841592c7
commit 5683cebf71
4 changed files with 130 additions and 137 deletions

View file

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

View file

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

View file

@ -1,16 +1,10 @@
/* const logger = require('@overleaf/logger')
* 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 prom = require('prom-client') const prom = require('prom-client')
const registry = require('prom-client').register const registry = require('prom-client').register
const metrics = new Map() const metrics = new Map()
const optsKey = function (opts) { const labelsKey = function (labels) {
let keys = Object.keys(opts) let keys = Object.keys(labels)
if (keys.length === 0) { if (keys.length === 0) {
return '' return ''
} }
@ -18,31 +12,20 @@ const optsKey = function (opts) {
keys = keys.sort() keys = keys.sort()
let hash = '' let hash = ''
for (const key of Array.from(keys)) { for (const key of keys) {
if (hash.length) { if (hash.length) {
hash += ',' hash += ','
} }
hash += `${key}:${opts[key]}` hash += `${key}:${labels[key]}`
} }
return hash return hash
} }
const extendOpts = function (opts, labelNames) { const labelsAsArgs = function (labels, 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 args = [] const args = []
for (const label of Array.from(labelNames)) { for (const label of labelNames) {
args.push(opts[label] || '') args.push(labels[label] || '')
} }
return args return args
} }
@ -51,74 +34,68 @@ const PromWrapper = {
ttlInMinutes: 0, ttlInMinutes: 0,
registry, registry,
metric(type, name, buckets) { metric(type, name, labels, buckets) {
return metrics.get(name) || new MetricWrapper(type, name, buckets) return metrics.get(name) || new MetricWrapper(type, name, labels, buckets)
}, },
collectDefaultMetrics: prom.collectDefaultMetrics, collectDefaultMetrics: prom.collectDefaultMetrics,
} }
class MetricWrapper { class MetricWrapper {
constructor(type, name, buckets) { constructor(type, name, labels, buckets) {
metrics.set(name, this) metrics.set(name, this)
this.name = name this.name = name
this.instances = new Map() this.instances = new Map()
this.lastAccess = new Date() this.lastAccess = new Date()
this.metric = (() => {
switch (type) { const labelNames = labels ? Object.keys(labels) : []
case 'counter': switch (type) {
return new prom.Counter({ case 'counter':
name, this.metric = new prom.Counter({
help: name, name,
labelNames: ['status', 'method', 'path'], help: name,
}) labelNames,
case 'histogram': })
return new prom.Histogram({ break
name, case 'histogram':
help: name, this.metric = new prom.Histogram({
labelNames: [ name,
'path', help: name,
'status_code', labelNames,
'method', buckets,
'collection', })
'query', break
], case 'summary':
buckets, this.metric = new prom.Summary({
}) name,
case 'summary': help: name,
return new prom.Summary({ maxAgeSeconds: 60,
name, ageBuckets: 10,
help: name, labelNames,
maxAgeSeconds: 60, })
ageBuckets: 10, break
labelNames: [ case 'gauge':
'path', this.metric = new prom.Gauge({
'status_code', name,
'method', help: name,
'collection', labelNames,
'query', })
], break
}) default:
case 'gauge': throw new Error(`Unknown metric type: ${type}`)
return new prom.Gauge({ }
name,
help: name,
labelNames: ['host', 'status'],
})
}
})()
} }
inc(opts, value) { inc(labels, value) {
return this._execMethod('inc', opts, value) this._execMethod('inc', labels, value)
} }
observe(opts, value) { observe(labels, value) {
return this._execMethod('observe', opts, value) this._execMethod('observe', labels, value)
} }
set(opts, value) { set(labels, value) {
return this._execMethod('set', opts, value) this._execMethod('set', labels, value)
} }
sweep() { sweep() {
@ -130,12 +107,12 @@ class MetricWrapper {
console.log( console.log(
'Sweeping stale metric instance', 'Sweeping stale metric instance',
this.name, this.name,
{ opts: instance.opts }, { labels: instance.labels },
key key
) )
} }
return this.metric.remove( this.metric.remove(
...Array.from(optsAsArgs(instance.opts, this.metric.labelNames) || []) ...labelsAsArgs(instance.labels, this.metric.labelNames)
) )
} }
}) })
@ -146,18 +123,24 @@ class MetricWrapper {
console.log('Sweeping stale metric', this.name, thresh, this.lastAccess) console.log('Sweeping stale metric', this.name, thresh, this.lastAccess)
} }
metrics.delete(this.name) metrics.delete(this.name)
return registry.removeSingleMetric(this.name) registry.removeSingleMetric(this.name)
} }
} }
_execMethod(method, opts, value) { _execMethod(method, labels, value) {
opts = extendOpts(opts, this.metric.labelNames) const key = labelsKey(labels)
const key = optsKey(opts)
if (key !== '') { if (key !== '') {
this.instances.set(key, { time: new Date(), opts }) this.instances.set(key, { time: new Date(), labels })
} }
this.lastAccess = new Date() 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 // eslint-disable-next-line no-console
console.log('Sweeping metrics') console.log('Sweeping metrics')
} }
return metrics.forEach((metric, key) => { metrics.forEach((metric, key) => {
return metric.sweep() metric.sweep()
}) })
}, 60000) }, 60000)

18
package-lock.json generated
View file

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