overleaf/libraries/metrics/prom_wrapper.js

178 lines
4.1 KiB
JavaScript
Raw Normal View History

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