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