traceProfilingLink.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import type {Location, LocationDescriptor} from 'history';
  2. import type {Organization} from 'sentry/types/organization';
  3. import {getDateFromTimestamp} from 'sentry/utils/dates';
  4. import {
  5. generateContinuousProfileFlamechartRouteWithQuery,
  6. generateProfileFlamechartRouteWithQuery,
  7. } from 'sentry/utils/profiling/routes';
  8. import {isSpanNode, isTransactionNode} from '../traceGuards';
  9. import {TraceTree} from '../traceModels/traceTree';
  10. import type {TraceTreeNode} from '../traceModels/traceTreeNode';
  11. function getNodeId(node: TraceTreeNode<TraceTree.NodeValue>): string | undefined {
  12. if (isTransactionNode(node)) {
  13. return node.value.event_id;
  14. }
  15. if (isSpanNode(node)) {
  16. return node.value.span_id;
  17. }
  18. return undefined;
  19. }
  20. // In the current version, a segment is a parent transaction
  21. function getEventId(node: TraceTreeNode<TraceTree.NodeValue>): string | undefined {
  22. if (isTransactionNode(node)) {
  23. return node.value.event_id;
  24. }
  25. return TraceTree.ParentTransaction(node)?.value?.event_id;
  26. }
  27. export function makeTransactionProfilingLink(
  28. profileId: string,
  29. options: {
  30. organization: Organization;
  31. projectSlug: string;
  32. },
  33. query: Location['query'] = {}
  34. ): LocationDescriptor | null {
  35. if (!options.projectSlug || !options.organization) {
  36. return null;
  37. }
  38. return generateProfileFlamechartRouteWithQuery({
  39. organization: options.organization,
  40. projectSlug: options.projectSlug,
  41. profileId,
  42. query,
  43. });
  44. }
  45. /**
  46. * Generates a link to a continuous profile for a given trace element type
  47. */
  48. export function makeTraceContinuousProfilingLink(
  49. node: TraceTreeNode<TraceTree.NodeValue>,
  50. profilerId: string,
  51. options: {
  52. organization: Organization;
  53. projectSlug: string;
  54. threadId: string | undefined;
  55. traceId: string;
  56. },
  57. query: Location['query'] = {}
  58. ): LocationDescriptor | null {
  59. if (!options.projectSlug || !options.organization) {
  60. return null;
  61. }
  62. // We compute a time offset based on the duration of the span so that
  63. // users can see some context of things that occurred before and after the span.
  64. const transaction = isTransactionNode(node) ? node : TraceTree.ParentTransaction(node);
  65. if (!transaction) {
  66. return null;
  67. }
  68. let start: Date | null = getDateFromTimestamp(transaction.space[0]);
  69. let end: Date | null = getDateFromTimestamp(
  70. transaction.space[0] + transaction.space[1]
  71. );
  72. // End timestamp is required to generate a link
  73. if (end === null || typeof profilerId !== 'string' || profilerId === '') {
  74. return null;
  75. }
  76. // If we have an end, but no start, then we'll generate a window of time around end timestamp
  77. // so that we can show context around the event.
  78. if (end && end.getTime() === start?.getTime()) {
  79. const PRE_CONTEXT_WINDOW_MS = 100;
  80. const POST_CONTEXT_WINDOW_MS = 100;
  81. start = new Date(start.getTime() - PRE_CONTEXT_WINDOW_MS);
  82. end = new Date(end.getTime() + POST_CONTEXT_WINDOW_MS);
  83. }
  84. // We require a full time range to open a flamechart
  85. if (start === null) {
  86. return null;
  87. }
  88. // TransactionId is required to generate a link because
  89. // we need to link to the segment of the trace and fetch its spans
  90. const eventId = getEventId(node);
  91. if (!eventId) {
  92. return null;
  93. }
  94. const queryWithEventData: Record<string, string> = {
  95. ...query,
  96. eventId,
  97. traceId: options.traceId,
  98. };
  99. if (typeof options.threadId === 'string') {
  100. queryWithEventData.tid = options.threadId;
  101. }
  102. const spanId = getNodeId(node);
  103. if (spanId) {
  104. queryWithEventData.spanId = spanId;
  105. }
  106. return generateContinuousProfileFlamechartRouteWithQuery({
  107. organization: options.organization,
  108. projectSlug: options.projectSlug,
  109. profilerId,
  110. start: start.toISOString(),
  111. end: end.toISOString(),
  112. query: queryWithEventData,
  113. });
  114. }