prom_wrapper.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  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: ['app', 'host', '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. 'app',
  71. 'host',
  72. 'path',
  73. 'status_code',
  74. 'method',
  75. 'collection',
  76. 'query'
  77. ]
  78. })
  79. case 'gauge':
  80. return new prom.Gauge({
  81. name,
  82. help: name,
  83. labelNames: ['app', 'host', 'status']
  84. })
  85. }
  86. })()
  87. }
  88. inc(opts, value) {
  89. return this._execMethod('inc', opts, value)
  90. }
  91. observe(opts, value) {
  92. return this._execMethod('observe', opts, value)
  93. }
  94. set(opts, value) {
  95. return this._execMethod('set', opts, value)
  96. }
  97. sweep() {
  98. const thresh = new Date(Date.now() - 1000 * 60 * PromWrapper.ttlInMinutes)
  99. this.instances.forEach((instance, key) => {
  100. if (thresh > instance.time) {
  101. if (process.env.DEBUG_METRICS) {
  102. console.log(
  103. 'Sweeping stale metric instance',
  104. this.name,
  105. { opts: instance.opts },
  106. key
  107. )
  108. }
  109. return this.metric.remove(
  110. ...Array.from(optsAsArgs(instance.opts, this.metric.labelNames) || [])
  111. )
  112. }
  113. })
  114. if (thresh > this.lastAccess) {
  115. if (process.env.DEBUG_METRICS) {
  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. if (!PromWrapper.sweepRegistered) {
  133. if (process.env.DEBUG_METRICS) {
  134. console.log('Registering sweep method')
  135. }
  136. PromWrapper.sweepRegistered = true
  137. setInterval(function() {
  138. if (PromWrapper.ttlInMinutes) {
  139. if (process.env.DEBUG_METRICS) {
  140. console.log('Sweeping metrics')
  141. }
  142. return metrics.forEach((metric, key) => {
  143. return metric.sweep()
  144. })
  145. }
  146. }, 60000)
  147. }
  148. module.exports = PromWrapper