profileFlamechart.tsx 4.3 KB

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