index.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import { basename, extname } from 'node:path';
  2. import picocolors from 'picocolors';
  3. const SPAN_STATUS_START = 0;
  4. const SPAN_STATUS_END = 1;
  5. const spanTag = Symbol('span');
  6. export interface TraceResult {
  7. name: string,
  8. start: number,
  9. end: number,
  10. children: TraceResult[]
  11. }
  12. const rootTraceResult: TraceResult = {
  13. name: 'root',
  14. start: 0,
  15. end: 0,
  16. children: []
  17. };
  18. export interface Span {
  19. [spanTag]: true,
  20. readonly stop: (time?: number) => void,
  21. readonly traceChild: (name: string) => Span,
  22. readonly traceSyncFn: <T>(fn: (span: Span) => T) => T,
  23. readonly traceAsyncFn: <T>(fn: (span: Span) => T | Promise<T>) => Promise<T>,
  24. readonly tracePromise: <T>(promise: Promise<T>) => Promise<T>,
  25. readonly traceChildSync: <T>(name: string, fn: (span: Span) => T) => T,
  26. readonly traceChildAsync: <T>(name: string, fn: (span: Span) => Promise<T>) => Promise<T>,
  27. readonly traceChildPromise: <T>(name: string, promise: Promise<T>) => Promise<T>,
  28. readonly traceResult: TraceResult
  29. }
  30. export const createSpan = (name: string, parentTraceResult?: TraceResult): Span => {
  31. const start = performance.now();
  32. let curTraceResult: TraceResult;
  33. if (parentTraceResult == null) {
  34. curTraceResult = rootTraceResult;
  35. } else {
  36. curTraceResult = {
  37. name,
  38. start,
  39. end: 0,
  40. children: []
  41. };
  42. parentTraceResult.children.push(curTraceResult);
  43. }
  44. let status: typeof SPAN_STATUS_START | typeof SPAN_STATUS_END = SPAN_STATUS_START;
  45. const stop = (time?: number) => {
  46. if (status === SPAN_STATUS_END) {
  47. throw new Error(`span already stopped: ${name}`);
  48. }
  49. const end = time ?? performance.now();
  50. curTraceResult.end = end;
  51. status = SPAN_STATUS_END;
  52. };
  53. const traceChild = (name: string) => createSpan(name, curTraceResult);
  54. const span: Span = {
  55. [spanTag]: true,
  56. stop,
  57. traceChild,
  58. traceSyncFn<T>(fn: (span: Span) => T) {
  59. const res = fn(span);
  60. span.stop();
  61. return res;
  62. },
  63. async traceAsyncFn<T>(fn: (span: Span) => T | Promise<T>): Promise<T> {
  64. const res = await fn(span);
  65. span.stop();
  66. return res;
  67. },
  68. traceResult: curTraceResult,
  69. async tracePromise<T>(promise: Promise<T>): Promise<T> {
  70. const res = await promise;
  71. span.stop();
  72. return res;
  73. },
  74. traceChildSync: <T>(name: string, fn: (span: Span) => T): T => traceChild(name).traceSyncFn(fn),
  75. traceChildAsync: <T>(name: string, fn: (span: Span) => T | Promise<T>): Promise<T> => traceChild(name).traceAsyncFn(fn),
  76. traceChildPromise: <T>(name: string, promise: Promise<T>): Promise<T> => traceChild(name).tracePromise(promise)
  77. };
  78. // eslint-disable-next-line sukka/no-redundant-variable -- self reference
  79. return span;
  80. };
  81. export const dummySpan = createSpan('');
  82. export const task = (importMetaMain: boolean, importMetaPath: string) => <T>(fn: (span: Span) => Promise<T>, customName?: string) => {
  83. const taskName = customName ?? basename(importMetaPath, extname(importMetaPath));
  84. const dummySpan = createSpan(taskName);
  85. if (importMetaMain) {
  86. fn(dummySpan);
  87. }
  88. return async (span?: Span) => {
  89. if (span) {
  90. return span.traceChildAsync(taskName, fn);
  91. }
  92. return fn(dummySpan);
  93. };
  94. };
  95. // const isSpan = (obj: any): obj is Span => {
  96. // return typeof obj === 'object' && obj && spanTag in obj;
  97. // };
  98. // export const universalify = <A extends any[], R>(taskname: string, fn: (this: void, ...args: A) => R) => {
  99. // return (...args: A) => {
  100. // const lastArg = args[args.length - 1];
  101. // if (isSpan(lastArg)) {
  102. // return lastArg.traceChild(taskname).traceSyncFn(() => fn(...args));
  103. // }
  104. // return fn(...args);
  105. // };
  106. // };
  107. export const printTraceResult = (traceResult: TraceResult = rootTraceResult) => {
  108. printStats(traceResult.children);
  109. printTree(traceResult, node => `${node.name} ${picocolors.bold(`${(node.end - node.start).toFixed(3)}ms`)}`);
  110. };
  111. function printTree(initialTree: TraceResult, printNode: (node: TraceResult, branch: string) => string) {
  112. function printBranch(tree: TraceResult, branch: string, isGraphHead: boolean, isChildOfLastBranch: boolean) {
  113. const children = tree.children;
  114. let branchHead = '';
  115. if (!isGraphHead) {
  116. branchHead = children.length > 0 ? '┬ ' : '─ ';
  117. }
  118. const toPrint = printNode(tree, `${branch}${branchHead}`);
  119. if (typeof toPrint === 'string') {
  120. console.log(`${branch}${branchHead}${toPrint}`);
  121. }
  122. let baseBranch = branch;
  123. if (!isGraphHead) {
  124. baseBranch = branch.slice(0, -2) + (isChildOfLastBranch ? ' ' : '│ ');
  125. }
  126. const nextBranch = `${baseBranch}├─`;
  127. const lastBranch = `${baseBranch}└─`;
  128. children.forEach((child, index) => {
  129. const last = children.length - 1 === index;
  130. printBranch(child, last ? lastBranch : nextBranch, false, last);
  131. });
  132. }
  133. printBranch(initialTree, '', true, false);
  134. }
  135. function printStats(stats: TraceResult[]): void {
  136. const longestTaskName = Math.max(...stats.map(i => i.name.length));
  137. const realStart = Math.min(...stats.map(i => i.start));
  138. const realEnd = Math.max(...stats.map(i => i.end));
  139. const statsStep = ((realEnd - realStart) / 120) | 0;
  140. stats
  141. .sort((a, b) => a.start - b.start)
  142. .forEach(stat => {
  143. console.log(
  144. `[${stat.name}]${' '.repeat(longestTaskName - stat.name.length)}`,
  145. ' '.repeat(((stat.start - realStart) / statsStep) | 0),
  146. '='.repeat(Math.max(((stat.end - stat.start) / statsStep) | 0, 1))
  147. );
  148. });
  149. }