prom_wrapper.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. /*
  2. * decaffeinate suggestions:
  3. * DS101: Remove unnecessary use of Array.from
  4. * DS102: Remove unnecessary code created because of implicit returns
  5. * DS205: Consider reworking code to avoid use of IIFEs
  6. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  7. */
  8. const prom = require('prom-client')
  9. const registry = require('prom-client').register
  10. const metrics = new Map()
  11. const optsKey = function (opts) {
  12. let keys = Object.keys(opts)
  13. if (keys.length === 0) {
  14. return ''
  15. }
  16. keys = keys.sort()
  17. let hash = ''
  18. for (const key of Array.from(keys)) {
  19. if (hash.length) {
  20. hash += ','
  21. }
  22. hash += `${key}:${opts[key]}`
  23. }
  24. return hash
  25. }
  26. const extendOpts = function (opts, labelNames) {
  27. for (const label of Array.from(labelNames)) {
  28. if (!opts[label]) {
  29. opts[label] = ''
  30. }
  31. }
  32. return opts
  33. }
  34. const optsAsArgs = function (opts, labelNames) {
  35. const args = []
  36. for (const label of Array.from(labelNames)) {
  37. args.push(opts[label] || '')
  38. }
  39. return args
  40. }
  41. const PromWrapper = {
  42. ttlInMinutes: 0,
  43. registry,
  44. metric(type, name) {
  45. return metrics.get(name) || new MetricWrapper(type, name)
  46. },
  47. collectDefaultMetrics: prom.collectDefaultMetrics,
  48. }
  49. class MetricWrapper {
  50. constructor(type, name) {
  51. metrics.set(name, this)
  52. this.name = name
  53. this.instances = new Map()
  54. this.lastAccess = new Date()
  55. this.metric = (() => {
  56. switch (type) {
  57. case 'counter':
  58. return new prom.Counter({
  59. name,
  60. help: name,
  61. labelNames: ['status', 'method', 'path'],
  62. })
  63. case 'summary':
  64. return new prom.Summary({
  65. name,
  66. help: name,
  67. maxAgeSeconds: 60,
  68. ageBuckets: 10,
  69. labelNames: [
  70. 'path',
  71. 'status_code',
  72. 'method',
  73. 'collection',
  74. 'query',
  75. ],
  76. })
  77. case 'gauge':
  78. return new prom.Gauge({
  79. name,
  80. help: name,
  81. labelNames: ['host', 'status'],
  82. })
  83. }
  84. })()
  85. }
  86. inc(opts, value) {
  87. return this._execMethod('inc', opts, value)
  88. }
  89. observe(opts, value) {
  90. return this._execMethod('observe', opts, value)
  91. }
  92. set(opts, value) {
  93. return this._execMethod('set', opts, value)
  94. }
  95. sweep() {
  96. const thresh = new Date(Date.now() - 1000 * 60 * PromWrapper.ttlInMinutes)
  97. this.instances.forEach((instance, key) => {
  98. if (thresh > instance.time) {
  99. if (process.env.DEBUG_METRICS) {
  100. // eslint-disable-next-line no-console
  101. console.log(
  102. 'Sweeping stale metric instance',
  103. this.name,
  104. { opts: instance.opts },
  105. key
  106. )
  107. }
  108. return this.metric.remove(
  109. ...Array.from(optsAsArgs(instance.opts, this.metric.labelNames) || [])
  110. )
  111. }
  112. })
  113. if (thresh > this.lastAccess) {
  114. if (process.env.DEBUG_METRICS) {
  115. // eslint-disable-next-line no-console
  116. console.log('Sweeping stale metric', this.name, thresh, this.lastAccess)
  117. }
  118. metrics.delete(this.name)
  119. return registry.removeSingleMetric(this.name)
  120. }
  121. }
  122. _execMethod(method, opts, value) {
  123. opts = extendOpts(opts, this.metric.labelNames)
  124. const key = optsKey(opts)
  125. if (key !== '') {
  126. this.instances.set(key, { time: new Date(), opts })
  127. }
  128. this.lastAccess = new Date()
  129. return this.metric[method](opts, value)
  130. }
  131. }
  132. let sweepingInterval
  133. PromWrapper.setupSweeping = function () {
  134. if (sweepingInterval) {
  135. clearInterval(sweepingInterval)
  136. }
  137. if (!PromWrapper.ttlInMinutes) {
  138. if (process.env.DEBUG_METRICS) {
  139. // eslint-disable-next-line no-console
  140. console.log('Not registering sweep method -- empty ttl')
  141. }
  142. return
  143. }
  144. if (process.env.DEBUG_METRICS) {
  145. // eslint-disable-next-line no-console
  146. console.log('Registering sweep method')
  147. }
  148. sweepingInterval = setInterval(function () {
  149. if (process.env.DEBUG_METRICS) {
  150. // eslint-disable-next-line no-console
  151. console.log('Sweeping metrics')
  152. }
  153. return metrics.forEach((metric, key) => {
  154. return metric.sweep()
  155. })
  156. }, 60000)
  157. const Metrics = require('./index')
  158. Metrics.registerDestructor(() => clearInterval(sweepingInterval))
  159. }
  160. module.exports = PromWrapper