utils.tsx 6.2 KB

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