Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Profiler plug-in for DevTools #1069

Merged
merged 138 commits into from
Aug 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
138 commits
Select commit Hold shift + click to select a range
0126b31
Added package script shortcut for testing plain shell
May 24, 2018
dcff7db
Added record profile button and ooked up to Store/Agent/Hook
May 30, 2018
ecd2b8c
ProfilerManager accummulates commit snapsots
May 30, 2018
245683d
ProfilerManager turns profiling on/off for roots
May 30, 2018
6f6d3ac
Deeply enable ProfileMode for all roots
May 30, 2018
2d44eb2
Added Profiler plugin/panel
May 30, 2018
f1a8a51
Added some debug logging of commit snapshot
May 31, 2018
5330628
Removed react-addons-create-fragment dependency and usage
May 31, 2018
28ccef7
Added prop-types package and ran codemod
May 31, 2018
5018f5a
Upgrade react and react-dom to 16
May 31, 2018
341f226
Hackily building a tree with profiling data on commit
Jun 1, 2018
e094f98
Profile contains DevTools internal (string) ID rather than Fiber
Jun 1, 2018
697e4d0
Not re-constructing the full tree anymore. Too hacky. Just using the …
Jun 1, 2018
c9a3c0e
Iterating on the Profiler tab and flame graph. Stashing
Jun 8, 2018
54ac11c
Iterating on Profiler
Jun 14, 2018
7e09743
STASHING
Jun 18, 2018
27dc2c5
Added react-virtualized-auto-sizer to make graph fill panel height
Jun 18, 2018
1428065
Color non-rendering nodes differently
Jun 18, 2018
7dfafdc
Cleaned up flamegraph a little
Jun 18, 2018
02ea7d3
Removed d3-tip in favor of built-in browser title
Jun 18, 2018
83bff00
Cleaned up cell and graph size variable names
Jun 18, 2018
d683797
Flamegraph resizes width dynamically
Jun 18, 2018
17ef7dd
Merge branch 'master' into profiler-poc
Jun 18, 2018
a72e0f7
Removed package-lock
Jun 18, 2018
a0fd026
Fixed duplicate naming conflicts
Jun 18, 2018
d0fe44c
Reverted changes to plain shell HTML
Jun 18, 2018
1588457
Reorganized views
Jun 18, 2018
80a4b0a
Added initial weighted graph, re-organized styles for better sharing
Jun 18, 2018
78598ea
Stashing
Jun 19, 2018
ee5e9da
Flow fixes
Jun 19, 2018
7973460
Weighted -> Ranked
Jun 19, 2018
3a5523a
Click to zoom into Ranked chart
Jun 19, 2018
9d8054f
Replaced D3 in RankedSnapshot with SVG
Jun 19, 2018
75bc843
Removed d3 entirely
Jun 19, 2018
22dc1a9
Cleanup CSS and constants shared between graphs
Jun 20, 2018
35acf8b
Fixed animated moving
Jun 20, 2018
9a05b3e
Added icons to commit range buttons
Jun 20, 2018
4ee74f8
Removed chart CSS; inline styles (with theme compat) replaced
Jun 20, 2018
55a313d
Added initial times-for-fiber view
Jun 20, 2018
9f78a14
Double click in fiber-times graph to jump to commit in previously sel…
Jun 20, 2018
21c5ce7
Iterating on bar chart
Jun 20, 2018
e508b95
Bugfix and TODO in bar chart
Jun 20, 2018
78331ba
Added hover cursor to chart rects
Jun 20, 2018
40aa22a
Disabled commit button styles
Jun 20, 2018
b9c0f28
Bar chart lables above small nodes
Jun 20, 2018
e079292
Added 'render N/X' label
Jun 20, 2018
33c77ee
Fixed clumping selected/focused node bug
Jun 20, 2018
01f512a
Cleaned up comments
Jun 21, 2018
e835379
Updated react-window to not create so many <svg> tags
Jun 21, 2018
194e2d1
Filter native components from ranked view
Jun 21, 2018
6430a90
Iterating on UI
Jun 21, 2018
b158ba6
STashing
Jun 22, 2018
5dbf9f5
Stashing
Jun 22, 2018
b0be942
Fixed some lint warnings
Jun 22, 2018
93eb866
Virtualized ranked chart
Jun 22, 2018
e68c2ed
Filter tiny flamegraph nodes that are too small to see/click to rende…
Jun 22, 2018
7a93fc9
Lazily create Snapshot graph data. Add option to filter native nodes.
Jun 25, 2018
b01dbb6
Cache computed chart data in Profiler store
Jun 25, 2018
8afa11d
Improve TTI for flamegraph by reducing the amount of parsed info
Jun 25, 2018
43093d8
Track focused root ID as well
Jun 25, 2018
907709d
Fixed some filtering bugs
Jun 26, 2018
c7afd9d
Fixed edge-case scaling bug with conditional fibers
Jun 26, 2018
f1da4e5
Differentiate between mount/update and timing changes
Jun 26, 2018
ed385ec
Edge case handling for empty charts
Jun 26, 2018
9142348
Initial stab at multi-root UI
Jun 26, 2018
4ee6298
Flattened data cache structure
Jun 26, 2018
1654000
Improved no-data check for flamegraph
Jun 26, 2018
e5dc241
Force root selection in elements tab before profiling
Jun 26, 2018
25581a0
Updated TODO comments; deleted unused file
Jun 27, 2018
55f11ce
Comments, mostly
Jun 27, 2018
fb275a4
Ranked view supports show/hide native components too
Jun 27, 2018
2d17b29
Fixed filtering bug in ranked view
Jun 28, 2018
98f800f
Upgraded to react-window alpha 4
Jul 4, 2018
4fcae91
SVG text shouldn't block rect mouse clicks
Jul 4, 2018
ee2ad5d
Renamed *BaseTime to *BaseDuration
Jul 6, 2018
3fb1a78
Changed color scheme to help with red/green color blindness
Jul 8, 2018
9883f15
Merge branch 'master' into profiler-poc
Jul 17, 2018
88790b5
Changed "No Data..." message
Jul 20, 2018
02d367b
Upgraded [email protected]
Jul 22, 2018
e050b2e
MVP integration with "interactions" based on PR #13253
Jul 27, 2018
fde3917
Renamed RankedSnapshot to SnapshotRanked
Jul 27, 2018
87ff4c2
Refactored Profiler toolbar UI
Jul 27, 2018
3e5c709
Cleaned up UI code a little more
Jul 27, 2018
d1146e6
Improved interactions UI; fixed queueing bug in ProfilerStore snapsho…
Jul 27, 2018
4639094
Improved interactions UI
Jul 27, 2018
685d80b
Improved InteractionTimeline based on feedback
Jul 28, 2018
2732883
Improved Interaction selected state indicator
Jul 28, 2018
309ac53
Cache interaction data to avoid recomputing
Jul 28, 2018
58a48ff
Cleaned up inline styles a bit in the ProfilerTab
Jul 28, 2018
fc12835
Added Interactions detail pane
Jul 28, 2018
add34da
Removed outdated TODO
Jul 28, 2018
ad96063
Bikeshedding styles
Jul 28, 2018
8f366d0
Changed interactions window not to trim by startTime
Jul 28, 2018
8ce6bfb
Offset commit times by start record time
Jul 28, 2018
9e70dd8
Re-added show-native-nodes toggle
Jul 28, 2018
430cec8
Reset isInspectingSelectedFiber when recording
Jul 28, 2018
8f64583
Fixed "ms" to "s" mislabel
Jul 28, 2018
4459702
Improved Interactions list hover UX slightly
Jul 29, 2018
7958a96
Fixed occasional Profiler plugin detection issue
Jul 30, 2018
ca8bb88
ProfilerTab settings are saved between sessions using chrome.storage.…
Jul 30, 2018
3d0863b
UI tweaks
Jul 30, 2018
ef6ae64
Add chrome-is-not-undefined check
Jul 30, 2018
9277107
Fixed interactions-to-snapshot link
Jul 30, 2018
f91fa18
Added color bounds checking
Jul 31, 2018
c980d25
Fixed interaction start time / display
Jul 31, 2018
202f5ca
Edge case guards against null profilerData
Jul 31, 2018
a74cad9
Fixex UI bug with interaction bar width
Jul 31, 2018
d6ab9fb
Added CPU % to interaction details pane
Jul 31, 2018
f9e7ae0
Added snapshot/commit details pane
Jul 31, 2018
966075e
Replaced browser storage API with localStorage
Jul 31, 2018
d64d218
Edge case guards against CPU% bounds
Jul 31, 2018
71d0161
UI tweaks
Jul 31, 2018
1ddfef7
Cleaned up Snapshot duration calculation a bit
Aug 1, 2018
8ccd884
JSON serialize localStorage so non-string values also work
Aug 1, 2018
1aaa6cd
Improved UI/UX when inspecting a fiber
Aug 1, 2018
541ef7b
Flow fix
Aug 1, 2018
fe47b4c
Merge branch 'master' into profiler-poc
Aug 2, 2018
dd2862b
Added up/down key navigation for interactions list
Aug 2, 2018
3307255
Removed some TODO comments that were outdated
Aug 3, 2018
32459d1
Renamed TimeoutComponent -> Placeholder
Aug 3, 2018
8dadc0e
Nit method rename
Aug 3, 2018
b4aab3d
Removed unused CSS/style loaders
Aug 3, 2018
1a14e8b
Reverted some more unnecessary changes
Aug 3, 2018
c586c73
Reverted changes to 'test' folder
Aug 3, 2018
4fcbf52
Profiler works with non-interactions-capable versions of React too
Aug 4, 2018
bd1d916
Surface render count in Profiler fiber detail pane
Aug 6, 2018
471bd16
Updated to react-window 1.1.1
Aug 6, 2018
fba1159
Replaced HTML slider with windowed bar chart selector
Aug 6, 2018
cdaf733
Converted Fiber render bar chart to use react-window also
Aug 7, 2018
c836def
Selector snapshot tweaks: click-to-drag and disabled state
Aug 8, 2018
adfb09f
Selector snapshot tweak: color 0ms durations gray
Aug 9, 2018
215fd6b
Lots of little UI tweaks
Aug 9, 2018
dedb6a4
Added interaction tracking API link. Hide multi-root message for apps…
Aug 9, 2018
a25a047
Added text for show-native-nodes button
Aug 9, 2018
27fc92f
Highlight the selected snapshot in the fiber render times pane
Aug 9, 2018
54ba1f8
Improved no-render-times message
Aug 9, 2018
ba75086
Replaced reactjs.org URL with fb.me URL
Aug 14, 2018
50d4505
Organized hook subscriptions in inject for better clarity
Aug 14, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ plugins:
- react

