handlebars_helpers.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. var $ = require('jquery');
  2. var _ = require('underscore');
  3. var Handlebars = require('hbsfy/runtime');
  4. var AppState = require('../app_state');
  5. var formatStatsdKey = function(metricType, key) {
  6. var fullKey = key;
  7. var fmt;
  8. if (metricType === 'counter') {
  9. fmt = AppState.get('STATSD_COUNTER_FORMAT');
  10. fullKey = fmt.replace(/%s/g, key);
  11. } else if (metricType === 'gauge') {
  12. fmt = AppState.get('STATSD_GAUGE_FORMAT');
  13. fullKey = fmt.replace(/%s/g, key);
  14. }
  15. return fullKey;
  16. };
  17. var statsdPrefix = function(host) {
  18. var prefix = AppState.get('STATSD_PREFIX');
  19. var statsdHostKey = host.replace(/[\.:]/g, '_');
  20. prefix = prefix.replace(/%s/g, statsdHostKey);
  21. if (prefix.substring(prefix.length, 1) !== '.') {
  22. prefix += '.';
  23. }
  24. return prefix;
  25. };
  26. /* eslint-disable key-spacing */
  27. var metricType = function(key) {
  28. return {
  29. 'depth': 'gauge',
  30. 'in_flight_count': 'gauge',
  31. 'deferred_count': 'gauge',
  32. 'requeue_count': 'counter',
  33. 'timeout_count': 'counter',
  34. 'message_count': 'counter',
  35. 'clients': 'gauge',
  36. '*_bytes': 'gauge',
  37. 'gc_pause_*': 'gauge',
  38. 'gc_runs': 'counter',
  39. 'heap_objects': 'gauge',
  40. 'e2e_processing_latency': 'gauge'
  41. }[key];
  42. };
  43. /* eslint-enable key-spacing */
  44. var genColorList = function(typ, key) {
  45. if (typ === 'topic' || typ === 'channel') {
  46. if (key === 'depth' || key === 'deferred_count') {
  47. return 'red';
  48. }
  49. } else if (typ === 'node') {
  50. return 'red,green,blue,purple';
  51. } else if (typ === 'counter') {
  52. return 'green';
  53. }
  54. return 'blue';
  55. };
  56. // sanitizeGraphiteKey removes special characters from a graphite key
  57. // this matches behavior of bitly/statsdaemon
  58. // https://github.com/bitly/statsdaemon/blob/fc46d9cfe29b674a0c8abc723afaa9370430cdcd/statsdaemon.go#L64-L88
  59. var sanitizeGraphiteKey = function(s) {
  60. return s.replaceAll(' ', '_').replaceAll('/', '-').replaceAll(/[^a-zA-Z0-9-_.]/g, '');
  61. }
  62. var genTargets = function(typ, node, ns1, ns2, key) {
  63. var targets = [];
  64. var prefix = statsdPrefix(node ? node : '*');
  65. var fullKey;
  66. var target;
  67. if (typ === 'topic') {
  68. fullKey = formatStatsdKey(metricType(key), prefix + 'topic.' + sanitizeGraphiteKey(ns1) + '.' + key);
  69. targets.push('sumSeries(' + fullKey + ')');
  70. } else if (typ === 'channel') {
  71. fullKey = formatStatsdKey(metricType(key), prefix + 'topic.' + sanitizeGraphiteKey(ns1) + '.channel.' +
  72. sanitizeGraphiteKey(ns2) + '.' + key);
  73. targets.push('sumSeries(' + fullKey + ')');
  74. } else if (typ === 'node') {
  75. target = prefix + 'mem.' + key;
  76. if (key === 'gc_runs') {
  77. target = 'movingAverage(' + target + ',45)';
  78. }
  79. targets.push(formatStatsdKey(metricType(key), target));
  80. } else if (typ === 'e2e') {
  81. targets = _.map(ns1['percentiles'], function(p) {
  82. var t;
  83. if (ns1['channel'] !== '') {
  84. t = prefix + 'topic.' + ns1['topic'] + '.channel.' + ns1['channel'] + '.' +
  85. key + '_' + (p['quantile'] * 100);
  86. } else {
  87. t = prefix + 'topic.' + ns1['topic'] + '.' + key + '_' + (p['quantile'] * 100);
  88. }
  89. if (node === '*') {
  90. t = 'averageSeries(' + t + ')';
  91. }
  92. return 'scale(' + formatStatsdKey(metricType(key), t) + ',0.000001)';
  93. });
  94. } else if (typ === 'counter') {
  95. fullKey = formatStatsdKey(metricType(key), prefix + 'topic.*.channel.*.' + key);
  96. targets.push('sumSeries(' + fullKey + ')');
  97. }
  98. return targets;
  99. };
  100. Handlebars.registerHelper('default', function(x, defaultValue) {
  101. return x ? x : defaultValue;
  102. });
  103. Handlebars.registerHelper('ifeq', function(a, b, options) {
  104. return (a === b) ? options.fn(this) : options.inverse(this);
  105. });
  106. Handlebars.registerHelper('unlesseq', function(a, b, options) {
  107. return (a !== b) ? options.fn(this) : options.inverse(this);
  108. });
  109. Handlebars.registerHelper('ifgteq', function(a, b, options) {
  110. return (a >= b) ? options.fn(this) : options.inverse(this);
  111. });
  112. Handlebars.registerHelper('iflteq', function(a, b, options) {
  113. return (a <= b) ? options.fn(this) : options.inverse(this);
  114. });
  115. Handlebars.registerHelper('length', function(xs) {
  116. return xs.length;
  117. });
  118. Handlebars.registerHelper('lowercase', function(s) {
  119. return s.toLowerCase();
  120. });
  121. Handlebars.registerHelper('uppercase', function(s) {
  122. return s.toUpperCase();
  123. });
  124. // this helper is inclusive of the top number
  125. Handlebars.registerHelper('for', function(from, to, incr, block) {
  126. var accum = '';
  127. for (var i = from; i <= to; i += incr) {
  128. accum += block.fn(i);
  129. }
  130. return accum;
  131. });
  132. // Logical operators as helper functions, which can be useful when used within
  133. // an `if` or `unless` block via the new helper composition syntax, like so:
  134. //
  135. // {{#if (or step.unlocked step.is_finished)}}
  136. // Step is unlocked or finished!
  137. // {{/if}}
  138. //
  139. // Any number of arguments may be given to either helper. NOTE: _.initial() is
  140. // used below because every helper takes an options hash as its last argument.
  141. Handlebars.registerHelper('and', function() {
  142. return _.all(_.initial(arguments));
  143. });
  144. Handlebars.registerHelper('or', function() {
  145. return _.any(_.initial(arguments));
  146. });
  147. Handlebars.registerHelper('eq', function(a, b) {
  148. return a === b;
  149. });
  150. Handlebars.registerHelper('neq', function(a, b) {
  151. return a !== b;
  152. });
  153. Handlebars.registerHelper('urlencode', function(a) {
  154. return encodeURIComponent(a);
  155. });
  156. Handlebars.registerHelper('floatToPercent', function(f) {
  157. return Math.floor(f * 100);
  158. });
  159. Handlebars.registerHelper('percSuffix', function(f) {
  160. var v = Math.floor(f * 100) % 10;
  161. if (v === 1) {
  162. return 'st';
  163. } else if (v === 2) {
  164. return 'nd';
  165. } else if (v === 3) {
  166. return 'rd';
  167. }
  168. return 'th';
  169. });
  170. Handlebars.registerHelper('commafy', function(n) {
  171. n = n || 0;
  172. return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
  173. });
  174. function round(num, places) {
  175. var multiplier = Math.pow(10, places);
  176. return Math.round(num * multiplier) / multiplier;
  177. }
  178. Handlebars.registerHelper('nanotohuman', function(n) {
  179. var s = '';
  180. var v;
  181. if (n >= 3600000000000) {
  182. v = Math.floor(n / 3600000000000);
  183. n = n % 3600000000000;
  184. s = v + 'h';
  185. }
  186. if (n >= 60000000000) {
  187. v = Math.floor(n / 60000000000);
  188. n = n % 60000000000;
  189. s += v + 'm';
  190. }
  191. if (n >= 1000000000) {
  192. n = round(n / 1000000000, 2);
  193. s += n + 's';
  194. } else if (n >= 1000000) {
  195. n = round(n / 1000000, 2);
  196. s += n + 'ms';
  197. } else if (n >= 1000) {
  198. n = round(n / 1000, 2);
  199. s += n + 'us';
  200. } else {
  201. s = n + 'ns';
  202. }
  203. return s;
  204. });
  205. Handlebars.registerHelper('sparkline', function(typ, node, ns1, ns2, key) {
  206. var q = {
  207. 'colorList': genColorList(typ, key),
  208. 'height': '20',
  209. 'width': '120',
  210. 'hideGrid': 'true',
  211. 'hideLegend': 'true',
  212. 'hideAxes': 'true',
  213. 'bgcolor': 'ff000000', // transparent
  214. 'fgcolor': 'black',
  215. 'margin': '0',
  216. 'yMin': '0',
  217. 'lineMode': 'connected',
  218. 'drawNullAsZero': 'false',
  219. 'from': '-' + AppState.get('graph_interval'),
  220. 'until': '-1min'
  221. };
  222. var interval = AppState.get('STATSD_INTERVAL') + 'sec';
  223. q['target'] = _.map(genTargets(typ, node, ns1, ns2, key), function(t) {
  224. return 'summarize(' + t + ',"' + interval + '","avg")';
  225. });
  226. return AppState.get('GRAPHITE_URL') + '/render?' + $.param(q);
  227. });
  228. Handlebars.registerHelper('large_graph', function(typ, node, ns1, ns2, key) {
  229. var q = {
  230. 'colorList': genColorList(typ, key),
  231. 'height': '450',
  232. 'width': '800',
  233. 'bgcolor': 'ff000000', // transparent
  234. 'fgcolor': '999999',
  235. 'yMin': '0',
  236. 'lineMode': 'connected',
  237. 'drawNullAsZero': 'false',
  238. 'from': '-' + AppState.get('graph_interval'),
  239. 'until': '-1min'
  240. };
  241. var interval = AppState.get('STATSD_INTERVAL') + 'sec';
  242. q['target'] = _.map(genTargets(typ, node, ns1, ns2, key), function(t) {
  243. if (metricType(key) === 'counter') {
  244. var scale = 1 / AppState.get('STATSD_INTERVAL');
  245. t = 'scale(' + t + ',' + scale + ')';
  246. }
  247. return 'summarize(' + t + ',"' + interval + '","avg")';
  248. });
  249. return AppState.get('GRAPHITE_URL') + '/render?' + $.param(q);
  250. });
  251. Handlebars.registerHelper('rate', function(typ, node, ns1, ns2) {
  252. return genTargets(typ, node, ns1, ns2, 'message_count')[0];
  253. });
  254. Handlebars.registerPartial('error', require('../views/error.hbs'));
  255. Handlebars.registerPartial('warning', require('../views/warning.hbs'));
  256. Handlebars.registerHelper('basePath', function(p) {
  257. return AppState.basePath(p);
  258. });