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>,