diff --git a/src/backend/renderer.js b/src/backend/renderer.js index b4adeeff9b..63cae9238d 100644 --- a/src/backend/renderer.js +++ b/src/backend/renderer.js @@ -892,7 +892,10 @@ export function attach( actualDurations: [], commitTime: performance.now() - profilingStartTime, interactions: Array.from(root.memoizedInteractions).map( - (interaction: Interaction) => ({ ...interaction }) + (interaction: Interaction) => ({ + ...interaction, + timestamp: interaction.timestamp - profilingStartTime, + }) ), maxActualDuration: 0, }; diff --git a/src/devtools/ProfilingCache.js b/src/devtools/ProfilingCache.js index ad872c1a7a..fa80195470 100644 --- a/src/devtools/ProfilingCache.js +++ b/src/devtools/ProfilingCache.js @@ -10,6 +10,10 @@ import { getChartData as getFlamegraphChartData, invalidateChartData as invalidateFlamegraphChartData, } from 'src/devtools/views/Profiler/FlamegraphChartBuilder'; +import { + getChartData as getInteractionsChartData, + invalidateChartData as invalidateInteractionsChartData, +} from 'src/devtools/views/Profiler/InteractionsChartBuilder'; import { getChartData as getRankedChartData, invalidateChartData as invalidateRankedChartData, @@ -25,10 +29,12 @@ import type { import type { CommitDetails as CommitDetailsFrontend, Interactions as InteractionsFrontend, + InteractionWithCommits, CommitTree as CommitTreeFrontend, ProfilingSummary as ProfilingSummaryFrontend, } from 'src/devtools/views/Profiler/types'; import type { ChartData as FlamegraphChartData } from 'src/devtools/views/Profiler/FlamegraphChartBuilder'; +import type { ChartData as InteractionsChartData } from 'src/devtools/views/Profiler/InteractionsChartBuilder'; import type { ChartData as RankedChartData } from 'src/devtools/views/Profiler/RankedChartBuilder'; type CommitDetailsParams = {| @@ -110,10 +116,7 @@ export default class ProfilingCache { return new Promise(resolve => { if (!this._store.profilingOperations.has(rootID)) { // If no profiling data was recorded for this root, skip the round trip. - resolve({ - interactions: [], - rootID, - }); + resolve([]); } else { this._pendingInteractionsMap.set(rootID, resolve); this._bridge.send('getInteractions', { @@ -190,6 +193,21 @@ export default class ProfilingCache { rootID, }); + getInteractionsChartData = ({ + interactions, + profilingSummary, + rootID, + }: {| + interactions: Array, + profilingSummary: ProfilingSummaryFrontend, + rootID: number, + |}): InteractionsChartData => + getInteractionsChartData({ + interactions, + profilingSummary, + rootID, + }); + getRankedChartData = ({ commitDetails, commitIndex, @@ -215,6 +233,7 @@ export default class ProfilingCache { // Invalidate non-Suspense caches too. invalidateCommitTrees(); invalidateFlamegraphChartData(); + invalidateInteractionsChartData(); invalidateRankedChartData(); this._pendingCommitDetailsMap.clear(); @@ -249,10 +268,7 @@ export default class ProfilingCache { if (resolve != null) { this._pendingInteractionsMap.delete(rootID); - resolve({ - interactions, - rootID, - }); + resolve(interactions); } }; diff --git a/src/devtools/views/Profiler/InteractionListItem.css b/src/devtools/views/Profiler/InteractionListItem.css index 2f17864beb..f34920230b 100644 --- a/src/devtools/views/Profiler/InteractionListItem.css +++ b/src/devtools/views/Profiler/InteractionListItem.css @@ -11,6 +11,31 @@ } .SelectedInteraction { - background-color: var(--color-selected-background); - color: var(--color-selected-foreground); + background-color: var(--color-hover-background); +} + +.Name { + white-space: nowrap; + overflow-x: hidden; + text-overflow: ellipsis; +} + +.Timeline { + position: relative; + height: 100%; +} + +.InteractionLine { + position: absolute; + height: 3px; + background-color: var(--color-commit-did-not-render); + border-radius: 0.125rem; +} + +.CommitBox { + position: absolute; + width: 10px; + height: 10px; + background-color: var(--color-commit-did-not-render); + cursor: pointer; } diff --git a/src/devtools/views/Profiler/InteractionListItem.js b/src/devtools/views/Profiler/InteractionListItem.js index 6e679ea140..d7b85d6bd7 100644 --- a/src/devtools/views/Profiler/InteractionListItem.js +++ b/src/devtools/views/Profiler/InteractionListItem.js @@ -2,6 +2,7 @@ import React, { memo, useCallback } from 'react'; import { areEqual } from 'react-window'; +import { getGradientColor } from './utils'; import styles from './InteractionListItem.css'; @@ -14,7 +15,18 @@ type Props = { }; function InteractionListItem({ data: itemData, index, style }: Props) { - const { interactions, selectedInteractionID, selectInteraction } = itemData; + const { + chartData, + interactions, + labelWidth, + profilingSummary, + scaleX, + selectedInteractionID, + selectInteraction, + } = itemData; + + const { maxCommitDuration } = chartData; + const { commitDurations, commitTimes } = profilingSummary; const interaction = interactions[index]; @@ -22,7 +34,11 @@ function InteractionListItem({ data: itemData, index, style }: Props) { selectInteraction(interaction.id); }, [interaction, selectInteraction]); - // TODO (profiling Render commit bar) + const startTime = interaction.timestamp; + const stopTime = + interaction.commits.length > 0 + ? commitTimes[interaction.commits[interaction.commits.length - 1]] + : interaction.timestamp; return (
- {interaction.name} +
+ {interaction.name} +
+
+ {interaction.commits.map(commitIndex => ( +
+ ))}
); } diff --git a/src/devtools/views/Profiler/Interactions.js b/src/devtools/views/Profiler/Interactions.js index 4b1c3462c8..64032ea8a7 100644 --- a/src/devtools/views/Profiler/Interactions.js +++ b/src/devtools/views/Profiler/Interactions.js @@ -7,13 +7,19 @@ import { ProfilerContext } from './ProfilerContext'; import InteractionListItem from './InteractionListItem'; import NoInteractions from './NoInteractions'; import { StoreContext } from '../context'; +import { scale } from './utils'; import styles from './Interactions.css'; -import type { InteractionWithCommits } from './types'; +import type { ChartData } from './InteractionsChartBuilder'; +import type { InteractionWithCommits, ProfilingSummary } from './types'; export type ItemData = {| + chartData: ChartData, interactions: Array, + labelWidth: number, + profilingSummary: ProfilingSummary, + scaleX: (value: number, fallbackValue: number) => number, selectedInteractionID: number | null, selectInteraction: (id: number | null) => void, |}; @@ -37,19 +43,44 @@ function Interactions({ height, width }: {| height: number, width: number |}) { } = useContext(ProfilerContext); const { profilingCache } = useContext(StoreContext); - const { interactions } = profilingCache.Interactions.read({ + const interactions = profilingCache.Interactions.read({ rendererID: ((rendererID: any): number), rootID: ((rootID: any): number), }); - const itemData = useMemo( - () => ({ + const profilingSummary = profilingCache.ProfilingSummary.read({ + rendererID: ((rendererID: any): number), + rootID: ((rootID: any): number), + }); + + const chartData = profilingCache.getInteractionsChartData({ + interactions, + profilingSummary, + rootID: ((rootID: any): number), + }); + + const itemData = useMemo(() => { + // TODO (profiling) constants + const labelWidth = Math.min(200, width / 5); + const timelineWidth = width - labelWidth - 10; + + return { + chartData, interactions, + labelWidth, + profilingSummary, + scaleX: scale(0, chartData.lastInteractionTime, 0, timelineWidth), selectedInteractionID, selectInteraction, - }), - [interactions, selectedInteractionID, selectInteraction] - ); + }; + }, [ + chartData, + interactions, + profilingSummary, + selectedInteractionID, + selectInteraction, + width, + ]); // TODO (profiling) Up/down arrow keys to select prev/next interaction. diff --git a/src/devtools/views/Profiler/InteractionsChartBuilder.js b/src/devtools/views/Profiler/InteractionsChartBuilder.js new file mode 100644 index 0000000000..17bc9d6610 --- /dev/null +++ b/src/devtools/views/Profiler/InteractionsChartBuilder.js @@ -0,0 +1,45 @@ +// @flow + +import type { InteractionWithCommits, ProfilingSummary } from './types'; + +export type ChartData = {| + lastInteractionTime: number, + maxCommitDuration: number, +|}; + +const cachedChartData: Map = new Map(); + +export function getChartData({ + interactions, + profilingSummary, + rootID, +}: {| + interactions: Array, + profilingSummary: ProfilingSummary, + rootID: number, +|}): ChartData { + if (cachedChartData.has(rootID)) { + return ((cachedChartData.get(rootID): any): ChartData); + } + + const { commitDurations, commitTimes } = profilingSummary; + + const lastInteractionTime = + commitTimes.length > 0 ? commitTimes[commitTimes.length - 1] : 0; + + let maxCommitDuration = 0; + + commitDurations.forEach(commitDuration => { + maxCommitDuration = Math.max(maxCommitDuration, commitDuration); + }); + + const chartData = { lastInteractionTime, maxCommitDuration }; + + cachedChartData.set(rootID, chartData); + + return chartData; +} + +export function invalidateChartData(): void { + cachedChartData.clear(); +} diff --git a/src/devtools/views/Profiler/SidebarInteractions.js b/src/devtools/views/Profiler/SidebarInteractions.js index d6a5929f01..423ad29389 100644 --- a/src/devtools/views/Profiler/SidebarInteractions.js +++ b/src/devtools/views/Profiler/SidebarInteractions.js @@ -22,7 +22,7 @@ export default function SidebarInteractions(_: Props) { return
Nothing selected
; } - const { interactions } = profilingCache.Interactions.read({ + const interactions = profilingCache.Interactions.read({ rendererID: ((rendererID: any): number), rootID: ((rootID: any): number), }); diff --git a/src/devtools/views/Profiler/types.js b/src/devtools/views/Profiler/types.js index 98b15a8bae..6efa951bd1 100644 --- a/src/devtools/views/Profiler/types.js +++ b/src/devtools/views/Profiler/types.js @@ -25,10 +25,7 @@ export type InteractionWithCommits = {| commits: Array, |}; -export type Interactions = {| - interactions: Array, - rootID: number, -|}; +export type Interactions = Array; export type CommitDetails = {| actualDurations: Map,