Interaction chart with commit blocks rendering

This commit is contained in:
Brian Vaughn
2019-03-24 09:07:00 -07:00
parent 64e355bb74
commit 69cdd38c9d
8 changed files with 187 additions and 27 deletions

View File

@@ -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,
};

View File

@@ -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<InteractionWithCommits>,
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);
}
};

View File

@@ -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;
}

View File

@@ -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 (
<div
@@ -33,9 +49,36 @@ function InteractionListItem({ data: itemData, index, style }: Props) {
}
onClick={handleClick}
style={style}
title={interaction.name}
>
{interaction.name}
<div
className={styles.Name}
style={{ maxWidth: labelWidth }}
title={interaction.name}
>
{interaction.name}
</div>
<div
className={styles.InteractionLine}
style={{
left: labelWidth + scaleX(startTime, 0),
width: scaleX(stopTime - startTime, 0),
}}
/>
{interaction.commits.map(commitIndex => (
<div
className={styles.CommitBox}
key={commitIndex}
style={{
backgroundColor: getGradientColor(
Math.min(
1,
Math.max(0, commitDurations[commitIndex] / maxCommitDuration)
) || 0
),
left: labelWidth + scaleX(commitTimes[commitIndex], 0),
}}
/>
))}
</div>
);
}

View File

@@ -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<InteractionWithCommits>,
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<ItemData>(
() => ({
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<ItemData>(() => {
// 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.

View File

@@ -0,0 +1,45 @@
// @flow
import type { InteractionWithCommits, ProfilingSummary } from './types';
export type ChartData = {|
lastInteractionTime: number,
maxCommitDuration: number,
|};
const cachedChartData: Map<number, ChartData> = new Map();
export function getChartData({
interactions,
profilingSummary,
rootID,
}: {|
interactions: Array<InteractionWithCommits>,
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();
}

View File

@@ -22,7 +22,7 @@ export default function SidebarInteractions(_: Props) {
return <div className={styles.NothingSelected}>Nothing selected</div>;
}
const { interactions } = profilingCache.Interactions.read({
const interactions = profilingCache.Interactions.read({
rendererID: ((rendererID: any): number),
rootID: ((rootID: any): number),
});

View File

@@ -25,10 +25,7 @@ export type InteractionWithCommits = {|
commits: Array<number>,
|};
export type Interactions = {|
interactions: Array<InteractionWithCommits>,
rootID: number,
|};
export type Interactions = Array<InteractionWithCommits>;
export type CommitDetails = {|
actualDurations: Map<number, number>,