utils.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. import * as Sentry from '@sentry/react';
  2. import keyBy from 'lodash/keyBy';
  3. import {t} from 'sentry/locale';
  4. import {
  5. EntrySpans,
  6. EntryType,
  7. EventTransaction,
  8. IssueCategory,
  9. IssueType,
  10. PlatformType,
  11. } from 'sentry/types';
  12. import {RawSpanType} from '../spans/types';
  13. import {ResourceLink} from './resources';
  14. import {TraceContextSpanProxy} from './spanEvidence';
  15. const RESOURCES_DESCRIPTIONS: Record<IssueType, string> = {
  16. [IssueType.PERFORMANCE_CONSECUTIVE_DB_QUERIES]: t(
  17. 'Consecutive DB Queries are a sequence of database spans where one or more have been identified as parallelizable, or in other words, spans that may be shifted to the start of the sequence. This often occurs when a db query performs no filtering on the data, for example a query without a WHERE clause. To learn more about how to fix consecutive DB queries, check out these resources:'
  18. ),
  19. [IssueType.PERFORMANCE_N_PLUS_ONE_DB_QUERIES]: t(
  20. "N+1 queries are extraneous queries (N) caused by a single, initial query (+1). In the Span Evidence above, we've identified the parent span where the extraneous spans are located and the extraneous spans themselves. To learn more about how to fix N+1 problems, check out these resources:"
  21. ),
  22. [IssueType.PERFORMANCE_N_PLUS_ONE_API_CALLS]: t(
  23. "N+1 API Calls are repeated concurrent calls to fetch a resource type. In the Span Evidence section we've identified the repeated calls. To learn more about how and when to fix N+1 API Calls, check out these resources:"
  24. ),
  25. [IssueType.PERFORMANCE_FILE_IO_MAIN_THREAD]: t(
  26. 'File IO operations on your main thread may cause app hangs.'
  27. ),
  28. [IssueType.PERFORMANCE_SLOW_SPAN]: t(
  29. 'Slow DB Queries are SELECT query spans that take longer than 1s. A quick method to understand why this may be the case is running an EXPLAIN command on the query itself. To learn more about how to fix slow DB queries, check out these resources:'
  30. ),
  31. [IssueType.PERFORMANCE_UNCOMPRESSED_ASSET]: t(
  32. 'Uncompressed assets are asset spans that take over 50ms and are larger than 512kB which can usually be made faster with compression. Check that your server or CDN serving your assets is accepting the content encoding header from the browser and is returning them compressed.'
  33. ),
  34. [IssueType.ERROR]: '',
  35. };
  36. type PlatformSpecificResources = Partial<Record<PlatformType, ResourceLink[]>>;
  37. const DEFAULT_RESOURCE_LINK: Record<IssueType, ResourceLink[]> = {
  38. [IssueType.PERFORMANCE_CONSECUTIVE_DB_QUERIES]: [
  39. {
  40. text: t('Sentry Docs: Consecutive DB Queries'),
  41. link: 'https://docs.sentry.io/product/issues/issue-details/performance-issues/consecutive-db-queries/',
  42. },
  43. ],
  44. [IssueType.PERFORMANCE_N_PLUS_ONE_DB_QUERIES]: [
  45. {
  46. text: t('Sentry Docs: N+1 Queries'),
  47. link: 'https://docs.sentry.io/product/issues/issue-details/performance-issues/n-one-queries/',
  48. },
  49. ],
  50. [IssueType.PERFORMANCE_N_PLUS_ONE_API_CALLS]: [
  51. {
  52. text: t('Sentry Docs: N+1 API Calls'),
  53. link: 'https://docs.sentry.io/product/issues/issue-details/performance-issues/n-one-api-calls/',
  54. },
  55. ],
  56. [IssueType.PERFORMANCE_UNCOMPRESSED_ASSET]: [],
  57. [IssueType.PERFORMANCE_FILE_IO_MAIN_THREAD]: [],
  58. [IssueType.PERFORMANCE_SLOW_SPAN]: [],
  59. [IssueType.ERROR]: [],
  60. };
  61. // TODO: When the Sentry blogpost for N+1s and documentation has been released, add them as resources for all platforms
  62. const RESOURCE_LINKS: Record<IssueType, PlatformSpecificResources> = {
  63. [IssueType.PERFORMANCE_N_PLUS_ONE_DB_QUERIES]: {
  64. python: [
  65. {
  66. text: t('Finding and Fixing Django N+1 Problems'),
  67. link: 'https://blog.sentry.io/2020/09/14/finding-and-fixing-django-n-1-problems',
  68. },
  69. ],
  70. 'python-django': [
  71. {
  72. text: t('Finding and Fixing Django N+1 Problems'),
  73. link: 'https://blog.sentry.io/2020/09/14/finding-and-fixing-django-n-1-problems',
  74. },
  75. ],
  76. },
  77. [IssueType.PERFORMANCE_N_PLUS_ONE_API_CALLS]: {},
  78. [IssueType.PERFORMANCE_CONSECUTIVE_DB_QUERIES]: {},
  79. [IssueType.PERFORMANCE_FILE_IO_MAIN_THREAD]: {},
  80. [IssueType.PERFORMANCE_SLOW_SPAN]: {},
  81. [IssueType.PERFORMANCE_UNCOMPRESSED_ASSET]: {},
  82. [IssueType.ERROR]: {},
  83. };
  84. export function getResourceDescription(issueType: IssueType): string {
  85. return RESOURCES_DESCRIPTIONS[issueType];
  86. }
  87. export function getResourceLinks(
  88. issueType: IssueType,
  89. platform: PlatformType | undefined
  90. ): ResourceLink[] {
  91. let links: ResourceLink[] = [];
  92. if (DEFAULT_RESOURCE_LINK[issueType]) {
  93. links = [...DEFAULT_RESOURCE_LINK[issueType]];
  94. }
  95. if (RESOURCE_LINKS[issueType] && platform) {
  96. const platformLink = RESOURCE_LINKS[issueType][platform];
  97. if (platformLink) {
  98. links = [...links, ...platformLink];
  99. }
  100. }
  101. return links;
  102. }
  103. export function getSpanInfoFromTransactionEvent(
  104. event: Pick<
  105. EventTransaction,
  106. 'entries' | 'perfProblem' | 'issueCategory' | 'endTimestamp' | 'contexts'
  107. >
  108. ) {
  109. if (!event.perfProblem) {
  110. if (
  111. event.issueCategory === IssueCategory.PERFORMANCE &&
  112. event.endTimestamp > 1663560000 // (Sep 19, 2022 onward), Some events could have been missing evidence before EA
  113. ) {
  114. Sentry.captureException(new Error('Span Evidence missing for performance issue.'));
  115. }
  116. return null;
  117. }
  118. // Let's dive into the event to pick off the span evidence data by using the IDs we know
  119. const spanEntry = event.entries.find((entry: EntrySpans | any): entry is EntrySpans => {
  120. return entry.type === EntryType.SPANS;
  121. });
  122. const spans: Array<RawSpanType | TraceContextSpanProxy> = spanEntry?.data
  123. ? [...spanEntry.data]
  124. : [];
  125. if (event?.contexts?.trace && event?.contexts?.trace?.span_id) {
  126. // TODO: Fix this conditional and check if span_id is ever actually undefined.
  127. spans.push(event.contexts.trace as TraceContextSpanProxy);
  128. }
  129. const spansById = keyBy(spans, 'span_id');
  130. const parentSpanIDs = event?.perfProblem?.parentSpanIds ?? [];
  131. const offendingSpanIDs = event?.perfProblem?.offenderSpanIds ?? [];
  132. const causeSpanIDs = event?.perfProblem?.causeSpanIds ?? [];
  133. const affectedSpanIds = [...offendingSpanIDs];
  134. if (event?.perfProblem?.issueType !== IssueType.PERFORMANCE_N_PLUS_ONE_API_CALLS) {
  135. affectedSpanIds.push(...parentSpanIDs);
  136. }
  137. return {
  138. parentSpan: spansById[parentSpanIDs[0]],
  139. offendingSpans: offendingSpanIDs.map(spanID => spansById[spanID]),
  140. causeSpans: causeSpanIDs.map(spanID => spansById[spanID]),
  141. affectedSpanIds,
  142. };
  143. }