| 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 { |
| 11 | ElementTypeForwardRef, |
| 12 | ElementTypeMemo, |
| 13 | } from 'react-devtools-shared/src/frontend/types'; |
| 14 | import {formatDuration} from './utils'; |
| 15 | import ProfilerStore from 'react-devtools-shared/src/devtools/ProfilerStore'; |
| 16 | |
| 17 | import type {CommitTree} from './types'; |
| 18 | |
| 19 | export type ChartNode = { |
| 20 | id: number, |
| 21 | label: string, |
| 22 | name: string, |
| 23 | value: number, |
| 24 | }; |
| 25 | |
| 26 | export type ChartData = { |
| 27 | maxValue: number, |
| 28 | nodes: Array<ChartNode>, |
| 29 | }; |
| 30 | |
| 31 | const cachedChartData: Map<string, ChartData> = new Map(); |
| 32 | |
| 33 | export function getChartData({ |
| 34 | commitIndex, |
| 35 | commitTree, |
| 36 | profilerStore, |
| 37 | rootID, |
| 38 | }: { |
| 39 | commitIndex: number, |
| 40 | commitTree: CommitTree, |
| 41 | profilerStore: ProfilerStore, |
| 42 | rootID: number, |
| 43 | }): ChartData { |
| 44 | const commitDatum = profilerStore.getCommitData(rootID, commitIndex); |
| 45 | |
| 46 | const {fiberActualDurations, fiberSelfDurations} = commitDatum; |
| 47 | const {nodes} = commitTree; |
| 48 | |
| 49 | const chartDataKey = `${rootID}-${commitIndex}`; |
| 50 | if (cachedChartData.has(chartDataKey)) { |
| 51 | return cachedChartData.get(chartDataKey) as any as ChartData; |
| 52 | } |
| 53 | |
| 54 | let maxSelfDuration = 0; |
| 55 | |
| 56 | const chartNodes: Array<ChartNode> = []; |
| 57 | fiberActualDurations.forEach((actualDuration, id) => { |
| 58 | const node = nodes.get(id); |
| 59 | |
| 60 | if (node == null) { |
| 61 | throw Error(`Could not find node with id "${id}" in commit tree`); |
| 62 | } |
| 63 | |
| 64 | const {displayName, key, parentID, type, compiledWithForget} = node; |
| 65 | |
| 66 | // Don't show the root node in this chart. |
| 67 | if (parentID === 0) { |
| 68 | return; |
| 69 | } |
| 70 | const selfDuration = fiberSelfDurations.get(id) || 0; |
| 71 | maxSelfDuration = Math.max(maxSelfDuration, selfDuration); |
| 72 | |
| 73 | const name = displayName || 'Anonymous'; |
| 74 | const maybeKey = key !== null ? ` key="${key}"` : ''; |
| 75 | const maybeForgetBadge = compiledWithForget ? '✨ ' : ''; |
| 76 | |
| 77 | let maybeBadge = ''; |
| 78 | if (type === ElementTypeForwardRef) { |
| 79 | maybeBadge = ' (ForwardRef)'; |
| 80 | } else if (type === ElementTypeMemo) { |
| 81 | maybeBadge = ' (Memo)'; |
| 82 | } |
| 83 | |
| 84 | const label = `${maybeForgetBadge}${name}${maybeBadge}${maybeKey} (${formatDuration( |
| 85 | selfDuration, |
| 86 | )}ms)`; |
| 87 | chartNodes.push({ |
| 88 | id, |
| 89 | label, |
| 90 | name, |
| 91 | value: selfDuration, |
| 92 | }); |
| 93 | }); |
| 94 | |
| 95 | const chartData = { |
| 96 | maxValue: maxSelfDuration, |
| 97 | nodes: chartNodes.sort((a, b) => b.value - a.value), |
| 98 | }; |
| 99 | |
| 100 | cachedChartData.set(chartDataKey, chartData); |
| 101 | |
| 102 | return chartData; |
| 103 | } |
| 104 | |
| 105 | export function invalidateChartData(): void { |
| 106 | cachedChartData.clear(); |
| 107 | } |