spanTree.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import {uuid4} from '@sentry/utils';
  2. import {RawSpanType} from 'sentry/components/events/interfaces/spans/types';
  3. import {t} from 'sentry/locale';
  4. import {EventOrGroupType, EventTransaction} from 'sentry/types';
  5. // Empty transaction to use as a default value with duration of 1 second
  6. const EmptyEventTransaction: EventTransaction = {
  7. id: '',
  8. projectID: '',
  9. user: {},
  10. contexts: {},
  11. entries: [],
  12. errors: [],
  13. dateCreated: '',
  14. startTimestamp: Date.now(),
  15. endTimestamp: Date.now() + 1000,
  16. title: '',
  17. type: EventOrGroupType.TRANSACTION,
  18. culprit: '',
  19. dist: null,
  20. eventID: '',
  21. fingerprints: [],
  22. dateReceived: new Date().toISOString(),
  23. message: '',
  24. metadata: {},
  25. size: 0,
  26. tags: [],
  27. occurrence: null,
  28. location: '',
  29. crashFile: null,
  30. };
  31. function sortByStartTimeAndDuration(a: RawSpanType, b: RawSpanType) {
  32. return a.start_timestamp - b.start_timestamp;
  33. }
  34. export class SpanTreeNode {
  35. parent?: SpanTreeNode | null = null;
  36. span: RawSpanType;
  37. children: SpanTreeNode[] = [];
  38. constructor(span: RawSpanType, parent?: SpanTreeNode | null) {
  39. this.span = span;
  40. this.parent = parent;
  41. }
  42. static Root(partial: Partial<RawSpanType> = {}): SpanTreeNode {
  43. return new SpanTreeNode(
  44. {
  45. description: 'root',
  46. op: 'root',
  47. start_timestamp: 0,
  48. exclusive_time: 0,
  49. timestamp: Number.MAX_SAFE_INTEGER,
  50. parent_span_id: '',
  51. data: {},
  52. span_id: '<root>',
  53. trace_id: '',
  54. hash: '',
  55. ...partial,
  56. },
  57. null
  58. );
  59. }
  60. contains(span: RawSpanType) {
  61. return (
  62. this.span.start_timestamp <= span.start_timestamp &&
  63. this.span.timestamp >= span.timestamp
  64. );
  65. }
  66. }
  67. class SpanTree {
  68. root: SpanTreeNode;
  69. orphanedSpans: RawSpanType[] = [];
  70. transaction: EventTransaction;
  71. constructor(transaction: EventTransaction, spans: RawSpanType[]) {
  72. this.transaction = transaction;
  73. this.root = SpanTreeNode.Root({
  74. description: transaction.title,
  75. start_timestamp: transaction.startTimestamp,
  76. timestamp: transaction.endTimestamp,
  77. exclusive_time: transaction.contexts?.trace?.exclusive_time ?? undefined,
  78. span_id: transaction.contexts?.trace?.span_id ?? undefined,
  79. parent_span_id: undefined,
  80. op: 'transaction',
  81. });
  82. this.buildCollapsedSpanTree(spans);
  83. }
  84. static Empty = new SpanTree(EmptyEventTransaction, []);
  85. isEmpty(): boolean {
  86. return this === SpanTree.Empty;
  87. }
  88. buildCollapsedSpanTree(spans: RawSpanType[]) {
  89. const spansSortedByStartTime = [...spans].sort(sortByStartTimeAndDuration);
  90. const MISSING_INSTRUMENTATION_THRESHOLD_S = 0.1;
  91. for (let i = 0; i < spansSortedByStartTime.length; i++) {
  92. const span = spansSortedByStartTime[i];
  93. let parent = this.root;
  94. while (parent.contains(span)) {
  95. let nextParent: SpanTreeNode | null = null;
  96. for (let j = 0; j < parent.children.length; j++) {
  97. const child = parent.children[j];
  98. if (child.span.op !== 'missing instrumentation' && child.contains(span)) {
  99. nextParent = child;
  100. break;
  101. }
  102. }
  103. if (nextParent === null) {
  104. break;
  105. }
  106. parent = nextParent;
  107. }
  108. if (parent.span.span_id === span.parent_span_id) {
  109. // If the missing instrumentation threshold is exceeded, add a span to
  110. // indicate that there is a gap in instrumentation. We can rely on this check
  111. // because the spans are sorted by start time, so we know that we will not be
  112. // updating anything before span.start_timestamp.
  113. if (
  114. parent.children.length > 0 &&
  115. span.start_timestamp -
  116. parent.children[parent.children.length - 1].span.timestamp >
  117. MISSING_INSTRUMENTATION_THRESHOLD_S
  118. ) {
  119. parent.children.push(
  120. new SpanTreeNode(
  121. {
  122. description: t('Missing span instrumentation'),
  123. op: 'missing span instrumentation',
  124. start_timestamp:
  125. parent.children[parent.children.length - 1].span.timestamp,
  126. timestamp: span.start_timestamp,
  127. span_id: uuid4(),
  128. data: {},
  129. trace_id: span.trace_id,
  130. },
  131. parent
  132. )
  133. );
  134. }
  135. let foundOverlap = false;
  136. let start = parent.children.length - 1;
  137. while (start >= 0) {
  138. const child = parent.children[start];
  139. if (span.start_timestamp < child.span.timestamp) {
  140. foundOverlap = true;
  141. break;
  142. }
  143. start--;
  144. }
  145. if (foundOverlap) {
  146. this.orphanedSpans.push(span);
  147. continue;
  148. }
  149. // Insert child span
  150. parent.children.push(new SpanTreeNode(span, parent));
  151. continue;
  152. }
  153. this.orphanedSpans.push(span);
  154. }
  155. }
  156. }
  157. export {SpanTree};