mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #11679 from overleaf/em-upgrade-prom-client
Upgrade prom-client library in metrics GitOrigin-RevId: 4ff60d94b17e7f236d4b40103365ed6fe1515148
This commit is contained in:
parent
b0841592c7
commit
5683cebf71
4 changed files with 130 additions and 137 deletions
|
@ -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() {
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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
18
package-lock.json
generated
|
@ -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"
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue