123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808 |
- import type {CSSProperties} from 'react';
- import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
- import styled from '@emotion/styled';
- import {vec2} from 'gl-matrix';
- import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
- import {FlamegraphContextMenu} from 'sentry/components/profiling/flamegraph/flamegraphContextMenu';
- import {FlamegraphTooltip} from 'sentry/components/profiling/flamegraph/flamegraphTooltip';
- import {t} from 'sentry/locale';
- import type {CanvasPoolManager} from 'sentry/utils/profiling/canvasScheduler';
- import {useCanvasScheduler} from 'sentry/utils/profiling/canvasScheduler';
- import type {CanvasView} from 'sentry/utils/profiling/canvasView';
- import type {DifferentialFlamegraph} from 'sentry/utils/profiling/differentialFlamegraph';
- import type {Flamegraph} from 'sentry/utils/profiling/flamegraph';
- import {handleFlamegraphKeyboardNavigation} from 'sentry/utils/profiling/flamegraph/flamegraphKeyboardNavigation';
- import {useFlamegraphSearch} from 'sentry/utils/profiling/flamegraph/hooks/useFlamegraphSearch';
- import {
- useDispatchFlamegraphState,
- useFlamegraphState,
- } from 'sentry/utils/profiling/flamegraph/hooks/useFlamegraphState';
- import {useFlamegraphTheme} from 'sentry/utils/profiling/flamegraph/useFlamegraphTheme';
- import type {FlamegraphCanvas} from 'sentry/utils/profiling/flamegraphCanvas';
- import type {FlamegraphFrame} from 'sentry/utils/profiling/flamegraphFrame';
- import {
- computeMinZoomConfigViewForFrames,
- getConfigViewTranslationBetweenVectors,
- getPhysicalSpacePositionFromOffset,
- } from 'sentry/utils/profiling/gl/utils';
- import {useContextMenu} from 'sentry/utils/profiling/hooks/useContextMenu';
- import {useInternalFlamegraphDebugMode} from 'sentry/utils/profiling/hooks/useInternalFlamegraphDebugMode';
- import type {ProfileGroup} from 'sentry/utils/profiling/profile/importProfile';
- import type {FlamegraphRenderer} from 'sentry/utils/profiling/renderers/flamegraphRenderer';
- import {FlamegraphTextRenderer} from 'sentry/utils/profiling/renderers/flamegraphTextRenderer';
- import {GridRenderer} from 'sentry/utils/profiling/renderers/gridRenderer';
- import {SampleTickRenderer} from 'sentry/utils/profiling/renderers/sampleTickRenderer';
- import {SelectedFrameRenderer} from 'sentry/utils/profiling/renderers/selectedFrameRenderer';
- import {Rect} from 'sentry/utils/profiling/speedscope';
- import {useCanvasScroll} from './interactions/useCanvasScroll';
- import {useCanvasZoomOrScroll} from './interactions/useCanvasZoomOrScroll';
- import {useDrawHoveredBorderEffect} from './interactions/useDrawHoveredBorderEffect';
- import {useDrawSelectedBorderEffect} from './interactions/useDrawSelectedBorderEffect';
- import {useInteractionViewCheckPoint} from './interactions/useInteractionViewCheckPoint';
- import {useWheelCenterZoom} from './interactions/useWheelCenterZoom';
- function isHighlightingAllOccurrences(
- node: FlamegraphFrame | null,
- selectedNodes: FlamegraphFrame[] | null
- ) {
- return !!(
- selectedNodes &&
- node &&
- selectedNodes.length > 1 &&
- selectedNodes.includes(node)
- );
- }
- interface FlamegraphZoomViewProps {
- canvasBounds: Rect;
- canvasPoolManager: CanvasPoolManager;
- flamegraph: Flamegraph | DifferentialFlamegraph;
- flamegraphCanvas: FlamegraphCanvas | null;
- flamegraphCanvasRef: HTMLCanvasElement | null;
- flamegraphOverlayCanvasRef: HTMLCanvasElement | null;
- flamegraphRenderer: FlamegraphRenderer | null;
- flamegraphView: CanvasView<Flamegraph> | null;
- profileGroup: ProfileGroup;
- setFlamegraphCanvasRef: React.Dispatch<React.SetStateAction<HTMLCanvasElement | null>>;
- setFlamegraphOverlayCanvasRef: React.Dispatch<
- React.SetStateAction<HTMLCanvasElement | null>
- >;
- disableCallOrderSort?: boolean;
- disableColorCoding?: boolean;
- disableGrid?: boolean;
- disablePanX?: boolean;
- disableZoom?: boolean;
- }
- function FlamegraphZoomView({
- canvasPoolManager,
- canvasBounds,
- flamegraphRenderer,
- flamegraph,
- flamegraphCanvas,
- flamegraphCanvasRef,
- flamegraphOverlayCanvasRef,
- flamegraphView,
- profileGroup,
- setFlamegraphCanvasRef,
- setFlamegraphOverlayCanvasRef,
- disablePanX = false,
- disableZoom = false,
- disableGrid = false,
- disableCallOrderSort = false,
- disableColorCoding = false,
- }: FlamegraphZoomViewProps): React.ReactElement {
- const flamegraphTheme = useFlamegraphTheme();
- const flamegraphSearch = useFlamegraphSearch();
- const isInternalFlamegraphDebugModeEnabled = useInternalFlamegraphDebugMode();
- const [lastInteraction, setLastInteraction] = useState<
- 'pan' | 'click' | 'zoom' | 'scroll' | 'select' | 'resize' | null
- >(null);
- const dispatch = useDispatchFlamegraphState();
- const scheduler = useCanvasScheduler(canvasPoolManager);
- const canvasContainerRef = useRef<HTMLDivElement | null>(null);
- const [flamegraphState, {previousState, nextState}] = useFlamegraphState();
- const [startInteractionVector, setStartInteractionVector] = useState<vec2 | null>(null);
- const [configSpaceCursor, setConfigSpaceCursor] = useState<vec2 | null>(null);
- const selectedFramesRef = useRef<FlamegraphFrame[] | null>(null);
- const textRenderer: FlamegraphTextRenderer | null = useMemo(() => {
- if (!flamegraphOverlayCanvasRef) {
- return null;
- }
- return new FlamegraphTextRenderer(
- flamegraphOverlayCanvasRef,
- flamegraphTheme,
- flamegraph
- );
- }, [flamegraph, flamegraphOverlayCanvasRef, flamegraphTheme]);
- const gridRenderer: GridRenderer | null = useMemo(() => {
- if (!flamegraphOverlayCanvasRef || disableGrid) {
- return null;
- }
- return new GridRenderer(
- flamegraphOverlayCanvasRef,
- flamegraphTheme,
- flamegraph.formatter
- );
- }, [flamegraphOverlayCanvasRef, flamegraph, flamegraphTheme, disableGrid]);
- const sampleTickRenderer: SampleTickRenderer | null = useMemo(() => {
- if (!isInternalFlamegraphDebugModeEnabled) {
- return null;
- }
- if (!flamegraphOverlayCanvasRef || !flamegraphView?.configSpace) {
- return null;
- }
- return new SampleTickRenderer(
- flamegraphOverlayCanvasRef,
- flamegraph,
- flamegraphView.configSpace,
- flamegraphTheme
- );
- }, [
- isInternalFlamegraphDebugModeEnabled,
- flamegraphOverlayCanvasRef,
- flamegraph,
- flamegraphView?.configSpace,
- flamegraphTheme,
- ]);
- const selectedFrameRenderer = useMemo(() => {
- if (!flamegraphOverlayCanvasRef) {
- return null;
- }
- return new SelectedFrameRenderer(flamegraphOverlayCanvasRef);
- }, [flamegraphOverlayCanvasRef]);
- const hoveredNode: FlamegraphFrame | null = useMemo(() => {
- if (!configSpaceCursor || !flamegraphRenderer) {
- return null;
- }
- return flamegraphRenderer.findHoveredNode(configSpaceCursor);
- }, [configSpaceCursor, flamegraphRenderer]);
- const hoveredNodeOnContextMenuOpen = useRef<FlamegraphFrame | null>(null);
- const contextMenu = useContextMenu({container: flamegraphCanvasRef});
- const [highlightingAllOccurrences, setHighlightingAllOccurrences] = useState(
- isHighlightingAllOccurrences(hoveredNode, selectedFramesRef.current)
- );
- useEffect(() => {
- if (!flamegraphCanvas || !flamegraphView || !textRenderer || !flamegraphRenderer) {
- return undefined;
- }
- const clearOverlayCanvas = () => {
- textRenderer.context.clearRect(
- 0,
- 0,
- textRenderer.canvas.width,
- textRenderer.canvas.height
- );
- };
- const drawText = () => {
- textRenderer.draw(
- flamegraphView.toOriginConfigView(flamegraphView.configView),
- flamegraphView.fromTransformedConfigView(flamegraphCanvas.physicalSpace),
- flamegraphSearch.results.frames
- );
- };
- const drawInternalSampleTicks = () => {
- if (!sampleTickRenderer) {
- return;
- }
- sampleTickRenderer.draw(
- flamegraphView.fromTransformedConfigView(flamegraphCanvas.physicalSpace),
- flamegraphView.toOriginConfigView(flamegraphView.configView)
- );
- };
- const drawGrid = gridRenderer
- ? () => {
- gridRenderer.draw(
- flamegraphView.configView,
- flamegraphCanvas.physicalSpace,
- flamegraphView.fromConfigView(flamegraphCanvas.physicalSpace),
- flamegraphView.toConfigView(flamegraphCanvas.logicalSpace),
- flamegraph.profile.type === 'flamechart'
- );
- }
- : undefined;
- const drawRectangles = () => {
- flamegraphRenderer.draw(
- flamegraphView.fromTransformedConfigView(flamegraphCanvas.physicalSpace)
- );
- };
- scheduler.registerBeforeFrameCallback(drawRectangles);
- scheduler.registerBeforeFrameCallback(clearOverlayCanvas);
- scheduler.registerAfterFrameCallback(drawText);
- // We want to register the grid as the last frame callback so that it is drawn on top of everything else,
- // including text, hovered or clicked nodes, but after the sample tick renderer as those are overlaid on top of it
- if (drawGrid) {
- scheduler.registerAfterFrameCallback(drawGrid);
- }
- scheduler.registerAfterFrameCallback(drawInternalSampleTicks);
- scheduler.draw();
- return () => {
- scheduler.unregisterBeforeFrameCallback(clearOverlayCanvas);
- scheduler.unregisterAfterFrameCallback(drawText);
- scheduler.unregisterAfterFrameCallback(drawInternalSampleTicks);
- if (drawGrid) {
- scheduler.unregisterAfterFrameCallback(drawGrid);
- }
- scheduler.unregisterBeforeFrameCallback(drawRectangles);
- };
- }, [
- flamegraphCanvas,
- flamegraphView,
- scheduler,
- flamegraph,
- flamegraphTheme,
- textRenderer,
- gridRenderer,
- flamegraphRenderer,
- sampleTickRenderer,
- canvasPoolManager,
- flamegraphSearch.results.frames,
- ]);
- useEffect(() => {
- if (!flamegraphRenderer) {
- return;
- }
- if (flamegraphState.search.highlightFrames) {
- let frames = flamegraph.findAllMatchingFrames(
- flamegraphState.search.highlightFrames.name,
- flamegraphState.search.highlightFrames.package
- );
- // there is a chance that the reason we did not find any frames is because
- // for node, we try to infer some package from the frontend code.
- // If that happens, we'll try and just do a search by name. This logic
- // is duplicated in flamegraph.tsx and should be kept in sync
- if (
- !frames.length &&
- !flamegraphState.search.highlightFrames.package &&
- flamegraphState.search.highlightFrames.name
- ) {
- frames = flamegraph.findAllMatchingFramesBy(
- flamegraphState.search.highlightFrames.name,
- ['name']
- );
- }
- selectedFramesRef.current = frames;
- }
- if (flamegraphState.search.query && !flamegraphState.search.highlightFrames) {
- flamegraphRenderer.setSearchResults(
- flamegraphState.search.query,
- flamegraphState.search.results.frames
- );
- selectedFramesRef.current = null;
- }
- if (!flamegraphState.search.query && !flamegraphState.search.highlightFrames) {
- flamegraphRenderer.setSearchResults('', new Map());
- selectedFramesRef.current = null;
- }
- }, [
- flamegraph,
- flamegraphRenderer,
- flamegraphState.search.results.frames,
- flamegraphState.search.query,
- flamegraphState.search.highlightFrames,
- ]);
- useInteractionViewCheckPoint({
- view: flamegraphView,
- lastInteraction,
- });
- useDrawSelectedBorderEffect({
- scheduler,
- selectedRef: selectedFramesRef,
- canvas: flamegraphCanvas,
- view: flamegraphView,
- eventKey: 'highlight frame',
- theme: flamegraphTheme,
- renderer: selectedFrameRenderer,
- });
- useDrawHoveredBorderEffect({
- scheduler,
- hoveredNode: hoveredNode
- ? hoveredNode
- : contextMenu.open
- ? hoveredNodeOnContextMenuOpen.current
- : null,
- canvas: flamegraphCanvas,
- view: flamegraphView,
- theme: flamegraphTheme,
- renderer: selectedFrameRenderer,
- });
- useEffect(() => {
- if (!flamegraphCanvas || !flamegraphView) {
- return undefined;
- }
- const onResetZoom = () => {
- setConfigSpaceCursor(null);
- };
- const onZoomIntoFrame = (frame: FlamegraphFrame, _strategy: 'min' | 'exact') => {
- if (frame) {
- selectedFramesRef.current = [frame];
- }
- setConfigSpaceCursor(null);
- };
- scheduler.on('reset zoom', onResetZoom);
- scheduler.on('zoom at frame', onZoomIntoFrame);
- return () => {
- scheduler.off('reset zoom', onResetZoom);
- scheduler.off('zoom at frame', onZoomIntoFrame);
- };
- }, [flamegraphCanvas, canvasPoolManager, dispatch, scheduler, flamegraphView]);
- const previousKeyPress = useRef<{at: number; key: string | null}>({
- key: null,
- at: 0,
- });
- useEffect(() => {
- const onKeyDown = (evt: KeyboardEvent) => {
- if (!flamegraphView) {
- return;
- }
- if (evt.key === 'Escape') {
- if (highlightingAllOccurrences) {
- setHighlightingAllOccurrences(false);
- dispatch({type: 'set highlight all frames', payload: null});
- canvasPoolManager.dispatch('highlight frame', [null, 'selected']);
- previousKeyPress.current = {key: null, at: 0};
- return;
- }
- // We'll keep 300ms as the threshold
- if (
- previousKeyPress.current.key === 'Escape' &&
- previousKeyPress.current.at - performance.now() < 300
- ) {
- canvasPoolManager.dispatch('reset zoom', []);
- previousKeyPress.current = {key: null, at: 0};
- } else {
- previousKeyPress.current = {key: evt.key, at: performance.now()};
- }
- }
- if (evt.key === 'z' && evt.metaKey) {
- const action = evt.shiftKey ? 'redo' : 'undo';
- if (action === 'undo') {
- const previousPosition = previousState?.position?.view;
- // If previous position is empty, reset the view to its max
- if (previousPosition?.isEmpty()) {
- canvasPoolManager.dispatch('reset zoom', []);
- } else if (
- previousPosition &&
- !previousPosition?.equals(flamegraphView.configView)
- ) {
- // We need to always dispatch with the height of the current view,
- // because the height may have changed due to window resizing and
- // calling it with the old height may result in the flamegraph
- // being drawn into a very small or very large area.
- canvasPoolManager.dispatch('set config view', [
- previousPosition,
- flamegraphView,
- ]);
- }
- }
- if (action === 'redo') {
- const nextPosition = nextState?.position?.view;
- if (nextPosition && !nextPosition.equals(flamegraphView.configView)) {
- // We need to always dispatch with the height of the current view,
- // because the height may have changed due to window resizing and
- // calling it with the old height may result in the flamegraph
- // being drawn into a very small or very large area.
- canvasPoolManager.dispatch('set config view', [nextPosition, flamegraphView]);
- }
- }
- dispatch({type: action});
- }
- if (evt.target === flamegraphCanvasRef) {
- const nextSelected = handleFlamegraphKeyboardNavigation(
- evt,
- selectedFramesRef.current?.[0],
- flamegraph.inverted
- );
- if (nextSelected) {
- canvasPoolManager.dispatch('zoom at frame', [nextSelected, 'min']);
- }
- }
- };
- document.addEventListener('keydown', onKeyDown);
- return () => {
- document.removeEventListener('keydown', onKeyDown);
- };
- }, [
- canvasPoolManager,
- setHighlightingAllOccurrences,
- highlightingAllOccurrences,
- dispatch,
- nextState,
- previousState,
- flamegraphView,
- scheduler,
- flamegraphCanvasRef,
- flamegraph.inverted,
- ]);
- const onCanvasMouseDown = useCallback((evt: React.MouseEvent<HTMLCanvasElement>) => {
- setLastInteraction('click');
- setStartInteractionVector(
- getPhysicalSpacePositionFromOffset(evt.nativeEvent.offsetX, evt.nativeEvent.offsetY)
- );
- }, []);
- const onCanvasDoubleClick = useCallback(
- (evt: React.MouseEvent<HTMLCanvasElement>) => {
- evt.preventDefault();
- evt.stopPropagation();
- if (!configSpaceCursor) {
- setLastInteraction(null);
- setStartInteractionVector(null);
- return;
- }
- // Only dispatch the zoom action if the new clicked node is not the same as the old selected node.
- // This essentially tracks double click action on a rectangle
- if (hoveredNode) {
- // If double click is fired on a node, then zoom into it
- canvasPoolManager.dispatch('zoom at frame', [hoveredNode, 'exact']);
- canvasPoolManager.dispatch('show in table view', [hoveredNode]);
- canvasPoolManager.dispatch('highlight frame', [[hoveredNode], 'selected']);
- flamegraphRenderer?.setSearchResults('', new Map());
- } else {
- canvasPoolManager.dispatch('highlight frame', [null, 'selected']);
- if (!flamegraphSearch.query) {
- flamegraphRenderer?.setSearchResults('', new Map());
- }
- }
- setLastInteraction('click');
- setStartInteractionVector(null);
- },
- [
- configSpaceCursor,
- hoveredNode,
- canvasPoolManager,
- flamegraphRenderer,
- flamegraphSearch.query,
- ]
- );
- const onMouseDrag = useCallback(
- (evt: React.MouseEvent<HTMLCanvasElement>) => {
- if (!flamegraphCanvas || !flamegraphView || !startInteractionVector) {
- return;
- }
- const configDelta = getConfigViewTranslationBetweenVectors(
- evt.nativeEvent.offsetX,
- evt.nativeEvent.offsetY,
- startInteractionVector,
- flamegraphView,
- flamegraphCanvas
- );
- if (!configDelta) {
- return;
- }
- canvasPoolManager.dispatch('transform config view', [configDelta, flamegraphView]);
- setStartInteractionVector(
- getPhysicalSpacePositionFromOffset(
- evt.nativeEvent.offsetX,
- evt.nativeEvent.offsetY
- )
- );
- },
- [flamegraphCanvas, flamegraphView, startInteractionVector, canvasPoolManager]
- );
- const onCanvasMouseMove = useCallback(
- (evt: React.MouseEvent<HTMLCanvasElement>) => {
- if (!flamegraphCanvas || !flamegraphView) {
- return;
- }
- setConfigSpaceCursor(
- flamegraphView.getTransformedConfigViewCursor(
- vec2.fromValues(evt.nativeEvent.offsetX, evt.nativeEvent.offsetY),
- flamegraphCanvas
- )
- );
- if (startInteractionVector) {
- onMouseDrag(evt);
- setLastInteraction('pan');
- } else {
- setLastInteraction(null);
- }
- },
- [
- flamegraphCanvas,
- flamegraphView,
- setConfigSpaceCursor,
- onMouseDrag,
- startInteractionVector,
- ]
- );
- const onCanvasMouseUp = useCallback(() => {
- if (hoveredNode) {
- // If double click is fired on a node, then zoom into it
- canvasPoolManager.dispatch('highlight frame', [[hoveredNode], 'selected']);
- }
- setLastInteraction(null);
- setStartInteractionVector(null);
- }, [hoveredNode, canvasPoolManager]);
- const onCanvasMouseLeave = useCallback(() => {
- setConfigSpaceCursor(null);
- setStartInteractionVector(null);
- setLastInteraction(null);
- }, []);
- const onWheelCenterZoom = useWheelCenterZoom(
- flamegraphCanvas,
- flamegraphView,
- canvasPoolManager,
- disableZoom
- );
- const onCanvasScroll = useCanvasScroll(
- flamegraphCanvas,
- flamegraphView,
- canvasPoolManager,
- disablePanX
- );
- useCanvasZoomOrScroll({
- setConfigSpaceCursor,
- setLastInteraction,
- handleWheel: onWheelCenterZoom,
- handleScroll: onCanvasScroll,
- canvas: flamegraphCanvasRef,
- });
- // When a user click anywhere outside the spans, clear cursor and selected node
- useEffect(() => {
- const onClickOutside = (evt: MouseEvent) => {
- if (
- !canvasContainerRef.current ||
- canvasContainerRef.current.contains(evt.target as Node)
- ) {
- return;
- }
- if (contextMenu.open) {
- evt.preventDefault();
- evt.stopPropagation();
- }
- setConfigSpaceCursor(null);
- };
- document.addEventListener('click', onClickOutside);
- return () => {
- document.removeEventListener('click', onClickOutside);
- };
- }, [canvasContainerRef, contextMenu, canvasPoolManager]);
- const handleContextMenuOpen = useCallback(
- (event: React.MouseEvent) => {
- hoveredNodeOnContextMenuOpen.current = hoveredNode;
- contextMenu.handleContextMenu(event);
- // Make sure we set the highlight state relative to the newly hovered node
- setHighlightingAllOccurrences(
- isHighlightingAllOccurrences(hoveredNode, selectedFramesRef.current)
- );
- },
- [contextMenu, hoveredNode]
- );
- const handleHighlightAllFramesClick = useCallback(() => {
- if (!hoveredNodeOnContextMenuOpen.current || !flamegraphView) {
- return;
- }
- // If all Occurrences are currently being highlighted, we want to unhighlight them now
- if (
- isHighlightingAllOccurrences(
- hoveredNodeOnContextMenuOpen.current,
- selectedFramesRef.current
- )
- ) {
- setHighlightingAllOccurrences(false);
- dispatch({type: 'set highlight all frames', payload: null});
- canvasPoolManager.dispatch('highlight frame', [null, 'selected']);
- return;
- }
- setHighlightingAllOccurrences(true);
- const frameName = hoveredNodeOnContextMenuOpen.current.frame.name;
- const packageName =
- hoveredNodeOnContextMenuOpen.current.frame.package ??
- hoveredNodeOnContextMenuOpen.current.frame.module ??
- '';
- dispatch({
- type: 'set highlight all frames',
- payload: {
- name: frameName,
- package: packageName,
- },
- });
- let frames = flamegraph.findAllMatchingFrames(frameName, packageName);
- if (
- !frames.length &&
- !packageName &&
- frameName &&
- profileGroup.metadata.platform === 'node'
- ) {
- // there is a chance that the reason we did not find any frames is because
- // for node, we try to infer some package from the frontend code.
- // If that happens, we'll try and just do a search by name. This logic
- // is duplicated in flamegraphZoomView.tsx and should be kept in sync
- frames = flamegraph.findAllMatchingFramesBy(frameName, ['name']);
- }
- const rectFrames = frames.map(f => new Rect(f.start, f.depth, f.end - f.start, 1));
- const newConfigView = computeMinZoomConfigViewForFrames(
- flamegraphView.configView,
- rectFrames
- ).transformRect(flamegraphView.configSpaceTransform);
- canvasPoolManager.dispatch('highlight frame', [frames, 'selected']);
- canvasPoolManager.dispatch('set config view', [newConfigView, flamegraphView]);
- }, [
- canvasPoolManager,
- flamegraph,
- flamegraphView,
- dispatch,
- profileGroup.metadata.platform,
- ]);
- const handleCopyFunctionName = useCallback(() => {
- if (!hoveredNodeOnContextMenuOpen.current) {
- return;
- }
- navigator.clipboard
- .writeText(hoveredNodeOnContextMenuOpen.current.frame.name)
- .then(() => {
- addSuccessMessage(t('Function name copied to clipboard'));
- })
- .catch(() => {
- addErrorMessage(t('Failed to copy function name to clipboard'));
- });
- }, []);
- const handleCopyFunctionSource = useCallback(() => {
- if (!hoveredNodeOnContextMenuOpen.current) {
- return;
- }
- const frame = hoveredNodeOnContextMenuOpen.current.frame;
- navigator.clipboard
- .writeText(frame.file ?? frame.path ?? '')
- .then(() => {
- addSuccessMessage(t('Function source copied to clipboard'));
- })
- .catch(() => {
- addErrorMessage(t('Failed to copy function source to clipboard'));
- });
- }, []);
- return (
- <CanvasContainer ref={canvasContainerRef}>
- <Canvas
- ref={setFlamegraphCanvasRef}
- onMouseDown={onCanvasMouseDown}
- onMouseMove={onCanvasMouseMove}
- onMouseLeave={onCanvasMouseLeave}
- onMouseUp={onCanvasMouseUp}
- onDoubleClick={onCanvasDoubleClick}
- onContextMenu={handleContextMenuOpen}
- cursor={lastInteraction === 'pan' ? 'grabbing' : 'default'}
- tabIndex={1}
- />
- <Canvas ref={setFlamegraphOverlayCanvasRef} pointerEvents="none" />
- <FlamegraphContextMenu
- contextMenu={contextMenu}
- profileGroup={profileGroup}
- hoveredNode={hoveredNodeOnContextMenuOpen.current}
- isHighlightingAllOccurrences={highlightingAllOccurrences}
- onCopyFunctionNameClick={handleCopyFunctionName}
- onCopyFunctionSource={handleCopyFunctionSource}
- onHighlightAllOccurrencesClick={handleHighlightAllFramesClick}
- disableCallOrderSort={disableCallOrderSort}
- disableColorCoding={disableColorCoding}
- />
- {flamegraphCanvas &&
- flamegraphRenderer &&
- flamegraphView &&
- configSpaceCursor &&
- hoveredNode ? (
- <FlamegraphTooltip
- flamegraph={flamegraph}
- frame={hoveredNode}
- configSpaceCursor={configSpaceCursor}
- flamegraphCanvas={flamegraphCanvas}
- flamegraphRenderer={flamegraphRenderer}
- flamegraphView={flamegraphView}
- canvasBounds={canvasBounds}
- platform={profileGroup.metadata.platform}
- />
- ) : null}
- </CanvasContainer>
- );
- }
- const CanvasContainer = styled('div')`
- display: flex;
- flex-direction: column;
- height: 100%;
- width: 100%;
- position: relative;
- `;
- const Canvas = styled('canvas')<{
- cursor?: CSSProperties['cursor'];
- pointerEvents?: CSSProperties['pointerEvents'];
- }>`
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
- user-select: none;
- position: absolute;
- pointer-events: ${p => p.pointerEvents || 'auto'};
- cursor: ${p => p.cursor || 'default'};
- &:focus {
- outline: none;
- }
- `;
- export {FlamegraphZoomView};
|