utils.tsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  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('Total Count'),
  64. field: SpanSortOthers.COUNT,
  65. },
  66. {
  67. prefix: t('Sort By'),
  68. label: t('p50 Self Time'),
  69. field: SpanSortPercentiles.P50_EXCLUSIVE_TIME,
  70. },
  71. {
  72. prefix: t('Sort By'),
  73. label: t('p75 Self Time'),
  74. field: SpanSortPercentiles.P75_EXCLUSIVE_TIME,
  75. },
  76. {
  77. prefix: t('Sort By'),
  78. label: t('p95 Self Time'),
  79. field: SpanSortPercentiles.P95_EXCLUSIVE_TIME,
  80. },
  81. {
  82. prefix: t('Sort By'),
  83. label: t('p99 Self Time'),
  84. field: SpanSortPercentiles.P99_EXCLUSIVE_TIME,
  85. },
  86. ];
  87. const DEFAULT_SORT = SpanSortOthers.SUM_EXCLUSIVE_TIME;
  88. function getSuspectSpanSort(sort: string): SpanSortOption {
  89. const selected = SPAN_SORT_OPTIONS.find(option => option.field === sort);
  90. if (selected) {
  91. return selected;
  92. }
  93. return SPAN_SORT_OPTIONS.find(option => option.field === DEFAULT_SORT)!;
  94. }
  95. export function getSuspectSpanSortFromLocation(
  96. location: Location,
  97. sortKey: string = 'sort'
  98. ): SpanSortOption {
  99. const sort = decodeScalar(location?.query?.[sortKey]) ?? DEFAULT_SORT;
  100. return getSuspectSpanSort(sort);
  101. }
  102. export function getSuspectSpanSortFromEventView(eventView: EventView): SpanSortOption {
  103. const sort = eventView.sorts.length ? eventView.sorts[0].field : DEFAULT_SORT;
  104. return getSuspectSpanSort(sort);
  105. }
  106. export function parseSpanSlug(spanSlug: string | undefined): SpanSlug | undefined {
  107. if (!defined(spanSlug)) {
  108. return undefined;
  109. }
  110. const delimiterPos = spanSlug.lastIndexOf(':');
  111. if (delimiterPos < 0) {
  112. return undefined;
  113. }
  114. const op = spanSlug.slice(0, delimiterPos);
  115. const group = spanSlug.slice(delimiterPos + 1);
  116. return {op, group};
  117. }
  118. export function generateSpansEventView({
  119. location,
  120. transactionName,
  121. }: {
  122. location: Location;
  123. transactionName: string;
  124. }): EventView {
  125. const query = decodeScalar(location.query.query, '');
  126. const conditions = new MutableSearch(query);
  127. conditions.setFilterValues('event.type', ['transaction']);
  128. conditions.setFilterValues('transaction', [transactionName]);
  129. Object.keys(conditions.filters).forEach(field => {
  130. if (isAggregateField(field)) {
  131. conditions.removeFilter(field);
  132. }
  133. });
  134. const eventView = EventView.fromNewQueryWithLocation(
  135. {
  136. id: undefined,
  137. version: 2,
  138. name: transactionName,
  139. fields: [...Object.values(SpanSortOthers), ...Object.values(SpanSortPercentiles)],
  140. query: conditions.formatString(),
  141. projects: [],
  142. },
  143. location
  144. );
  145. const sort = getSuspectSpanSortFromLocation(location);
  146. return eventView.withSorts([{field: sort.field, kind: 'desc'}]);
  147. }
  148. /**
  149. * For the totals view, we want to get some transaction level stats like
  150. * the number of transactions and the sum of the transaction duration.
  151. * This requires the removal of any aggregate conditions as they can result
  152. * in unexpected empty responses.
  153. */
  154. export function getTotalsView(eventView: EventView): EventView {
  155. const totalsView = eventView.withColumns([
  156. {kind: 'function', function: ['count', '', undefined, undefined]},
  157. ]);
  158. const conditions = new MutableSearch(eventView.query);
  159. // filter out any aggregate conditions
  160. Object.keys(conditions.filters).forEach(field => {
  161. if (isAggregateField(field)) {
  162. conditions.removeFilter(field);
  163. }
  164. });
  165. totalsView.query = conditions.formatString();
  166. return totalsView;
  167. }
  168. export const SPAN_SORT_TO_FIELDS: Record<SpanSort, string[]> = {
  169. [SpanSortOthers.SUM_EXCLUSIVE_TIME]: [
  170. 'percentileArray(spans_exclusive_time, 0.75)',
  171. 'count()',
  172. 'count_unique(id)',
  173. 'sumArray(spans_exclusive_time)',
  174. ],
  175. [SpanSortOthers.AVG_OCCURRENCE]: [
  176. 'percentileArray(spans_exclusive_time, 0.75)',
  177. 'count()',
  178. 'count_unique(id)',
  179. 'equation|count() / count_unique(id)',
  180. 'sumArray(spans_exclusive_time)',
  181. ],
  182. [SpanSortOthers.COUNT]: [
  183. 'percentileArray(spans_exclusive_time, 0.75)',
  184. 'count()',
  185. 'count_unique(id)',
  186. 'sumArray(spans_exclusive_time)',
  187. ],
  188. [SpanSortPercentiles.P50_EXCLUSIVE_TIME]: [
  189. 'percentileArray(spans_exclusive_time, 0.5)',
  190. 'count()',
  191. 'count_unique(id)',
  192. 'sumArray(spans_exclusive_time)',
  193. ],
  194. [SpanSortPercentiles.P75_EXCLUSIVE_TIME]: [
  195. 'percentileArray(spans_exclusive_time, 0.75)',
  196. 'count()',
  197. 'count_unique(id)',
  198. 'sumArray(spans_exclusive_time)',
  199. ],
  200. [SpanSortPercentiles.P95_EXCLUSIVE_TIME]: [
  201. 'percentileArray(spans_exclusive_time, 0.95)',
  202. 'count()',
  203. 'count_unique(id)',
  204. 'sumArray(spans_exclusive_time)',
  205. ],
  206. [SpanSortPercentiles.P99_EXCLUSIVE_TIME]: [
  207. 'percentileArray(spans_exclusive_time, 0.99)',
  208. 'count()',
  209. 'count_unique(id)',
  210. 'sumArray(spans_exclusive_time)',
  211. ],
  212. };
  213. export function getExclusiveTimeDisplayedValue(value: string): string {
  214. return value.replace('exclusive', 'self');
  215. }