profileFlamechart.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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 type {DeepPartial} from 'sentry/types/utils';
  9. import {trackAnalytics} from 'sentry/utils/analytics';
  10. import type {FlamegraphState} from 'sentry/utils/profiling/flamegraph/flamegraphStateProvider/flamegraphContext';
  11. import {DEFAULT_FLAMEGRAPH_STATE} from 'sentry/utils/profiling/flamegraph/flamegraphStateProvider/flamegraphContext';
  12. import {FlamegraphStateProvider} from 'sentry/utils/profiling/flamegraph/flamegraphStateProvider/flamegraphContextProvider';
  13. import {
  14. decodeFlamegraphStateFromQueryParams,
  15. FLAMEGRAPH_LOCALSTORAGE_PREFERENCES_KEY,
  16. FlamegraphStateLocalStorageSync,
  17. FlamegraphStateQueryParamSync,
  18. } from 'sentry/utils/profiling/flamegraph/flamegraphStateProvider/flamegraphQueryParamSync';
  19. import {FlamegraphThemeProvider} from 'sentry/utils/profiling/flamegraph/flamegraphThemeProvider';
  20. import {useFlamegraphPreferences} from 'sentry/utils/profiling/flamegraph/hooks/useFlamegraphPreferences';
  21. import {useCurrentProjectFromRouteParam} from 'sentry/utils/profiling/hooks/useCurrentProjectFromRouteParam';
  22. import {useLocalStorageState} from 'sentry/utils/useLocalStorageState';
  23. import useOrganization from 'sentry/utils/useOrganization';
  24. import {useParams} from 'sentry/utils/useParams';
  25. import {ProfileGroupProvider} from 'sentry/views/profiling/profileGroupProvider';
  26. import {useProfiles, useProfileTransaction} from './profilesProvider';
  27. function ProfileFlamegraph(): React.ReactElement {
  28. const organization = useOrganization();
  29. const profiles = useProfiles();
  30. const profiledTransaction = useProfileTransaction();
  31. const params = useParams();
  32. const [storedPreferences] = useLocalStorageState<DeepPartial<FlamegraphState>>(
  33. FLAMEGRAPH_LOCALSTORAGE_PREFERENCES_KEY,
  34. {
  35. preferences: {
  36. layout: DEFAULT_FLAMEGRAPH_STATE.preferences.layout,
  37. view: DEFAULT_FLAMEGRAPH_STATE.preferences.view,
  38. colorCoding: DEFAULT_FLAMEGRAPH_STATE.preferences.colorCoding,
  39. sorting: DEFAULT_FLAMEGRAPH_STATE.preferences.sorting,
  40. },
  41. }
  42. );
  43. const currentProject = useCurrentProjectFromRouteParam();
  44. useEffect(() => {
  45. trackAnalytics('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 === 'loading' || profiledTransaction.type === 'loading' ? (
  91. <LoadingIndicatorContainer>
  92. <LoadingIndicator />
  93. </LoadingIndicatorContainer>
  94. ) : null}
  95. <Flamegraph />
  96. </FlamegraphContainer>
  97. </FlamegraphThemeProvider>
  98. </ProfileGroupTypeProvider>
  99. </FlamegraphStateProvider>
  100. </SentryDocumentTitle>
  101. );
  102. }
  103. // This only exists because we need to call useFlamegraphPreferences
  104. // to get the type of visualization that the user is looking at and
  105. // we cannot do it in the component above as it is not a child of the
  106. // FlamegraphStateProvider.
  107. function ProfileGroupTypeProvider({
  108. children,
  109. input,
  110. traceID,
  111. }: {
  112. children: React.ReactNode;
  113. input: Profiling.ProfileInput | null;
  114. traceID: string;
  115. }) {
  116. const preferences = useFlamegraphPreferences();
  117. return (
  118. <ProfileGroupProvider
  119. input={input}
  120. traceID={traceID}
  121. type={preferences.sorting === 'call order' ? 'flamechart' : 'flamegraph'}
  122. >
  123. {children}
  124. </ProfileGroupProvider>
  125. );
  126. }
  127. const LoadingIndicatorContainer = styled('div')`
  128. position: absolute;
  129. display: flex;
  130. flex-direction: column;
  131. justify-content: center;
  132. width: 100%;
  133. height: 100%;
  134. `;
  135. const FlamegraphContainer = styled('div')`
  136. display: flex;
  137. flex-direction: column;
  138. flex: 1 1 100%;
  139. /*
  140. * The footer component is a sibling of this div.
  141. * Remove it so the flamegraph can take up the
  142. * entire screen.
  143. */
  144. ~ footer {
  145. display: none;
  146. }
  147. `;
  148. export default ProfileFlamegraph;