@samitouri / QOS-React / commits / 69cdd38c9d

Interaction chart with commit blocks rendering

Brian Vaughn committed Mar 24, 2019 at 09:07 UTC 69cdd38c9d6411a7f8d8aab1ea40ffff941db07a
8 files changed +187 -27
src/backend/renderer.js
+4 -1
@@ -892,7 +892,10 @@ export function attach(
892 actualDurations: [],
893 commitTime: performance.now() - profilingStartTime,
894 interactions: Array.from(root.memoizedInteractions).map(
895 - (interaction: Interaction) => ({ ...interaction })
895 + (interaction: Interaction) => ({
896 + ...interaction,
897 + timestamp: interaction.timestamp - profilingStartTime,
898 + })
899 ),
900 maxActualDuration: 0,
901 };
src/devtools/ProfilingCache.js
+24 -8
@@ -10,6 +10,10 @@ import {
10 getChartData as getFlamegraphChartData,
11 invalidateChartData as invalidateFlamegraphChartData,
12 } from 'src/devtools/views/Profiler/FlamegraphChartBuilder';
13 +import {
14 + getChartData as getInteractionsChartData,
15 + invalidateChartData as invalidateInteractionsChartData,
16 +} from 'src/devtools/views/Profiler/InteractionsChartBuilder';
17 import {
18 getChartData as getRankedChartData,
19 invalidateChartData as invalidateRankedChartData,
@@ -25,10 +29,12 @@ import type {
29 import type {
30 CommitDetails as CommitDetailsFrontend,
31 Interactions as InteractionsFrontend,
32 + InteractionWithCommits,
33 CommitTree as CommitTreeFrontend,
34 ProfilingSummary as ProfilingSummaryFrontend,
35 } from 'src/devtools/views/Profiler/types';
36 import type { ChartData as FlamegraphChartData } from 'src/devtools/views/Profiler/FlamegraphChartBuilder';
37 +import type { ChartData as InteractionsChartData } from 'src/devtools/views/Profiler/InteractionsChartBuilder';
38 import type { ChartData as RankedChartData } from 'src/devtools/views/Profiler/RankedChartBuilder';
39
40 type CommitDetailsParams = {|
@@ -110,10 +116,7 @@ export default class ProfilingCache {
116 return new Promise(resolve => {
117 if (!this._store.profilingOperations.has(rootID)) {
118 // If no profiling data was recorded for this root, skip the round trip.
113 - resolve({
114 - interactions: [],
115 - rootID,
116 - });
119 + resolve([]);
120 } else {
121 this._pendingInteractionsMap.set(rootID, resolve);
122 this._bridge.send('getInteractions', {
@@ -190,6 +193,21 @@ export default class ProfilingCache {
193 rootID,
194 });
195
196 + getInteractionsChartData = ({
197 + interactions,
198 + profilingSummary,
199 + rootID,
200 + }: {|
201 + interactions: Array<InteractionWithCommits>,
202 + profilingSummary: ProfilingSummaryFrontend,
203 + rootID: number,
204 + |}): InteractionsChartData =>
205 + getInteractionsChartData({
206 + interactions,
207 + profilingSummary,
208 + rootID,
209 + });
210 +
211 getRankedChartData = ({
212 commitDetails,
213 commitIndex,
@@ -215,6 +233,7 @@ export default class ProfilingCache {
233 // Invalidate non-Suspense caches too.
234 invalidateCommitTrees();
235 invalidateFlamegraphChartData();
236 + invalidateInteractionsChartData();
237 invalidateRankedChartData();
238
239 this._pendingCommitDetailsMap.clear();
@@ -249,10 +268,7 @@ export default class ProfilingCache {
268 if (resolve != null) {
269 this._pendingInteractionsMap.delete(rootID);
270
252 - resolve({
253 - interactions,
254 - rootID,
255 - });
271 + resolve(interactions);
272 }
273 };
274
src/devtools/views/Profiler/InteractionListItem.css
+27 -2
@@ -11,6 +11,31 @@
11 }
12
13 .SelectedInteraction {
14 - background-color: var(--color-selected-background);
15 - color: var(--color-selected-foreground);
14 + background-color: var(--color-hover-background);
15 +}
16 +
17 +.Name {
18 + white-space: nowrap;
19 + overflow-x: hidden;
20 + text-overflow: ellipsis;
21 +}
22 +
23 +.Timeline {
24 + position: relative;
25 + height: 100%;
26 +}
27 +
28 +.InteractionLine {
29 + position: absolute;
30 + height: 3px;
31 + background-color: var(--color-commit-did-not-render);
32 + border-radius: 0.125rem;
33 +}
34 +
35 +.CommitBox {
36 + position: absolute;
37 + width: 10px;
38 + height: 10px;
39 + background-color: var(--color-commit-did-not-render);
40 + cursor: pointer;
41 }
src/devtools/views/Profiler/InteractionListItem.js
+47 -4
@@ -2,6 +2,7 @@
2
3 import React, { memo, useCallback } from 'react';
4 import { areEqual } from 'react-window';
5 +import { getGradientColor } from './utils';
6
7 import styles from './InteractionListItem.css';
8
@@ -14,7 +15,18 @@ type Props = {
15 };
16
17 function InteractionListItem({ data: itemData, index, style }: Props) {
17 - const { interactions, selectedInteractionID, selectInteraction } = itemData;
18 + const {
19 + chartData,
20 + interactions,
21 + labelWidth,
22 + profilingSummary,
23 + scaleX,
24 + selectedInteractionID,
25 + selectInteraction,
26 + } = itemData;
27 +
28 + const { maxCommitDuration } = chartData;
29 + const { commitDurations, commitTimes } = profilingSummary;
30
31 const interaction = interactions[index];
32
@@ -22,7 +34,11 @@ function InteractionListItem({ data: itemData, index, style }: Props) {
34 selectInteraction(interaction.id);
35 }, [interaction, selectInteraction]);
36
25 - // TODO (profiling Render commit bar)
37 + const startTime = interaction.timestamp;
38 + const stopTime =
39 + interaction.commits.length > 0
40 + ? commitTimes[interaction.commits[interaction.commits.length - 1]]
41 + : interaction.timestamp;
42
43 return (
44 <div
@@ -33,9 +49,36 @@ function InteractionListItem({ data: itemData, index, style }: Props) {
49 }
50 onClick={handleClick}
51 style={style}
36 - title={interaction.name}
52 >
38 - {interaction.name}
53 + <div
54 + className={styles.Name}
55 + style={{ maxWidth: labelWidth }}
56 + title={interaction.name}
57 + >
58 + {interaction.name}
59 + </div>
60 + <div
61 + className={styles.InteractionLine}
62 + style={{
63 + left: labelWidth + scaleX(startTime, 0),
64 + width: scaleX(stopTime - startTime, 0),
65 + }}
66 + />
67 + {interaction.commits.map(commitIndex => (
68 + <div
69 + className={styles.CommitBox}
70 + key={commitIndex}
71 + style={{
72 + backgroundColor: getGradientColor(
73 + Math.min(
74 + 1,
75 + Math.max(0, commitDurations[commitIndex] / maxCommitDuration)
76 + ) || 0
77 + ),
78 + left: labelWidth + scaleX(commitTimes[commitIndex], 0),
79 + }}
80 + />
81 + ))}
82 </div>
83 );
84 }
src/devtools/views/Profiler/Interactions.js
+38 -7
@@ -7,13 +7,19 @@ import { ProfilerContext } from './ProfilerContext';
7 import InteractionListItem from './InteractionListItem';
8 import NoInteractions from './NoInteractions';
9 import { StoreContext } from '../context';
10 +import { scale } from './utils';
11
12 import styles from './Interactions.css';
13
13 -import type { InteractionWithCommits } from './types';
14 +import type { ChartData } from './InteractionsChartBuilder';
15 +import type { InteractionWithCommits, ProfilingSummary } from './types';
16
17 export type ItemData = {|
18 + chartData: ChartData,
19 interactions: Array<InteractionWithCommits>,
20 + labelWidth: number,
21 + profilingSummary: ProfilingSummary,
22 + scaleX: (value: number, fallbackValue: number) => number,
23 selectedInteractionID: number | null,
24 selectInteraction: (id: number | null) => void,
25 |};
@@ -37,19 +43,44 @@ function Interactions({ height, width }: {| height: number, width: number |}) {
43 } = useContext(ProfilerContext);
44 const { profilingCache } = useContext(StoreContext);
45
40 - const { interactions } = profilingCache.Interactions.read({
46 + const interactions = profilingCache.Interactions.read({
47 rendererID: ((rendererID: any): number),
48 rootID: ((rootID: any): number),
49 });
50
45 - const itemData = useMemo<ItemData>(
46 - () => ({
51 + const profilingSummary = profilingCache.ProfilingSummary.read({
52 + rendererID: ((rendererID: any): number),
53 + rootID: ((rootID: any): number),
54 + });
55 +
56 + const chartData = profilingCache.getInteractionsChartData({
57 + interactions,
58 + profilingSummary,
59 + rootID: ((rootID: any): number),
60 + });
61 +
62 + const itemData = useMemo<ItemData>(() => {
63 + // TODO (profiling) constants
64 + const labelWidth = Math.min(200, width / 5);
65 + const timelineWidth = width - labelWidth - 10;
66 +
67 + return {
68 + chartData,
69 interactions,
70 + labelWidth,
71 + profilingSummary,
72 + scaleX: scale(0, chartData.lastInteractionTime, 0, timelineWidth),
73 selectedInteractionID,
74 selectInteraction,
50 - }),
51 - [interactions, selectedInteractionID, selectInteraction]
52 - );
75 + };
76 + }, [
77 + chartData,
78 + interactions,
79 + profilingSummary,
80 + selectedInteractionID,
81 + selectInteraction,
82 + width,
83 + ]);
84
85 // TODO (profiling) Up/down arrow keys to select prev/next interaction.
86
src/devtools/views/Profiler/InteractionsChartBuilder.js new
+45
@@ -0,0 +1,45 @@
1 +// @flow
2 +
3 +import type { InteractionWithCommits, ProfilingSummary } from './types';
4 +
5 +export type ChartData = {|
6 + lastInteractionTime: number,
7 + maxCommitDuration: number,
8 +|};
9 +
10 +const cachedChartData: Map<number, ChartData> = new Map();
11 +
12 +export function getChartData({
13 + interactions,
14 + profilingSummary,
15 + rootID,
16 +}: {|
17 + interactions: Array<InteractionWithCommits>,
18 + profilingSummary: ProfilingSummary,
19 + rootID: number,
20 +|}): ChartData {
21 + if (cachedChartData.has(rootID)) {
22 + return ((cachedChartData.get(rootID): any): ChartData);
23 + }
24 +
25 + const { commitDurations, commitTimes } = profilingSummary;
26 +
27 + const lastInteractionTime =
28 + commitTimes.length > 0 ? commitTimes[commitTimes.length - 1] : 0;
29 +
30 + let maxCommitDuration = 0;
31 +
32 + commitDurations.forEach(commitDuration => {
33 + maxCommitDuration = Math.max(maxCommitDuration, commitDuration);
34 + });
35 +
36 + const chartData = { lastInteractionTime, maxCommitDuration };
37 +
38 + cachedChartData.set(rootID, chartData);
39 +
40 + return chartData;
41 +}
42 +
43 +export function invalidateChartData(): void {
44 + cachedChartData.clear();
45 +}
src/devtools/views/Profiler/SidebarInteractions.js
+1 -1
@@ -22,7 +22,7 @@ export default function SidebarInteractions(_: Props) {
22 return <div className={styles.NothingSelected}>Nothing selected</div>;
23 }
24
25 - const { interactions } = profilingCache.Interactions.read({
25 + const interactions = profilingCache.Interactions.read({
26 rendererID: ((rendererID: any): number),
27 rootID: ((rootID: any): number),
28 });
src/devtools/views/Profiler/types.js
+1 -4
@@ -25,10 +25,7 @@ export type InteractionWithCommits = {|
25 commits: Array<number>,
26 |};
27
28 -export type Interactions = {|
29 - interactions: Array<InteractionWithCommits>,
30 - rootID: number,
31 -|};
28 +export type Interactions = Array<InteractionWithCommits>;
29
30 export type CommitDetails = {|
31 actualDurations: Map<number, number>,