You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
204 lines
5.5 KiB
204 lines
5.5 KiB
/** |
|
* Copyright 2013-2015, Facebook, Inc. |
|
* All rights reserved. |
|
* |
|
* This source code is licensed under the BSD-style license found in the |
|
* 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 ReactDefaultPerfAnalysis |
|
*/ |
|
|
|
var assign = require("./Object.assign"); |
|
|
|
// Don't try to save users less than 1.2ms (a number I made up) |
|
var DONT_CARE_THRESHOLD = 1.2; |
|
var DOM_OPERATION_TYPES = { |
|
'_mountImageIntoNode': 'set innerHTML', |
|
INSERT_MARKUP: 'set innerHTML', |
|
MOVE_EXISTING: 'move', |
|
REMOVE_NODE: 'remove', |
|
TEXT_CONTENT: 'set textContent', |
|
'updatePropertyByID': 'update attribute', |
|
'deletePropertyByID': 'delete attribute', |
|
'updateStylesByID': 'update styles', |
|
'updateInnerHTMLByID': 'set innerHTML', |
|
'dangerouslyReplaceNodeWithMarkupByID': 'replace' |
|
}; |
|
|
|
function getTotalTime(measurements) { |
|
// TODO: return number of DOM ops? could be misleading. |
|
// TODO: measure dropped frames after reconcile? |
|
// TODO: log total time of each reconcile and the top-level component |
|
// class that triggered it. |
|
var totalTime = 0; |
|
for (var i = 0; i < measurements.length; i++) { |
|
var measurement = measurements[i]; |
|
totalTime += measurement.totalTime; |
|
} |
|
return totalTime; |
|
} |
|
|
|
function getDOMSummary(measurements) { |
|
var items = []; |
|
for (var i = 0; i < measurements.length; i++) { |
|
var measurement = measurements[i]; |
|
var id; |
|
|
|
for (id in measurement.writes) { |
|
measurement.writes[id].forEach(function(write) { |
|
items.push({ |
|
id: id, |
|
type: DOM_OPERATION_TYPES[write.type] || write.type, |
|
args: write.args |
|
}); |
|
}); |
|
} |
|
} |
|
return items; |
|
} |
|
|
|
function getExclusiveSummary(measurements) { |
|
var candidates = {}; |
|
var displayName; |
|
|
|
for (var i = 0; i < measurements.length; i++) { |
|
var measurement = measurements[i]; |
|
var allIDs = assign( |
|
{}, |
|
measurement.exclusive, |
|
measurement.inclusive |
|
); |
|
|
|
for (var id in allIDs) { |
|
displayName = measurement.displayNames[id].current; |
|
|
|
candidates[displayName] = candidates[displayName] || { |
|
componentName: displayName, |
|
inclusive: 0, |
|
exclusive: 0, |
|
render: 0, |
|
count: 0 |
|
}; |
|
if (measurement.render[id]) { |
|
candidates[displayName].render += measurement.render[id]; |
|
} |
|
if (measurement.exclusive[id]) { |
|
candidates[displayName].exclusive += measurement.exclusive[id]; |
|
} |
|
if (measurement.inclusive[id]) { |
|
candidates[displayName].inclusive += measurement.inclusive[id]; |
|
} |
|
if (measurement.counts[id]) { |
|
candidates[displayName].count += measurement.counts[id]; |
|
} |
|
} |
|
} |
|
|
|
// Now make a sorted array with the results. |
|
var arr = []; |
|
for (displayName in candidates) { |
|
if (candidates[displayName].exclusive >= DONT_CARE_THRESHOLD) { |
|
arr.push(candidates[displayName]); |
|
} |
|
} |
|
|
|
arr.sort(function(a, b) { |
|
return b.exclusive - a.exclusive; |
|
}); |
|
|
|
return arr; |
|
} |
|
|
|
function getInclusiveSummary(measurements, onlyClean) { |
|
var candidates = {}; |
|
var inclusiveKey; |
|
|
|
for (var i = 0; i < measurements.length; i++) { |
|
var measurement = measurements[i]; |
|
var allIDs = assign( |
|
{}, |
|
measurement.exclusive, |
|
measurement.inclusive |
|
); |
|
var cleanComponents; |
|
|
|
if (onlyClean) { |
|
cleanComponents = getUnchangedComponents(measurement); |
|
} |
|
|
|
for (var id in allIDs) { |
|
if (onlyClean && !cleanComponents[id]) { |
|
continue; |
|
} |
|
|
|
var displayName = measurement.displayNames[id]; |
|
|
|
// Inclusive time is not useful for many components without knowing where |
|
// they are instantiated. So we aggregate inclusive time with both the |
|
// owner and current displayName as the key. |
|
inclusiveKey = displayName.owner + ' > ' + displayName.current; |
|
|
|
candidates[inclusiveKey] = candidates[inclusiveKey] || { |
|
componentName: inclusiveKey, |
|
time: 0, |
|
count: 0 |
|
}; |
|
|
|
if (measurement.inclusive[id]) { |
|
candidates[inclusiveKey].time += measurement.inclusive[id]; |
|
} |
|
if (measurement.counts[id]) { |
|
candidates[inclusiveKey].count += measurement.counts[id]; |
|
} |
|
} |
|
} |
|
|
|
// Now make a sorted array with the results. |
|
var arr = []; |
|
for (inclusiveKey in candidates) { |
|
if (candidates[inclusiveKey].time >= DONT_CARE_THRESHOLD) { |
|
arr.push(candidates[inclusiveKey]); |
|
} |
|
} |
|
|
|
arr.sort(function(a, b) { |
|
return b.time - a.time; |
|
}); |
|
|
|
return arr; |
|
} |
|
|
|
function getUnchangedComponents(measurement) { |
|
// For a given reconcile, look at which components did not actually |
|
// render anything to the DOM and return a mapping of their ID to |
|
// the amount of time it took to render the entire subtree. |
|
var cleanComponents = {}; |
|
var dirtyLeafIDs = Object.keys(measurement.writes); |
|
var allIDs = assign({}, measurement.exclusive, measurement.inclusive); |
|
|
|
for (var id in allIDs) { |
|
var isDirty = false; |
|
// For each component that rendered, see if a component that triggered |
|
// a DOM op is in its subtree. |
|
for (var i = 0; i < dirtyLeafIDs.length; i++) { |
|
if (dirtyLeafIDs[i].indexOf(id) === 0) { |
|
isDirty = true; |
|
break; |
|
} |
|
} |
|
if (!isDirty && measurement.counts[id] > 0) { |
|
cleanComponents[id] = true; |
|
} |
|
} |
|
return cleanComponents; |
|
} |
|
|
|
var ReactDefaultPerfAnalysis = { |
|
getExclusiveSummary: getExclusiveSummary, |
|
getInclusiveSummary: getInclusiveSummary, |
|
getDOMSummary: getDOMSummary, |
|
getTotalTime: getTotalTime |
|
}; |
|
|
|
module.exports = ReactDefaultPerfAnalysis;
|
|
|