globals:
chrome: false
React$Element: false

rules:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
build
node_modules
npm-debug.log
yarn-error.log
.DS_Store

30 changes: 29 additions & 1 deletion agent/Agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ class Agent extends EventEmitter {
bridge.on('startInspecting', () => this.emit('startInspecting'));
bridge.on('stopInspecting', () => this.emit('stopInspecting'));
bridge.on('selected', id => this.emit('selected', id));
bridge.on('isRecording', isRecording => this.emit('isRecording', isRecording));
bridge.on('setInspectEnabled', enabled => {
this._inspectEnabled = enabled;
this.emit('stopInspecting');
Expand Down Expand Up @@ -210,6 +211,7 @@ class Agent extends EventEmitter {
this.on('root', id => bridge.send('root', id));
this.on('mount', data => bridge.send('mount', data));
this.on('update', data => bridge.send('update', data));
this.on('updateProfileTimes', data => bridge.send('updateProfileTimes', data));
this.on('unmount', id => {
bridge.send('unmount', id);
// once an element has been unmounted, the bridge doesn't need to be
Expand All @@ -218,6 +220,9 @@ class Agent extends EventEmitter {
});
this.on('setSelection', data => bridge.send('select', data));
this.on('setInspectEnabled', data => bridge.send('setInspectEnabled', data));
this.on('isRecording', isRecording => bridge.send('isRecording', isRecording));
this.on('storeSnapshot', (data) => bridge.send('storeSnapshot', data));
this.on('clearSnapshots', () => bridge.send('clearSnapshots'));
}

scrollToNode(id: ElementID): void {
Expand Down Expand Up @@ -375,6 +380,11 @@ class Agent extends EventEmitter {
this.emit('root', id);
}

rootCommitted(renderer: RendererID, internalInstance: OpaqueNodeHandle) {
var id = this.getId(internalInstance);
this.emit('rootCommitted', id, internalInstance);
}

onMounted(renderer: RendererID, component: OpaqueNodeHandle, data: DataType) {
var id = this.getId(component);
this.renderers.set(id, renderer);
Expand Down Expand Up @@ -406,10 +416,28 @@ class Agent extends EventEmitter {
this.emit('update', send);
}

onUpdatedProfileTimes(component: OpaqueNodeHandle, data: DataType) {
var id = this.getId(component);
this.elementData.set(id, data);

var send = assign({}, data);
if (send.children && send.children.map) {
send.children = send.children.map(c => this.getId(c));
}
send.id = id;
send.canUpdate = send.updater && !!send.updater.forceUpdate;
delete send.type;
delete send.updater;
this.emit('updateProfileTimes', send);
}

onUnmounted(component: OpaqueNodeHandle) {
var id = this.getId(component);
this.elementData.delete(id);
this.roots.delete(id);
if (this.roots.has(id)) {
this.roots.delete(id);
this.emit('rootUnmounted', id);
}
this.renderers.delete(id);
this.emit('unmount', id);
this.idsByInternalInstances.delete(component);
Expand Down
9 changes: 7 additions & 2 deletions agent/inject.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@ var setupBackend = require('../backend/backend');

module.exports = function(hook: Hook, agent: Agent) {
var subs = [
// Basic functionality
hook.sub('renderer-attached', ({id, renderer, helpers}) => {
agent.setReactInternals(id, helpers);
helpers.walkTree(agent.onMounted.bind(agent, id), agent.addRoot.bind(agent, id));
}),
hook.sub('root', ({renderer, internalInstance}) => agent.addRoot(renderer, internalInstance)),
hook.sub('mount', ({renderer, internalInstance, data}) => agent.onMounted(renderer, internalInstance, data)),
hook.sub('update', ({renderer, internalInstance, data}) => agent.onUpdated(internalInstance, data)),
hook.sub('unmount', ({renderer, internalInstance}) => agent.onUnmounted(internalInstance)),
hook.sub('update', ({renderer, internalInstance, data}) => agent.onUpdated(internalInstance, data)),

// Required by Profiler plugin
hook.sub('root', ({renderer, internalInstance}) => agent.addRoot(renderer, internalInstance)),
hook.sub('rootCommitted', ({renderer, internalInstance}) => agent.rootCommitted(renderer, internalInstance)),
hook.sub('updateProfileTimes', ({renderer, internalInstance, data}) => agent.onUpdatedProfileTimes(internalInstance, data)),
];

var success = setupBackend(hook);
Expand Down
2 changes: 2 additions & 0 deletions backend/ReactTypeOfWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@ module.exports = {
ContextConsumer: 12,
ContextProvider: 13,
ForwardRef: 14,
Profiler: 15,
Placeholder: 16,
};
40 changes: 39 additions & 1 deletion backend/attachRendererFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ function attachRendererFiber(hook: Hook, rid: string, renderer: ReactRenderer):
}
}

function haveProfilerTimesChanged(prevFiber, nextFiber) {
return (
prevFiber.actualDuration !== undefined && // Short-circuit check for non-profiling builds
(
prevFiber.actualDuration !== nextFiber.actualDuration ||
prevFiber.actualStartTime !== nextFiber.actualStartTime ||
prevFiber.selfBaseDuration !== nextFiber.selfBaseDuration ||
prevFiber.treeBaseDuration !== nextFiber.treeBaseDuration
)
);
}

let pendingEvents = [];

function flushPendingEvents() {
Expand Down Expand Up @@ -94,7 +106,22 @@ function attachRendererFiber(hook: Hook, rid: string, renderer: ReactRenderer):
}

function enqueueUpdateIfNecessary(fiber, hasChildOrderChanged) {
if (!hasChildOrderChanged && !hasDataChanged(fiber.alternate, fiber)) {
if (
!hasChildOrderChanged &&
!hasDataChanged(fiber.alternate, fiber)
) {
// If only timing information has changed, we still need to update the nodes.
// But we can do it in a faster way since we know it's safe to skip the children.
// It's also important to avoid emitting an "update" signal for the node in this case,
// Since that would indicate to the Profiler that it was part of the "commit" when it wasn't.
if (haveProfilerTimesChanged(fiber.alternate, fiber)) {
pendingEvents.push({
internalInstance: getOpaqueNode(fiber),
data: getDataFiber(fiber, getOpaqueNode),
renderer: rid,
type: 'updateProfileTimes',
});
}
return;
}
pendingEvents.push({
Expand Down Expand Up @@ -125,6 +152,14 @@ function attachRendererFiber(hook: Hook, rid: string, renderer: ReactRenderer):
opaqueNodes.delete(opaqueNode);
}

function markRootCommitted(fiber) {
pendingEvents.push({
internalInstance: getOpaqueNode(fiber),
renderer: rid,
type: 'rootCommitted',
});
}

function mountFiber(fiber) {
// Depth-first.
// Logs mounting of children first, parents later.
Expand Down Expand Up @@ -207,6 +242,7 @@ function attachRendererFiber(hook: Hook, rid: string, renderer: ReactRenderer):
hook.getFiberRoots(rid).forEach(root => {
// Hydrate all the roots for the first time.
mountFiber(root.current);
markRootCommitted(root.current);
});
flushPendingEvents();
}
Expand All @@ -226,6 +262,7 @@ function attachRendererFiber(hook: Hook, rid: string, renderer: ReactRenderer):
function handleCommitFiberRoot(root) {
const current = root.current;
const alternate = current.alternate;

if (alternate) {
// TODO: relying on this seems a bit fishy.
const wasMounted = alternate.memoizedState != null && alternate.memoizedState.element != null;
Expand All @@ -244,6 +281,7 @@ function attachRendererFiber(hook: Hook, rid: string, renderer: ReactRenderer):
// Mount a new root.
mountFiber(current);
}
markRootCommitted(current);
// We're done here.
flushPendingEvents();
}
Expand Down
18 changes: 17 additions & 1 deletion backend/getDataFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ function getDataFiber(fiber: Object, getOpaqueNode: (fiber: Object) => Object):
var name = null;
var text = null;

// Profiler data
var actualDuration = null;
var actualStartTime = null;
var treeBaseDuration = null;

switch (fiber.tag) {
case FunctionalComponent:
case ClassComponent:
Expand Down Expand Up @@ -185,7 +190,7 @@ function getDataFiber(fiber: Object, getOpaqueNode: (fiber: Object) => Object):
case PROFILER_SYMBOL_STRING:
nodeType = 'Special';
props = fiber.memoizedProps;
name = 'Profiler';
name = `Profiler(${fiber.memoizedProps.id})`;
children = [];
break;
default:
Expand All @@ -206,6 +211,12 @@ function getDataFiber(fiber: Object, getOpaqueNode: (fiber: Object) => Object):
}
}

if (fiber.actualDuration !== undefined) {
actualDuration = fiber.actualDuration;
actualStartTime = fiber.actualStartTime;
treeBaseDuration = fiber.treeBaseDuration;
}

// $FlowFixMe
return {
nodeType,
Expand All @@ -221,6 +232,11 @@ function getDataFiber(fiber: Object, getOpaqueNode: (fiber: Object) => Object):
text,
updater,
publicInstance,

// Profiler data
actualDuration,
actualStartTime,
treeBaseDuration,
};
}

Expand Down
6 changes: 3 additions & 3 deletions frontend/Breadcrumb.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import type Store from './Store';
import type {ElementID} from './types';
import type {Theme} from './types';

var {sansSerif} = require('./Themes/Fonts');
const {sansSerif} = require('./Themes/Fonts');
const PropTypes = require('prop-types');
var React = require('react');
var decorate = require('./decorate');
const React = require('react');
const decorate = require('./decorate');

type BreadcrumbPath = Array<{id: ElementID, node: Object}>;

Expand Down
11 changes: 5 additions & 6 deletions frontend/ContextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,12 @@
'use strict';

const PropTypes = require('prop-types');
const React = require('react');
const nullthrows = require('nullthrows').default;
const {sansSerif} = require('./Themes/Fonts');
const HighlightHover = require('./HighlightHover');

var React = require('react');
var nullthrows = require('nullthrows').default;
var {sansSerif} = require('./Themes/Fonts');
var HighlightHover = require('./HighlightHover');

var decorate = require('./decorate');
const decorate = require('./decorate');

import type {Theme} from './types';

Expand Down
12 changes: 6 additions & 6 deletions frontend/DataView/DataView.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@

import type {Theme, DOMEvent} from '../types';

var {sansSerif} = require('../Themes/Fonts');
const {sansSerif} = require('../Themes/Fonts');
const PropTypes = require('prop-types');
var React = require('react');
var Simple = require('./Simple');
var nullthrows = require('nullthrows').default;
const React = require('react');
const Simple = require('./Simple');
const nullthrows = require('nullthrows').default;

var consts = require('../../agent/consts');
var previewComplex = require('./previewComplex');
const consts = require('../../agent/consts');
const previewComplex = require('./previewComplex');

type Inspect = (path: Array<string>, cb: () => void) => void;
type ShowMenu = boolean | (e: DOMEvent, val: any, path: Array<string>, name: string) => void;
Expand Down
15 changes: 7 additions & 8 deletions frontend/DataView/Simple.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,15 @@
*/
'use strict';

const PropTypes = require('prop-types');

var React = require('react');
var ReactDOM = require('react-dom');
import type {Theme, DOMEvent, DOMNode} from '../types';

var Input = require('../Input');
var flash = require('../flash');
var {monospace} = require('../Themes/Fonts');
const PropTypes = require('prop-types');
const React = require('react');
const ReactDOM = require('react-dom');

import type {Theme, DOMEvent, DOMNode} from '../types';
const Input = require('../Input');
const flash = require('../flash');
const {monospace} = require('../Themes/Fonts');

type Props = {
data: any,
Expand Down
4 changes: 2 additions & 2 deletions frontend/DataView/previewComplex.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
*/
'use strict';

import type {Theme} from '../types';

var React = require('react');

var consts = require('../../agent/consts');

import type {Theme} from '../types';

function previewComplex(data: Object, theme: Theme) {
const style={ color: theme.special04 };

Expand Down
5 changes: 2 additions & 3 deletions frontend/HighlightHover.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@
'use strict';

const PropTypes = require('prop-types');

var React = require('react');
var assign = require('object-assign');
const React = require('react');
const assign = require('object-assign');

import type {Theme} from './types';

Expand Down
Loading