profileFlamechart.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import {useEffect, useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import * as qs from 'query-string';
  4. import {Alert} from 'sentry/components/alert';
  5. import LoadingIndicator from 'sentry/components/loadingIndicator';
  6. import {Flamegraph} from 'sentry/components/profiling/flamegraph/flamegraph';
  7. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  8. import {t} from 'sentry/locale';
  9. import {DeepPartial} from 'sentry/types/utils';
  10. import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
  11. import {
  12. DEFAULT_FLAMEGRAPH_STATE,
  13. FlamegraphState,
  14. } from 'sentry/utils/profiling/flamegraph/flamegraphStateProvider/flamegraphContext';
  15. import {FlamegraphStateProvider} from 'sentry/utils/profiling/flamegraph/flamegraphStateProvider/flamegraphContextProvider';
  16. import {
  17. decodeFlamegraphStateFromQueryParams,
  18. FLAMEGRAPH_LOCALSTORAGE_PREFERENCES_KEY,
  19. FlamegraphStateLocalStorageSync,
  20. FlamegraphStateQueryParamSync,
  21. } from 'sentry/utils/profiling/flamegraph/flamegraphStateProvider/flamegraphQueryParamSync';
  22. import {FlamegraphThemeProvider} from 'sentry/utils/profiling/flamegraph/flamegraphThemeProvider';
  23. import {useFlamegraphPreferences} from 'sentry/utils/profiling/flamegraph/hooks/useFlamegraphPreferences';
  24. import {useCurrentProjectFromRouteParam} from 'sentry/utils/profiling/hooks/useCurrentProjectFromRouteParam';
  25. import {useLocalStorageState} from 'sentry/utils/useLocalStorageState';
  26. import useOrganization from 'sentry/utils/useOrganization';
  27. import {useParams} from 'sentry/utils/useParams';
  28. import {ProfileGroupProvider} from 'sentry/views/profiling/profileGroupProvider';
  29. import {useProfiles} from './profilesProvider';
  30. function ProfileFlamegraph(): React.ReactElement {
  31. const organization = useOrganization();
  32. const profiles = useProfiles();
  33. const params = useParams();
  34. const [storedPreferences] = useLocalStorageState<DeepPartial<FlamegraphState>>(
  35. FLAMEGRAPH_LOCALSTORAGE_PREFERENCES_KEY,
  36. {
  37. preferences: {
  38. layout: DEFAULT_FLAMEGRAPH_STATE.preferences.layout,
  39. view: DEFAULT_FLAMEGRAPH_STATE.preferences.view,
  40. },
  41. }
  42. );
  43. const currentProject = useCurrentProjectFromRouteParam();
  44. useEffect(() => {
  45. trackAdvancedAnalyticsEvent('profiling_views.profile_flamegraph', {
  46. organization,
  47. project_platform: currentProject?.platform,
  48. project_id: currentProject?.id,
  49. });
  50. // ignore currentProject so we don't block the analytics event
  51. // or fire more than once unnecessarily
  52. // eslint-disable-next-line react-hooks/exhaustive-deps
  53. }, [organization]);
  54. const initialFlamegraphPreferencesState = useMemo((): DeepPartial<FlamegraphState> => {
  55. const queryStringState = decodeFlamegraphStateFromQueryParams(
  56. qs.parse(window.location.search)
  57. );
  58. return {
  59. ...queryStringState,
  60. preferences: {
  61. ...storedPreferences.preferences,
  62. ...queryStringState.preferences,
  63. timelines: {
  64. ...DEFAULT_FLAMEGRAPH_STATE.preferences.timelines,
  65. ...(storedPreferences?.preferences?.timelines ?? {}),
  66. },
  67. layout:
  68. storedPreferences?.preferences?.layout ??
  69. queryStringState.preferences?.layout ??
  70. DEFAULT_FLAMEGRAPH_STATE.preferences.layout,
  71. },
  72. };
  73. // We only want to decode this when our component mounts
  74. // eslint-disable-next-line react-hooks/exhaustive-deps
  75. }, []);
  76. return (
  77. <SentryDocumentTitle
  78. title={t('Profiling \u2014 Flamechart')}
  79. orgSlug={organization.slug}
  80. >
  81. <FlamegraphStateProvider initialState={initialFlamegraphPreferencesState}>
  82. <ProfileGroupTypeProvider
  83. input={profiles.type === 'resolved' ? profiles.data : null}
  84. traceID={params.eventID}
  85. >
  86. <FlamegraphThemeProvider>
  87. <FlamegraphStateQueryParamSync />
  88. <FlamegraphStateLocalStorageSync />
  89. <FlamegraphContainer>
  90. {profiles.type === 'errored' ? (
  91. <Alert type="error" showIcon>
  92. {profiles.error}
  93. </Alert>
  94. ) : profiles.type === 'loading' ? (
  95. <LoadingIndicatorContainer>
  96. <LoadingIndicator />
  97. </LoadingIndicatorContainer>
  98. ) : null}
  99. <Flamegraph />
  100. </FlamegraphContainer>
  101. </FlamegraphThemeProvider>
  102. </ProfileGroupTypeProvider>
  103. </FlamegraphStateProvider>
  104. </SentryDocumentTitle>
  105. );
  106. }
  107. // This only exists because we need to call useFlamegraphPreferences
  108. // to get the type of visualization that the user is looking at and
  109. // we cannot do it in the component above as it is not a child of the
  110. // FlamegraphStateProvider.
  111. function ProfileGroupTypeProvider({
  112. children,
  113. input,
  114. traceID,
  115. }: {
  116. children: React.ReactNode;
  117. input: Profiling.ProfileInput | null;
  118. traceID: string;
  119. }) {
  120. const preferences = useFlamegraphPreferences();
  121. return (
  122. <ProfileGroupProvider
  123. input={input}
  124. traceID={traceID}
  125. type={preferences.sorting === 'call order' ? 'flamechart' : 'flamegraph'}
  126. >
  127. {children}
  128. </ProfileGroupProvider>
  129. );
  130. }
  131. const LoadingIndicatorContainer = styled('div')`
  132. position: absolute;
  133. display: flex;
  134. flex-direction: column;
  135. justify-content: center;
  136. width: 100%;
  137. height: 100%;
  138. `;
  139. const FlamegraphContainer = styled('div')`
  140. display: flex;
  141. flex-direction: column;
  142. flex: 1 1 100%;
  143. /*
  144. * The footer component is a sibling of this div.
  145. * Remove it so the flamegraph can take up the
  146. * entire screen.
  147. */
  148. ~ footer {
  149. display: none;
  150. }
  151. `;
  152. export default ProfileFlamegraph;