spanOpBreakdown.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import styled from '@emotion/styled';
  2. import {Location} from 'history';
  3. import EmptyStateWarning from 'sentry/components/emptyStateWarning';
  4. import {DataSection} from 'sentry/components/events/styles';
  5. import LoadingIndicator from 'sentry/components/loadingIndicator';
  6. import {t} from 'sentry/locale';
  7. import {space} from 'sentry/styles/space';
  8. import {Event} from 'sentry/types';
  9. import {useDiscoverQuery} from 'sentry/utils/discover/discoverQuery';
  10. import EventView from 'sentry/utils/discover/eventView';
  11. import {useLocation} from 'sentry/utils/useLocation';
  12. import useOrganization from 'sentry/utils/useOrganization';
  13. import PieChart from './pieChart';
  14. const SPAN_OPS = ['db', 'http', 'resource', 'browser', 'ui'];
  15. const REQUEST_FIELDS = SPAN_OPS.map(op => ({field: `p100(spans.${op})`}));
  16. const SPAN_OPS_NAME_MAP = {
  17. ['p100(spans.db)']: 'db',
  18. ['p100(spans.http)']: 'http',
  19. ['p100(spans.resource)']: 'resource',
  20. ['p100(spans.browser)']: 'browser',
  21. ['p100(spans.ui)']: 'ui',
  22. };
  23. function getPostBreakpointEventView(location: Location, event: Event) {
  24. const eventView = EventView.fromLocation(location);
  25. eventView.fields = REQUEST_FIELDS;
  26. if (event?.occurrence) {
  27. const {breakpoint, aggregateRange2, transaction, requestEnd} =
  28. event?.occurrence?.evidenceData;
  29. eventView.start = new Date(breakpoint * 1000).toISOString();
  30. eventView.end = new Date(requestEnd * 1000).toISOString();
  31. eventView.query = `event.type:transaction transaction:"${transaction}" transaction.duration:<${
  32. aggregateRange2 * 1.15
  33. }`;
  34. }
  35. return eventView;
  36. }
  37. function getPreBreakpointEventView(location: Location, event: Event) {
  38. const eventView = EventView.fromLocation(location);
  39. eventView.fields = REQUEST_FIELDS;
  40. if (event?.occurrence) {
  41. const {breakpoint, aggregateRange1, transaction, requestStart} =
  42. event?.occurrence?.evidenceData;
  43. eventView.start = new Date(requestStart * 1000).toISOString();
  44. eventView.end = new Date(breakpoint * 1000).toISOString();
  45. eventView.query = `event.type:transaction transaction:"${transaction}" transaction.duration:<${
  46. aggregateRange1 * 1.15
  47. }`;
  48. }
  49. return eventView;
  50. }
  51. function EventSpanOpBreakdown({event}: {event: Event}) {
  52. const organization = useOrganization();
  53. const location = useLocation();
  54. const postBreakpointEventView = getPostBreakpointEventView(location, event);
  55. const preBreakpointEventView = getPreBreakpointEventView(location, event);
  56. const queryExtras = {dataset: 'metricsEnhanced'};
  57. const {
  58. data: postBreakpointData,
  59. isLoading: postBreakpointIsLoading,
  60. isError: postBreakpointIsError,
  61. } = useDiscoverQuery({
  62. eventView: postBreakpointEventView,
  63. orgSlug: organization.slug,
  64. location,
  65. queryExtras,
  66. });
  67. const {
  68. data: preBreakpointData,
  69. isLoading: preBreakpointIsLoading,
  70. isError: preBreakpointIsError,
  71. } = useDiscoverQuery({
  72. eventView: preBreakpointEventView,
  73. orgSlug: organization.slug,
  74. location,
  75. queryExtras,
  76. });
  77. const postBreakpointPrunedSpanOps = Object.entries(postBreakpointData?.data[0] || {})
  78. .filter(entry => (entry[1] as number) > 0)
  79. .map(entry => ({
  80. value: entry[1] as number,
  81. name: SPAN_OPS_NAME_MAP[entry[0]],
  82. }));
  83. const spanOpDiffs = SPAN_OPS.map(op => {
  84. const preBreakpointValue =
  85. (preBreakpointData?.data[0][`p100(spans.${op})`] as string) || undefined;
  86. const preBreakpointValueAsNumber = preBreakpointValue
  87. ? parseInt(preBreakpointValue, 10)
  88. : 0;
  89. const postBreakpointValue =
  90. (postBreakpointData?.data[0][`p100(spans.${op})`] as string) || undefined;
  91. const postBreakpointValueAsNumber = postBreakpointValue
  92. ? parseInt(postBreakpointValue, 10)
  93. : 0;
  94. if (preBreakpointValueAsNumber === 0 || postBreakpointValueAsNumber === 0) {
  95. return null;
  96. }
  97. return {
  98. [op]: {
  99. percentChange: postBreakpointValueAsNumber / preBreakpointValueAsNumber,
  100. oldBaseline: preBreakpointValueAsNumber,
  101. newBaseline: postBreakpointValueAsNumber,
  102. },
  103. };
  104. })
  105. .filter(Boolean)
  106. .reduce((acc, opDiffData) => {
  107. if (opDiffData && acc) {
  108. Object.keys(opDiffData).forEach(op => {
  109. acc[op] = opDiffData[op];
  110. });
  111. }
  112. return acc;
  113. }, {});
  114. const series = [
  115. {
  116. seriesName: 'Aggregate Span Op Breakdown',
  117. data: postBreakpointPrunedSpanOps,
  118. },
  119. ];
  120. if (postBreakpointIsLoading || preBreakpointIsLoading) {
  121. return (
  122. <Wrapper>
  123. <LoadingIndicator />
  124. </Wrapper>
  125. );
  126. }
  127. if (postBreakpointIsError || preBreakpointIsError) {
  128. return (
  129. <Wrapper>
  130. <EmptyStateWrapper>
  131. <EmptyStateWarning withIcon>
  132. <div>{t('Unable to fetch span breakdowns')}</div>
  133. </EmptyStateWarning>
  134. </EmptyStateWrapper>
  135. </Wrapper>
  136. );
  137. }
  138. return (
  139. <Wrapper>
  140. <DataSection>
  141. <strong>{t('Operation Breakdown:')}</strong>
  142. <PieChart data={spanOpDiffs} series={series} />
  143. </DataSection>
  144. </Wrapper>
  145. );
  146. }
  147. const EmptyStateWrapper = styled('div')`
  148. border: ${({theme}) => `1px solid ${theme.border}`};
  149. border-radius: ${({theme}) => theme.borderRadius};
  150. display: flex;
  151. justify-content: center;
  152. align-items: center;
  153. margin: ${space(1.5)} ${space(4)};
  154. `;
  155. const Wrapper = styled('div')`
  156. display: grid;
  157. grid-template-columns: 1fr 1fr;
  158. `;
  159. export default EventSpanOpBreakdown;