utils.tsx 6.6 KB

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