overleaf/libraries/metrics/prom_wrapper.js

167 lines
3.9 KiB
JavaScript
Raw Normal View History

2020-07-17 11:01:58 -04:00
/*
* 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
*/
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
const optsKey = function(opts) {
2020-09-11 14:18:22 -04:00
let keys = Object.keys(opts)
if (keys.length === 0) {
return ''
}
keys = keys.sort()
let hash = ''
for (const key of Array.from(keys)) {
if (hash.length) {
hash += ','
}
hash += `${key}:${opts[key]}`
}
return hash
}
2020-07-17 11:01:58 -04:00
const extendOpts = function(opts, labelNames) {
2020-09-11 14:18:22 -04:00
for (const label of Array.from(labelNames)) {
if (!opts[label]) {
opts[label] = ''
}
}
return opts
}
2020-07-17 11:01:58 -04:00
const optsAsArgs = function(opts, labelNames) {
2020-09-11 14:18:22 -04:00
const args = []
for (const label of Array.from(labelNames)) {
args.push(opts[label] || '')
}
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
2020-09-11 14:18:22 -04:00
metric(type, name) {
return metrics.get(name) || new MetricWrapper(type, name)
},
2020-07-17 11:01:58 -04:00
2020-09-11 14:18:22 -04:00
collectDefaultMetrics: prom.collectDefaultMetrics
}
2020-07-17 11:01:58 -04:00
class MetricWrapper {
2020-09-11 14:18:22 -04:00
constructor(type, name) {
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: ['app', 'host', 'status', 'method', 'path']
})
case 'summary':
return new prom.Summary({
name,
help: name,
maxAgeSeconds: 60,
ageBuckets: 10,
labelNames: [
'app',
'host',
'path',
'status_code',
'method',
'collection',
'query'
]
})
case 'gauge':
return new prom.Gauge({
name,
help: name,
labelNames: ['app', 'host', 'status']
})
}
})()
}
inc(opts, value) {
return this._execMethod('inc', opts, value)
}
observe(opts, value) {
return this._execMethod('observe', opts, value)
}
set(opts, value) {
return this._execMethod('set', opts, value)
}
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) {
console.log(
'Sweeping stale metric instance',
this.name,
{ opts: instance.opts },
key
)
}
return this.metric.remove(
...Array.from(optsAsArgs(instance.opts, this.metric.labelNames) || [])
)
}
})
if (thresh > this.lastAccess) {
if (process.env.DEBUG_METRICS) {
console.log('Sweeping stale metric', this.name, thresh, this.lastAccess)
}
metrics.delete(this.name)
return registry.removeSingleMetric(this.name)
}
}
_execMethod(method, opts, value) {
opts = extendOpts(opts, this.metric.labelNames)
const key = optsKey(opts)
if (key !== '') {
this.instances.set(key, { time: new Date(), opts })
}
this.lastAccess = new Date()
return this.metric[method](opts, value)
}
2020-07-17 11:01:58 -04:00
}
if (!PromWrapper.sweepRegistered) {
2020-09-11 14:18:22 -04:00
if (process.env.DEBUG_METRICS) {
console.log('Registering sweep method')
}
PromWrapper.sweepRegistered = true
setInterval(function() {
if (PromWrapper.ttlInMinutes) {
if (process.env.DEBUG_METRICS) {
console.log('Sweeping metrics')
}
return metrics.forEach((metric, key) => {
return metric.sweep()
})
}
}, 60000)
2020-07-17 11:01:58 -04:00
}
2020-09-11 14:18:22 -04:00
module.exports = PromWrapper