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
* 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() {

View file

@ -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": {

View file

@ -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)

18
package-lock.json generated
View file

@ -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"
}