Profiler shows commit times and durations for selected fiber
Brian Vaughn committed
Apr 7, 2019 at 08:36 UTC
44c0febd234bb7c4c26af0f2643f850395db386f
17 files changed
+307
-18
src/backend/agent.js
+21
@@ -65,6 +65,7 @@ export default class Agent extends EventEmitter {
65
bridge.addListener('captureScreenshot', this.captureScreenshot);
66
bridge.addListener('exportProfilingSummary', this.exportProfilingSummary);
67
bridge.addListener('getCommitDetails', this.getCommitDetails);
68
+ bridge.addListener('getFiberCommits', this.getFiberCommits);
69
bridge.addListener('getInteractions', this.getInteractions);
70
bridge.addListener('getProfilingStatus', this.getProfilingStatus);
71
bridge.addListener('getProfilingSummary', this.getProfilingSummary);
@@ -158,6 +159,26 @@ export default class Agent extends EventEmitter {
159
}
160
};
161
162
+ getFiberCommits = ({
163
+ fiberID,
164
+ rendererID,
165
+ rootID,
166
+ }: {
167
+ fiberID: number,
168
+ rendererID: number,
169
+ rootID: number,
170
+ }) => {
171
+ const renderer = this._rendererInterfaces[rendererID];
172
+ if (renderer == null) {
173
+ console.warn(`Invalid renderer id "${rendererID}"`);
174
+ } else {
175
+ this._bridge.send(
176
+ 'fiberCommits',
177
+ renderer.getFiberCommits(rootID, fiberID)
178
+ );
179
+ }
180
+ };
181
+
182
getInteractions = ({
183
rendererID,
184
rootID,
src/backend/renderer.js
+35
@@ -32,6 +32,7 @@ import type {
32
CommitDetails,
33
DevToolsHook,
34
Fiber,
35
+ FiberCommits,
36
FiberData,
37
Interaction,
38
Interactions,
@@ -1652,6 +1653,39 @@ export function attach(
1653
};
1654
}
1655
1656
+ function getFiberCommits(rootID: number, fiberID: number): FiberCommits {
1657
+ const commitProfilingMetadata = ((rootToCommitProfilingMetadataMap: any): CommitProfilingMetadataMap).get(
1658
+ rootID
1659
+ );
1660
+ if (commitProfilingMetadata != null) {
1661
+ const commitDurations = [];
1662
+ commitProfilingMetadata.forEach(({ actualDurations }, commitIndex) => {
1663
+ for (let i = 0; i < actualDurations.length; i += 2) {
1664
+ if (actualDurations[i] === fiberID) {
1665
+ commitDurations.push(commitIndex, actualDurations[i + 1]);
1666
+ break;
1667
+ }
1668
+ }
1669
+ });
1670
+
1671
+ return {
1672
+ commitDurations,
1673
+ fiberID,
1674
+ rootID,
1675
+ };
1676
+ }
1677
+
1678
+ console.warn(
1679
+ `getFiberCommits(): No profiling info recorded for root "${rootID}"`
1680
+ );
1681
+
1682
+ return {
1683
+ commitDurations: [],
1684
+ fiberID,
1685
+ rootID,
1686
+ };
1687
+ }
1688
+
1689
function getInteractions(rootID: number): Interactions {
1690
const commitProfilingMetadata = ((rootToCommitProfilingMetadataMap: any): CommitProfilingMetadataMap).get(
1691
rootID
@@ -1817,6 +1851,7 @@ export function attach(
1851
flushInitialOperations,
1852
getCommitDetails,
1853
getFiberIDFromNative,
1854
+ getFiberCommits,
1855
getInteractions,
1856
findNativeByFiberID,
1857
getProfilingDataForDownload,
src/backend/types.js
+7
@@ -66,6 +66,12 @@ export type CommitDetails = {|
66
rootID: number,
67
|};
68
69
+export type FiberCommits = {|
70
+ commitDurations: Array<number>,
71
+ fiberID: number,
72
+ rootID: number,
73
+|};
74
+
75
export type InteractionWithCommits = {|
76
...Interaction,
77
commits: Array<number>,
@@ -93,6 +99,7 @@ export type RendererInterface = {
99
component: NativeType,
100
findNearestUnfilteredAncestor?: boolean
101
) => number | null,
102
+ getFiberCommits: (rootID: number, fiberID: number) => FiberCommits,
103
getInteractions: (rootID: number) => Interactions,
104
getProfilingDataForDownload: (rootID: number) => Object,
105
getProfilingSummary: (rootID: number) => ProfilingSummary,
src/devtools/ProfilingCache.js
+67
-2
@@ -23,11 +23,13 @@ import type { Resource } from './cache';
23
import type { Bridge } from '../types';
24
import type {
25
CommitDetails as CommitDetailsBackend,
26
+ FiberCommits as FiberCommitsBackend,
27
Interactions as InteractionsBackend,
28
ProfilingSummary as ProfilingSummaryBackend,
29
} from 'src/backend/types';
30
import type {
31
CommitDetails as CommitDetailsFrontend,
32
+ FiberCommits as FiberCommitsFrontend,
33
Interactions as InteractionsFrontend,
34
InteractionWithCommits,
35
CommitTree as CommitTreeFrontend,
@@ -39,13 +41,19 @@ import type { ChartData as RankedChartData } from 'src/devtools/views/Profiler/R
41
42
type CommitDetailsParams = {|
43
commitIndex: number,
44
+ rendererID: number,
45
rootID: number,
46
+|};
47
+
48
+type FiberCommitsParams = {|
49
+ fiberID: number,
50
rendererID: number,
51
+ rootID: number,
52
|};
53
54
type InteractionsParams = {|
47
- rootID: number,
55
rendererID: number,
56
+ rootID: number,
57
|};
58
59
type GetCommitTreeParams = {|
@@ -54,8 +62,8 @@ type GetCommitTreeParams = {|
62
|};
63
64
type ProfilingSummaryParams = {|
57
- rootID: number,
65
rendererID: number,
66
+ rootID: number,
67
|};
68
69
export default class ProfilingCache {
@@ -67,6 +75,11 @@ export default class ProfilingCache {
75
(commitDetails: CommitDetailsFrontend) => void
76
> = new Map();
77
78
+ _pendingFiberCommitsMap: Map<
79
+ string,
80
+ (fiberCommits: FiberCommitsFrontend) => void
81
+ > = new Map();
82
+
83
_pendingInteractionsMap: Map<
84
number,
85
(interactions: InteractionsFrontend) => void
@@ -121,6 +134,38 @@ export default class ProfilingCache {
134
`${rootID}-${commitIndex}`
135
);
136
137
+ FiberCommits: Resource<
138
+ FiberCommitsParams,
139
+ FiberCommitsFrontend
140
+ > = createResource(
141
+ ({ fiberID, rendererID, rootID }: FiberCommitsParams) => {
142
+ return new Promise(resolve => {
143
+ const importedProfilingData = this._store.importedProfilingData;
144
+ if (importedProfilingData !== null) {
145
+ // TODO (profiling) commit details
146
+ // Copy from renderer getFiberCommits()
147
+ } else if (this._store.profilingOperations.has(rootID)) {
148
+ this._pendingFiberCommitsMap.set(`${rootID}-${fiberID}`, resolve);
149
+ this._bridge.send('getFiberCommits', {
150
+ fiberID,
151
+ rendererID,
152
+ rootID,
153
+ });
154
+ return;
155
+ }
156
+
157
+ // If no profiling data was recorded for this root, skip the round trip.
158
+ resolve({
159
+ commitDurations: [],
160
+ fiberID,
161
+ rootID,
162
+ });
163
+ });
164
+ },
165
+ ({ fiberID, rendererID, rootID }: FiberCommitsParams) =>
166
+ `${rootID}-${fiberID}`
167
+ );
168
+
169
Interactions: Resource<
170
InteractionsParams,
171
InteractionsFrontend
@@ -192,6 +237,7 @@ export default class ProfilingCache {
237
this._store = store;
238
239
bridge.addListener('commitDetails', this.onCommitDetails);
240
+ bridge.addListener('fiberCommits', this.onFiberCommits);
241
bridge.addListener('interactions', this.onInteractions);
242
bridge.addListener('profilingSummary', this.onProfileSummary);
243
}
@@ -285,6 +331,24 @@ export default class ProfilingCache {
331
}
332
};
333
334
+ onFiberCommits = ({
335
+ commitDurations,
336
+ fiberID,
337
+ rootID,
338
+ }: FiberCommitsBackend) => {
339
+ const key = `${rootID}-${fiberID}`;
340
+ const resolve = this._pendingFiberCommitsMap.get(key);
341
+ if (resolve != null) {
342
+ this._pendingFiberCommitsMap.delete(key);
343
+
344
+ resolve({
345
+ commitDurations,
346
+ fiberID,
347
+ rootID,
348
+ });
349
+ }
350
+ };
351
+
352
onInteractions = ({ interactions, rootID }: InteractionsBackend) => {
353
const resolve = this._pendingInteractionsMap.get(rootID);
354
if (resolve != null) {
@@ -304,6 +368,7 @@ export default class ProfilingCache {
368
const resolve = this._pendingProfileSummaryMap.get(rootID);
369
if (resolve != null) {
370
this._pendingProfileSummaryMap.delete(rootID);
371
+
372
const initialTreeBaseDurationsMap = new Map();
373
for (let i = 0; i < initialTreeBaseDurations.length; i += 2) {
374
initialTreeBaseDurationsMap.set(
src/devtools/views/Profiler/CommitFlamegraph.js
+2
-2
@@ -20,7 +20,7 @@ export type ItemData = {|
20
scaleX: (value: number, fallbackValue: number) => number,
21
selectedChartNode: ChartNode,
22
selectedChartNodeIndex: number,
23
- selectFiber: (id: number | null) => void,
23
+ selectFiber: (id: number | null, name: string | null) => void,
24
width: number,
25
|};
26
@@ -33,7 +33,7 @@ export default function CommitFlamegraphAutoSizer(_: {||}) {
33
const deselectCurrentFiber = useCallback(
34
event => {
35
event.stopPropagation();
36
- selectFiber(null);
36
+ selectFiber(null, null);
37
},
38
[selectFiber]
39
);
src/devtools/views/Profiler/CommitFlamegraphListItem.js
+4
-3
@@ -26,9 +26,9 @@ function CommitFlamegraphListItem({ data, index, style }: Props) {
26
const { maxSelfDuration, rows } = chartData;
27
28
const handleClick = useCallback(
29
- (event: MouseEvent, id: number) => {
29
+ (event: MouseEvent, id: number, name: string) => {
30
event.stopPropagation();
31
- selectFiber(id);
31
+ selectFiber(id, name);
32
},
33
[selectFiber]
34
);
@@ -50,6 +50,7 @@ function CommitFlamegraphListItem({ data, index, style }: Props) {
50
didRender,
51
id,
52
label,
53
+ name,
54
offset,
55
selfDuration,
56
treeBaseDuration,
@@ -84,7 +85,7 @@ function CommitFlamegraphListItem({ data, index, style }: Props) {
85
isDimmed={index < selectedChartNodeIndex}
86
key={id}
87
label={label}
87
- onClick={event => handleClick(event, id)}
88
+ onClick={event => handleClick(event, id, name)}
89
width={nodeWidth}
90
x={nodeOffset - selectedNodeOffset}
91
y={top}
src/devtools/views/Profiler/CommitRanked.js
+2
-2
@@ -20,7 +20,7 @@ export type ItemData = {|
20
scaleX: (value: number, fallbackValue: number) => number,
21
selectedFiberID: number | null,
22
selectedFiberIndex: number,
23
- selectFiber: (id: number | null) => void,
23
+ selectFiber: (id: number | null, name: string | null) => void,
24
width: number,
25
|};
26
@@ -33,7 +33,7 @@ export default function CommitRankedAutoSizer(_: {||}) {
33
const deselectCurrentFiber = useCallback(
34
event => {
35
event.stopPropagation();
36
- selectFiber(null);
36
+ selectFiber(null, null);
37
},
38
[selectFiber]
39
);
src/devtools/views/Profiler/CommitRankedListItem.js
+1
-1
@@ -22,7 +22,7 @@ function CommitRankedListItem({ data, index, style }: Props) {
22
const handleClick = useCallback(
23
event => {
24
event.stopPropagation();
25
- selectFiber(node.id);
25
+ selectFiber(node.id, node.name);
26
},
27
[node, selectFiber]
28
);
src/devtools/views/Profiler/FlamegraphChartBuilder.js
+2
@@ -9,6 +9,7 @@ export type ChartNode = {|
9
didRender: boolean,
10
id: number,
11
label: string,
12
+ name: string,
13
offset: number,
14
selfDuration: number,
15
treeBaseDuration: number,
@@ -82,6 +83,7 @@ export function getChartData({
83
didRender,
84
id,
85
label,
86
+ name,
87
offset: parentOffset,
88
selfDuration,
89
treeBaseDuration: node.treeBaseDuration,
src/devtools/views/Profiler/Profiler.js
+9
-2
@@ -19,6 +19,7 @@ import ProfilingImportExportButtons from './ProfilingImportExportButtons';
19
import SnapshotSelector from './SnapshotSelector';
20
import SidebarCommitInfo from './SidebarCommitInfo';
21
import SidebarInteractions from './SidebarInteractions';
22
+import SidebarSelectedFiberInfo from './SidebarSelectedFiberInfo';
23
import ToggleCommitFilterModalButton from './ToggleCommitFilterModalButton';
24
25
import styles from './Profiler.css';
@@ -118,7 +119,9 @@ function SnapshotSelectorFallback() {
119
// This view's subtree uses suspense to request profiler data from the backend.
120
// NOTE that the structure of this UI should mirror NonSuspendingProfiler.
121
function SuspendingProfiler() {
121
- const { selectedTabID, selectTab } = useContext(ProfilerContext);
122
+ const { selectedFiberID, selectedTabID, selectTab } = useContext(
123
+ ProfilerContext
124
+ );
125
const { isFilterModalShowing, setIsFilterModalShowing } = useContext(
126
CommitFilterModalContext
127
);
@@ -147,7 +150,11 @@ function SuspendingProfiler() {
150
break;
151
case 'flame-chart':
152
case 'ranked-chart':
150
- sidebar = <SidebarCommitInfo />;
153
+ if (selectedFiberID !== null) {
154
+ sidebar = <SidebarSelectedFiberInfo />;
155
+ } else {
156
+ sidebar = <SidebarCommitInfo />;
157
+ }
158
break;
159
default:
160
break;
src/devtools/views/Profiler/ProfilerContext.js
+9
-3
@@ -56,7 +56,8 @@ type Context = {|
56
57
// Which fiber is currently selected in the Ranked or Flamegraph charts?
58
selectedFiberID: number | null,
59
- selectFiber: (id: number | null) => void,
59
+ selectedFiberName: string | null,
60
+ selectFiber: (id: number | null, name: string | null) => void,
61
62
// Which interaction is currently selected in the Interactions graph?
63
selectedInteractionID: number | null,
@@ -141,13 +142,15 @@ function ProfilerContextController({ children }: Props) {
142
);
143
const [selectedTabID, selectTab] = useState<TabID>('flame-chart');
144
const [selectedFiberID, selectFiberID] = useState<number | null>(null);
145
+ const [selectedFiberName, selectFiberName] = useState<string | null>(null);
146
const [selectedInteractionID, selectInteraction] = useState<number | null>(
147
null
148
);
149
150
const selectFiber = useCallback(
149
- (id: number | null) => {
151
+ (id: number | null, name: string | null) => {
152
selectFiberID(id);
153
+ selectFiberName(name);
154
if (id !== null) {
155
const index = store.getIndexOfElementID(id);
156
if (index !== null) {
@@ -155,7 +158,7 @@ function ProfilerContextController({ children }: Props) {
158
}
159
}
160
},
158
- [selectElementAtIndex, selectFiberID, store]
161
+ [selectElementAtIndex, selectFiberID, selectFiberName, store]
162
);
163
164
if (isProfiling) {
@@ -165,6 +168,7 @@ function ProfilerContextController({ children }: Props) {
168
}
169
if (selectedFiberID !== null) {
170
selectFiberID(null);
171
+ selectFiberName(null);
172
}
173
if (selectedInteractionID !== null) {
174
selectInteraction(null);
@@ -195,6 +199,7 @@ function ProfilerContextController({ children }: Props) {
199
selectCommitIndex,
200
201
selectedFiberID,
202
+ selectedFiberName,
203
selectFiber,
204
205
selectedInteractionID,
@@ -222,6 +227,7 @@ function ProfilerContextController({ children }: Props) {
227
selectCommitIndex,
228
229
selectedFiberID,
230
+ selectedFiberName,
231
selectFiber,
232
233
selectedInteractionID,
src/devtools/views/Profiler/RankedChartBuilder.js
+2
@@ -7,6 +7,7 @@ import type { CommitDetails, CommitTree, Node } from './types';
7
export type ChartNode = {|
8
id: number,
9
label: string,
10
+ name: string,
11
value: number,
12
|};
13
@@ -58,6 +59,7 @@ export function getChartData({
59
chartNodes.push({
60
id,
61
label,
62
+ name,
63
value: selfDuration,
64
});
65
});
src/devtools/views/Profiler/SidebarCommitInfo.css
+7
-2
@@ -20,20 +20,25 @@
20
}
21
22
.InteractionList {
23
- padding: 0.25rem;
23
+ margin: 0 0 0.5rem;
24
}
25
26
.ListItem {
27
margin: 0 0 0.5rem;
28
}
29
30
-.InteractionListItem {
30
+.InteractionListItem,
31
+.InteractionListItemNone {
32
padding: 0.25rem 0.5rem;
33
}
34
.InteractionListItem:hover {
35
background-color: var(--color-hover-background);
36
}
37
38
+.InteractionListItemNone {
39
+ color: var(--color-dim);
40
+}
41
+
42
.Label {
43
overflow: hidden;
44
text-overflow: ellipsis;
src/devtools/views/Profiler/SidebarCommitInfo.js
+2
-1
@@ -88,7 +88,7 @@ export default function SidebarCommitInfo(_: Props) {
88
<label className={styles.Label}>Interactions</label>:
89
<ul className={styles.InteractionList}>
90
{interactions.length === 0 ? (
91
- <li className={styles.InteractionListItem}>None</li>
91
+ <li className={styles.InteractionListItemNone}>None</li>
92
) : null}
93
{interactions.map((interaction, index) => (
94
<li
@@ -103,6 +103,7 @@ export default function SidebarCommitInfo(_: Props) {
103
</li>
104
{captureScreenshots && (
105
<li>
106
+ <label className={styles.Label}>Screenshot</label>:
107
{screenshot != null ? (
108
<img
109
alt="Screenshot"
src/devtools/views/Profiler/SidebarSelectedFiberInfo.css
new
+52
@@ -0,0 +1,52 @@
1
+.Toolbar {
2
+ height: 2.25rem;
3
+ padding: 0 0.5rem;
4
+ flex: 0 0 auto;
5
+ display: flex;
6
+ align-items: center;
7
+}
8
+
9
+.Content {
10
+ padding: 0.5rem;
11
+ user-select: none;
12
+ border-top: 1px solid var(--color-border);
13
+}
14
+
15
+.Component {
16
+ flex: 1;
17
+ color: var(--color-component-name);
18
+}
19
+.Component:before {
20
+ white-space: nowrap;
21
+ content: '<';
22
+ color: var(--color-jsx-arrow-brackets);
23
+}
24
+.Component:after {
25
+ white-space: nowrap;
26
+ content: '>';
27
+ color: var(--color-jsx-arrow-brackets);
28
+}
29
+
30
+.Label {
31
+ font-weight: bold;
32
+ margin-bottom: 0.5rem;
33
+}
34
+
35
+.List {
36
+ list-style: none;
37
+ margin: 0;
38
+ padding: 0;
39
+}
40
+
41
+.CurrentListItem,
42
+.ListItem {
43
+ padding: 0.25rem 0.5rem;
44
+}
45
+.ListItem:hover {
46
+ background-color: var(--color-hover-background);
47
+}
48
+
49
+.CurrentListItem {
50
+ background-color: var(--color-selected-background);
51
+ color: var(--color-selected-foreground);
52
+}
src/devtools/views/Profiler/SidebarSelectedFiberInfo.js
new
+79
@@ -0,0 +1,79 @@
1
+// @flow
2
+
3
+import React, { Fragment, useContext } from 'react';
4
+import { ProfilerContext } from './ProfilerContext';
5
+import { formatDuration, formatTime } from './utils';
6
+import { StoreContext } from '../context';
7
+import Button from '../Button';
8
+import ButtonIcon from '../ButtonIcon';
9
+
10
+import styles from './SidebarSelectedFiberInfo.css';
11
+
12
+export type Props = {||};
13
+
14
+export default function SidebarSelectedFiberInfo(_: Props) {
15
+ const { profilingCache } = useContext(StoreContext);
16
+ const {
17
+ rendererID,
18
+ rootID,
19
+ selectCommitIndex,
20
+ selectedCommitIndex,
21
+ selectedFiberID,
22
+ selectedFiberName,
23
+ selectFiber,
24
+ } = useContext(ProfilerContext);
25
+
26
+ const { commitTimes } = profilingCache.ProfilingSummary.read({
27
+ rendererID: ((rendererID: any): number),
28
+ rootID: ((rootID: any): number),
29
+ });
30
+
31
+ const { commitDurations } = profilingCache.FiberCommits.read({
32
+ fiberID: ((selectedFiberID: any): number),
33
+ rendererID: ((rendererID: any): number),
34
+ rootID: ((rootID: any): number),
35
+ });
36
+
37
+ const listItems = [];
38
+ for (let i = 0; i < commitDurations.length; i += 2) {
39
+ const commitIndex = commitDurations[i];
40
+ const duration = commitDurations[i + 1];
41
+ const time = commitTimes[commitIndex];
42
+
43
+ listItems.push(
44
+ <li
45
+ key={commitIndex}
46
+ className={
47
+ selectedCommitIndex === commitIndex
48
+ ? styles.CurrentListItem
49
+ : styles.ListItem
50
+ }
51
+ onClick={() => selectCommitIndex(commitIndex)}
52
+ >
53
+ {formatTime(time)}s for {formatDuration(duration)}ms
54
+ </li>
55
+ );
56
+ }
57
+
58
+ return (
59
+ <Fragment>
60
+ <div className={styles.Toolbar}>
61
+ <div className={styles.Component}>
62
+ {selectedFiberName || 'Selected component'}
63
+ </div>
64
+
65
+ <Button
66
+ className={styles.IconButton}
67
+ onClick={() => selectFiber(null, null)}
68
+ title="Back to commit view"
69
+ >
70
+ <ButtonIcon type="close" />
71
+ </Button>
72
+ </div>
73
+ <div className={styles.Content}>
74
+ <label className={styles.Label}>Rendered at</label>:
75
+ <ul className={styles.List}>{listItems}</ul>
76
+ </div>
77
+ </Fragment>
78
+ );
79
+}
src/devtools/views/Profiler/types.js
+6
@@ -34,6 +34,12 @@ export type CommitDetails = {|
34
interactions: Array<Interaction>,
35
|};
36
37
+export type FiberCommits = {|
38
+ commitDurations: Array<number>,
39
+ fiberID: number,
40
+ rootID: number,
41
+|};
42
+
43
export type ProfilingSummary = {|
44
rootID: number,
45