profileFlamechart.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import {Fragment, useEffect, useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import Alert from 'sentry/components/alert';
  4. import LoadingIndicator from 'sentry/components/loadingIndicator';
  5. import {Flamegraph} from 'sentry/components/profiling/flamegraph';
  6. import {ProfileDragDropImportProps} from 'sentry/components/profiling/profileDragDropImport';
  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 {ProfileGroup} from 'sentry/utils/profiling/profile/importProfile';
  24. import {Profile} from 'sentry/utils/profiling/profile/profile';
  25. import {useLocalStorageState} from 'sentry/utils/useLocalStorageState';
  26. import {useLocation} from 'sentry/utils/useLocation';
  27. import useOrganization from 'sentry/utils/useOrganization';
  28. import {useProfileGroup} from './profileGroupProvider';
  29. const LoadingGroup: ProfileGroup = {
  30. name: 'Loading',
  31. activeProfileIndex: 0,
  32. transactionID: null,
  33. metadata: {},
  34. traceID: '',
  35. profiles: [Profile.Empty],
  36. };
  37. function ProfileFlamegraph(): React.ReactElement {
  38. const location = useLocation();
  39. const organization = useOrganization();
  40. const [profileGroup, setProfileGroup] = useProfileGroup();
  41. const [storedPreferences] = useLocalStorageState<DeepPartial<FlamegraphState>>(
  42. FLAMEGRAPH_LOCALSTORAGE_PREFERENCES_KEY,
  43. {
  44. preferences: {layout: DEFAULT_FLAMEGRAPH_STATE.preferences.layout},
  45. }
  46. );
  47. useEffect(() => {
  48. trackAdvancedAnalyticsEvent('profiling_views.profile_flamegraph', {
  49. organization,
  50. });
  51. }, [organization]);
  52. const onImport: ProfileDragDropImportProps['onImport'] = profiles => {
  53. setProfileGroup({type: 'resolved', data: profiles});
  54. };
  55. const initialFlamegraphPreferencesState = useMemo((): DeepPartial<FlamegraphState> => {
  56. const queryStringState = decodeFlamegraphStateFromQueryParams(location.query);
  57. return {
  58. ...queryStringState,
  59. preferences: {
  60. ...storedPreferences.preferences,
  61. ...queryStringState.preferences,
  62. layout:
  63. storedPreferences?.preferences?.layout ??
  64. queryStringState.preferences?.layout ??
  65. DEFAULT_FLAMEGRAPH_STATE.preferences.layout,
  66. },
  67. };
  68. // We only want to decode this when our component mounts
  69. // eslint-disable-next-line react-hooks/exhaustive-deps
  70. }, []);
  71. return (
  72. <SentryDocumentTitle
  73. title={t('Profiling \u2014 Flamechart')}
  74. orgSlug={organization.slug}
  75. >
  76. <FlamegraphStateProvider initialState={initialFlamegraphPreferencesState}>
  77. <FlamegraphThemeProvider>
  78. <FlamegraphStateQueryParamSync />
  79. <FlamegraphStateLocalStorageSync />
  80. <FlamegraphContainer>
  81. {profileGroup.type === 'errored' ? (
  82. <Alert type="error" showIcon>
  83. {profileGroup.error}
  84. </Alert>
  85. ) : profileGroup.type === 'loading' ? (
  86. <Fragment>
  87. <Flamegraph onImport={onImport} profiles={LoadingGroup} />
  88. <LoadingIndicatorContainer>
  89. <LoadingIndicator />
  90. </LoadingIndicatorContainer>
  91. </Fragment>
  92. ) : profileGroup.type === 'resolved' ? (
  93. <Flamegraph onImport={onImport} profiles={profileGroup.data} />
  94. ) : null}
  95. </FlamegraphContainer>
  96. </FlamegraphThemeProvider>
  97. </FlamegraphStateProvider>
  98. </SentryDocumentTitle>
  99. );
  100. }
  101. const LoadingIndicatorContainer = styled('div')`
  102. position: absolute;
  103. display: flex;
  104. flex-direction: column;
  105. justify-content: center;
  106. width: 100%;
  107. height: 100%;
  108. `;
  109. const FlamegraphContainer = styled('div')`
  110. display: flex;
  111. flex-direction: column;
  112. flex: 1 1 100%;
  113. /*
  114. * The footer component is a sibling of this div.
  115. * Remove it so the flamegraph can take up the
  116. * entire screen.
  117. */
  118. ~ footer {
  119. display: none;
  120. }
  121. `;
  122. export default ProfileFlamegraph;