spanTree.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. import {RawSpanType} from 'sentry/components/events/interfaces/spans/types';
  2. import {EventOrGroupType, EventTransaction} from 'sentry/types';
  3. // Empty transaction to use as a default value with duration of 1 second
  4. const EmptyEventTransaction: EventTransaction = {
  5. id: '',
  6. projectID: '',
  7. user: {},
  8. contexts: {},
  9. entries: [],
  10. errors: [],
  11. dateCreated: '',
  12. startTimestamp: Date.now(),
  13. endTimestamp: Date.now() + 1000,
  14. title: '',
  15. type: EventOrGroupType.TRANSACTION,
  16. culprit: '',
  17. dist: null,
  18. eventID: '',
  19. fingerprints: [],
  20. dateReceived: new Date().toISOString(),
  21. message: '',
  22. metadata: {},
  23. size: 0,
  24. tags: [],
  25. occurrence: null,
  26. location: '',
  27. crashFile: null,
  28. };
  29. function sortByStartTimeAndDuration(a: RawSpanType, b: RawSpanType) {
  30. if (a.start_timestamp < b.start_timestamp) {
  31. return -1;
  32. }
  33. // if the start times are the same, we want to sort by end time
  34. if (a.start_timestamp === b.start_timestamp) {
  35. if (a.timestamp < b.timestamp) {
  36. return 1; // a is a child of b
  37. }
  38. return -1; // b is a child of a
  39. }
  40. return 1;
  41. }
  42. export class SpanTreeNode {
  43. parent?: SpanTreeNode | null = null;
  44. span: RawSpanType;
  45. children: SpanTreeNode[] = [];
  46. constructor(span: RawSpanType, parent?: SpanTreeNode | null) {
  47. this.span = span;
  48. this.parent = parent;
  49. }
  50. static Root(partial: Partial<RawSpanType> = {}): SpanTreeNode {
  51. return new SpanTreeNode(
  52. {
  53. description: 'root',
  54. op: 'root',
  55. start_timestamp: 0,
  56. exclusive_time: 0,
  57. timestamp: Number.MAX_SAFE_INTEGER,
  58. parent_span_id: '',
  59. data: {},
  60. span_id: '<root>',
  61. trace_id: '',
  62. hash: '',
  63. ...partial,
  64. },
  65. null
  66. );
  67. }
  68. contains(span: RawSpanType) {
  69. return (
  70. this.span.start_timestamp <= span.start_timestamp &&
  71. this.span.timestamp >= span.timestamp
  72. );
  73. }
  74. }
  75. class SpanTree {
  76. root: SpanTreeNode;
  77. orphanedSpans: RawSpanType[] = [];
  78. constructor(transaction: EventTransaction, spans: RawSpanType[]) {
  79. this.root = SpanTreeNode.Root({
  80. description: transaction.title,
  81. start_timestamp: transaction.startTimestamp,
  82. timestamp: transaction.endTimestamp,
  83. exclusive_time: transaction.contexts?.trace?.exclusive_time ?? undefined,
  84. span_id: transaction.contexts?.trace?.span_id ?? undefined,
  85. parent_span_id: undefined,
  86. op: 'transaction',
  87. });
  88. this.buildCollapsedSpanTree(spans);
  89. }
  90. static Empty = new SpanTree(EmptyEventTransaction, []);
  91. isEmpty(): boolean {
  92. return this === SpanTree.Empty;
  93. }
  94. buildCollapsedSpanTree(spans: RawSpanType[]) {
  95. const spansSortedByStartTime = [...spans].sort(sortByStartTimeAndDuration);
  96. for (let i = 0; i < spansSortedByStartTime.length; i++) {
  97. const span = spansSortedByStartTime[i];
  98. let parent = this.root;
  99. while (parent.contains(span)) {
  100. let nextParent: SpanTreeNode | null = null;
  101. for (let j = 0; j < parent.children.length; j++) {
  102. const child = parent.children[j];
  103. if (child.contains(span)) {
  104. nextParent = child;
  105. break;
  106. }
  107. }
  108. if (nextParent === null) {
  109. break;
  110. }
  111. parent = nextParent;
  112. }
  113. if (parent.span.span_id === span.parent_span_id) {
  114. parent.children.push(new SpanTreeNode(span, parent));
  115. continue;
  116. }
  117. this.orphanedSpans.push(span);
  118. }
  119. }
  120. }
  121. export {SpanTree};