1. /**
    
  2.  * Copyright (c) Meta Platforms, Inc. and affiliates.
    
  3.  *
    
  4.  * This source code is licensed under the MIT license found in the
    
  5.  * LICENSE file in the root directory of this source tree.
    
  6.  *
    
  7.  * @flow
    
  8.  */
    
  9. 
    
  10. import * as React from 'react';
    
  11. import {forwardRef, useCallback, useContext, useMemo, useState} from 'react';
    
  12. import AutoSizer from 'react-virtualized-auto-sizer';
    
  13. import {FixedSizeList} from 'react-window';
    
  14. import {ProfilerContext} from './ProfilerContext';
    
  15. import NoCommitData from './NoCommitData';
    
  16. import CommitFlamegraphListItem from './CommitFlamegraphListItem';
    
  17. import HoveredFiberInfo from './HoveredFiberInfo';
    
  18. import {scale} from './utils';
    
  19. import {useHighlightNativeElement} from '../hooks';
    
  20. import {StoreContext} from '../context';
    
  21. import {SettingsContext} from '../Settings/SettingsContext';
    
  22. import Tooltip from './Tooltip';
    
  23. 
    
  24. import styles from './CommitFlamegraph.css';
    
  25. 
    
  26. import type {TooltipFiberData} from './HoveredFiberInfo';
    
  27. import type {ChartData, ChartNode} from './FlamegraphChartBuilder';
    
  28. import type {CommitTree} from './types';
    
  29. 
    
  30. export type ItemData = {
    
  31.   chartData: ChartData,
    
  32.   onElementMouseEnter: (fiberData: TooltipFiberData) => void,
    
  33.   onElementMouseLeave: () => void,
    
  34.   scaleX: (value: number, fallbackValue: number) => number,
    
  35.   selectedChartNode: ChartNode | null,
    
  36.   selectedChartNodeIndex: number,
    
  37.   selectFiber: (id: number | null, name: string | null) => void,
    
  38.   width: number,
    
  39. };
    
  40. 
    
  41. export default function CommitFlamegraphAutoSizer(_: {}): React.Node {
    
  42.   const {profilerStore} = useContext(StoreContext);
    
  43.   const {rootID, selectedCommitIndex, selectFiber} =
    
  44.     useContext(ProfilerContext);
    
  45.   const {profilingCache} = profilerStore;
    
  46. 
    
  47.   const deselectCurrentFiber = useCallback(
    
  48.     (event: $FlowFixMe) => {
    
  49.       event.stopPropagation();
    
  50.       selectFiber(null, null);
    
  51.     },
    
  52.     [selectFiber],
    
  53.   );
    
  54. 
    
  55.   let commitTree: CommitTree | null = null;
    
  56.   let chartData: ChartData | null = null;
    
  57.   if (selectedCommitIndex !== null) {
    
  58.     commitTree = profilingCache.getCommitTree({
    
  59.       commitIndex: selectedCommitIndex,
    
  60.       rootID: ((rootID: any): number),
    
  61.     });
    
  62. 
    
  63.     chartData = profilingCache.getFlamegraphChartData({
    
  64.       commitIndex: selectedCommitIndex,
    
  65.       commitTree,
    
  66.       rootID: ((rootID: any): number),
    
  67.     });
    
  68.   }
    
  69. 
    
  70.   if (commitTree != null && chartData != null && chartData.depth > 0) {
    
  71.     return (
    
  72.       <div className={styles.Container} onClick={deselectCurrentFiber}>
    
  73.         <AutoSizer>
    
  74.           {({height, width}) => (
    
  75.             // Force Flow types to avoid checking for `null` here because there's no static proof that
    
  76.             // by the time this render prop function is called, the values of the `let` variables have not changed.
    
  77.             <CommitFlamegraph
    
  78.               chartData={((chartData: any): ChartData)}
    
  79.               commitTree={((commitTree: any): CommitTree)}
    
  80.               height={height}
    
  81.               width={width}
    
  82.             />
    
  83.           )}
    
  84.         </AutoSizer>
    
  85.       </div>
    
  86.     );
    
  87.   } else {
    
  88.     return <NoCommitData />;
    
  89.   }
    
  90. }
    
  91. 
    
  92. type Props = {
    
  93.   chartData: ChartData,
    
  94.   commitTree: CommitTree,
    
  95.   height: number,
    
  96.   width: number,
    
  97. };
    
  98. 
    
  99. function CommitFlamegraph({chartData, commitTree, height, width}: Props) {
    
  100.   const [hoveredFiberData, setHoveredFiberData] =
    
  101.     useState<TooltipFiberData | null>(null);
    
  102.   const {lineHeight} = useContext(SettingsContext);
    
  103.   const {selectFiber, selectedFiberID} = useContext(ProfilerContext);
    
  104.   const {highlightNativeElement, clearHighlightNativeElement} =
    
  105.     useHighlightNativeElement();
    
  106. 
    
  107.   const selectedChartNodeIndex = useMemo<number>(() => {
    
  108.     if (selectedFiberID === null) {
    
  109.       return 0;
    
  110.     }
    
  111.     // The selected node might not be in the tree for this commit,
    
  112.     // so it's important that we have a fallback plan.
    
  113.     const depth = chartData.idToDepthMap.get(selectedFiberID);
    
  114.     return depth !== undefined ? depth - 1 : 0;
    
  115.   }, [chartData, selectedFiberID]);
    
  116. 
    
  117.   const selectedChartNode = useMemo(() => {
    
  118.     if (selectedFiberID !== null) {
    
  119.       return (
    
  120.         chartData.rows[selectedChartNodeIndex].find(
    
  121.           chartNode => chartNode.id === selectedFiberID,
    
  122.         ) || null
    
  123.       );
    
  124.     }
    
  125.     return null;
    
  126.   }, [chartData, selectedFiberID, selectedChartNodeIndex]);
    
  127. 
    
  128.   const handleElementMouseEnter = useCallback(
    
  129.     ({id, name}: $FlowFixMe) => {
    
  130.       highlightNativeElement(id); // Highlight last hovered element.
    
  131.       setHoveredFiberData({id, name}); // Set hovered fiber data for tooltip
    
  132.     },
    
  133.     [highlightNativeElement],
    
  134.   );
    
  135. 
    
  136.   const handleElementMouseLeave = useCallback(() => {
    
  137.     clearHighlightNativeElement(); // clear highlighting of element on mouse leave
    
  138.     setHoveredFiberData(null); // clear hovered fiber data for tooltip
    
  139.   }, [clearHighlightNativeElement]);
    
  140. 
    
  141.   const itemData = useMemo<ItemData>(
    
  142.     () => ({
    
  143.       chartData,
    
  144.       onElementMouseEnter: handleElementMouseEnter,
    
  145.       onElementMouseLeave: handleElementMouseLeave,
    
  146.       scaleX: scale(
    
  147.         0,
    
  148.         selectedChartNode !== null
    
  149.           ? selectedChartNode.treeBaseDuration
    
  150.           : chartData.baseDuration,
    
  151.         0,
    
  152.         width,
    
  153.       ),
    
  154.       selectedChartNode,
    
  155.       selectedChartNodeIndex,
    
  156.       selectFiber,
    
  157.       width,
    
  158.     }),
    
  159.     [
    
  160.       chartData,
    
  161.       handleElementMouseEnter,
    
  162.       handleElementMouseLeave,
    
  163.       selectedChartNode,
    
  164.       selectedChartNodeIndex,
    
  165.       selectFiber,
    
  166.       width,
    
  167.     ],
    
  168.   );
    
  169. 
    
  170.   // Tooltip used to show summary of fiber info on hover
    
  171.   const tooltipLabel = useMemo(
    
  172.     () =>
    
  173.       hoveredFiberData !== null ? (
    
  174.         <HoveredFiberInfo fiberData={hoveredFiberData} />
    
  175.       ) : null,
    
  176.     [hoveredFiberData],
    
  177.   );
    
  178. 
    
  179.   return (
    
  180.     <Tooltip label={tooltipLabel}>
    
  181.       <FixedSizeList
    
  182.         height={height}
    
  183.         innerElementType={InnerElementType}
    
  184.         itemCount={chartData.depth}
    
  185.         itemData={itemData}
    
  186.         itemSize={lineHeight}
    
  187.         width={width}>
    
  188.         {CommitFlamegraphListItem}
    
  189.       </FixedSizeList>
    
  190.     </Tooltip>
    
  191.   );
    
  192. }
    
  193. 
    
  194. const InnerElementType = forwardRef(({children, ...rest}, ref) => (
    
  195.   <svg ref={ref} {...rest}>
    
  196.     <defs>
    
  197.       <pattern
    
  198.         id="didNotRenderPattern"
    
  199.         patternUnits="userSpaceOnUse"
    
  200.         width="4"
    
  201.         height="4">
    
  202.         <path
    
  203.           d="M-1,1 l2,-2 M0,4 l4,-4 M3,5 l2,-2"
    
  204.           className={styles.PatternPath}
    
  205.         />
    
  206.       </pattern>
    
  207.     </defs>
    
  208.     {children}
    
  209.   </svg>
    
  210. ));