@samitouri / QOS-React / commits / 44c0febd23

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