timing.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. /**
  2. * @fileoverview Tracks performance of individual rules.
  3. * @author Brandon Mills
  4. */
  5. "use strict";
  6. const { startTime, endTime } = require("../shared/stats");
  7. //------------------------------------------------------------------------------
  8. // Helpers
  9. //------------------------------------------------------------------------------
  10. /* c8 ignore next */
  11. /**
  12. * Align the string to left
  13. * @param {string} str string to evaluate
  14. * @param {number} len length of the string
  15. * @param {string} ch delimiter character
  16. * @returns {string} modified string
  17. * @private
  18. */
  19. function alignLeft(str, len, ch) {
  20. return str + new Array(len - str.length + 1).join(ch || " ");
  21. }
  22. /* c8 ignore next */
  23. /**
  24. * Align the string to right
  25. * @param {string} str string to evaluate
  26. * @param {number} len length of the string
  27. * @param {string} ch delimiter character
  28. * @returns {string} modified string
  29. * @private
  30. */
  31. function alignRight(str, len, ch) {
  32. return new Array(len - str.length + 1).join(ch || " ") + str;
  33. }
  34. //------------------------------------------------------------------------------
  35. // Module definition
  36. //------------------------------------------------------------------------------
  37. const enabled = !!process.env.TIMING;
  38. const HEADERS = ["Rule", "Time (ms)", "Relative"];
  39. const ALIGN = [alignLeft, alignRight, alignRight];
  40. /**
  41. * Decide how many rules to show in the output list.
  42. * @returns {number} the number of rules to show
  43. */
  44. function getListSize() {
  45. const MINIMUM_SIZE = 10;
  46. if (typeof process.env.TIMING !== "string") {
  47. return MINIMUM_SIZE;
  48. }
  49. if (process.env.TIMING.toLowerCase() === "all") {
  50. return Number.POSITIVE_INFINITY;
  51. }
  52. const TIMING_ENV_VAR_AS_INTEGER = Number.parseInt(process.env.TIMING, 10);
  53. return TIMING_ENV_VAR_AS_INTEGER > 10
  54. ? TIMING_ENV_VAR_AS_INTEGER
  55. : MINIMUM_SIZE;
  56. }
  57. /* c8 ignore next */
  58. /**
  59. * display the data
  60. * @param {Object} data Data object to be displayed
  61. * @returns {void} prints modified string with console.log
  62. * @private
  63. */
  64. function display(data) {
  65. let total = 0;
  66. const rows = Object.keys(data)
  67. .map(key => {
  68. const time = data[key];
  69. total += time;
  70. return [key, time];
  71. })
  72. .sort((a, b) => b[1] - a[1])
  73. .slice(0, getListSize());
  74. rows.forEach(row => {
  75. row.push(`${((row[1] * 100) / total).toFixed(1)}%`);
  76. row[1] = row[1].toFixed(3);
  77. });
  78. rows.unshift(HEADERS);
  79. const widths = [];
  80. rows.forEach(row => {
  81. const len = row.length;
  82. for (let i = 0; i < len; i++) {
  83. const n = row[i].length;
  84. if (!widths[i] || n > widths[i]) {
  85. widths[i] = n;
  86. }
  87. }
  88. });
  89. const table = rows.map(row =>
  90. row.map((cell, index) => ALIGN[index](cell, widths[index])).join(" | "),
  91. );
  92. table.splice(
  93. 1,
  94. 0,
  95. widths
  96. .map((width, index) => {
  97. const extraAlignment =
  98. index !== 0 && index !== widths.length - 1 ? 2 : 1;
  99. return ALIGN[index](":", width + extraAlignment, "-");
  100. })
  101. .join("|"),
  102. );
  103. console.log(table.join("\n")); // eslint-disable-line no-console -- Debugging function
  104. }
  105. /* c8 ignore next */
  106. module.exports = (function () {
  107. const data = Object.create(null);
  108. let displayEnabled = true;
  109. /**
  110. * Time the run
  111. * @param {any} key key from the data object
  112. * @param {Function} fn function to be called
  113. * @param {boolean} stats if 'stats' is true, return the result and the time difference
  114. * @returns {Function} function to be executed
  115. * @private
  116. */
  117. function time(key, fn, stats) {
  118. return function (...args) {
  119. const t = startTime();
  120. const result = fn(...args);
  121. const tdiff = endTime(t);
  122. if (enabled) {
  123. if (typeof data[key] === "undefined") {
  124. data[key] = 0;
  125. }
  126. data[key] += tdiff;
  127. }
  128. return stats ? { result, tdiff } : result;
  129. };
  130. }
  131. /**
  132. * Returns a shallow copy of the collected timings data.
  133. * @returns {Record<string, number>} mapping of ruleId to total time in ms
  134. */
  135. function getData() {
  136. return { ...data };
  137. }
  138. /**
  139. * Merges rule timing totals collected elsewhere into this process' totals.
  140. * @param {Record<string, number>} dataToMerge mapping of ruleId to total time in ms
  141. * @returns {void}
  142. */
  143. function mergeData(dataToMerge) {
  144. for (const [key, value] of Object.entries(dataToMerge)) {
  145. if (typeof data[key] === "undefined") {
  146. data[key] = 0;
  147. }
  148. data[key] += value;
  149. }
  150. }
  151. /**
  152. * Disables printing of timing data on process exit.
  153. * Intended for worker threads or non-main contexts.
  154. * @returns {void}
  155. */
  156. function disableDisplay() {
  157. displayEnabled = false;
  158. }
  159. if (enabled) {
  160. process.on("exit", () => {
  161. if (displayEnabled && Object.keys(data).length > 0) {
  162. display(data);
  163. }
  164. });
  165. }
  166. return {
  167. time,
  168. enabled,
  169. getListSize,
  170. getData,
  171. mergeData,
  172. disableDisplay,
  173. };
  174. })();