profile.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import {CallTreeNode} from '../callTreeNode';
  2. import {Frame} from '../frame';
  3. interface ProfileStats {
  4. discardedSamplesCount: number;
  5. negativeSamplesCount: number;
  6. }
  7. export class Profile {
  8. // Duration of the profile
  9. duration: number;
  10. // Started at ts of the profile - varies between implementations of the profiler.
  11. // For JS self profiles, this is the time origin (https://www.w3.org/TR/hr-time-2/#dfn-time-origin), for others it's epoch time
  12. startedAt: number;
  13. // Ended at ts of the profile - varies between implementations of the profiler.
  14. // For JS self profiles, this is the time origin (https://www.w3.org/TR/hr-time-2/#dfn-time-origin), for others it's epoch time
  15. endedAt: number;
  16. threadId: number;
  17. type: string;
  18. // Unit in which the timings are reported in
  19. unit = 'microseconds';
  20. // Name of the profile
  21. name = 'Unknown';
  22. callTree: CallTreeNode = new CallTreeNode(Frame.Root, null);
  23. framesInStack: Set<Profiling.Event['frame']> = new Set();
  24. // Min duration of a single frame in our profile
  25. minFrameDuration = Number.POSITIVE_INFINITY;
  26. samples: CallTreeNode[] = [];
  27. weights: number[] = [];
  28. rawWeights: number[] = [];
  29. stats: ProfileStats = {
  30. discardedSamplesCount: 0,
  31. negativeSamplesCount: 0,
  32. };
  33. callTreeNodeProfileIdMap: Map<CallTreeNode, string[]> = new Map();
  34. constructor({
  35. duration,
  36. startedAt,
  37. endedAt,
  38. name,
  39. unit,
  40. threadId,
  41. type,
  42. }: {
  43. duration: number;
  44. endedAt: number;
  45. name: string;
  46. startedAt: number;
  47. threadId: number;
  48. type: string;
  49. unit: string;
  50. }) {
  51. this.threadId = threadId;
  52. this.duration = duration;
  53. this.startedAt = startedAt;
  54. this.endedAt = endedAt;
  55. this.name = name;
  56. this.unit = unit;
  57. this.type = type ?? '';
  58. }
  59. static Empty = new Profile({
  60. duration: 1000,
  61. startedAt: 0,
  62. endedAt: 1000,
  63. name: 'Empty Profile',
  64. unit: 'milliseconds',
  65. threadId: 0,
  66. type: '',
  67. }).build();
  68. isEmpty(): boolean {
  69. return this === Profile.Empty;
  70. }
  71. trackSampleStats(duration: number) {
  72. // Keep track of discarded samples and ones that may have negative weights
  73. if (duration === 0) {
  74. this.stats.discardedSamplesCount++;
  75. }
  76. if (duration < 0) {
  77. this.stats.negativeSamplesCount++;
  78. }
  79. if (duration > 0) {
  80. this.rawWeights.push(duration);
  81. }
  82. }
  83. forEach(
  84. openFrame: (node: CallTreeNode, value: number) => void,
  85. closeFrame: (node: CallTreeNode, value: number) => void
  86. ): void {
  87. const prevStack: CallTreeNode[] = [];
  88. let value = 0;
  89. let sampleIndex = 0;
  90. for (const stackTop of this.samples) {
  91. let top: CallTreeNode | null = stackTop;
  92. while (top && !top.isRoot && !prevStack.includes(top)) {
  93. top = top.parent;
  94. }
  95. while (prevStack.length > 0 && prevStack[prevStack.length - 1] !== top) {
  96. const node = prevStack.pop()!;
  97. closeFrame(node, value);
  98. }
  99. const toOpen: CallTreeNode[] = [];
  100. let node: CallTreeNode | null = stackTop;
  101. while (node && !node.isRoot && node !== top) {
  102. toOpen.push(node);
  103. node = node.parent;
  104. }
  105. for (let i = toOpen.length - 1; i >= 0; i--) {
  106. openFrame(toOpen[i], value);
  107. prevStack.push(toOpen[i]);
  108. }
  109. value += this.weights[sampleIndex++];
  110. }
  111. // Close any remaining frames
  112. for (let i = prevStack.length - 1; i >= 0; i--) {
  113. closeFrame(prevStack[i], value);
  114. }
  115. }
  116. build(): Profile {
  117. this.duration = Math.max(
  118. this.duration,
  119. this.weights.reduce((a, b) => a + b, 0)
  120. );
  121. // We had no frames with duration > 0, so set min duration to timeline duration
  122. // which effectively disables any zooming on the flamegraphs
  123. if (
  124. this.minFrameDuration === Number.POSITIVE_INFINITY ||
  125. this.minFrameDuration === 0
  126. ) {
  127. this.minFrameDuration = this.duration;
  128. }
  129. return this;
  130. }
  131. }