continuousProfileFlamegraph.tsx 5.6 KB

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