traceTreeNode.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import type {Theme} from '@emotion/react';
  2. import type {EventTransaction} from 'sentry/types/event';
  3. import type {TraceTree} from './traceTree';
  4. function isTraceTransaction(value: TraceTree.NodeValue): value is TraceTree.Transaction {
  5. return !!(value && 'transaction' in value);
  6. }
  7. function isTraceError(value: TraceTree.NodeValue): value is TraceTree.TraceError {
  8. return !!(value && 'level' in value);
  9. }
  10. function isTraceSpan(value: TraceTree.NodeValue): value is TraceTree.Span {
  11. return !!(
  12. value &&
  13. 'span_id' in value &&
  14. !isTraceAutogroup(value) &&
  15. !isTraceTransaction(value)
  16. );
  17. }
  18. function isTraceAutogroup(
  19. value: TraceTree.NodeValue
  20. ): value is TraceTree.ChildrenAutogroup | TraceTree.SiblingAutogroup {
  21. return !!(value && 'autogrouped_by' in value);
  22. }
  23. function shouldCollapseNodeByDefault(node: TraceTreeNode<TraceTree.NodeValue>) {
  24. if (isTraceSpan(node.value)) {
  25. // Android creates TCP connection spans which are noisy and not useful in most cases.
  26. // Unless the span has a child txn which would indicate a continuaton of the trace, we collapse it.
  27. if (node.value.op === 'http.client' && node.value.origin === 'auto.http.okhttp') {
  28. return true;
  29. }
  30. }
  31. return false;
  32. }
  33. export class TraceTreeNode<T extends TraceTree.NodeValue = TraceTree.NodeValue> {
  34. parent: TraceTreeNode | null = null;
  35. reparent_reason: 'pageload server handler' | null = null;
  36. fetchStatus: 'resolved' | 'error' | 'idle' | 'loading' = 'idle';
  37. value: T;
  38. canFetch: boolean = false;
  39. expanded: boolean = true;
  40. zoomedIn: boolean = false;
  41. metadata: TraceTree.Metadata = {
  42. project_slug: undefined,
  43. event_id: undefined,
  44. spans: undefined,
  45. };
  46. event: EventTransaction | null = null;
  47. // Events associated with the node, these are inferred from the node value.
  48. errors = new Set<TraceTree.TraceError>();
  49. performance_issues = new Set<TraceTree.TracePerformanceIssue>();
  50. profiles: TraceTree.Profile[] = [];
  51. space: [number, number] = [0, 0];
  52. children: TraceTreeNode[] = [];
  53. depth: number | undefined;
  54. connectors: number[] | undefined;
  55. constructor(parent: TraceTreeNode | null, value: T, metadata: TraceTree.Metadata) {
  56. this.parent = parent ?? null;
  57. this.value = value;
  58. this.metadata = metadata;
  59. // The node can fetch its children if it has more than one span, or if we failed to fetch the span count.
  60. this.canFetch =
  61. typeof metadata.spans === 'number'
  62. ? metadata.spans > 1
  63. : isTraceTransaction(this.value);
  64. // If a node has both a start and end timestamp, then we can infer a duration,
  65. // otherwise we can only infer a timestamp.
  66. if (
  67. value &&
  68. 'timestamp' in value &&
  69. 'start_timestamp' in value &&
  70. typeof value.timestamp === 'number' &&
  71. typeof value.start_timestamp === 'number'
  72. ) {
  73. this.space = [
  74. value.start_timestamp * 1e3,
  75. (value.timestamp - value.start_timestamp) * 1e3,
  76. ];
  77. } else if (value && 'timestamp' in value && typeof value.timestamp === 'number') {
  78. this.space = [value.timestamp * 1e3, 0];
  79. }
  80. if (value) {
  81. if ('errors' in value && Array.isArray(value.errors)) {
  82. value.errors.forEach(error => this.errors.add(error));
  83. }
  84. if ('performance_issues' in value && Array.isArray(value.performance_issues)) {
  85. value.performance_issues.forEach(issue => this.performance_issues.add(issue));
  86. }
  87. if ('profile_id' in value && typeof value.profile_id === 'string') {
  88. this.profiles.push({profile_id: value.profile_id});
  89. }
  90. if ('profiler_id' in value && typeof value.profiler_id === 'string') {
  91. this.profiles.push({profiler_id: value.profiler_id});
  92. }
  93. }
  94. // For error nodes, its value is the only associated issue.
  95. if (isTraceError(this.value)) {
  96. this.errors.add(this.value);
  97. }
  98. // Android http spans generate sub spans for things like dns resolution in http requests,
  99. // which creates a lot of noise and is not useful to display.
  100. if (shouldCollapseNodeByDefault(this)) {
  101. this.expanded = false;
  102. }
  103. }
  104. get hasErrors(): boolean {
  105. return this.errors.size > 0 || this.performance_issues.size > 0;
  106. }
  107. private _max_severity: keyof Theme['level'] | undefined;
  108. get maxIssueSeverity(): keyof Theme['level'] {
  109. if (this._max_severity) {
  110. return this._max_severity;
  111. }
  112. for (const error of this.errors) {
  113. if (error.level === 'error' || error.level === 'fatal') {
  114. this._max_severity = error.level;
  115. return this.maxIssueSeverity;
  116. }
  117. }
  118. return 'default';
  119. }
  120. invalidate() {
  121. this.connectors = undefined;
  122. this.depth = undefined;
  123. }
  124. static Root() {
  125. return new TraceTreeNode(null, null, {
  126. event_id: undefined,
  127. project_slug: undefined,
  128. });
  129. }
  130. }