differentialFlamegraph.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. import {useCallback, useLayoutEffect, useMemo, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import * as Sentry from '@sentry/react';
  4. import type {mat3} from 'gl-matrix';
  5. import {vec2} from 'gl-matrix';
  6. import {addErrorMessage} from 'sentry/actionCreators/indicator';
  7. import Feature from 'sentry/components/acl/feature';
  8. import {DifferentialFlamegraphLayout} from 'sentry/components/profiling/flamegraph/differentialFlamegraphLayout';
  9. import {FlamegraphContextMenu} from 'sentry/components/profiling/flamegraph/flamegraphContextMenu';
  10. import {DifferentialFlamegraphDrawer} from 'sentry/components/profiling/flamegraph/flamegraphDrawer/differentialFlamegraphDrawer';
  11. import {DifferentialFlamegraphToolbar} from 'sentry/components/profiling/flamegraph/flamegraphToolbar/differentialFlamegraphToolbar';
  12. import {FlamegraphZoomView} from 'sentry/components/profiling/flamegraph/flamegraphZoomView';
  13. import {FlamegraphZoomViewMinimap} from 'sentry/components/profiling/flamegraph/flamegraphZoomViewMinimap';
  14. import {
  15. CanvasPoolManager,
  16. useCanvasScheduler,
  17. } from 'sentry/utils/profiling/canvasScheduler';
  18. import {CanvasView} from 'sentry/utils/profiling/canvasView';
  19. import type {DifferentialFlamegraph as DifferentialFlamegraphModel} from 'sentry/utils/profiling/differentialFlamegraph';
  20. import {FlamegraphStateProvider} from 'sentry/utils/profiling/flamegraph/flamegraphStateProvider/flamegraphContextProvider';
  21. import {FlamegraphThemeProvider} from 'sentry/utils/profiling/flamegraph/flamegraphThemeProvider';
  22. import {useFlamegraphPreferences} from 'sentry/utils/profiling/flamegraph/hooks/useFlamegraphPreferences';
  23. import {useFlamegraphProfiles} from 'sentry/utils/profiling/flamegraph/hooks/useFlamegraphProfiles';
  24. import {useFlamegraphTheme} from 'sentry/utils/profiling/flamegraph/useFlamegraphTheme';
  25. import {FlamegraphCanvas} from 'sentry/utils/profiling/flamegraphCanvas';
  26. import type {FlamegraphFrame} from 'sentry/utils/profiling/flamegraphFrame';
  27. import type {Frame} from 'sentry/utils/profiling/frame';
  28. import {
  29. computeConfigViewWithStrategy,
  30. formatColorForFrame,
  31. initializeFlamegraphRenderer,
  32. useResizeCanvasObserver,
  33. } from 'sentry/utils/profiling/gl/utils';
  34. import {useCurrentProjectFromRouteParam} from 'sentry/utils/profiling/hooks/useCurrentProjectFromRouteParam';
  35. import {useDifferentialFlamegraphModel} from 'sentry/utils/profiling/hooks/useDifferentialFlamegraphModel';
  36. import {useDifferentialFlamegraphQuery} from 'sentry/utils/profiling/hooks/useDifferentialFlamegraphQuery';
  37. import {FlamegraphRenderer2D} from 'sentry/utils/profiling/renderers/flamegraphRenderer2D';
  38. import {FlamegraphRendererWebGL} from 'sentry/utils/profiling/renderers/flamegraphRendererWebGL';
  39. import {Rect} from 'sentry/utils/profiling/speedscope';
  40. import {useLocation} from 'sentry/utils/useLocation';
  41. import usePageFilters from 'sentry/utils/usePageFilters';
  42. import {LOADING_PROFILE_GROUP} from 'sentry/views/profiling/profileGroupProvider';
  43. const noopFormatDuration = () => '';
  44. function applicationFrameOnly(frame: Frame): boolean {
  45. return frame.is_application;
  46. }
  47. function systemFrameOnly(frame: Frame): boolean {
  48. return !frame.is_application;
  49. }
  50. function DifferentialFlamegraphView() {
  51. const location = useLocation();
  52. const selection = usePageFilters();
  53. const flamegraphTheme = useFlamegraphTheme();
  54. const {colorCoding} = useFlamegraphPreferences();
  55. const {selectedRoot} = useFlamegraphProfiles();
  56. const [frameFilterSetting, setFrameFilterSetting] = useState<
  57. 'application' | 'system' | 'all'
  58. >('all');
  59. const frameFilter =
  60. frameFilterSetting === 'application'
  61. ? applicationFrameOnly
  62. : frameFilterSetting === 'system'
  63. ? systemFrameOnly
  64. : undefined;
  65. const project = useCurrentProjectFromRouteParam();
  66. const [negated, setNegated] = useState<boolean>(false);
  67. const canvasPoolManager = useMemo(() => new CanvasPoolManager(), []);
  68. const scheduler = useCanvasScheduler(canvasPoolManager);
  69. const {before, after} = useDifferentialFlamegraphQuery({
  70. projectID: parseInt((project?.id as string) ?? 0, 10),
  71. breakpoint: location.query.breakpoint as unknown as number,
  72. environments: selection.selection.environments,
  73. fingerprint: location.query.fingerprint as unknown as string,
  74. transaction: location.query.transaction as unknown as string,
  75. });
  76. const differentialFlamegraph = useDifferentialFlamegraphModel({
  77. before,
  78. after,
  79. frameFilter,
  80. negated,
  81. });
  82. const [flamegraphCanvasRef, setFlamegraphCanvasRef] =
  83. useState<HTMLCanvasElement | null>(null);
  84. const [flamegraphOverlayCanvasRef, setFlamegraphOverlayCanvasRef] =
  85. useState<HTMLCanvasElement | null>(null);
  86. const [flamegraphMiniMapCanvasRef, setFlamegraphMiniMapCanvasRef] =
  87. useState<HTMLCanvasElement | null>(null);
  88. const [flamegraphMiniMapOverlayCanvasRef, setFlamegraphMiniMapOverlayCanvasRef] =
  89. useState<HTMLCanvasElement | null>(null);
  90. const flamegraphCanvas = useMemo(() => {
  91. if (!flamegraphCanvasRef) {
  92. return null;
  93. }
  94. return new FlamegraphCanvas(flamegraphCanvasRef, vec2.fromValues(0, 0));
  95. }, [flamegraphCanvasRef]);
  96. const flamegraphView = useMemo<CanvasView<DifferentialFlamegraphModel> | null>(
  97. () => {
  98. if (!flamegraphCanvas || !differentialFlamegraph.differentialFlamegraph) {
  99. return null;
  100. }
  101. const newView = new CanvasView({
  102. canvas: flamegraphCanvas,
  103. model: differentialFlamegraph.differentialFlamegraph,
  104. options: {
  105. inverted: differentialFlamegraph.differentialFlamegraph.inverted,
  106. minWidth:
  107. differentialFlamegraph.differentialFlamegraph.profile.minFrameDuration,
  108. barHeight: flamegraphTheme.SIZES.BAR_HEIGHT,
  109. depthOffset: flamegraphTheme.SIZES.AGGREGATE_FLAMEGRAPH_DEPTH_OFFSET,
  110. configSpaceTransform: undefined,
  111. },
  112. });
  113. return newView;
  114. },
  115. // We skip position.view dependency because it will go into an infinite loop
  116. // eslint-disable-next-line react-hooks/exhaustive-deps
  117. [differentialFlamegraph.differentialFlamegraph, flamegraphCanvas, flamegraphTheme]
  118. );
  119. // Uses a useLayoutEffect to ensure that these top level/global listeners are added before
  120. // any of the children components effects actually run. This way we do not lose events
  121. // when we register/unregister these top level listeners.
  122. useLayoutEffect(() => {
  123. if (!flamegraphCanvas || !flamegraphView) {
  124. return undefined;
  125. }
  126. // This code below manages the synchronization of the config views between spans and flamegraph
  127. // We do so by listening to the config view change event and then updating the other views accordingly which
  128. // allows us to keep the X axis in sync between the two views but keep the Y axis independent
  129. const onConfigViewChange = (rect: Rect, sourceConfigViewChange: CanvasView<any>) => {
  130. if (sourceConfigViewChange === flamegraphView) {
  131. flamegraphView.setConfigView(rect.withHeight(flamegraphView.configView.height));
  132. }
  133. canvasPoolManager.draw();
  134. };
  135. const onTransformConfigView = (
  136. mat: mat3,
  137. sourceTransformConfigView: CanvasView<any>
  138. ) => {
  139. if (sourceTransformConfigView === flamegraphView) {
  140. flamegraphView.transformConfigView(mat);
  141. }
  142. canvasPoolManager.draw();
  143. };
  144. const onResetZoom = () => {
  145. flamegraphView.resetConfigView(flamegraphCanvas);
  146. canvasPoolManager.draw();
  147. };
  148. const onZoomIntoFrame = (frame: FlamegraphFrame, strategy: 'min' | 'exact') => {
  149. const newConfigView = computeConfigViewWithStrategy(
  150. strategy,
  151. flamegraphView.configView,
  152. new Rect(frame.start, frame.depth, frame.end - frame.start, 1)
  153. ).transformRect(flamegraphView.configSpaceTransform);
  154. flamegraphView.setConfigView(newConfigView);
  155. canvasPoolManager.draw();
  156. };
  157. scheduler.on('set config view', onConfigViewChange);
  158. scheduler.on('transform config view', onTransformConfigView);
  159. scheduler.on('reset zoom', onResetZoom);
  160. scheduler.on('zoom at frame', onZoomIntoFrame);
  161. return () => {
  162. scheduler.off('set config view', onConfigViewChange);
  163. scheduler.off('transform config view', onTransformConfigView);
  164. scheduler.off('reset zoom', onResetZoom);
  165. scheduler.off('zoom at frame', onZoomIntoFrame);
  166. };
  167. }, [canvasPoolManager, flamegraphCanvas, flamegraphView, scheduler]);
  168. const flamegraphCanvases = useMemo(() => {
  169. return [flamegraphCanvasRef, flamegraphOverlayCanvasRef];
  170. }, [flamegraphCanvasRef, flamegraphOverlayCanvasRef]);
  171. const flamegraphCanvasBounds = useResizeCanvasObserver(
  172. flamegraphCanvases,
  173. canvasPoolManager,
  174. flamegraphCanvas,
  175. flamegraphView
  176. );
  177. const minimapCanvases = useMemo(() => {
  178. return [flamegraphMiniMapCanvasRef, flamegraphMiniMapOverlayCanvasRef];
  179. }, [flamegraphMiniMapCanvasRef, flamegraphMiniMapOverlayCanvasRef]);
  180. const flamegraphMiniMapCanvas = useMemo(() => {
  181. if (!flamegraphMiniMapCanvasRef) {
  182. return null;
  183. }
  184. return new FlamegraphCanvas(flamegraphMiniMapCanvasRef, vec2.fromValues(0, 0));
  185. }, [flamegraphMiniMapCanvasRef]);
  186. useResizeCanvasObserver(
  187. minimapCanvases,
  188. canvasPoolManager,
  189. flamegraphMiniMapCanvas,
  190. null
  191. );
  192. const flamegraphRenderer = useMemo(() => {
  193. if (!flamegraphCanvasRef || !differentialFlamegraph) {
  194. return null;
  195. }
  196. const renderer = initializeFlamegraphRenderer(
  197. [FlamegraphRendererWebGL, FlamegraphRenderer2D],
  198. [
  199. flamegraphCanvasRef,
  200. differentialFlamegraph.differentialFlamegraph,
  201. flamegraphTheme,
  202. {
  203. colorCoding,
  204. draw_border: true,
  205. },
  206. ]
  207. );
  208. if (renderer === null) {
  209. Sentry.captureException('Failed to initialize a flamegraph renderer');
  210. addErrorMessage('Failed to initialize renderer');
  211. return null;
  212. }
  213. return renderer;
  214. }, [colorCoding, differentialFlamegraph, flamegraphCanvasRef, flamegraphTheme]);
  215. const getFrameColor = useCallback(
  216. (frame: FlamegraphFrame) => {
  217. if (!flamegraphRenderer) {
  218. return '';
  219. }
  220. return formatColorForFrame(frame, flamegraphRenderer);
  221. },
  222. [flamegraphRenderer]
  223. );
  224. const rootNodes = useMemo(() => {
  225. return selectedRoot
  226. ? [selectedRoot]
  227. : differentialFlamegraph.differentialFlamegraph.root.children;
  228. }, [selectedRoot, differentialFlamegraph.differentialFlamegraph.root]);
  229. const referenceNode = useMemo(
  230. () =>
  231. selectedRoot ? selectedRoot : differentialFlamegraph.differentialFlamegraph.root,
  232. [selectedRoot, differentialFlamegraph.differentialFlamegraph.root]
  233. );
  234. return (
  235. <Feature features={['profiling-differential-flamegraph-page']}>
  236. <DifferentialFlamegraphContainer>
  237. <DifferentialFlamegraphToolbar
  238. frameFilter={frameFilterSetting}
  239. onFrameFilterChange={setFrameFilterSetting}
  240. negated={negated}
  241. onNegatedChange={setNegated}
  242. flamegraph={differentialFlamegraph.differentialFlamegraph}
  243. canvasPoolManager={canvasPoolManager}
  244. />
  245. <DifferentialFlamegraphLayout
  246. minimap={
  247. <FlamegraphZoomViewMinimap
  248. canvasPoolManager={canvasPoolManager}
  249. flamegraph={differentialFlamegraph.differentialFlamegraph}
  250. flamegraphMiniMapCanvas={flamegraphMiniMapCanvas}
  251. flamegraphMiniMapCanvasRef={flamegraphMiniMapCanvasRef}
  252. flamegraphMiniMapOverlayCanvasRef={flamegraphMiniMapOverlayCanvasRef}
  253. flamegraphMiniMapView={flamegraphView}
  254. setFlamegraphMiniMapCanvasRef={setFlamegraphMiniMapCanvasRef}
  255. setFlamegraphMiniMapOverlayCanvasRef={setFlamegraphMiniMapOverlayCanvasRef}
  256. />
  257. }
  258. flamegraph={
  259. <FlamegraphZoomView
  260. scheduler={scheduler}
  261. profileGroup={
  262. differentialFlamegraph.afterProfileGroup ?? LOADING_PROFILE_GROUP
  263. }
  264. disableGrid
  265. disableCallOrderSort
  266. disableColorCoding
  267. canvasBounds={flamegraphCanvasBounds}
  268. canvasPoolManager={canvasPoolManager}
  269. flamegraph={differentialFlamegraph.differentialFlamegraph}
  270. flamegraphRenderer={flamegraphRenderer}
  271. flamegraphCanvas={flamegraphCanvas}
  272. flamegraphCanvasRef={flamegraphCanvasRef}
  273. flamegraphOverlayCanvasRef={flamegraphOverlayCanvasRef}
  274. flamegraphView={flamegraphView}
  275. setFlamegraphCanvasRef={setFlamegraphCanvasRef}
  276. setFlamegraphOverlayCanvasRef={setFlamegraphOverlayCanvasRef}
  277. contextMenu={FlamegraphContextMenu}
  278. />
  279. }
  280. flamegraphDrawer={
  281. <DifferentialFlamegraphDrawer
  282. profileGroup={
  283. differentialFlamegraph.afterProfileGroup ?? LOADING_PROFILE_GROUP
  284. }
  285. getFrameColor={getFrameColor}
  286. referenceNode={referenceNode}
  287. rootNodes={rootNodes}
  288. flamegraph={differentialFlamegraph.differentialFlamegraph}
  289. formatDuration={
  290. differentialFlamegraph.differentialFlamegraph
  291. ? differentialFlamegraph.differentialFlamegraph.formatter
  292. : noopFormatDuration
  293. }
  294. canvasPoolManager={canvasPoolManager}
  295. canvasScheduler={scheduler}
  296. />
  297. }
  298. />
  299. </DifferentialFlamegraphContainer>
  300. </Feature>
  301. );
  302. }
  303. const DifferentialFlamegraphContainer = styled('div')`
  304. display: flex;
  305. flex-direction: column;
  306. flex: 1;
  307. ~ footer {
  308. display: none;
  309. }
  310. `;
  311. function DifferentialFlamegraphWithProviders() {
  312. return (
  313. <FlamegraphThemeProvider>
  314. <FlamegraphStateProvider
  315. initialState={{
  316. preferences: {
  317. sorting: 'alphabetical',
  318. view: 'top down',
  319. },
  320. }}
  321. >
  322. <DifferentialFlamegraphView />
  323. </FlamegraphStateProvider>
  324. </FlamegraphThemeProvider>
  325. );
  326. }
  327. export default DifferentialFlamegraphWithProviders;