aggregateSpanDiff.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. import {useMemo, useState} from 'react';
  2. import {EventDataSection} from 'sentry/components/events/eventDataSection';
  3. import {COL_WIDTH_UNDEFINED} from 'sentry/components/gridEditable';
  4. import {SegmentedControl} from 'sentry/components/segmentedControl';
  5. import {t} from 'sentry/locale';
  6. import type {Event, Project} from 'sentry/types';
  7. import {useRelativeDateTime} from 'sentry/utils/profiling/hooks/useRelativeDateTime';
  8. import {useApiQuery} from 'sentry/utils/queryClient';
  9. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  10. import {useLocation} from 'sentry/utils/useLocation';
  11. import useOrganization from 'sentry/utils/useOrganization';
  12. import {spanDetailsRouteWithQuery} from 'sentry/views/performance/transactionSummary/transactionSpans/spanDetails/utils';
  13. import {useSpanMetrics} from 'sentry/views/starfish/queries/useDiscover';
  14. import {EventRegressionTable} from './eventRegressionTable';
  15. interface SpanDiff {
  16. p95_after: number;
  17. p95_before: number;
  18. score: number;
  19. span_description: string;
  20. span_group: string;
  21. span_op: string;
  22. spm_after: number;
  23. spm_before: number;
  24. }
  25. interface UseFetchAdvancedAnalysisProps {
  26. breakpoint: string;
  27. enabled: boolean;
  28. end: string;
  29. projectId: string;
  30. start: string;
  31. transaction: string;
  32. }
  33. function useFetchAdvancedAnalysis({
  34. transaction,
  35. start,
  36. end,
  37. breakpoint,
  38. projectId,
  39. enabled,
  40. }: UseFetchAdvancedAnalysisProps) {
  41. const organization = useOrganization();
  42. return useApiQuery<SpanDiff[]>(
  43. [
  44. `/organizations/${organization.slug}/events-root-cause-analysis/`,
  45. {
  46. query: {
  47. transaction,
  48. project: projectId,
  49. start,
  50. end,
  51. breakpoint,
  52. per_page: 10,
  53. },
  54. },
  55. ],
  56. {
  57. staleTime: 60000,
  58. retry: false,
  59. enabled,
  60. }
  61. );
  62. }
  63. const ADDITIONAL_COLUMNS = [
  64. {key: 'operation', name: t('Operation'), width: 120},
  65. {key: 'description', name: t('Description'), width: COL_WIDTH_UNDEFINED},
  66. ];
  67. interface AggregateSpanDiffProps {
  68. event: Event;
  69. project: Project;
  70. }
  71. function AggregateSpanDiff({event, project}: AggregateSpanDiffProps) {
  72. const location = useLocation();
  73. const organization = useOrganization();
  74. const isSpansOnly = organization.features.includes(
  75. 'statistical-detectors-rca-spans-only'
  76. );
  77. const [causeType, setCauseType] = useState<'duration' | 'throughput'>('duration');
  78. const {transaction, breakpoint} = event?.occurrence?.evidenceData ?? {};
  79. const breakpointTimestamp = new Date(breakpoint * 1000).toISOString();
  80. const {start, end} = useRelativeDateTime({
  81. anchor: breakpoint,
  82. relativeDays: 7,
  83. retentionDays: 30,
  84. });
  85. const {
  86. data: rcaData,
  87. isLoading: isRcaLoading,
  88. isError: isRcaError,
  89. } = useFetchAdvancedAnalysis({
  90. transaction,
  91. start: (start as Date).toISOString(),
  92. end: (end as Date).toISOString(),
  93. breakpoint: breakpointTimestamp,
  94. projectId: project.id,
  95. enabled: !isSpansOnly,
  96. });
  97. // Initialize the search query with has:span.group because only
  98. // specific operations have their span.group recorded in the span
  99. // metrics dataset
  100. const search = new MutableSearch('has:span.group');
  101. search.addFilterValue('transaction', transaction);
  102. const {
  103. data: spansData,
  104. isLoading: isSpansDataLoading,
  105. isError: isSpansDataError,
  106. } = useSpanMetrics(
  107. {
  108. search,
  109. fields: [
  110. 'span.op',
  111. 'any(span.description)',
  112. 'span.group',
  113. `regression_score(span.self_time,${breakpoint})`,
  114. `avg_by_timestamp(span.self_time,less,${breakpoint})`,
  115. `avg_by_timestamp(span.self_time,greater,${breakpoint})`,
  116. `epm_by_timestamp(less,${breakpoint})`,
  117. `epm_by_timestamp(greater,${breakpoint})`,
  118. ],
  119. sorts: [{field: `regression_score(span.self_time,${breakpoint})`, kind: 'desc'}],
  120. limit: 10,
  121. enabled: isSpansOnly,
  122. },
  123. 'api.performance.transactions.statistical-detector-root-cause-analysis'
  124. );
  125. const tableData = useMemo(() => {
  126. if (isSpansOnly) {
  127. return spansData?.map(row => {
  128. const commonProps = {
  129. operation: row['span.op'],
  130. group: row['span.group'],
  131. description: row['any(span.description)'] || undefined,
  132. };
  133. if (causeType === 'throughput') {
  134. const throughputBefore = row[`epm_by_timestamp(less,${breakpoint})`];
  135. const throughputAfter = row[`epm_by_timestamp(greater,${breakpoint})`];
  136. return {
  137. ...commonProps,
  138. throughputBefore,
  139. throughputAfter,
  140. percentageChange: throughputAfter / throughputBefore - 1,
  141. };
  142. }
  143. const durationBefore =
  144. row[`avg_by_timestamp(span.self_time,less,${breakpoint})`] / 1e3;
  145. const durationAfter =
  146. row[`avg_by_timestamp(span.self_time,greater,${breakpoint})`] / 1e3;
  147. return {
  148. ...commonProps,
  149. durationBefore,
  150. durationAfter,
  151. percentageChange: durationAfter / durationBefore - 1,
  152. };
  153. });
  154. }
  155. return (
  156. rcaData?.map(row => {
  157. if (causeType === 'throughput') {
  158. return {
  159. operation: row.span_op,
  160. group: row.span_group,
  161. description: row.span_description,
  162. throughputBefore: row.spm_before,
  163. throughputAfter: row.spm_after,
  164. percentageChange: row.spm_after / row.spm_before - 1,
  165. };
  166. }
  167. return {
  168. operation: row.span_op,
  169. group: row.span_group,
  170. description: row.span_description,
  171. durationBefore: row.p95_before / 1e3,
  172. durationAfter: row.p95_after / 1e3,
  173. percentageChange: row.p95_after / row.p95_before - 1,
  174. };
  175. }) || []
  176. );
  177. }, [isSpansOnly, rcaData, spansData, causeType, breakpoint]);
  178. const tableOptions = useMemo(() => {
  179. return {
  180. description: {
  181. defaultValue: t('(unnamed span)'),
  182. link: dataRow => ({
  183. target: spanDetailsRouteWithQuery({
  184. orgSlug: organization.slug,
  185. spanSlug: {op: dataRow.operation, group: dataRow.group},
  186. transaction,
  187. projectID: project.id,
  188. query: {
  189. ...location.query,
  190. statsPeriod: undefined,
  191. query: undefined,
  192. start: (start as Date).toISOString(),
  193. end: (end as Date).toISOString(),
  194. },
  195. }),
  196. }),
  197. },
  198. };
  199. }, [location, organization, project, transaction, start, end]);
  200. return (
  201. <EventDataSection
  202. type="potential-causes"
  203. title={t('Potential Causes')}
  204. actions={
  205. <SegmentedControl
  206. size="xs"
  207. aria-label={t('Duration or Throughput')}
  208. value={causeType}
  209. onChange={setCauseType}
  210. >
  211. <SegmentedControl.Item key="duration">
  212. {isSpansOnly ? t('Average Duration') : t('Duration (P95)')}
  213. </SegmentedControl.Item>
  214. <SegmentedControl.Item key="throughput">
  215. {t('Throughput')}
  216. </SegmentedControl.Item>
  217. </SegmentedControl>
  218. }
  219. >
  220. <EventRegressionTable
  221. causeType={causeType}
  222. columns={ADDITIONAL_COLUMNS}
  223. data={tableData}
  224. isLoading={isSpansOnly ? isSpansDataLoading : isRcaLoading}
  225. isError={isSpansOnly ? isSpansDataError : isRcaError}
  226. options={tableOptions}
  227. />
  228. </EventDataSection>
  229. );
  230. }
  231. export default AggregateSpanDiff;