profileFlamechart.tsx 5.5 KB

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