prom_wrapper.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  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: ['path', 'status_code', 'method', 'collection', 'query']
  70. })
  71. case 'gauge':
  72. return new prom.Gauge({
  73. name,
  74. help: name,
  75. labelNames: ['host', 'status']
  76. })
  77. }
  78. })()
  79. }
  80. inc(opts, value) {
  81. return this._execMethod('inc', opts, value)
  82. }
  83. observe(opts, value) {
  84. return this._execMethod('observe', opts, value)
  85. }
  86. set(opts, value) {
  87. return this._execMethod('set', opts, value)
  88. }
  89. sweep() {
  90. const thresh = new Date(Date.now() - 1000 * 60 * PromWrapper.ttlInMinutes)
  91. this.instances.forEach((instance, key) => {
  92. if (thresh > instance.time) {
  93. if (process.env.DEBUG_METRICS) {
  94. console.log(
  95. 'Sweeping stale metric instance',
  96. this.name,
  97. { opts: instance.opts },
  98. key
  99. )
  100. }
  101. return this.metric.remove(
  102. ...Array.from(optsAsArgs(instance.opts, this.metric.labelNames) || [])
  103. )
  104. }
  105. })
  106. if (thresh > this.lastAccess) {
  107. if (process.env.DEBUG_METRICS) {
  108. console.log('Sweeping stale metric', this.name, thresh, this.lastAccess)
  109. }
  110. metrics.delete(this.name)
  111. return registry.removeSingleMetric(this.name)
  112. }
  113. }
  114. _execMethod(method, opts, value) {
  115. opts = extendOpts(opts, this.metric.labelNames)
  116. const key = optsKey(opts)
  117. if (key !== '') {
  118. this.instances.set(key, { time: new Date(), opts })
  119. }
  120. this.lastAccess = new Date()
  121. return this.metric[method](opts, value)
  122. }
  123. }
  124. let sweepingInterval
  125. PromWrapper.setupSweeping = function() {
  126. if (sweepingInterval) {
  127. clearInterval(sweepingInterval)
  128. }
  129. if (process.env.DEBUG_METRICS) {
  130. console.log('Registering sweep method')
  131. }
  132. sweepingInterval = setInterval(function() {
  133. if (PromWrapper.ttlInMinutes) {
  134. if (process.env.DEBUG_METRICS) {
  135. console.log('Sweeping metrics')
  136. }
  137. return metrics.forEach((metric, key) => {
  138. return metric.sweep()
  139. })
  140. }
  141. }, 60000)
  142. const Metrics = require('./index')
  143. Metrics.registerDestructor(() => clearInterval(sweepingInterval))
  144. }
  145. module.exports = PromWrapper