profileFlamechart.tsx 5.5 KB

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