profile.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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. sample_durations_ns: number[] = [];
  30. weights: number[] = [];
  31. rawWeights: number[] = [];
  32. stats: ProfileStats = {
  33. discardedSamplesCount: 0,
  34. negativeSamplesCount: 0,
  35. };
  36. callTreeNodeProfileIdMap: Map<CallTreeNode, string[]> = new Map();
  37. constructor({
  38. duration,
  39. startedAt,
  40. endedAt,
  41. name,
  42. unit,
  43. threadId,
  44. timestamp,
  45. type,
  46. }: {
  47. duration: number;
  48. endedAt: number;
  49. name: string;
  50. startedAt: number;
  51. threadId: number;
  52. type: string;
  53. unit: string;
  54. timestamp?: number;
  55. }) {
  56. this.threadId = threadId;
  57. this.duration = duration;
  58. this.startedAt = startedAt;
  59. this.endedAt = endedAt;
  60. this.name = name;
  61. this.unit = unit;
  62. this.type = type ?? '';
  63. this.timestamp = timestamp ?? null;
  64. }
  65. static Empty = new Profile({
  66. duration: 1000,
  67. startedAt: 0,
  68. endedAt: 1000,
  69. name: 'Empty Profile',
  70. unit: 'milliseconds',
  71. threadId: 0,
  72. type: '',
  73. }).build();
  74. isEmpty(): boolean {
  75. return this === Profile.Empty;
  76. }
  77. trackSampleStats(duration: number) {
  78. // Keep track of discarded samples and ones that may have negative weights
  79. if (duration === 0) {
  80. this.stats.discardedSamplesCount++;
  81. }
  82. if (duration < 0) {
  83. this.stats.negativeSamplesCount++;
  84. }
  85. if (duration > 0) {
  86. this.rawWeights.push(duration);
  87. }
  88. }
  89. forEach(
  90. openFrame: (node: CallTreeNode, value: number) => void,
  91. closeFrame: (node: CallTreeNode, value: number) => void
  92. ): void {
  93. const prevStack: CallTreeNode[] = [];
  94. let value = 0;
  95. let sampleIndex = 0;
  96. for (const stackTop of this.samples) {
  97. let top: CallTreeNode | null = stackTop;
  98. while (top && !top.isRoot && !prevStack.includes(top)) {
  99. top = top.parent;
  100. }
  101. while (prevStack.length > 0 && prevStack[prevStack.length - 1] !== top) {
  102. const node = prevStack.pop()!;
  103. closeFrame(node, value);
  104. }
  105. const toOpen: CallTreeNode[] = [];
  106. let node: CallTreeNode | null = stackTop;
  107. while (node && !node.isRoot && node !== top) {
  108. toOpen.push(node);
  109. node = node.parent;
  110. }
  111. for (let i = toOpen.length - 1; i >= 0; i--) {
  112. openFrame(toOpen[i], value);
  113. prevStack.push(toOpen[i]);
  114. }
  115. value += this.weights[sampleIndex++];
  116. }
  117. // Close any remaining frames
  118. for (let i = prevStack.length - 1; i >= 0; i--) {
  119. closeFrame(prevStack[i], value);
  120. }
  121. }
  122. build(): Profile {
  123. this.duration = Math.max(
  124. this.duration,
  125. this.weights.reduce((a, b) => a + b, 0)
  126. );
  127. // We had no frames with duration > 0, so set min duration to timeline duration
  128. // which effectively disables any zooming on the flamegraphs
  129. if (
  130. this.minFrameDuration === Number.POSITIVE_INFINITY ||
  131. this.minFrameDuration === 0
  132. ) {
  133. this.minFrameDuration = this.duration;
  134. }
  135. return this;
  136. }
  137. }