index.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. /* eslint-disable no-console */
  2. const ExpressCompression = require('compression')
  3. const promClient = require('prom-client')
  4. const promWrapper = require('./prom_wrapper')
  5. const destructors = []
  6. require('./uv_threadpool_size')
  7. function registerDestructor(func) {
  8. destructors.push(func)
  9. }
  10. function injectMetricsRoute(app) {
  11. app.get(
  12. '/metrics',
  13. ExpressCompression({
  14. level: parseInt(process.env.METRICS_COMPRESSION_LEVEL || '1', 10),
  15. }),
  16. function (req, res, next) {
  17. res.set('Content-Type', promWrapper.registry.contentType)
  18. promWrapper.registry
  19. .metrics()
  20. .then(metrics => {
  21. res.end(metrics)
  22. })
  23. .catch(err => {
  24. next(err)
  25. })
  26. }
  27. )
  28. }
  29. function buildPromKey(key) {
  30. return key.replace(/[^a-zA-Z0-9]/g, '_')
  31. }
  32. function sanitizeValue(value) {
  33. return parseFloat(value)
  34. }
  35. function set(key, value, sampleRate = 1) {
  36. console.log('counts are not currently supported')
  37. }
  38. function inc(key, sampleRate = 1, labels = {}) {
  39. if (arguments.length === 2 && typeof sampleRate === 'object') {
  40. labels = sampleRate
  41. }
  42. key = buildPromKey(key)
  43. promWrapper.metric('counter', key, labels).inc(labels)
  44. if (process.env.DEBUG_METRICS) {
  45. console.log('doing inc', key, labels)
  46. }
  47. }
  48. function count(key, count, sampleRate = 1, labels = {}) {
  49. if (arguments.length === 3 && typeof sampleRate === 'object') {
  50. labels = sampleRate
  51. }
  52. key = buildPromKey(key)
  53. promWrapper.metric('counter', key, labels).inc(labels, count)
  54. if (process.env.DEBUG_METRICS) {
  55. console.log('doing count/inc', key, labels)
  56. }
  57. }
  58. function summary(key, value, labels = {}) {
  59. key = buildPromKey(key)
  60. promWrapper.metric('summary', key, labels).observe(labels, value)
  61. if (process.env.DEBUG_METRICS) {
  62. console.log('doing summary', key, value, labels)
  63. }
  64. }
  65. function timing(key, timeSpan, sampleRate = 1, labels = {}) {
  66. if (arguments.length === 3 && typeof sampleRate === 'object') {
  67. labels = sampleRate
  68. }
  69. key = buildPromKey('timer_' + key)
  70. promWrapper.metric('summary', key, labels).observe(labels, timeSpan)
  71. if (process.env.DEBUG_METRICS) {
  72. console.log('doing timing', key, labels)
  73. }
  74. }
  75. function histogram(key, value, buckets, labels = {}) {
  76. key = buildPromKey('histogram_' + key)
  77. promWrapper.metric('histogram', key, labels, buckets).observe(labels, value)
  78. if (process.env.DEBUG_METRICS) {
  79. console.log('doing histogram', key, buckets, labels)
  80. }
  81. }
  82. class Timer {
  83. constructor(key, sampleRate = 1, labels = {}, buckets = undefined) {
  84. if (typeof sampleRate === 'object') {
  85. // called with (key, labels, buckets)
  86. if (arguments.length === 3) {
  87. buckets = labels
  88. labels = sampleRate
  89. }
  90. // called with (key, labels)
  91. if (arguments.length === 2) {
  92. labels = sampleRate
  93. }
  94. sampleRate = 1 // default value to pass to timing function
  95. }
  96. this.start = new Date()
  97. key = buildPromKey(key)
  98. this.key = key
  99. this.sampleRate = sampleRate
  100. this.labels = labels
  101. this.buckets = buckets
  102. }
  103. // any labels passed into the done method override labels from constructor
  104. done(labels = {}) {
  105. const timeSpan = new Date() - this.start
  106. if (this.buckets) {
  107. histogram(this.key, timeSpan, this.buckets, { ...this.labels, ...labels })
  108. } else {
  109. timing(this.key, timeSpan, this.sampleRate, { ...this.labels, ...labels })
  110. }
  111. return timeSpan
  112. }
  113. }
  114. function gauge(key, value, sampleRate = 1, labels = {}) {
  115. if (arguments.length === 3 && typeof sampleRate === 'object') {
  116. labels = sampleRate
  117. }
  118. key = buildPromKey(key)
  119. promWrapper.metric('gauge', key, labels).set(labels, sanitizeValue(value))
  120. if (process.env.DEBUG_METRICS) {
  121. console.log('doing gauge', key, labels)
  122. }
  123. }
  124. function globalGauge(key, value, sampleRate = 1, labels = {}) {
  125. key = buildPromKey(key)
  126. labels = { host: 'global', ...labels }
  127. promWrapper.metric('gauge', key, labels).set(labels, sanitizeValue(value))
  128. }
  129. function close() {
  130. for (const func of destructors) {
  131. func()
  132. }
  133. }
  134. module.exports.registerDestructor = registerDestructor
  135. module.exports.injectMetricsRoute = injectMetricsRoute
  136. module.exports.buildPromKey = buildPromKey
  137. module.exports.sanitizeValue = sanitizeValue
  138. module.exports.set = set
  139. module.exports.inc = inc
  140. module.exports.count = count
  141. module.exports.summary = summary
  142. module.exports.timing = timing
  143. module.exports.histogram = histogram
  144. module.exports.Timer = Timer
  145. module.exports.gauge = gauge
  146. module.exports.globalGauge = globalGauge
  147. module.exports.close = close
  148. module.exports.prom = promClient
  149. module.exports.register = promWrapper.registry
  150. module.exports.http = require('./http')
  151. module.exports.open_sockets = require('./open_sockets')
  152. module.exports.leaked_sockets = require('./leaked_sockets')
  153. module.exports.event_loop = require('./event_loop')
  154. module.exports.memory = require('./memory')
  155. module.exports.mongodb = require('./mongodb')