useSpanList.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. import {Location} from 'history';
  2. import moment, {Moment} from 'moment';
  3. import {defined} from 'sentry/utils';
  4. import EventView from 'sentry/utils/discover/eventView';
  5. import {DiscoverDatasets} from 'sentry/utils/discover/types';
  6. import {useLocation} from 'sentry/utils/useLocation';
  7. import usePageFilters from 'sentry/utils/usePageFilters';
  8. import {ModuleName} from 'sentry/views/starfish/types';
  9. import {getDateFilters} from 'sentry/views/starfish/utils/dates';
  10. import {getDateQueryFilter} from 'sentry/views/starfish/utils/getDateQueryFilter';
  11. import {useSpansQuery} from 'sentry/views/starfish/utils/useSpansQuery';
  12. const SPAN_FILTER_KEYS = ['span.op', 'span.domain', 'span.action'];
  13. const SPAN_FILTER_KEY_TO_LOCAL_FIELD = {
  14. 'span.op': 'span_operation',
  15. 'span.domain': 'domain',
  16. 'span.action': 'action',
  17. };
  18. export type SpanMetrics = {
  19. 'p95(span.duration)': number;
  20. 'span.description': string;
  21. 'span.domain': string;
  22. 'span.group': string;
  23. 'span.op': string;
  24. 'spm()': number;
  25. 'sum(span.duration)': number;
  26. 'time_spent_percentage()': number;
  27. };
  28. export const useSpanList = (
  29. moduleName: ModuleName,
  30. transaction?: string,
  31. spanCategory?: string,
  32. orderBy?: string,
  33. limit?: number,
  34. _referrer = 'span-metrics'
  35. ) => {
  36. const location = useLocation();
  37. const pageFilters = usePageFilters();
  38. const {startTime, endTime} = getDateFilters(pageFilters);
  39. const dateFilters = getDateQueryFilter(startTime, endTime);
  40. const query = getQuery(
  41. moduleName,
  42. location,
  43. startTime,
  44. endTime,
  45. dateFilters,
  46. transaction,
  47. limit
  48. );
  49. const eventView = getEventView(
  50. moduleName,
  51. location,
  52. transaction,
  53. spanCategory,
  54. orderBy
  55. );
  56. // TODO: Add referrer
  57. const {isLoading, data} = useSpansQuery<SpanMetrics[]>({
  58. eventView,
  59. queryString: query,
  60. initialData: [],
  61. enabled: Boolean(query),
  62. limit,
  63. });
  64. return {isLoading, data};
  65. };
  66. function getQuery(
  67. moduleName: ModuleName,
  68. location: Location,
  69. startTime: Moment,
  70. endTime: Moment,
  71. dateFilters: string,
  72. transaction?: string,
  73. limit?: number
  74. ) {
  75. const conditions = buildQueryConditions(moduleName, location).filter(Boolean);
  76. return `SELECT
  77. group_id as "span.group",
  78. span_operation as "span.operation",
  79. description as "span.description",
  80. domain as "span.domain",
  81. sum(exclusive_time) as "sum(span.duration)",
  82. quantile(0.95)(exclusive_time) as "p95(span.duration)",
  83. divide(count(), ${
  84. moment(endTime ?? undefined).unix() - moment(startTime).unix()
  85. }) as "spm()"
  86. FROM spans_experimental_starfish
  87. WHERE 1 = 1
  88. ${conditions.length > 0 ? 'AND' : ''}
  89. ${conditions.join(' AND ')}
  90. ${transaction ? `AND transaction = '${transaction}'` : ''}
  91. ${dateFilters}
  92. GROUP BY group_id, span_operation, domain, description
  93. ORDER BY count() desc
  94. ${limit ? `LIMIT ${limit}` : ''}`;
  95. }
  96. function buildQueryConditions(moduleName: ModuleName, location: Location) {
  97. const {query} = location;
  98. const result = Object.keys(query)
  99. .filter(key => SPAN_FILTER_KEYS.includes(key))
  100. .filter(key => Boolean(query[key]))
  101. .map(key => {
  102. return `${SPAN_FILTER_KEY_TO_LOCAL_FIELD[key]} = '${query[key]}'`;
  103. });
  104. if (moduleName !== ModuleName.ALL) {
  105. result.push(`module = '${moduleName}'`);
  106. }
  107. return result;
  108. }
  109. function getEventView(
  110. moduleName: ModuleName,
  111. location: Location,
  112. transaction?: string,
  113. spanCategory?: string,
  114. orderBy?: string
  115. ) {
  116. const query = buildEventViewQuery(moduleName, location, transaction, spanCategory)
  117. .filter(Boolean)
  118. .join(' ');
  119. return EventView.fromNewQueryWithLocation(
  120. {
  121. name: '',
  122. query,
  123. fields: [
  124. 'span.op',
  125. 'span.group',
  126. 'span.description',
  127. 'span.domain',
  128. 'spm()',
  129. 'sum(span.duration)',
  130. 'p95(span.duration)',
  131. 'time_spent_percentage()',
  132. ],
  133. orderby: orderBy,
  134. dataset: DiscoverDatasets.SPANS_METRICS,
  135. projects: [1],
  136. version: 2,
  137. },
  138. location
  139. );
  140. }
  141. function buildEventViewQuery(
  142. moduleName: ModuleName,
  143. location: Location,
  144. transaction?: string,
  145. spanCategory?: string
  146. ) {
  147. const {query} = location;
  148. const result = Object.keys(query)
  149. .filter(key => SPAN_FILTER_KEYS.includes(key))
  150. .filter(key => Boolean(query[key]))
  151. .map(key => {
  152. return `${key}:${query[key]}`;
  153. });
  154. if (moduleName !== ModuleName.ALL) {
  155. result.push(`span.module:${moduleName}`);
  156. }
  157. if (defined(spanCategory)) {
  158. result.push(`span.category:${spanCategory}`);
  159. }
  160. if (transaction) {
  161. result.push(`transaction:${transaction}`);
  162. }
  163. return result;
  164. }