sampledProfile.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import {lastOfArray} from 'sentry/utils';
  2. import {CallTreeNode} from 'sentry/utils/profiling/callTreeNode';
  3. import {Frame} from './../frame';
  4. import {Profile} from './profile';
  5. import {createFrameIndex} from './utils';
  6. export class SampledProfile extends Profile {
  7. static FromProfile(
  8. sampledProfile: Profiling.SampledProfile,
  9. frameIndex: ReturnType<typeof createFrameIndex>
  10. ): Profile {
  11. const profile = new SampledProfile(
  12. sampledProfile.endValue - sampledProfile.startValue,
  13. sampledProfile.startValue,
  14. sampledProfile.endValue,
  15. sampledProfile.name,
  16. sampledProfile.unit,
  17. sampledProfile.threadID
  18. );
  19. if (sampledProfile.samples.length !== sampledProfile.weights.length) {
  20. throw new Error(
  21. `Expected samples.length (${sampledProfile.samples.length}) to equal weights.length (${sampledProfile.weights.length})`
  22. );
  23. }
  24. for (let i = 0; i < sampledProfile.samples.length; i++) {
  25. const stack = sampledProfile.samples[i];
  26. const weight = sampledProfile.weights[i];
  27. profile.appendSampleWithWeight(
  28. stack.map(n => {
  29. if (!frameIndex[n]) {
  30. throw new Error(`Could not resolve frame ${n} in frame index`);
  31. }
  32. return frameIndex[n];
  33. }),
  34. weight
  35. );
  36. }
  37. return profile.build();
  38. }
  39. appendSampleWithWeight(stack: Frame[], weight: number): void {
  40. // Ignore samples with 0 weight
  41. if (weight === 0) {
  42. return;
  43. }
  44. let node = this.appendOrderTree;
  45. const framesInStack: CallTreeNode[] = [];
  46. for (const frame of stack) {
  47. const last = lastOfArray(node.children);
  48. // Find common frame between two stacks
  49. if (last && !last.isLocked() && last.frame === frame) {
  50. node = last;
  51. } else {
  52. const parent = node;
  53. node = new CallTreeNode(frame, node);
  54. parent.children.push(node);
  55. }
  56. node.addToTotalWeight(weight);
  57. // TODO: This is On^2, because we iterate over all frames in the stack to check if our
  58. // frame is a recursive frame. We could do this in O(1) by keeping a map of frames in the stack
  59. // We check the stack in a top-down order to find the first recursive frame.
  60. let start = framesInStack.length - 1;
  61. while (start >= 0) {
  62. if (framesInStack[start].frame === node.frame) {
  63. // The recursion edge is bidirectional
  64. framesInStack[start].setRecursive(node);
  65. node.setRecursive(framesInStack[start]);
  66. break;
  67. }
  68. start--;
  69. }
  70. framesInStack.push(node);
  71. }
  72. node.addToSelfWeight(weight);
  73. this.minFrameDuration = Math.min(weight, this.minFrameDuration);
  74. // Lock the stack node, so we make sure we dont mutate it in the future.
  75. // The samples should be ordered by timestamp when processed so we should never
  76. // iterate over them again in the future.
  77. for (const child of node.children) {
  78. child.lock();
  79. }
  80. node.frame.addToSelfWeight(weight);
  81. for (const stackNode of framesInStack) {
  82. stackNode.frame.addToTotalWeight(weight);
  83. }
  84. // If node is the same as the previous sample, add the weight to the previous sample
  85. if (node === lastOfArray(this.samples)) {
  86. this.weights[this.weights.length - 1] += weight;
  87. } else {
  88. this.samples.push(node);
  89. this.weights.push(weight);
  90. }
  91. }
  92. build(): Profile {
  93. this.duration = Math.max(
  94. this.duration,
  95. this.weights.reduce((a, b) => a + b, 0)
  96. );
  97. // We had no frames with duration > 0, so set min duration to timeline duration
  98. // which effectively disables any zooming on the flamegraphs
  99. if (
  100. this.minFrameDuration === Number.POSITIVE_INFINITY ||
  101. this.minFrameDuration === 0
  102. ) {
  103. this.minFrameDuration = this.duration;
  104. }
  105. return this;
  106. }
  107. }