profileFlamechart.tsx 5.0 KB

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