spanChart.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import {Rect} from 'sentry/utils/profiling/gl/utils';
  2. import {SpanTree, SpanTreeNode} from 'sentry/utils/profiling/spanTree';
  3. import {
  4. makeFormatter,
  5. makeFormatTo,
  6. makeTimelineFormatter,
  7. } from 'sentry/utils/profiling/units/units';
  8. import {Profile} from './profile/profile';
  9. export interface SpanChartNode {
  10. children: SpanChartNode[];
  11. depth: number;
  12. duration: number;
  13. end: number;
  14. node: SpanTree['root'];
  15. parent: SpanChartNode | null;
  16. start: number;
  17. text: string;
  18. }
  19. class SpanChart {
  20. spans: SpanChartNode[];
  21. root: SpanChartNode = {
  22. parent: null,
  23. node: SpanTreeNode.Root(),
  24. text: 'root',
  25. duration: 0,
  26. depth: -1,
  27. start: 0,
  28. end: 0,
  29. children: [],
  30. };
  31. spanTrees: SpanTree[];
  32. depth: number = 0;
  33. minSpanDuration: number = Number.POSITIVE_INFINITY;
  34. configSpace: Rect;
  35. toFinalUnit = makeFormatTo('milliseconds', 'milliseconds');
  36. formatter = makeFormatter('milliseconds');
  37. timelineFormatter: (value: number) => string;
  38. constructor(
  39. spanTree: SpanTree,
  40. options: {unit: Profile['unit']; configSpace?: Rect} = {unit: 'milliseconds'}
  41. ) {
  42. // Units need to be init before any profile iteration is done
  43. this.toFinalUnit = makeFormatTo('seconds', options.unit);
  44. this.timelineFormatter = makeTimelineFormatter(options.unit);
  45. this.formatter = makeFormatter(options.unit);
  46. this.spanTrees = [spanTree];
  47. let tree = spanTree;
  48. while (tree.orphanedSpans.length > 0) {
  49. const newTree = new SpanTree(tree.transaction, tree.orphanedSpans);
  50. // If a tree has same number of orhpaned spans as the previous tree, we are
  51. // stuck in an infinite loop, so break out and do nothing.
  52. if (newTree.orphanedSpans.length === tree.orphanedSpans.length) {
  53. break;
  54. }
  55. this.spanTrees.push(newTree);
  56. tree = newTree;
  57. }
  58. this.spans = this.collectSpanNodes();
  59. const duration = this.toFinalUnit(
  60. Math.max(...this.spanTrees.map(t => t.root.span.timestamp)) -
  61. Math.min(...this.spanTrees.map(t => t.root.span.start_timestamp))
  62. );
  63. this.configSpace = new Rect(0, 0, duration, this.depth);
  64. this.root.end = duration;
  65. this.root.duration = duration;
  66. }
  67. // Bfs over the span tree while keeping track of level depth and calling the cb fn
  68. forEachSpanOfTree(
  69. tree: SpanTree,
  70. depthOffset: number,
  71. cb: (node: SpanChartNode) => void
  72. ): number {
  73. const transactionStart = tree.root.span.start_timestamp;
  74. // We only want to collect the root most node once
  75. const queue: [SpanChartNode | null, SpanTreeNode][] =
  76. depthOffset === 0
  77. ? [[null, tree.root]]
  78. : [...tree.root.children.map(child => [null, child] as [null, SpanTreeNode])];
  79. let depth = 0;
  80. while (queue.length) {
  81. let children_at_depth = queue.length;
  82. while (children_at_depth-- !== 0) {
  83. const [parent, node] = queue.shift()!;
  84. const duration = node.span.timestamp - node.span.start_timestamp;
  85. const start = node.span.start_timestamp - transactionStart;
  86. const end = start + duration;
  87. const spanChartNode: SpanChartNode = {
  88. duration: this.toFinalUnit(duration),
  89. start: this.toFinalUnit(start),
  90. end: this.toFinalUnit(end),
  91. text:
  92. node.span.op && node.span.description
  93. ? node.span.op + ': ' + node.span.description
  94. : node.span.op || node.span.description || '<unknown span>',
  95. node,
  96. depth: depth + depthOffset,
  97. parent,
  98. children: [],
  99. };
  100. cb(spanChartNode);
  101. if (parent) {
  102. parent.children.push(spanChartNode);
  103. } else {
  104. this.root.children.push(spanChartNode);
  105. }
  106. queue.push(
  107. ...node.children.map(
  108. // @todo use satisfies here when available
  109. child => [spanChartNode, child] as [SpanChartNode, SpanTreeNode]
  110. )
  111. );
  112. }
  113. depth++;
  114. }
  115. return depth;
  116. }
  117. collectSpanNodes(): SpanChartNode[] {
  118. const nodes: SpanChartNode[] = [];
  119. let depth = 0;
  120. const visit = (node: SpanChartNode): void => {
  121. this.depth = Math.max(this.depth, node.depth);
  122. this.minSpanDuration = Math.min(this.minSpanDuration, node.duration);
  123. nodes.push(node);
  124. };
  125. for (let i = 0; i < this.spanTrees.length; i++) {
  126. depth += this.forEachSpanOfTree(this.spanTrees[i], depth, visit);
  127. }
  128. return nodes;
  129. }
  130. }
  131. export {SpanChart};