2017-05-03 15:35:00 +02:00
/ * *
* Copyright 2016 - present , 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 .
*
*
* /
'use strict' ;
var _assign = require ( 'object-assign' ) ;
var _extends = _assign || function ( target ) { for ( var i = 1 ; i < arguments . length ; i ++ ) { var source = arguments [ i ] ; for ( var key in source ) { if ( Object . prototype . hasOwnProperty . call ( source , key ) ) { target [ key ] = source [ key ] ; } } } return target ; } ;
var ReactDebugTool = require ( './ReactDebugTool' ) ;
2017-08-14 05:01:11 +02:00
var lowPriorityWarning = require ( './lowPriorityWarning' ) ;
2017-05-03 15:35:00 +02:00
var alreadyWarned = false ;
function roundFloat ( val ) {
var base = arguments . length > 1 && arguments [ 1 ] !== undefined ? arguments [ 1 ] : 2 ;
var n = Math . pow ( 10 , base ) ;
return Math . floor ( val * n ) / n ;
}
// Flow type definition of console.table is too strict right now, see
// https://github.com/facebook/flow/pull/2353 for updates
function consoleTable ( table ) {
console . table ( table ) ;
}
function warnInProduction ( ) {
if ( alreadyWarned ) {
return ;
}
alreadyWarned = true ;
if ( typeof console !== 'undefined' ) {
console . error ( 'ReactPerf is not supported in the production builds of React. ' + 'To collect measurements, please use the development build of React instead.' ) ;
}
}
function getLastMeasurements ( ) {
if ( ! ( process . env . NODE _ENV !== 'production' ) ) {
warnInProduction ( ) ;
return [ ] ;
}
return ReactDebugTool . getFlushHistory ( ) ;
}
function getExclusive ( ) {
var flushHistory = arguments . length > 0 && arguments [ 0 ] !== undefined ? arguments [ 0 ] : getLastMeasurements ( ) ;
if ( ! ( process . env . NODE _ENV !== 'production' ) ) {
warnInProduction ( ) ;
return [ ] ;
}
var aggregatedStats = { } ;
var affectedIDs = { } ;
function updateAggregatedStats ( treeSnapshot , instanceID , timerType , applyUpdate ) {
var displayName = treeSnapshot [ instanceID ] . displayName ;
var key = displayName ;
var stats = aggregatedStats [ key ] ;
if ( ! stats ) {
affectedIDs [ key ] = { } ;
stats = aggregatedStats [ key ] = {
key : key ,
instanceCount : 0 ,
counts : { } ,
durations : { } ,
totalDuration : 0
} ;
}
if ( ! stats . durations [ timerType ] ) {
stats . durations [ timerType ] = 0 ;
}
if ( ! stats . counts [ timerType ] ) {
stats . counts [ timerType ] = 0 ;
}
affectedIDs [ key ] [ instanceID ] = true ;
applyUpdate ( stats ) ;
}
flushHistory . forEach ( function ( flush ) {
var measurements = flush . measurements ,
treeSnapshot = flush . treeSnapshot ;
measurements . forEach ( function ( measurement ) {
var duration = measurement . duration ,
instanceID = measurement . instanceID ,
timerType = measurement . timerType ;
updateAggregatedStats ( treeSnapshot , instanceID , timerType , function ( stats ) {
stats . totalDuration += duration ;
stats . durations [ timerType ] += duration ;
stats . counts [ timerType ] ++ ;
} ) ;
} ) ;
} ) ;
return Object . keys ( aggregatedStats ) . map ( function ( key ) {
return _extends ( { } , aggregatedStats [ key ] , {
instanceCount : Object . keys ( affectedIDs [ key ] ) . length
} ) ;
} ) . sort ( function ( a , b ) {
return b . totalDuration - a . totalDuration ;
} ) ;
}
function getInclusive ( ) {
var flushHistory = arguments . length > 0 && arguments [ 0 ] !== undefined ? arguments [ 0 ] : getLastMeasurements ( ) ;
if ( ! ( process . env . NODE _ENV !== 'production' ) ) {
warnInProduction ( ) ;
return [ ] ;
}
var aggregatedStats = { } ;
var affectedIDs = { } ;
function updateAggregatedStats ( treeSnapshot , instanceID , applyUpdate ) {
var _treeSnapshot$instanc = treeSnapshot [ instanceID ] ,
displayName = _treeSnapshot$instanc . displayName ,
ownerID = _treeSnapshot$instanc . ownerID ;
var owner = treeSnapshot [ ownerID ] ;
var key = ( owner ? owner . displayName + ' > ' : '' ) + displayName ;
var stats = aggregatedStats [ key ] ;
if ( ! stats ) {
affectedIDs [ key ] = { } ;
stats = aggregatedStats [ key ] = {
key : key ,
instanceCount : 0 ,
inclusiveRenderDuration : 0 ,
renderCount : 0
} ;
}
affectedIDs [ key ] [ instanceID ] = true ;
applyUpdate ( stats ) ;
}
var isCompositeByID = { } ;
flushHistory . forEach ( function ( flush ) {
var measurements = flush . measurements ;
measurements . forEach ( function ( measurement ) {
var instanceID = measurement . instanceID ,
timerType = measurement . timerType ;
if ( timerType !== 'render' ) {
return ;
}
isCompositeByID [ instanceID ] = true ;
} ) ;
} ) ;
flushHistory . forEach ( function ( flush ) {
var measurements = flush . measurements ,
treeSnapshot = flush . treeSnapshot ;
measurements . forEach ( function ( measurement ) {
var duration = measurement . duration ,
instanceID = measurement . instanceID ,
timerType = measurement . timerType ;
if ( timerType !== 'render' ) {
return ;
}
updateAggregatedStats ( treeSnapshot , instanceID , function ( stats ) {
stats . renderCount ++ ;
} ) ;
var nextParentID = instanceID ;
while ( 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 , function ( stats ) {
stats . inclusiveRenderDuration += duration ;
} ) ;
}
nextParentID = treeSnapshot [ nextParentID ] . parentID ;
}
} ) ;
} ) ;
return Object . keys ( aggregatedStats ) . map ( function ( key ) {
return _extends ( { } , aggregatedStats [ key ] , {
instanceCount : Object . keys ( affectedIDs [ key ] ) . length
} ) ;
} ) . sort ( function ( a , b ) {
return b . inclusiveRenderDuration - a . inclusiveRenderDuration ;
} ) ;
}
function getWasted ( ) {
var flushHistory = arguments . length > 0 && arguments [ 0 ] !== undefined ? arguments [ 0 ] : getLastMeasurements ( ) ;
if ( ! ( process . env . NODE _ENV !== 'production' ) ) {
warnInProduction ( ) ;
return [ ] ;
}
var aggregatedStats = { } ;
var affectedIDs = { } ;
function updateAggregatedStats ( treeSnapshot , instanceID , applyUpdate ) {
var _treeSnapshot$instanc2 = treeSnapshot [ instanceID ] ,
displayName = _treeSnapshot$instanc2 . displayName ,
ownerID = _treeSnapshot$instanc2 . ownerID ;
var owner = treeSnapshot [ ownerID ] ;
var key = ( owner ? owner . displayName + ' > ' : '' ) + displayName ;
var stats = aggregatedStats [ key ] ;
if ( ! stats ) {
affectedIDs [ key ] = { } ;
stats = aggregatedStats [ key ] = {
key : key ,
instanceCount : 0 ,
inclusiveRenderDuration : 0 ,
renderCount : 0
} ;
}
affectedIDs [ key ] [ instanceID ] = true ;
applyUpdate ( stats ) ;
}
flushHistory . forEach ( function ( flush ) {
var measurements = flush . measurements ,
treeSnapshot = flush . treeSnapshot ,
operations = flush . operations ;
var isDefinitelyNotWastedByID = { } ;
// Find host components associated with an operation in this batch.
// Mark all components in their parent tree as definitely not wasted.
operations . forEach ( function ( operation ) {
var instanceID = operation . instanceID ;
var nextParentID = instanceID ;
while ( nextParentID ) {
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 ( function ( measurement ) {
var instanceID = measurement . instanceID ,
timerType = measurement . timerType ;
if ( timerType !== 'render' ) {
return ;
}
renderedCompositeIDs [ instanceID ] = true ;
} ) ;
measurements . forEach ( function ( measurement ) {
var duration = measurement . duration ,
instanceID = measurement . instanceID ,
timerType = measurement . timerType ;
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 ] . updateCount ;
if ( isDefinitelyNotWastedByID [ instanceID ] || updateCount === 0 ) {
return ;
}
// We consider this render() wasted.
updateAggregatedStats ( treeSnapshot , instanceID , function ( stats ) {
stats . renderCount ++ ;
} ) ;
var nextParentID = instanceID ;
while ( nextParentID ) {
// 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 , function ( stats ) {
stats . inclusiveRenderDuration += duration ;
} ) ;
}
nextParentID = treeSnapshot [ nextParentID ] . parentID ;
}
} ) ;
} ) ;
return Object . keys ( aggregatedStats ) . map ( function ( key ) {
return _extends ( { } , aggregatedStats [ key ] , {
instanceCount : Object . keys ( affectedIDs [ key ] ) . length
} ) ;
} ) . sort ( function ( a , b ) {
return b . inclusiveRenderDuration - a . inclusiveRenderDuration ;
} ) ;
}
function getOperations ( ) {
var flushHistory = arguments . length > 0 && arguments [ 0 ] !== undefined ? arguments [ 0 ] : getLastMeasurements ( ) ;
if ( ! ( process . env . NODE _ENV !== 'production' ) ) {
warnInProduction ( ) ;
return [ ] ;
}
var stats = [ ] ;
flushHistory . forEach ( function ( flush , flushIndex ) {
var operations = flush . operations ,
treeSnapshot = flush . treeSnapshot ;
operations . forEach ( function ( operation ) {
var instanceID = operation . instanceID ,
type = operation . type ,
payload = operation . payload ;
var _treeSnapshot$instanc3 = treeSnapshot [ instanceID ] ,
displayName = _treeSnapshot$instanc3 . displayName ,
ownerID = _treeSnapshot$instanc3 . ownerID ;
var owner = treeSnapshot [ ownerID ] ;
var key = ( owner ? owner . displayName + ' > ' : '' ) + displayName ;
stats . push ( {
flushIndex : flushIndex ,
instanceID : instanceID ,
key : key ,
type : type ,
ownerID : ownerID ,
payload : payload
} ) ;
} ) ;
} ) ;
return stats ;
}
function printExclusive ( flushHistory ) {
if ( ! ( process . env . NODE _ENV !== 'production' ) ) {
warnInProduction ( ) ;
return ;
}
var stats = getExclusive ( flushHistory ) ;
var table = stats . map ( function ( item ) {
var key = item . key ,
instanceCount = item . instanceCount ,
totalDuration = item . totalDuration ;
var renderCount = item . counts . render || 0 ;
var renderDuration = item . durations . render || 0 ;
return {
2017-08-14 05:01:11 +02:00
Component : key ,
2017-05-03 15:35:00 +02:00
'Total time (ms)' : roundFloat ( totalDuration ) ,
'Instance count' : instanceCount ,
'Total render time (ms)' : roundFloat ( renderDuration ) ,
'Average render time (ms)' : renderCount ? roundFloat ( renderDuration / renderCount ) : undefined ,
'Render count' : renderCount ,
'Total lifecycle time (ms)' : roundFloat ( totalDuration - renderDuration )
} ;
} ) ;
consoleTable ( table ) ;
}
function printInclusive ( flushHistory ) {
if ( ! ( process . env . NODE _ENV !== 'production' ) ) {
warnInProduction ( ) ;
return ;
}
var stats = getInclusive ( flushHistory ) ;
var table = stats . map ( function ( item ) {
var key = item . key ,
instanceCount = item . instanceCount ,
inclusiveRenderDuration = item . inclusiveRenderDuration ,
renderCount = item . renderCount ;
return {
'Owner > Component' : key ,
'Inclusive render time (ms)' : roundFloat ( inclusiveRenderDuration ) ,
'Instance count' : instanceCount ,
'Render count' : renderCount
} ;
} ) ;
consoleTable ( table ) ;
}
function printWasted ( flushHistory ) {
if ( ! ( process . env . NODE _ENV !== 'production' ) ) {
warnInProduction ( ) ;
return ;
}
var stats = getWasted ( flushHistory ) ;
var table = stats . map ( function ( item ) {
var key = item . key ,
instanceCount = item . instanceCount ,
inclusiveRenderDuration = item . inclusiveRenderDuration ,
renderCount = item . renderCount ;
return {
'Owner > Component' : key ,
'Inclusive wasted time (ms)' : roundFloat ( inclusiveRenderDuration ) ,
'Instance count' : instanceCount ,
'Render count' : renderCount
} ;
} ) ;
consoleTable ( table ) ;
}
function printOperations ( flushHistory ) {
if ( ! ( process . env . NODE _ENV !== 'production' ) ) {
warnInProduction ( ) ;
return ;
}
var stats = getOperations ( flushHistory ) ;
var table = stats . map ( function ( stat ) {
return {
'Owner > Node' : stat . key ,
2017-08-14 05:01:11 +02:00
Operation : stat . type ,
Payload : typeof stat . payload === 'object' ? JSON . stringify ( stat . payload ) : stat . payload ,
2017-05-03 15:35:00 +02:00
'Flush index' : stat . flushIndex ,
'Owner Component ID' : stat . ownerID ,
'DOM Component ID' : stat . instanceID
} ;
} ) ;
consoleTable ( table ) ;
}
var warnedAboutPrintDOM = false ;
function printDOM ( measurements ) {
2017-08-14 05:01:11 +02:00
lowPriorityWarning ( warnedAboutPrintDOM , '`ReactPerf.printDOM(...)` is deprecated. Use ' + '`ReactPerf.printOperations(...)` instead.' ) ;
2017-05-03 15:35:00 +02:00
warnedAboutPrintDOM = true ;
return printOperations ( measurements ) ;
}
var warnedAboutGetMeasurementsSummaryMap = false ;
function getMeasurementsSummaryMap ( measurements ) {
2017-08-14 05:01:11 +02:00
lowPriorityWarning ( warnedAboutGetMeasurementsSummaryMap , '`ReactPerf.getMeasurementsSummaryMap(...)` is deprecated. Use ' + '`ReactPerf.getWasted(...)` instead.' ) ;
2017-05-03 15:35:00 +02:00
warnedAboutGetMeasurementsSummaryMap = true ;
return getWasted ( measurements ) ;
}
function start ( ) {
if ( ! ( process . env . NODE _ENV !== 'production' ) ) {
warnInProduction ( ) ;
return ;
}
ReactDebugTool . beginProfiling ( ) ;
}
function stop ( ) {
if ( ! ( process . env . NODE _ENV !== 'production' ) ) {
warnInProduction ( ) ;
return ;
}
ReactDebugTool . endProfiling ( ) ;
}
function isRunning ( ) {
if ( ! ( process . env . NODE _ENV !== 'production' ) ) {
warnInProduction ( ) ;
return false ;
}
return ReactDebugTool . isProfiling ( ) ;
}
var ReactPerfAnalysis = {
getLastMeasurements : getLastMeasurements ,
getExclusive : getExclusive ,
getInclusive : getInclusive ,
getWasted : getWasted ,
getOperations : getOperations ,
printExclusive : printExclusive ,
printInclusive : printInclusive ,
printWasted : printWasted ,
printOperations : printOperations ,
start : start ,
stop : stop ,
isRunning : isRunning ,
// Deprecated:
printDOM : printDOM ,
getMeasurementsSummaryMap : getMeasurementsSummaryMap
} ;
module . exports = ReactPerfAnalysis ;