timing.mjs 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import process from 'node:process';
  2. import { table } from 'table';
  3. // Inspired by ESLint's timing.js
  4. // https://github.com/eslint/eslint/blob/09bc2a88c00aa9a93c7de505795fc4e85b2e6357/lib/linter/timing.js
  5. /**
  6. * Start time measurement.
  7. * @returns {bigint} Variable for tracking time in nanoseconds.
  8. */
  9. function startTime() {
  10. return process.hrtime.bigint();
  11. }
  12. /**
  13. * End time measurement.
  14. * @param {bigint} start Variable for tracking time in nanoseconds.
  15. * @returns {number} The measured time in milliseconds.
  16. */
  17. function endTime(start) {
  18. const diff = process.hrtime.bigint() - start;
  19. return Number(diff) / 1e6;
  20. }
  21. const HEADERS = ['#', 'Rule', 'Time (ms)', 'Relative'];
  22. /**
  23. * Decide how many rules to show in the output list.
  24. * @returns {number} The number of rules to show.
  25. */
  26. function getListSize() {
  27. const TIMING = process.env.TIMING;
  28. if (typeof TIMING === 'undefined') {
  29. return 0;
  30. }
  31. if (TIMING.toLowerCase() === 'all') {
  32. return Number.POSITIVE_INFINITY;
  33. }
  34. const parsed = Number.parseInt(TIMING, 10);
  35. if (!Number.isNaN(parsed) && parsed >= 1) {
  36. return parsed;
  37. }
  38. return 0;
  39. }
  40. const listSize = getListSize();
  41. const enabled = listSize !== 0;
  42. /** @type {import('table').TableUserConfig} */
  43. const tableConfig = {
  44. columns: [
  45. { alignment: 'right' },
  46. { alignment: 'left' },
  47. { alignment: 'right' },
  48. { alignment: 'right' },
  49. ],
  50. };
  51. /**
  52. * Display the timing data.
  53. * @param {{ [key: string]: number }} data Data object to be displayed.
  54. * @returns {void}
  55. * @private
  56. */
  57. function display(data) {
  58. let total = 0;
  59. /** @type {Array<[string, number]>} */
  60. const rows = Object.keys(data).map((key) => {
  61. const t = data[key] ?? 0;
  62. total += t;
  63. return [key, t];
  64. });
  65. rows.sort((a, b) => b[1] - a[1]);
  66. const limitedRows = rows.slice(0, listSize);
  67. // Format rows with percentages
  68. const formattedRows = limitedRows.map((row, index) => {
  69. const percentage = total > 0 ? `${((row[1] * 100) / total).toFixed(1)}%` : '0.0%';
  70. const timeStr = row[1].toFixed(3);
  71. return [index + 1, row[0], timeStr, percentage];
  72. });
  73. formattedRows.unshift(HEADERS);
  74. // eslint-disable-next-line no-console
  75. console.log(table(formattedRows, tableConfig));
  76. }
  77. /** @type {{ [key: string]: number }} */
  78. const data = Object.create(null);
  79. /**
  80. * Time the execution of a function.
  81. * @param {string} key Key from the data object.
  82. * @param {Function} fn Function to be called.
  83. * @returns {Function} Function to be executed.
  84. * @private
  85. */
  86. function time(key, fn) {
  87. return function timedFunction(/** @type {any} */ ...args) {
  88. const t = startTime();
  89. const result = fn(...args);
  90. const timeDiff = endTime(t);
  91. data[key] = (data[key] ?? 0) + timeDiff;
  92. return result;
  93. };
  94. }
  95. if (enabled) {
  96. process.on('exit', () => {
  97. display(data);
  98. });
  99. }
  100. export default {
  101. display,
  102. enabled,
  103. getListSize,
  104. tableConfig,
  105. time,
  106. };