2023-02-09 07:24:35 -05:00
|
|
|
const logger = require('@overleaf/logger')
|
2020-09-11 14:18:22 -04:00
|
|
|
const prom = require('prom-client')
|
|
|
|
const registry = require('prom-client').register
|
|
|
|
const metrics = new Map()
|
2020-07-17 11:01:58 -04:00
|
|
|
|
2023-02-09 07:24:35 -05:00
|
|
|
const labelsKey = function (labels) {
|
|
|
|
let keys = Object.keys(labels)
|
2020-09-11 14:18:22 -04:00
|
|
|
if (keys.length === 0) {
|
|
|
|
return ''
|
|
|
|
}
|
|
|
|
|
|
|
|
keys = keys.sort()
|
|
|
|
|
|
|
|
let hash = ''
|
2023-02-09 07:24:35 -05:00
|
|
|
for (const key of keys) {
|
2020-09-11 14:18:22 -04:00
|
|
|
if (hash.length) {
|
|
|
|
hash += ','
|
|
|
|
}
|
2023-02-09 07:24:35 -05:00
|
|
|
hash += `${key}:${labels[key]}`
|
2020-09-11 14:18:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return hash
|
|
|
|
}
|
2020-07-17 11:01:58 -04:00
|
|
|
|
2023-02-09 07:24:35 -05:00
|
|
|
const labelsAsArgs = function (labels, labelNames) {
|
2020-09-11 14:18:22 -04:00
|
|
|
const args = []
|
2023-02-09 07:24:35 -05:00
|
|
|
for (const label of labelNames) {
|
|
|
|
args.push(labels[label] || '')
|
2020-09-11 14:18:22 -04:00
|
|
|
}
|
|
|
|
return args
|
|
|
|
}
|
2020-07-17 11:01:58 -04:00
|
|
|
|
|
|
|
const PromWrapper = {
|
2020-09-11 14:18:22 -04:00
|
|
|
ttlInMinutes: 0,
|
|
|
|
registry,
|
2020-07-17 11:01:58 -04:00
|
|
|
|
2023-02-09 07:24:35 -05:00
|
|
|
metric(type, name, labels, buckets) {
|
|
|
|
return metrics.get(name) || new MetricWrapper(type, name, labels, buckets)
|
2020-09-11 14:18:22 -04:00
|
|
|
},
|
2020-07-17 11:01:58 -04:00
|
|
|
|
2021-12-16 04:04:32 -05:00
|
|
|
collectDefaultMetrics: prom.collectDefaultMetrics,
|
2020-09-11 14:18:22 -04:00
|
|
|
}
|
2020-07-17 11:01:58 -04:00
|
|
|
|
|
|
|
class MetricWrapper {
|
2023-02-09 07:24:35 -05:00
|
|
|
constructor(type, name, labels, buckets) {
|
2020-09-11 14:18:22 -04:00
|
|
|
metrics.set(name, this)
|
|
|
|
this.name = name
|
|
|
|
this.instances = new Map()
|
|
|
|
this.lastAccess = new Date()
|
2023-02-09 07:24:35 -05:00
|
|
|
|
|
|
|
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}`)
|
|
|
|
}
|
2020-09-11 14:18:22 -04:00
|
|
|
}
|
|
|
|
|
2023-02-09 07:24:35 -05:00
|
|
|
inc(labels, value) {
|
|
|
|
this._execMethod('inc', labels, value)
|
2020-09-11 14:18:22 -04:00
|
|
|
}
|
|
|
|
|
2023-02-09 07:24:35 -05:00
|
|
|
observe(labels, value) {
|
|
|
|
this._execMethod('observe', labels, value)
|
2020-09-11 14:18:22 -04:00
|
|
|
}
|
|
|
|
|
2023-02-09 07:24:35 -05:00
|
|
|
set(labels, value) {
|
|
|
|
this._execMethod('set', labels, value)
|
2020-09-11 14:18:22 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
sweep() {
|
|
|
|
const thresh = new Date(Date.now() - 1000 * 60 * PromWrapper.ttlInMinutes)
|
|
|
|
this.instances.forEach((instance, key) => {
|
|
|
|
if (thresh > instance.time) {
|
|
|
|
if (process.env.DEBUG_METRICS) {
|
2021-12-16 04:04:32 -05:00
|
|
|
// eslint-disable-next-line no-console
|
2020-09-11 14:18:22 -04:00
|
|
|
console.log(
|
|
|
|
'Sweeping stale metric instance',
|
|
|
|
this.name,
|
2023-02-09 07:24:35 -05:00
|
|
|
{ labels: instance.labels },
|
2020-09-11 14:18:22 -04:00
|
|
|
key
|
|
|
|
)
|
|
|
|
}
|
2023-02-09 07:24:35 -05:00
|
|
|
this.metric.remove(
|
|
|
|
...labelsAsArgs(instance.labels, this.metric.labelNames)
|
2020-09-11 14:18:22 -04:00
|
|
|
)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
if (thresh > this.lastAccess) {
|
|
|
|
if (process.env.DEBUG_METRICS) {
|
2021-12-16 04:04:32 -05:00
|
|
|
// eslint-disable-next-line no-console
|
2020-09-11 14:18:22 -04:00
|
|
|
console.log('Sweeping stale metric', this.name, thresh, this.lastAccess)
|
|
|
|
}
|
|
|
|
metrics.delete(this.name)
|
2023-02-09 07:24:35 -05:00
|
|
|
registry.removeSingleMetric(this.name)
|
2020-09-11 14:18:22 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-09 07:24:35 -05:00
|
|
|
_execMethod(method, labels, value) {
|
|
|
|
const key = labelsKey(labels)
|
2020-09-11 14:18:22 -04:00
|
|
|
if (key !== '') {
|
2023-02-09 07:24:35 -05:00
|
|
|
this.instances.set(key, { time: new Date(), labels })
|
2020-09-11 14:18:22 -04:00
|
|
|
}
|
|
|
|
this.lastAccess = new Date()
|
2023-02-09 07:24:35 -05:00
|
|
|
try {
|
|
|
|
this.metric[method](labels, value)
|
|
|
|
} catch (err) {
|
|
|
|
logger.warn(
|
|
|
|
{ err, metric: this.metric.name, labels },
|
|
|
|
'failed to record metric'
|
|
|
|
)
|
|
|
|
}
|
2020-09-11 14:18:22 -04:00
|
|
|
}
|
2020-07-17 11:01:58 -04:00
|
|
|
}
|
|
|
|
|
2020-10-03 13:40:46 -04:00
|
|
|
let sweepingInterval
|
2021-12-16 04:04:32 -05:00
|
|
|
PromWrapper.setupSweeping = function () {
|
2020-10-03 13:40:46 -04:00
|
|
|
if (sweepingInterval) {
|
|
|
|
clearInterval(sweepingInterval)
|
|
|
|
}
|
2020-10-03 13:42:24 -04:00
|
|
|
if (!PromWrapper.ttlInMinutes) {
|
|
|
|
if (process.env.DEBUG_METRICS) {
|
2021-12-16 04:04:32 -05:00
|
|
|
// eslint-disable-next-line no-console
|
2020-10-03 13:42:24 -04:00
|
|
|
console.log('Not registering sweep method -- empty ttl')
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2020-09-11 14:18:22 -04:00
|
|
|
if (process.env.DEBUG_METRICS) {
|
2021-12-16 04:04:32 -05:00
|
|
|
// eslint-disable-next-line no-console
|
2020-09-11 14:18:22 -04:00
|
|
|
console.log('Registering sweep method')
|
|
|
|
}
|
2021-12-16 04:04:32 -05:00
|
|
|
sweepingInterval = setInterval(function () {
|
2020-10-03 13:42:24 -04:00
|
|
|
if (process.env.DEBUG_METRICS) {
|
2021-12-16 04:04:32 -05:00
|
|
|
// eslint-disable-next-line no-console
|
2020-10-03 13:42:24 -04:00
|
|
|
console.log('Sweeping metrics')
|
2020-09-11 14:18:22 -04:00
|
|
|
}
|
2023-02-09 07:24:35 -05:00
|
|
|
metrics.forEach((metric, key) => {
|
|
|
|
metric.sweep()
|
2020-10-03 13:42:24 -04:00
|
|
|
})
|
2020-09-11 14:18:22 -04:00
|
|
|
}, 60000)
|
2020-10-03 13:40:46 -04:00
|
|
|
|
|
|
|
const Metrics = require('./index')
|
|
|
|
Metrics.registerDestructor(() => clearInterval(sweepingInterval))
|
2020-07-17 11:01:58 -04:00
|
|
|
}
|
|
|
|
|
2020-09-11 14:18:22 -04:00
|
|
|
module.exports = PromWrapper
|