Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace ReactPerf with new implementation #6647

Merged
merged 11 commits into from
May 5, 2016
2 changes: 1 addition & 1 deletion grunt/tasks/npm-react-addons.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var addons = {
docs: 'two-way-binding-helpers',
},
Perf: {
module: 'ReactPerfAnalysis',
module: 'ReactPerf',
name: 'perf',
docs: 'perf',
},
Expand Down
2 changes: 1 addition & 1 deletion src/addons/ReactWithAddons.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ React.addons = {
};

if (__DEV__) {
React.addons.Perf = require('ReactPerfAnalysis');
React.addons.Perf = require('ReactPerf');
React.addons.TestUtils = require('ReactTestUtils');
}

Expand Down
7 changes: 4 additions & 3 deletions src/isomorphic/ReactDebugTool.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ function resetMeasurements() {
tree[id] = {
displayName: ReactComponentTreeDevtool.getDisplayName(id),
text: ReactComponentTreeDevtool.getText(id),
updateCount: ReactComponentTreeDevtool.getUpdateCount(id),
childIDs: ReactComponentTreeDevtool.getChildIDs(id),
// Text nodes don't have owners but this is close enough.
ownerID: ownerID || ReactComponentTreeDevtool.getOwnerID(parentID),
Expand Down Expand Up @@ -144,7 +145,7 @@ var ReactDebugTool = {
onBeginLifeCycleTimer(debugID, timerType) {
emitEvent('onBeginLifeCycleTimer', debugID, timerType);
if (__DEV__) {
if (isProfiling) {
if (isProfiling && currentFlushNesting > 0) {
warning(
!currentTimerType,
'There is an internal error in the React performance measurement code. ' +
Expand All @@ -162,7 +163,7 @@ var ReactDebugTool = {
},
onEndLifeCycleTimer(debugID, timerType) {
if (__DEV__) {
if (isProfiling) {
if (isProfiling && currentFlushNesting > 0) {
warning(
currentTimerType === timerType,
'There is an internal error in the React performance measurement code. ' +
Expand All @@ -175,7 +176,7 @@ var ReactDebugTool = {
currentFlushMeasurements.push({
timerType,
instanceID: debugID,
duration: performance.now() - currentTimerStartTime,
duration: performanceNow() - currentTimerStartTime,
});
currentTimerStartTime = null;
currentTimerDebugID = null;
Expand Down
90 changes: 50 additions & 40 deletions src/isomorphic/ReactPerfAnalysis.js → src/isomorphic/ReactPerf.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactPerfAnalysis
* @providesModule ReactPerf
*/

'use strict';
Expand All @@ -27,12 +27,10 @@ function getExclusive(flushHistory = getFlushHistory()) {
var aggregatedStats = {};
var affectedIDs = {};

function updateAggregatedStats(treeSnapshot, instanceID, applyUpdate) {
function updateAggregatedStats(treeSnapshot, instanceID, timerType, applyUpdate) {
var {displayName} = treeSnapshot[instanceID];

var key = displayName;
var stats = aggregatedStats[key];

if (!stats) {
affectedIDs[key] = {};
stats = aggregatedStats[key] = {
Expand All @@ -43,7 +41,12 @@ function getExclusive(flushHistory = getFlushHistory()) {
totalDuration: 0,
};
}

if (!stats.durations[timerType]) {
stats.durations[timerType] = 0;
}
if (!stats.counts[timerType]) {
stats.counts[timerType] = 0;
}
affectedIDs[key][instanceID] = true;
applyUpdate(stats);
}
Expand All @@ -52,17 +55,9 @@ function getExclusive(flushHistory = getFlushHistory()) {
var {measurements, treeSnapshot} = flush;
measurements.forEach(measurement => {
var {duration, instanceID, timerType} = measurement;
updateAggregatedStats(treeSnapshot, instanceID, stats => {
updateAggregatedStats(treeSnapshot, instanceID, timerType, stats => {
stats.totalDuration += duration;

if (!stats.durations[timerType]) {
stats.durations[timerType] = 0;
}
stats.durations[timerType] += duration;

if (!stats.counts[timerType]) {
stats.counts[timerType] = 0;
}
stats.counts[timerType]++;
});
});
Expand All @@ -73,20 +68,20 @@ function getExclusive(flushHistory = getFlushHistory()) {
...aggregatedStats[key],
instanceCount: Object.keys(affectedIDs[key]).length,
}))
.sort((a, b) => b.totalDuration - a.totalDuration);
.sort((a, b) =>
b.totalDuration - a.totalDuration
);
}

function getInclusive(flushHistory = getFlushHistory(), wastedOnly) {
function getInclusive(flushHistory = getFlushHistory()) {
var aggregatedStats = {};
var affectedIDs = {};

function updateAggregatedStats(treeSnapshot, instanceID, applyUpdate) {
var {displayName, ownerID} = treeSnapshot[instanceID];

var owner = treeSnapshot[ownerID];
var key = `${owner ? owner.displayName : '(no owner)'} > ${displayName}`;
var key = (owner ? owner.displayName + ' > ' : '') + displayName;
var stats = aggregatedStats[key];

if (!stats) {
affectedIDs[key] = {};
stats = aggregatedStats[key] = {
Expand All @@ -96,20 +91,19 @@ function getInclusive(flushHistory = getFlushHistory(), wastedOnly) {
renderCount: 0,
};
}

affectedIDs[key][instanceID] = true;
applyUpdate(stats);
}

var hasRenderedByID = {};
var isCompositeByID = {};
flushHistory.forEach(flush => {
var {measurements} = flush;
measurements.forEach(measurement => {
var {instanceID, timerType} = measurement;
if (timerType !== 'render') {
return;
}
hasRenderedByID[instanceID] = true;
isCompositeByID[instanceID] = true;
});
});

Expand All @@ -125,7 +119,9 @@ function getInclusive(flushHistory = getFlushHistory(), wastedOnly) {
});
var nextParentID = instanceID;
while (nextParentID) {
if (hasRenderedByID[nextParentID]) {
// As we traverse parents, only count inclusive time towards composites.
// We know something is a composite if its render() was called.
if (isCompositeByID[nextParentID]) {
updateAggregatedStats(treeSnapshot, nextParentID, stats => {
stats.inclusiveRenderDuration += duration;
});
Expand All @@ -140,7 +136,9 @@ function getInclusive(flushHistory = getFlushHistory(), wastedOnly) {
...aggregatedStats[key],
instanceCount: Object.keys(affectedIDs[key]).length,
}))
.sort((a, b) => b.inclusiveRenderDuration - a.inclusiveRenderDuration);
.sort((a, b) =>
b.inclusiveRenderDuration - a.inclusiveRenderDuration
);
}

function getWasted(flushHistory = getFlushHistory()) {
Expand All @@ -149,11 +147,9 @@ function getWasted(flushHistory = getFlushHistory()) {

function updateAggregatedStats(treeSnapshot, instanceID, applyUpdate) {
var {displayName, ownerID} = treeSnapshot[instanceID];

var owner = treeSnapshot[ownerID];
var key = `${owner ? owner.displayName : '(no owner)'} > ${displayName}`;
var key = (owner ? owner.displayName + ' > ' : '') + displayName;
var stats = aggregatedStats[key];

if (!stats) {
affectedIDs[key] = {};
stats = aggregatedStats[key] = {
Expand All @@ -163,25 +159,27 @@ function getWasted(flushHistory = getFlushHistory()) {
renderCount: 0,
};
}

affectedIDs[key][instanceID] = true;
applyUpdate(stats);
}

flushHistory.forEach(flush => {
var {measurements, treeSnapshot, operations} = flush;
var dirtyInstanceIDs = {};
var isDefinitelyNotWastedByID = {};

// Find native components associated with an operation in this batch.
// Mark all components in their parent tree as definitely not wasted.
operations.forEach(operation => {
var {instanceID} = operation;

var nextParentID = instanceID;
while (nextParentID) {
dirtyInstanceIDs[nextParentID] = true;
isDefinitelyNotWastedByID[nextParentID] = true;
nextParentID = treeSnapshot[nextParentID].parentID;
}
});

// Find composite components that rendered in this batch.
// These are potential candidates for being wasted renders.
var renderedCompositeIDs = {};
measurements.forEach(measurement => {
var {instanceID, timerType} = measurement;
Expand All @@ -196,21 +194,31 @@ function getWasted(flushHistory = getFlushHistory()) {
if (timerType !== 'render') {
return;
}

// If there was a DOM update below this component, or it has just been
// mounted, its render() is not considered wasted.
var { updateCount } = treeSnapshot[instanceID];
if (dirtyInstanceIDs[instanceID] || updateCount === 0) {
if (isDefinitelyNotWastedByID[instanceID] || updateCount === 0) {
return;
}

// We consider this render() wasted.
updateAggregatedStats(treeSnapshot, instanceID, stats => {
stats.renderCount++;
});

var nextParentID = instanceID;
while (nextParentID) {
if (!renderedCompositeIDs[nextParentID]) {
break;
// Any parents rendered during this batch are considered wasted
// unless we previously marked them as dirty.
var isWasted =
renderedCompositeIDs[nextParentID] &&
!isDefinitelyNotWastedByID[nextParentID];
if (isWasted) {
updateAggregatedStats(treeSnapshot, nextParentID, stats => {
stats.inclusiveRenderDuration += duration;
});
}
updateAggregatedStats(treeSnapshot, nextParentID, stats => {
stats.inclusiveRenderDuration += duration;
});
nextParentID = treeSnapshot[nextParentID].parentID;
}
});
Expand All @@ -221,7 +229,9 @@ function getWasted(flushHistory = getFlushHistory()) {
...aggregatedStats[key],
instanceCount: Object.keys(affectedIDs[key]).length,
}))
.sort((a, b) => b.inclusiveRenderDuration - a.inclusiveRenderDuration);
.sort((a, b) =>
b.inclusiveRenderDuration - a.inclusiveRenderDuration
);
}

function getOperations(flushHistory = getFlushHistory()) {
Expand All @@ -232,7 +242,7 @@ function getOperations(flushHistory = getFlushHistory()) {
var {instanceID, type, payload} = operation;
var {displayName, ownerID} = treeSnapshot[instanceID];
var owner = treeSnapshot[ownerID];
var key = `${(owner ? owner.displayName : '(no owner)')} > ${displayName}`;
var key = (owner ? owner.displayName + ' > ' : '') + displayName;

stats.push({
flushIndex,
Expand Down Expand Up @@ -342,7 +352,7 @@ function stop() {
}

var ReactPerfAnalysis = {
getFlushHistory,
getLastMeasurements: getFlushHistory,
getExclusive,
getInclusive,
getWasted,
Expand Down
Loading