profile.tsx 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import {lastOfArray} from 'sentry/utils';
  2. import {CallTreeNode} from '../callTreeNode';
  3. import {Frame} from '../frame';
  4. // This is ported from speedscope with a lot of modifications and simplifications
  5. // head at commit e37f6fa7c38c110205e22081560b99cb89ce885e
  6. export class Profile {
  7. // Duration of the profile
  8. duration: number;
  9. // Started at ts of the profile - varies between implementations of the profiler.
  10. // 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
  11. startedAt: number;
  12. // Ended at ts of the profile - varies between implementations of the profiler.
  13. // 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
  14. endedAt: number;
  15. threadId: number;
  16. // Unit in which the timings are reported in
  17. unit = 'microseconds';
  18. // Name of the profile
  19. name = 'Unknown';
  20. appendOrderTree: CallTreeNode = new CallTreeNode(Frame.Root, null);
  21. framesInStack: Set<Profiling.Event['frame']> = new Set();
  22. // Min duration of the profile
  23. minFrameDuration = Number.POSITIVE_INFINITY;
  24. samples: CallTreeNode[] = [];
  25. weights: number[] = [];
  26. constructor(
  27. duration: number,
  28. startedAt: number,
  29. endedAt: number,
  30. name: string,
  31. unit: string,
  32. threadId: number
  33. ) {
  34. this.threadId = threadId;
  35. this.duration = duration;
  36. this.startedAt = startedAt;
  37. this.endedAt = endedAt;
  38. this.name = name;
  39. this.unit = unit;
  40. }
  41. static Empty() {
  42. return new Profile(1000, 0, 1000, '', 'milliseconds', 0).build();
  43. }
  44. forEach(
  45. openFrame: (node: CallTreeNode, value: number) => void,
  46. closeFrame: (node: CallTreeNode, value: number) => void
  47. ): void {
  48. let prevStack: CallTreeNode[] = [];
  49. let value = 0;
  50. let sampleIndex = 0;
  51. for (const stackTop of this.samples) {
  52. let top: CallTreeNode | null = stackTop;
  53. while (top && !top.isRoot() && prevStack.indexOf(top) === -1) {
  54. top = top.parent;
  55. }
  56. while (prevStack.length > 0 && lastOfArray(prevStack) !== top) {
  57. const node = prevStack.pop()!;
  58. closeFrame(node, value);
  59. }
  60. const toOpen: CallTreeNode[] = [];
  61. let node: CallTreeNode | null = stackTop;
  62. while (node && !node.isRoot() && node !== top) {
  63. toOpen.unshift(node);
  64. node = node.parent;
  65. }
  66. for (const toOpenNode of toOpen) {
  67. openFrame(toOpenNode, value);
  68. }
  69. prevStack = prevStack.concat(toOpen);
  70. value += this.weights[sampleIndex++];
  71. }
  72. for (let i = prevStack.length - 1; i >= 0; i--) {
  73. closeFrame(prevStack[i], value);
  74. }
  75. }
  76. build(): Profile {
  77. this.duration = Math.max(
  78. this.duration,
  79. this.weights.reduce((a, b) => a + b, 0)
  80. );
  81. // We had no frames with duration > 0, so set min duration to timeline duration
  82. // which effectively disables any zooming on the flamegraphs
  83. if (
  84. this.minFrameDuration === Number.POSITIVE_INFINITY ||
  85. this.minFrameDuration === 0
  86. ) {
  87. this.minFrameDuration = this.duration;
  88. }
  89. return this;
  90. }
  91. }