profile.tsx 3.8 KB

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