debug.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. declare global {
  2. interface Window {
  3. debug: typeof Debug;
  4. }
  5. }
  6. const lessPrecise = (num: number, precision = 5) =>
  7. parseFloat(num.toPrecision(precision));
  8. const getAvgFrameTime = (times: number[]) =>
  9. lessPrecise(times.reduce((a, b) => a + b) / times.length);
  10. export class Debug {
  11. public static DEBUG_LOG_TIMES = true;
  12. private static TIMES_AGGR: Record<string, { t: number; times: number[] }> =
  13. {};
  14. private static TIMES_AVG: Record<
  15. string,
  16. { t: number; times: number[]; avg: number | null }
  17. > = {};
  18. private static LAST_DEBUG_LOG_CALL = 0;
  19. private static DEBUG_LOG_INTERVAL_ID: null | number = null;
  20. private static LAST_FRAME_TIMESTAMP = 0;
  21. private static FRAME_COUNT = 0;
  22. private static ANIMATION_FRAME_ID: null | number = null;
  23. private static scheduleAnimationFrame = () => {
  24. if (Debug.DEBUG_LOG_INTERVAL_ID !== null) {
  25. Debug.ANIMATION_FRAME_ID = requestAnimationFrame((timestamp) => {
  26. if (Debug.LAST_FRAME_TIMESTAMP !== timestamp) {
  27. Debug.LAST_FRAME_TIMESTAMP = timestamp;
  28. Debug.FRAME_COUNT++;
  29. }
  30. if (Debug.DEBUG_LOG_INTERVAL_ID !== null) {
  31. Debug.scheduleAnimationFrame();
  32. }
  33. });
  34. }
  35. };
  36. private static setupInterval = () => {
  37. if (Debug.DEBUG_LOG_INTERVAL_ID === null) {
  38. console.info("%c(starting perf recording)", "color: lime");
  39. Debug.DEBUG_LOG_INTERVAL_ID = window.setInterval(Debug.debugLogger, 1000);
  40. Debug.scheduleAnimationFrame();
  41. }
  42. Debug.LAST_DEBUG_LOG_CALL = Date.now();
  43. };
  44. private static debugLogger = () => {
  45. if (Debug.DEBUG_LOG_TIMES) {
  46. for (const [name, { t, times }] of Object.entries(Debug.TIMES_AGGR)) {
  47. if (times.length) {
  48. console.info(
  49. name,
  50. lessPrecise(times.reduce((a, b) => a + b)),
  51. times.sort((a, b) => a - b).map((x) => lessPrecise(x)),
  52. );
  53. Debug.TIMES_AGGR[name] = { t, times: [] };
  54. }
  55. }
  56. for (const [name, { t, times, avg }] of Object.entries(Debug.TIMES_AVG)) {
  57. if (times.length) {
  58. // const avgFrameTime = getAvgFrameTime(times);
  59. const totalTime = times.reduce((a, b) => a + b);
  60. const avgFrameTime = lessPrecise(totalTime / Debug.FRAME_COUNT);
  61. console.info(
  62. name,
  63. `- ${times.length} calls - ${avgFrameTime}ms/frame across ${
  64. Debug.FRAME_COUNT
  65. } frames (${lessPrecise(
  66. (avgFrameTime / 16.67) * 100,
  67. 1,
  68. )}% of frame budget)`,
  69. );
  70. Debug.TIMES_AVG[name] = {
  71. t,
  72. times: [],
  73. avg:
  74. avg != null ? getAvgFrameTime([avg, avgFrameTime]) : avgFrameTime,
  75. };
  76. }
  77. }
  78. }
  79. Debug.FRAME_COUNT = 0;
  80. // Check for stop condition after logging
  81. if (
  82. Date.now() - Debug.LAST_DEBUG_LOG_CALL > 600 &&
  83. Debug.DEBUG_LOG_INTERVAL_ID !== null
  84. ) {
  85. console.info("%c(stopping perf recording)", "color: red");
  86. window.clearInterval(Debug.DEBUG_LOG_INTERVAL_ID);
  87. window.cancelAnimationFrame(Debug.ANIMATION_FRAME_ID!);
  88. Debug.ANIMATION_FRAME_ID = null;
  89. Debug.FRAME_COUNT = 0;
  90. Debug.LAST_FRAME_TIMESTAMP = 0;
  91. Debug.DEBUG_LOG_INTERVAL_ID = null;
  92. Debug.TIMES_AGGR = {};
  93. Debug.TIMES_AVG = {};
  94. }
  95. };
  96. public static logTime = (time?: number, name = "default") => {
  97. Debug.setupInterval();
  98. const now = performance.now();
  99. const { t, times } = (Debug.TIMES_AGGR[name] = Debug.TIMES_AGGR[name] || {
  100. t: 0,
  101. times: [],
  102. });
  103. if (t) {
  104. times.push(time != null ? time : now - t);
  105. }
  106. Debug.TIMES_AGGR[name].t = now;
  107. };
  108. public static logTimeAverage = (time?: number, name = "default") => {
  109. Debug.setupInterval();
  110. const now = performance.now();
  111. const { t, times } = (Debug.TIMES_AVG[name] = Debug.TIMES_AVG[name] || {
  112. t: 0,
  113. times: [],
  114. });
  115. if (t) {
  116. times.push(time != null ? time : now - t);
  117. }
  118. Debug.TIMES_AVG[name].t = now;
  119. };
  120. private static logWrapper =
  121. (type: "logTime" | "logTimeAverage") =>
  122. <T extends any[], R>(fn: (...args: T) => R, name = "default") => {
  123. return (...args: T) => {
  124. const t0 = performance.now();
  125. const ret = fn(...args);
  126. Debug[type](performance.now() - t0, name);
  127. return ret;
  128. };
  129. };
  130. public static logTimeWrap = Debug.logWrapper("logTime");
  131. public static logTimeAverageWrap = Debug.logWrapper("logTimeAverage");
  132. public static perfWrap = <T extends any[], R>(
  133. fn: (...args: T) => R,
  134. name = "default",
  135. ) => {
  136. return (...args: T) => {
  137. // eslint-disable-next-line no-console
  138. console.time(name);
  139. const ret = fn(...args);
  140. // eslint-disable-next-line no-console
  141. console.timeEnd(name);
  142. return ret;
  143. };
  144. };
  145. }
  146. //@ts-ignore
  147. window.debug = Debug;