metricsCardinality.tsx 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. import {Fragment, ReactNode} from 'react';
  2. import {Location} from 'history';
  3. import {Organization} from 'sentry/types';
  4. import {parsePeriodToHours} from 'sentry/utils/dates';
  5. import EventView from 'sentry/utils/discover/eventView';
  6. import {canUseMetricsData} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
  7. import MetricsCompatibilityQuery, {
  8. MetricsCompatibilityData,
  9. } from 'sentry/utils/performance/metricsEnhanced/metricsCompatibilityQuery';
  10. import MetricsCompatibilitySumsQuery, {
  11. MetricsCompatibilitySumData,
  12. } from 'sentry/utils/performance/metricsEnhanced/metricsCompatibilityQuerySums';
  13. import {createDefinedContext} from './utils';
  14. export interface MetricDataSwitcherOutcome {
  15. forceTransactionsOnly: boolean;
  16. compatibleProjects?: number[];
  17. shouldNotifyUnnamedTransactions?: boolean;
  18. shouldWarnIncompatibleSDK?: boolean;
  19. }
  20. export interface MetricsCardinalityContext {
  21. isLoading: boolean;
  22. outcome?: MetricDataSwitcherOutcome;
  23. }
  24. type MergedMetricsData = MetricsCompatibilityData & MetricsCompatibilitySumData;
  25. const [_Provider, _useContext, _Context] =
  26. createDefinedContext<MetricsCardinalityContext>({
  27. name: 'MetricsCardinalityContext',
  28. strict: false,
  29. });
  30. /**
  31. * This provider determines whether the metrics data is storing performance information correctly before we
  32. * make dozens of requests on pages such as performance landing and dashboards.
  33. */
  34. export const MetricsCardinalityProvider = (props: {
  35. children: ReactNode;
  36. location: Location;
  37. organization: Organization;
  38. }) => {
  39. const isUsingMetrics = canUseMetricsData(props.organization);
  40. if (!isUsingMetrics) {
  41. return (
  42. <_Provider
  43. value={{
  44. isLoading: false,
  45. outcome: {
  46. forceTransactionsOnly: true,
  47. },
  48. }}
  49. >
  50. {props.children}
  51. </_Provider>
  52. );
  53. }
  54. const baseDiscoverProps = {
  55. location: props.location,
  56. orgSlug: props.organization.slug,
  57. cursor: '0:0:0',
  58. };
  59. const eventView = EventView.fromLocation(props.location);
  60. eventView.fields = [{field: 'tpm()'}];
  61. const _eventView = adjustEventViewTime(eventView);
  62. return (
  63. <Fragment>
  64. <MetricsCompatibilityQuery eventView={_eventView} {...baseDiscoverProps}>
  65. {compatabilityResult => (
  66. <MetricsCompatibilitySumsQuery eventView={_eventView} {...baseDiscoverProps}>
  67. {sumsResult => (
  68. <_Provider
  69. value={{
  70. isLoading: compatabilityResult.isLoading || sumsResult.isLoading,
  71. outcome:
  72. compatabilityResult.isLoading || sumsResult.isLoading
  73. ? undefined
  74. : getMetricsOutcome(
  75. compatabilityResult.tableData && sumsResult.tableData
  76. ? {
  77. ...compatabilityResult.tableData,
  78. ...sumsResult.tableData,
  79. }
  80. : null,
  81. !!compatabilityResult.error && !!sumsResult.error
  82. ),
  83. }}
  84. >
  85. {props.children}
  86. </_Provider>
  87. )}
  88. </MetricsCompatibilitySumsQuery>
  89. )}
  90. </MetricsCompatibilityQuery>
  91. </Fragment>
  92. );
  93. };
  94. export const MetricsCardinalityConsumer = _Context.Consumer;
  95. export const useMetricsCardinalityContext = _useContext;
  96. /**
  97. * Logic for picking sides of metrics vs. transactions along with the associated warnings.
  98. */
  99. function getMetricsOutcome(
  100. dataCounts: MergedMetricsData | null,
  101. hasOtherFallbackCondition: boolean
  102. ) {
  103. const fallbackOutcome: MetricDataSwitcherOutcome = {
  104. forceTransactionsOnly: true,
  105. };
  106. const successOutcome: MetricDataSwitcherOutcome = {
  107. forceTransactionsOnly: false,
  108. };
  109. if (!dataCounts) {
  110. return fallbackOutcome;
  111. }
  112. const compatibleProjects = dataCounts.compatible_projects;
  113. if (hasOtherFallbackCondition) {
  114. return fallbackOutcome;
  115. }
  116. if (!dataCounts) {
  117. return fallbackOutcome;
  118. }
  119. if (checkForSamplingRules(dataCounts)) {
  120. return fallbackOutcome;
  121. }
  122. if (checkNoDataFallback(dataCounts)) {
  123. return fallbackOutcome;
  124. }
  125. if (checkIncompatibleData(dataCounts)) {
  126. return {
  127. shouldWarnIncompatibleSDK: true,
  128. forceTransactionsOnly: true,
  129. compatibleProjects,
  130. };
  131. }
  132. if (checkIfAllOtherData(dataCounts)) {
  133. return {
  134. shouldNotifyUnnamedTransactions: true,
  135. forceTransactionsOnly: true,
  136. compatibleProjects,
  137. };
  138. }
  139. if (checkIfPartialOtherData(dataCounts)) {
  140. return {
  141. shouldNotifyUnnamedTransactions: true,
  142. compatibleProjects,
  143. forceTransactionsOnly: false,
  144. };
  145. }
  146. return successOutcome;
  147. }
  148. /**
  149. * Fallback if very similar amounts of metrics and transactions are found.
  150. * No projects with dynamic sampling means no rules have been enabled yet.
  151. */
  152. function checkForSamplingRules(dataCounts: MergedMetricsData) {
  153. const counts = normalizeCounts(dataCounts);
  154. if (!dataCounts.dynamic_sampling_projects?.length) {
  155. return true;
  156. }
  157. if (counts.metricsCount === 0) {
  158. return true;
  159. }
  160. return false;
  161. }
  162. /**
  163. * Fallback if no metrics found.
  164. */
  165. function checkNoDataFallback(dataCounts: MergedMetricsData) {
  166. const counts = normalizeCounts(dataCounts);
  167. return !counts.metricsCount;
  168. }
  169. /**
  170. * Fallback and warn if incompatible data found (old specific SDKs).
  171. */
  172. function checkIncompatibleData(dataCounts: MergedMetricsData) {
  173. const counts = normalizeCounts(dataCounts);
  174. return counts.nullCount > 0;
  175. }
  176. /**
  177. * Fallback and warn about unnamed transactions (specific SDKs).
  178. */
  179. function checkIfAllOtherData(dataCounts: MergedMetricsData) {
  180. const counts = normalizeCounts(dataCounts);
  181. return counts.unparamCount >= counts.metricsCount;
  182. }
  183. /**
  184. * Show metrics but warn about unnamed transactions.
  185. */
  186. function checkIfPartialOtherData(dataCounts: MergedMetricsData) {
  187. const counts = normalizeCounts(dataCounts);
  188. return counts.unparamCount > 0;
  189. }
  190. /**
  191. * Temporary function, can be removed after API changes.
  192. */
  193. function normalizeCounts({sum}: MergedMetricsData) {
  194. try {
  195. const metricsCount = Number(sum.metrics);
  196. const unparamCount = Number(sum.metrics_unparam);
  197. const nullCount = Number(sum.metrics_null);
  198. return {
  199. metricsCount,
  200. unparamCount,
  201. nullCount,
  202. };
  203. } catch (_) {
  204. return {
  205. metricsCount: 0,
  206. unparamCount: 0,
  207. nullCount: 0,
  208. };
  209. }
  210. }
  211. /**
  212. * Performance optimization to limit the amount of rows scanned before showing the landing page.
  213. */
  214. function adjustEventViewTime(eventView: EventView) {
  215. const _eventView = eventView.clone();
  216. if (!_eventView.start && !_eventView.end) {
  217. if (!_eventView.statsPeriod) {
  218. _eventView.statsPeriod = '1h';
  219. _eventView.start = undefined;
  220. _eventView.end = undefined;
  221. } else {
  222. const periodHours = parsePeriodToHours(_eventView.statsPeriod);
  223. if (periodHours > 1) {
  224. _eventView.statsPeriod = '1h';
  225. _eventView.start = undefined;
  226. _eventView.end = undefined;
  227. }
  228. }
  229. }
  230. return _eventView;
  231. }