utils.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. import type {Location, Query} from 'history';
  2. import pick from 'lodash/pick';
  3. import {DEFAULT_RELATIVE_PERIODS} from 'sentry/constants';
  4. import {t} from 'sentry/locale';
  5. import {defined} from 'sentry/utils';
  6. import EventView from 'sentry/utils/discover/eventView';
  7. import {isAggregateField} from 'sentry/utils/discover/fields';
  8. import type {SpanSlug} from 'sentry/utils/performance/suspectSpans/types';
  9. import {decodeScalar} from 'sentry/utils/queryString';
  10. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  11. import type {DomainView} from 'sentry/views/insights/pages/useFilters';
  12. import {getTransactionSummaryBaseUrl} from 'sentry/views/performance/transactionSummary/utils';
  13. import type {SpanSort, SpanSortOption} from './types';
  14. import {SpanSortOthers, SpanSortPercentiles} from './types';
  15. export function generateSpansRoute({
  16. orgSlug,
  17. view,
  18. }: {
  19. orgSlug: string;
  20. view?: DomainView;
  21. }): string {
  22. return `${getTransactionSummaryBaseUrl(orgSlug, view)}/spans/`;
  23. }
  24. export function spansRouteWithQuery({
  25. orgSlug,
  26. transaction,
  27. projectID,
  28. query,
  29. view,
  30. }: {
  31. orgSlug: string;
  32. query: Query;
  33. transaction: string;
  34. projectID?: string | string[];
  35. view?: DomainView;
  36. }) {
  37. const pathname = generateSpansRoute({
  38. orgSlug,
  39. view,
  40. });
  41. return {
  42. pathname,
  43. query: {
  44. transaction,
  45. project: projectID,
  46. environment: query.environment,
  47. statsPeriod: query.statsPeriod,
  48. start: query.start,
  49. end: query.end,
  50. query: query.query,
  51. },
  52. };
  53. }
  54. export const SPAN_RETENTION_DAYS = 30;
  55. export const SPAN_RELATIVE_PERIODS = pick(DEFAULT_RELATIVE_PERIODS, [
  56. '1h',
  57. '24h',
  58. '7d',
  59. '14d',
  60. '30d',
  61. ]);
  62. export const SPAN_SORT_OPTIONS: SpanSortOption[] = [
  63. {
  64. prefix: t('Sort By'),
  65. label: t('Total Self Time'),
  66. field: SpanSortOthers.SUM_EXCLUSIVE_TIME,
  67. },
  68. {
  69. prefix: t('Sort By'),
  70. label: t('Average Count'),
  71. field: SpanSortOthers.AVG_OCCURRENCE,
  72. },
  73. {
  74. prefix: t('Sort By'),
  75. label: t('p50 Self Time'),
  76. field: SpanSortPercentiles.P50_EXCLUSIVE_TIME,
  77. },
  78. {
  79. prefix: t('Sort By'),
  80. label: t('p75 Self Time'),
  81. field: SpanSortPercentiles.P75_EXCLUSIVE_TIME,
  82. },
  83. {
  84. prefix: t('Sort By'),
  85. label: t('p95 Self Time'),
  86. field: SpanSortPercentiles.P95_EXCLUSIVE_TIME,
  87. },
  88. {
  89. prefix: t('Sort By'),
  90. label: t('p99 Self Time'),
  91. field: SpanSortPercentiles.P99_EXCLUSIVE_TIME,
  92. },
  93. ];
  94. const DEFAULT_SORT = SpanSortOthers.SUM_EXCLUSIVE_TIME;
  95. function getSuspectSpanSort(sort: string): SpanSortOption {
  96. const selected = SPAN_SORT_OPTIONS.find(option => option.field === sort);
  97. if (selected) {
  98. return selected;
  99. }
  100. return SPAN_SORT_OPTIONS.find(option => option.field === DEFAULT_SORT)!;
  101. }
  102. export function getSuspectSpanSortFromLocation(
  103. location: Location,
  104. sortKey: string = 'sort'
  105. ): SpanSortOption {
  106. const sort = decodeScalar(location?.query?.[sortKey]) ?? DEFAULT_SORT;
  107. return getSuspectSpanSort(sort);
  108. }
  109. export function getSuspectSpanSortFromEventView(eventView: EventView): SpanSortOption {
  110. const sort = eventView.sorts.length ? eventView.sorts[0].field : DEFAULT_SORT;
  111. return getSuspectSpanSort(sort);
  112. }
  113. export function parseSpanSlug(spanSlug: string | undefined): SpanSlug | undefined {
  114. if (!defined(spanSlug)) {
  115. return undefined;
  116. }
  117. const delimiterPos = spanSlug.lastIndexOf(':');
  118. if (delimiterPos < 0) {
  119. return undefined;
  120. }
  121. const op = spanSlug.slice(0, delimiterPos);
  122. const group = spanSlug.slice(delimiterPos + 1);
  123. return {op, group};
  124. }
  125. export function generateSpansEventView({
  126. location,
  127. transactionName,
  128. }: {
  129. location: Location;
  130. transactionName: string;
  131. }): EventView {
  132. const query = decodeScalar(location.query.query, '');
  133. const conditions = new MutableSearch(query);
  134. conditions.setFilterValues('event.type', ['transaction']);
  135. conditions.setFilterValues('transaction', [transactionName]);
  136. Object.keys(conditions.filters).forEach(field => {
  137. if (isAggregateField(field)) {
  138. conditions.removeFilter(field);
  139. }
  140. });
  141. const eventView = EventView.fromNewQueryWithLocation(
  142. {
  143. id: undefined,
  144. version: 2,
  145. name: transactionName,
  146. fields: [
  147. ...Object.values(SpanSortOthers),
  148. ...Object.values(SpanSortPercentiles),
  149. 'trace',
  150. ],
  151. query: conditions.formatString(),
  152. projects: [],
  153. },
  154. location
  155. );
  156. const sort = getSuspectSpanSortFromLocation(location);
  157. return eventView.withSorts([{field: sort.field, kind: 'desc'}]);
  158. }
  159. /**
  160. * For the totals view, we want to get some transaction level stats like
  161. * the number of transactions and the sum of the transaction duration.
  162. * This requires the removal of any aggregate conditions as they can result
  163. * in unexpected empty responses.
  164. */
  165. export function getTotalsView(eventView: EventView): EventView {
  166. const totalsView = eventView.withColumns([
  167. {kind: 'function', function: ['count', '', undefined, undefined]},
  168. {kind: 'function', function: ['sum', 'transaction.duration', undefined, undefined]},
  169. ]);
  170. const conditions = new MutableSearch(eventView.query);
  171. // filter out any aggregate conditions
  172. Object.keys(conditions.filters).forEach(field => {
  173. if (isAggregateField(field)) {
  174. conditions.removeFilter(field);
  175. }
  176. });
  177. totalsView.query = conditions.formatString();
  178. return totalsView;
  179. }
  180. export const SPAN_SORT_TO_FIELDS: Record<SpanSort, string[]> = {
  181. [SpanSortOthers.SUM_EXCLUSIVE_TIME]: [
  182. 'percentileArray(spans_exclusive_time, 0.75)',
  183. 'count()',
  184. 'count_unique(id)',
  185. 'sumArray(spans_exclusive_time)',
  186. ],
  187. [SpanSortOthers.AVG_OCCURRENCE]: [
  188. 'percentileArray(spans_exclusive_time, 0.75)',
  189. 'count()',
  190. 'count_unique(id)',
  191. 'equation|count() / count_unique(id)',
  192. 'sumArray(spans_exclusive_time)',
  193. ],
  194. [SpanSortPercentiles.P50_EXCLUSIVE_TIME]: [
  195. 'percentileArray(spans_exclusive_time, 0.5)',
  196. 'count()',
  197. 'count_unique(id)',
  198. 'sumArray(spans_exclusive_time)',
  199. ],
  200. [SpanSortPercentiles.P75_EXCLUSIVE_TIME]: [
  201. 'percentileArray(spans_exclusive_time, 0.75)',
  202. 'count()',
  203. 'count_unique(id)',
  204. 'sumArray(spans_exclusive_time)',
  205. ],
  206. [SpanSortPercentiles.P95_EXCLUSIVE_TIME]: [
  207. 'percentileArray(spans_exclusive_time, 0.95)',
  208. 'count()',
  209. 'count_unique(id)',
  210. 'sumArray(spans_exclusive_time)',
  211. ],
  212. [SpanSortPercentiles.P99_EXCLUSIVE_TIME]: [
  213. 'percentileArray(spans_exclusive_time, 0.99)',
  214. 'count()',
  215. 'count_unique(id)',
  216. 'sumArray(spans_exclusive_time)',
  217. ],
  218. };
  219. export function getExclusiveTimeDisplayedValue(value: string): string {
  220. return value.replace('exclusive', 'self');
  221. }