Process heap capture into trace html
Reviewed By: bestander Differential Revision: D3642188 fbshipit-source-id: c9a4699b2a0d60eb5961333dec45941085e19324
This commit is contained in:
parent
cb59264e73
commit
54f867f0d6
|
@ -0,0 +1,2 @@
|
|||
/captures/*
|
||||
preLoadedCapture.js
|
|
@ -0,0 +1,5 @@
|
|||
all:
|
||||
NODE_PATH="../../../../node_modules/" babel --presets babel-preset-react-native -d out src
|
||||
|
||||
watch:
|
||||
NODE_PATH="../../../../node_modules/" babel --watch --presets babel-preset-react-native -d out src
|
|
@ -0,0 +1,16 @@
|
|||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" style="height: 100%;">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>JSC Heap Capture</title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.1/react.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.1/react-dom.min.js"></script>
|
||||
<script src="out/aggrow.js"></script>
|
||||
<script src="out/table.js"></script>
|
||||
</head>
|
||||
<body style="margin:0px; height: 100%">
|
||||
Loading... This could take a while depending on how big the profile is. Check devtools console for errors.
|
||||
</body>
|
||||
<script src="preLoadedCapture.js"></script>
|
||||
<script src="out/heapCapture.js"></script>
|
||||
</html>
|
|
@ -0,0 +1,616 @@
|
|||
/**
|
||||
* Copyright (c) 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';
|
||||
/*eslint no-bitwise: "off"*/
|
||||
/*eslint no-console-disallow: "off"*/
|
||||
|
||||
// TODO: future features
|
||||
// put in a module.exports
|
||||
// filtering / search
|
||||
// pivot around frames in the middle of a stack by callers / callees
|
||||
// graphing?
|
||||
|
||||
function stringInterner(){ // eslint-disable-line no-unused-vars
|
||||
var strings=[];
|
||||
var ids={};
|
||||
return {
|
||||
intern:function internString(s){
|
||||
var find=ids[s];
|
||||
if(find===undefined){
|
||||
var id=strings.length;
|
||||
ids[s]=id;
|
||||
strings.push(s);
|
||||
return id;}else
|
||||
{
|
||||
return find;}},
|
||||
|
||||
|
||||
get:function getString(id){
|
||||
return strings[id];}};}
|
||||
|
||||
|
||||
|
||||
|
||||
function stackData(stackIdMap,maxDepth){ // eslint-disable-line no-unused-vars
|
||||
return {
|
||||
maxDepth:maxDepth,
|
||||
get:function getStack(id){
|
||||
return stackIdMap[id];}};}
|
||||
|
||||
|
||||
|
||||
|
||||
function stackRegistry(interner){ // eslint-disable-line no-unused-vars
|
||||
return {
|
||||
root:{id:0},
|
||||
nodeCount:1,
|
||||
insert:function insertNode(parent,label){
|
||||
var labelId=interner.intern(label);
|
||||
var node=parent[labelId];
|
||||
if(node===undefined){
|
||||
node={id:this.nodeCount};
|
||||
this.nodeCount++;
|
||||
parent[labelId]=node;}
|
||||
|
||||
return node;},
|
||||
|
||||
flatten:function flattenStacks(){
|
||||
var stackFrameCount=0;
|
||||
function countStacks(tree,depth){
|
||||
var leaf=true;
|
||||
for(var frameId in tree){
|
||||
if(frameId!=='id'){
|
||||
leaf=countStacks(tree[frameId],depth+1);}}
|
||||
|
||||
|
||||
if(leaf){
|
||||
stackFrameCount+=depth;}
|
||||
|
||||
return false;}
|
||||
|
||||
countStacks(this.root,0);
|
||||
console.log('size needed to store stacks: '+(stackFrameCount*4).toString()+'B');
|
||||
var stackIdMap=new Array(this.nodeCount);
|
||||
var stackArray=new Int32Array(stackFrameCount);
|
||||
var maxStackDepth=0;
|
||||
stackFrameCount=0;
|
||||
function flattenStacksImpl(tree,stack){
|
||||
var childStack=void 0;
|
||||
maxStackDepth=Math.max(maxStackDepth,stack.length);
|
||||
for(var frameId in tree){
|
||||
if(frameId!=='id'){
|
||||
stack.push(Number(frameId));
|
||||
childStack=flattenStacksImpl(tree[frameId],stack);
|
||||
stack.pop();}}
|
||||
|
||||
|
||||
|
||||
var id=tree.id;
|
||||
if(id<0||id>=stackIdMap.length||stackIdMap[id]!==undefined){
|
||||
throw 'invalid stack id!';}
|
||||
|
||||
|
||||
if(childStack!==undefined){
|
||||
// each child must have our stack as a prefix, so just use that
|
||||
stackIdMap[id]=childStack.subarray(0,stack.length);}else
|
||||
{
|
||||
var newStack=stackArray.subarray(stackFrameCount,stackFrameCount+stack.length);
|
||||
stackFrameCount+=stack.length;
|
||||
for(var i=0;i<stack.length;i++){
|
||||
newStack[i]=stack[i];}
|
||||
|
||||
stackIdMap[id]=newStack;}
|
||||
|
||||
return stackIdMap[id];}
|
||||
|
||||
flattenStacksImpl(this.root,[]);
|
||||
|
||||
return new stackData(stackIdMap,maxStackDepth);}};}
|
||||
|
||||
|
||||
|
||||
|
||||
function aggrow(strings,stacks,numRows){ // eslint-disable-line no-unused-vars
|
||||
// expander ID definitions
|
||||
var FIELD_EXPANDER_ID_MIN=0x0000;
|
||||
var FIELD_EXPANDER_ID_MAX=0x7fff;
|
||||
var STACK_EXPANDER_ID_MIN=0x8000;
|
||||
var STACK_EXPANDER_ID_MAX=0xffff;
|
||||
|
||||
// used for row.expander which reference state.activeExpanders (with frame index masked in)
|
||||
var INVALID_ACTIVE_EXPANDER=-1;
|
||||
var ACTIVE_EXPANDER_MASK=0xffff;
|
||||
var ACTIVE_EXPANDER_FRAME_SHIFT=16;
|
||||
|
||||
// aggregator ID definitions
|
||||
var AGGREGATOR_ID_MAX=0xffff;
|
||||
|
||||
// active aggragators can have sort order changed in the reference
|
||||
var ACTIVE_AGGREGATOR_MASK=0xffff;
|
||||
var ACTIVE_AGGREGATOR_ASC_BIT=0x10000;
|
||||
|
||||
// tree node state definitions
|
||||
var NODE_EXPANDED_BIT=0x0001; // this row is expanded
|
||||
var NODE_REAGGREGATE_BIT=0x0002; // children need aggregates
|
||||
var NODE_REORDER_BIT=0x0004; // children need to be sorted
|
||||
var NODE_REPOSITION_BIT=0x0008; // children need position
|
||||
var NODE_INDENT_SHIFT=16;
|
||||
|
||||
function calleeFrameGetter(stack,depth){
|
||||
return stack[depth];}
|
||||
|
||||
|
||||
function callerFrameGetter(stack,depth){
|
||||
return stack[stack.length-depth-1];}
|
||||
|
||||
|
||||
function createStackComparers(stackGetter,frameGetter){
|
||||
var comparers=new Array(stacks.maxDepth);var _loop=function _loop(
|
||||
depth){
|
||||
var captureDepth=depth; // NB: to capture depth per loop iteration
|
||||
comparers[depth]=function calleeStackComparer(rowA,rowB){
|
||||
var a=stackGetter(rowA);
|
||||
var b=stackGetter(rowB);
|
||||
// NB: we put the stacks that are too short at the top,
|
||||
// so they can be grouped into the '<exclusive>' bucket
|
||||
if(a.length<=captureDepth&&b.length<=captureDepth){
|
||||
return 0;}else
|
||||
if(a.length<=captureDepth){
|
||||
return -1;}else
|
||||
if(b.length<=captureDepth){
|
||||
return 1;}
|
||||
|
||||
return frameGetter(a,captureDepth)-frameGetter(b,captureDepth);};};for(var depth=0;depth<stacks.maxDepth;depth++){_loop(depth);}
|
||||
|
||||
|
||||
return comparers;}
|
||||
|
||||
|
||||
function createTreeNode(parent,label,indices,expander){
|
||||
var indent=parent===null?0:(parent.state>>>NODE_INDENT_SHIFT)+1;
|
||||
var state=NODE_REPOSITION_BIT|
|
||||
NODE_REAGGREGATE_BIT|
|
||||
NODE_REORDER_BIT|
|
||||
indent<<NODE_INDENT_SHIFT;
|
||||
return {
|
||||
parent:parent, // null if root
|
||||
children:null, // array of children nodes
|
||||
label:label, // string to show in UI
|
||||
indices:indices, // row indices under this node
|
||||
aggregates:null, // result of aggregate on indices
|
||||
expander:expander, // index into state.activeExpanders
|
||||
top:0, // y position of top row (in rows)
|
||||
height:1, // number of rows including children
|
||||
state:state};} // see NODE_* definitions above
|
||||
|
||||
|
||||
|
||||
function noSortOrder(a,b){
|
||||
return 0;}
|
||||
|
||||
|
||||
var indices=new Int32Array(numRows);
|
||||
for(var i=0;i<numRows;i++){
|
||||
indices[i]=i;}
|
||||
|
||||
|
||||
var state={
|
||||
fieldExpanders:[], // tree expanders that expand on simple values
|
||||
stackExpanders:[], // tree expanders that expand stacks
|
||||
activeExpanders:[], // index into field or stack expanders, hierarchy of tree
|
||||
aggregators:[], // all available aggregators, might not be used
|
||||
activeAggregators:[], // index into aggregators, to actually compute
|
||||
sorter:noSortOrder, // compare function that uses sortOrder to sort row.children
|
||||
root:createTreeNode(null,'<root>',indices,INVALID_ACTIVE_EXPANDER)};
|
||||
|
||||
|
||||
function evaluateAggregate(row){
|
||||
var activeAggregators=state.activeAggregators;
|
||||
var aggregates=new Array(activeAggregators.length);
|
||||
for(var j=0;j<activeAggregators.length;j++){
|
||||
var aggregator=state.aggregators[activeAggregators[j]];
|
||||
aggregates[j]=aggregator.aggregator(row.indices);}
|
||||
|
||||
row.aggregates=aggregates;
|
||||
row.state|=NODE_REAGGREGATE_BIT;}
|
||||
|
||||
|
||||
function evaluateAggregates(row){
|
||||
if((row.state&NODE_EXPANDED_BIT)!==0){
|
||||
var children=row.children;
|
||||
for(var _i=0;_i<children.length;_i++){
|
||||
evaluateAggregate(children[_i]);}
|
||||
|
||||
row.state|=NODE_REORDER_BIT;}
|
||||
|
||||
row.state^=NODE_REAGGREGATE_BIT;}
|
||||
|
||||
|
||||
function evaluateOrder(row){
|
||||
if((row.state&NODE_EXPANDED_BIT)!==0){
|
||||
var children=row.children;
|
||||
for(var _i2=0;_i2<children.length;_i2++){
|
||||
var child=children[_i2];
|
||||
child.state|=NODE_REORDER_BIT;}
|
||||
|
||||
children.sort(state.sorter);
|
||||
row.state|=NODE_REPOSITION_BIT;}
|
||||
|
||||
row.state^=NODE_REORDER_BIT;}
|
||||
|
||||
|
||||
function evaluatePosition(row){
|
||||
if((row.state&NODE_EXPANDED_BIT)!==0){
|
||||
var children=row.children;
|
||||
var childTop=row.top+1;
|
||||
for(var _i3=0;_i3<children.length;_i3++){
|
||||
var child=children[_i3];
|
||||
if(child.top!==childTop){
|
||||
child.top=childTop;
|
||||
child.state|=NODE_REPOSITION_BIT;}
|
||||
|
||||
childTop+=child.height;}}
|
||||
|
||||
|
||||
row.state^=NODE_REPOSITION_BIT;}
|
||||
|
||||
|
||||
function getRowsImpl(row,top,height,result){
|
||||
if((row.state&NODE_REAGGREGATE_BIT)!==0){
|
||||
evaluateAggregates(row);}
|
||||
|
||||
if((row.state&NODE_REORDER_BIT)!==0){
|
||||
evaluateOrder(row);}
|
||||
|
||||
if((row.state&NODE_REPOSITION_BIT)!==0){
|
||||
evaluatePosition(row);}
|
||||
|
||||
|
||||
if(row.top>=top&&row.top<top+height){
|
||||
if(result[row.top-top]!=null){
|
||||
throw 'getRows put more than one row at position '+row.top+' into result';}
|
||||
|
||||
result[row.top-top]=row;}
|
||||
|
||||
if((row.state&NODE_EXPANDED_BIT)!==0){
|
||||
var children=row.children;
|
||||
for(var _i4=0;_i4<children.length;_i4++){
|
||||
var child=children[_i4];
|
||||
if(child.top<top+height&&top<child.top+child.height){
|
||||
getRowsImpl(child,top,height,result);}}}}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function updateHeight(row,heightChange){
|
||||
while(row!==null){
|
||||
row.height+=heightChange;
|
||||
row.state|=NODE_REPOSITION_BIT;
|
||||
row=row.parent;}}
|
||||
|
||||
|
||||
|
||||
function addChildrenWithFieldExpander(row,expander,nextActiveIndex){
|
||||
var rowIndices=row.indices;
|
||||
var comparer=expander.comparer;
|
||||
rowIndices.sort(comparer);
|
||||
var begin=0;
|
||||
var end=1;
|
||||
row.children=[];
|
||||
while(end<rowIndices.length){
|
||||
if(comparer(rowIndices[begin],rowIndices[end])!==0){
|
||||
row.children.push(createTreeNode(
|
||||
row,
|
||||
expander.name+': '+expander.formatter(rowIndices[begin]),
|
||||
rowIndices.subarray(begin,end),
|
||||
nextActiveIndex));
|
||||
begin=end;}
|
||||
|
||||
end++;}
|
||||
|
||||
row.children.push(createTreeNode(
|
||||
row,
|
||||
expander.name+': '+expander.formatter(rowIndices[begin]),
|
||||
rowIndices.subarray(begin,end),
|
||||
nextActiveIndex));}
|
||||
|
||||
|
||||
function addChildrenWithStackExpander(row,expander,activeIndex,depth,nextActiveIndex){
|
||||
var rowIndices=row.indices;
|
||||
var stackGetter=expander.stackGetter;
|
||||
var frameGetter=expander.frameGetter;
|
||||
var comparer=expander.comparers[depth];
|
||||
var expandNextFrame=activeIndex|depth+1<<ACTIVE_EXPANDER_FRAME_SHIFT;
|
||||
rowIndices.sort(comparer);
|
||||
var columnName='';
|
||||
if(depth===0){
|
||||
columnName=expander.name+': ';}
|
||||
|
||||
|
||||
// put all the too-short stacks under <exclusive>
|
||||
var begin=0;
|
||||
var beginStack=null;
|
||||
row.children=[];
|
||||
while(begin<rowIndices.length){
|
||||
beginStack=stackGetter(rowIndices[begin]);
|
||||
if(beginStack.length>depth){
|
||||
break;}
|
||||
|
||||
begin++;}
|
||||
|
||||
if(begin>0){
|
||||
row.children.push(createTreeNode(
|
||||
row,
|
||||
columnName+'<exclusive>',
|
||||
rowIndices.subarray(0,begin),
|
||||
nextActiveIndex));}
|
||||
|
||||
// aggregate the rest under frames
|
||||
if(begin<rowIndices.length){
|
||||
var end=begin+1;
|
||||
while(end<rowIndices.length){
|
||||
var endStack=stackGetter(rowIndices[end]);
|
||||
if(frameGetter(beginStack,depth)!==frameGetter(endStack,depth)){
|
||||
row.children.push(createTreeNode(
|
||||
row,
|
||||
columnName+strings.get(frameGetter(beginStack,depth)),
|
||||
rowIndices.subarray(begin,end),
|
||||
expandNextFrame));
|
||||
begin=end;
|
||||
beginStack=endStack;}
|
||||
|
||||
end++;}
|
||||
|
||||
row.children.push(createTreeNode(
|
||||
row,
|
||||
columnName+strings.get(frameGetter(beginStack,depth)),
|
||||
rowIndices.subarray(begin,end),
|
||||
expandNextFrame));}}
|
||||
|
||||
|
||||
|
||||
function contractRow(row){
|
||||
if((row.state&NODE_EXPANDED_BIT)===0){
|
||||
throw 'can not contract row, already contracted';}
|
||||
|
||||
row.state^=NODE_EXPANDED_BIT;
|
||||
var heightChange=1-row.height;
|
||||
updateHeight(row,heightChange);}
|
||||
|
||||
|
||||
function pruneExpanders(row,oldExpander,newExpander){
|
||||
row.state|=NODE_REPOSITION_BIT;
|
||||
if(row.expander===oldExpander){
|
||||
row.state|=NODE_REAGGREGATE_BIT|NODE_REORDER_BIT|NODE_REPOSITION_BIT;
|
||||
if((row.state&NODE_EXPANDED_BIT)!==0){
|
||||
contractRow(row);}
|
||||
|
||||
row.children=null;
|
||||
row.expander=newExpander;}else
|
||||
{
|
||||
row.state|=NODE_REPOSITION_BIT;
|
||||
var children=row.children;
|
||||
if(children!=null){
|
||||
for(var _i5=0;_i5<children.length;_i5++){
|
||||
var child=children[_i5];
|
||||
pruneExpanders(child,oldExpander,newExpander);}}}}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return {
|
||||
addFieldExpander:function addFieldExpander(name,formatter,comparer){
|
||||
if(FIELD_EXPANDER_ID_MIN+state.fieldExpanders.length>=FIELD_EXPANDER_ID_MAX){
|
||||
throw 'too many field expanders!';}
|
||||
|
||||
state.fieldExpanders.push({
|
||||
name:name, // name for column
|
||||
formatter:formatter, // row index -> display string
|
||||
comparer:comparer}); // compares by two row indices
|
||||
|
||||
return FIELD_EXPANDER_ID_MIN+state.fieldExpanders.length-1;},
|
||||
|
||||
addCalleeStackExpander:function addCalleeStackExpander(name,stackGetter){
|
||||
if(STACK_EXPANDER_ID_MIN+state.fieldExpanders.length>=STACK_EXPANDER_ID_MAX){
|
||||
throw 'too many stack expanders!';}
|
||||
|
||||
state.stackExpanders.push({
|
||||
name:name, // name for column
|
||||
stackGetter:stackGetter, // row index -> stack array
|
||||
comparers:createStackComparers(stackGetter,calleeFrameGetter), // depth -> comparer
|
||||
frameGetter:calleeFrameGetter}); // (stack, depth) -> string id
|
||||
|
||||
return STACK_EXPANDER_ID_MIN+state.stackExpanders.length-1;},
|
||||
|
||||
addCallerStackExpander:function addCallerStackExpander(name,stackGetter){
|
||||
if(STACK_EXPANDER_ID_MIN+state.fieldExpanders.length>=STACK_EXPANDER_ID_MAX){
|
||||
throw 'too many stack expanders!';}
|
||||
|
||||
state.stackExpanders.push({
|
||||
name:name,
|
||||
stackGetter:stackGetter,
|
||||
comparers:createStackComparers(stackGetter,callerFrameGetter),
|
||||
frameGetter:callerFrameGetter});
|
||||
|
||||
return STACK_EXPANDER_ID_MIN+state.stackExpanders.length-1;},
|
||||
|
||||
getExpanders:function getExpanders(){
|
||||
var expanders=[];
|
||||
for(var _i6=0;_i6<state.fieldExpanders.length;_i6++){
|
||||
expanders.push(FIELD_EXPANDER_ID_MIN+_i6);}
|
||||
|
||||
for(var _i7=0;_i7<state.stackExpanders.length;_i7++){
|
||||
expanders.push(STACK_EXPANDER_ID_MIN+_i7);}
|
||||
|
||||
return expanders;},
|
||||
|
||||
getExpanderName:function getExpanderName(id){
|
||||
if(id>=FIELD_EXPANDER_ID_MIN&&id<=FIELD_EXPANDER_ID_MAX){
|
||||
return state.fieldExpanders[id-FIELD_EXPANDER_ID_MIN].name;}else
|
||||
if(id>=STACK_EXPANDER_ID_MIN&&id<=STACK_EXPANDER_ID_MAX){
|
||||
return state.stackExpanders[id-STACK_EXPANDER_ID_MIN].name;}
|
||||
|
||||
throw 'Unknown expander ID '+id.toString();},
|
||||
|
||||
setActiveExpanders:function setActiveExpanders(ids){
|
||||
for(var _i8=0;_i8<ids.length;_i8++){
|
||||
var id=ids[_i8];
|
||||
if(id>=FIELD_EXPANDER_ID_MIN&&id<=FIELD_EXPANDER_ID_MAX){
|
||||
if(id-FIELD_EXPANDER_ID_MIN>=state.fieldExpanders.length){
|
||||
throw 'field expander for id '+id.toString()+' does not exist!';}}else
|
||||
|
||||
if(id>=STACK_EXPANDER_ID_MIN&&id<=STACK_EXPANDER_ID_MAX){
|
||||
if(id-STACK_EXPANDER_ID_MIN>=state.stackExpanders.length){
|
||||
throw 'stack expander for id '+id.toString()+' does not exist!';}}}
|
||||
|
||||
|
||||
|
||||
for(var _i9=0;_i9<ids.length;_i9++){
|
||||
if(state.activeExpanders.length<=_i9){
|
||||
pruneExpanders(state.root,INVALID_ACTIVE_EXPANDER,_i9);
|
||||
break;}else
|
||||
if(ids[_i9]!==state.activeExpanders[_i9]){
|
||||
pruneExpanders(state.root,_i9,_i9);
|
||||
break;}}
|
||||
|
||||
|
||||
// TODO: if ids is prefix of activeExpanders, we need to make an expander invalid
|
||||
state.activeExpanders=ids.slice();},
|
||||
|
||||
getActiveExpanders:function getActiveExpanders(){
|
||||
return state.activeExpanders.slice();},
|
||||
|
||||
addAggregator:function addAggregator(name,aggregator,formatter,sorter){
|
||||
if(state.aggregators.length>=AGGREGATOR_ID_MAX){
|
||||
throw 'too many aggregators!';}
|
||||
|
||||
state.aggregators.push({
|
||||
name:name, // name for column
|
||||
aggregator:aggregator, // index array -> aggregate value
|
||||
formatter:formatter, // aggregate value -> display string
|
||||
sorter:sorter}); // compare two aggregate values
|
||||
|
||||
return state.aggregators.length-1;},
|
||||
|
||||
getAggregators:function getAggregators(){
|
||||
var aggregators=[];
|
||||
for(var _i10=0;_i10<state.aggregators.length;_i10++){
|
||||
aggregators.push(_i10);}
|
||||
|
||||
return aggregators;},
|
||||
|
||||
getAggregatorName:function getAggregatorName(id){
|
||||
return state.aggregators[id&ACTIVE_AGGREGATOR_MASK].name;},
|
||||
|
||||
setActiveAggregators:function setActiveAggregators(ids){
|
||||
for(var _i11=0;_i11<ids.length;_i11++){
|
||||
var id=ids[_i11]&ACTIVE_AGGREGATOR_MASK;
|
||||
if(id<0||id>state.aggregators.length){
|
||||
throw 'aggregator id '+id.toString()+' not valid';}}
|
||||
|
||||
|
||||
state.activeAggregators=ids.slice();
|
||||
// NB: evaluate root here because dirty bit is for children
|
||||
// so someone has to start with root, and it might as well be right away
|
||||
evaluateAggregate(state.root);
|
||||
var sorter=noSortOrder;var _loop2=function _loop2(
|
||||
_i12){
|
||||
var ascending=(ids[_i12]&ACTIVE_AGGREGATOR_ASC_BIT)!==0;
|
||||
var id=ids[_i12]&ACTIVE_AGGREGATOR_MASK;
|
||||
var comparer=state.aggregators[id].sorter;
|
||||
var captureSorter=sorter;
|
||||
var captureIndex=_i12;
|
||||
sorter=function sorter(a,b){
|
||||
var c=comparer(a.aggregates[captureIndex],b.aggregates[captureIndex]);
|
||||
if(c===0){
|
||||
return captureSorter(a,b);}
|
||||
|
||||
return ascending?-c:c;};};for(var _i12=ids.length-1;_i12>=0;_i12--){_loop2(_i12);}
|
||||
|
||||
|
||||
state.sorter=sorter;
|
||||
state.root.state|=NODE_REORDER_BIT;},
|
||||
|
||||
getActiveAggregators:function getActiveAggregators(){
|
||||
return state.activeAggregators.slice();},
|
||||
|
||||
getRows:function getRows(top,height){
|
||||
var result=new Array(height);
|
||||
for(var _i13=0;_i13<height;_i13++){
|
||||
result[_i13]=null;}
|
||||
|
||||
getRowsImpl(state.root,top,height,result);
|
||||
return result;},
|
||||
|
||||
getRowLabel:function getRowLabel(row){
|
||||
return row.label;},
|
||||
|
||||
getRowIndent:function getRowIndent(row){
|
||||
return row.state>>>NODE_INDENT_SHIFT;},
|
||||
|
||||
getRowAggregate:function getRowAggregate(row,index){
|
||||
var aggregator=state.aggregators[state.activeAggregators[index]];
|
||||
return aggregator.formatter(row.aggregates[index]);},
|
||||
|
||||
getHeight:function getHeight(){
|
||||
return state.root.height;},
|
||||
|
||||
canExpand:function canExpand(row){
|
||||
return (row.state&NODE_EXPANDED_BIT)===0&&row.expander!==INVALID_ACTIVE_EXPANDER;},
|
||||
|
||||
canContract:function canContract(row){
|
||||
return (row.state&NODE_EXPANDED_BIT)!==0;},
|
||||
|
||||
expand:function expand(row){
|
||||
if((row.state&NODE_EXPANDED_BIT)!==0){
|
||||
throw 'can not expand row, already expanded';}
|
||||
|
||||
if(row.height!==1){
|
||||
throw 'unexpanded row has height '+row.height.toString()+' != 1';}
|
||||
|
||||
if(row.children===null){ // first expand, generate children
|
||||
var activeIndex=row.expander&ACTIVE_EXPANDER_MASK;
|
||||
var nextActiveIndex=activeIndex+1; // NB: if next is stack, frame is 0
|
||||
if(nextActiveIndex>=state.activeExpanders.length){
|
||||
nextActiveIndex=INVALID_ACTIVE_EXPANDER;}
|
||||
|
||||
if(activeIndex>=state.activeExpanders.length){
|
||||
throw 'invalid active expander index '+activeIndex.toString();}
|
||||
|
||||
var exId=state.activeExpanders[activeIndex];
|
||||
if(exId>=FIELD_EXPANDER_ID_MIN&&
|
||||
exId<FIELD_EXPANDER_ID_MIN+state.fieldExpanders.length){
|
||||
var expander=state.fieldExpanders[exId-FIELD_EXPANDER_ID_MIN];
|
||||
addChildrenWithFieldExpander(row,expander,nextActiveIndex);}else
|
||||
if(exId>=STACK_EXPANDER_ID_MIN&&
|
||||
exId<STACK_EXPANDER_ID_MIN+state.stackExpanders.length){
|
||||
var depth=row.expander>>>ACTIVE_EXPANDER_FRAME_SHIFT;
|
||||
var _expander=state.stackExpanders[exId-STACK_EXPANDER_ID_MIN];
|
||||
addChildrenWithStackExpander(row,_expander,activeIndex,depth,nextActiveIndex);}else
|
||||
{
|
||||
throw 'state.activeIndex '+activeIndex.toString()+
|
||||
' has invalid expander'+exId.toString();}}
|
||||
|
||||
|
||||
row.state|=NODE_EXPANDED_BIT|
|
||||
NODE_REAGGREGATE_BIT|NODE_REORDER_BIT|NODE_REPOSITION_BIT;
|
||||
var heightChange=0;
|
||||
for(var _i14=0;_i14<row.children.length;_i14++){
|
||||
heightChange+=row.children[_i14].height;}
|
||||
|
||||
updateHeight(row,heightChange);
|
||||
// if children only contains one node, then expand it as well
|
||||
if(row.children.length===1&&this.canExpand(row.children[0])){
|
||||
this.expand(row.children[0]);}},
|
||||
|
||||
|
||||
contract:function contract(row){
|
||||
contractRow(row);}};}
|
|
@ -0,0 +1,284 @@
|
|||
/**
|
||||
* Copyright (c) 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';
|
||||
/*eslint no-console-disallow: "off"*/
|
||||
/*global React ReactDOM Table stringInterner stackRegistry aggrow preLoadedCapture:true*/var _jsxFileName='src/heapCapture.js';
|
||||
|
||||
function registerReactComponentTreeImpl(refs,registry,parents,inEdgeNames,trees,id){
|
||||
if(parents[id]===undefined){
|
||||
// not a component
|
||||
}else if(parents[id]===null){
|
||||
trees[id]=registry.insert(registry.root,'<internalInstance>');}else
|
||||
{
|
||||
var parent=parents[id];
|
||||
var inEdgeName=inEdgeNames[id];
|
||||
var parentTree=trees[parent];
|
||||
if(parentTree===undefined){
|
||||
parentTree=registerReactComponentTreeImpl(
|
||||
refs,
|
||||
registry,
|
||||
parents,
|
||||
inEdgeNames,
|
||||
trees,
|
||||
parent);}
|
||||
|
||||
trees[id]=registry.insert(parentTree,inEdgeName);}
|
||||
|
||||
return trees[id];}
|
||||
|
||||
|
||||
// TODO: make it easier to query the heap graph, it's super annoying to deal with edges directly
|
||||
function registerReactComponentTree(refs,registry){
|
||||
// build list of parents for react interal instances, so we can connect a tree
|
||||
var parents={};
|
||||
var inEdgeNames={};
|
||||
for(var id in refs){
|
||||
var ref=refs[id];
|
||||
for(var linkId in ref.edges){
|
||||
if(linkId!=='0x0'){
|
||||
var name=ref.edges[linkId];
|
||||
if(name==='_renderedChildren'){
|
||||
if(parents[id]===undefined){
|
||||
// mark that we are a react component, even if we don't have a parent
|
||||
parents[id]=null;}
|
||||
|
||||
var childrenRef=refs[linkId];
|
||||
for(var childId in childrenRef.edges){
|
||||
var linkName=childrenRef.edges[childId];
|
||||
if(linkName.startsWith('.')){
|
||||
parents[childId]=id;
|
||||
inEdgeNames[childId]=linkName;}}}else
|
||||
|
||||
|
||||
if(name==='_renderedComponent'){
|
||||
if(parents[id]===undefined){
|
||||
parents[id]=null;}
|
||||
|
||||
parents[linkId]=id;
|
||||
inEdgeNames[linkId]='_renderedComponent';}}}}
|
||||
|
||||
|
||||
|
||||
|
||||
// build tree of react internal instances (since that's what has the structure)
|
||||
var trees={};
|
||||
for(var _id in refs){
|
||||
registerReactComponentTreeImpl(refs,registry,parents,inEdgeNames,trees,_id);}
|
||||
|
||||
// hook in components by looking at their _reactInternalInstance fields
|
||||
for(var _id2 in refs){
|
||||
var _ref=refs[_id2];
|
||||
for(var _linkId in _ref.edges){
|
||||
var _name=_ref.edges[_linkId];
|
||||
if(_name==='_reactInternalInstance'){
|
||||
if(trees[_linkId]!==undefined){
|
||||
trees[_id2]=registry.insert(trees[_linkId],'<component>');}}}}
|
||||
|
||||
|
||||
|
||||
|
||||
return trees;}
|
||||
|
||||
|
||||
function registerPathToRoot(roots,refs,registry,reactComponentTree){
|
||||
var visited={};
|
||||
var breadth=[];
|
||||
for(var i=0;i<roots.length;i++){
|
||||
var id=roots[i];
|
||||
if(visited[id]===undefined){
|
||||
var ref=refs[id];
|
||||
visited[id]=registry.insert(registry.root,ref.type);
|
||||
breadth.push(id);}}
|
||||
|
||||
|
||||
|
||||
while(breadth.length>0){
|
||||
var nextBreadth=[];var _loop=function _loop(
|
||||
_i){
|
||||
var id=breadth[_i];
|
||||
var ref=refs[id];
|
||||
var node=visited[id];
|
||||
// TODO: make edges map id -> name, (empty for none) seems that would be better
|
||||
|
||||
var edges=Object.getOwnPropertyNames(ref.edges);
|
||||
edges.sort(function putUnknownLast(a,b){
|
||||
var aName=ref.edges[a];
|
||||
var bName=ref.edges[b];
|
||||
if(aName===null&&bName!==null){
|
||||
return 1;}else
|
||||
if(aName!==null&&bName===null){
|
||||
return -1;}else
|
||||
if(aName===null&&bName===null){
|
||||
return 0;}else
|
||||
{
|
||||
return a.localeCompare(b);}});
|
||||
|
||||
|
||||
|
||||
for(var j=0;j<edges.length;j++){
|
||||
var edgeId=edges[j];
|
||||
var edgeName='';
|
||||
if(ref.edges[edgeId]){
|
||||
edgeName=ref.edges[edgeId]+': ';}
|
||||
|
||||
if(visited[edgeId]===undefined){
|
||||
var edgeRef=refs[edgeId];
|
||||
if(edgeRef===undefined){
|
||||
// TODO: figure out why we have edges that point to things not JSCell
|
||||
//console.log('registerPathToRoot unable to follow edge from ' + id + ' to ' + edgeId);
|
||||
}else {
|
||||
visited[edgeId]=registry.insert(node,edgeName+edgeRef.type);
|
||||
nextBreadth.push(edgeId);
|
||||
if(reactComponentTree[edgeId]===undefined){
|
||||
reactComponentTree[edgeId]=reactComponentTree[id];}}}}};for(var _i=0;_i<breadth.length;_i++){_loop(_i);}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
breadth=nextBreadth;}
|
||||
|
||||
return visited;}
|
||||
|
||||
|
||||
function captureRegistry(){
|
||||
var strings=stringInterner();
|
||||
var stacks=stackRegistry(strings);
|
||||
var data=new Int32Array(0);
|
||||
|
||||
var idField=0;
|
||||
var typeField=1;
|
||||
var sizeField=2;
|
||||
var traceField=3;
|
||||
var pathField=4;
|
||||
var reactField=5;
|
||||
var numFields=6;
|
||||
|
||||
return {
|
||||
strings:strings,
|
||||
stacks:stacks,
|
||||
data:data,
|
||||
register:function registerCapture(captureId,capture){
|
||||
var rowCount=0;
|
||||
for(var id in capture.refs){
|
||||
rowCount++;}
|
||||
|
||||
console.log(
|
||||
'increasing row data from '+(this.data.length*4).toString()+'B to '+
|
||||
(this.data.length*4+rowCount*numFields*4).toString()+'B');
|
||||
|
||||
var newData=new Int32Array(this.data.length+rowCount*numFields);
|
||||
newData.set(data);
|
||||
var dataOffset=this.data.length;
|
||||
this.data=null;
|
||||
|
||||
var reactComponentTreeMap=registerReactComponentTree(capture.refs,this.stacks);
|
||||
var rootPathMap=registerPathToRoot(
|
||||
capture.roots,
|
||||
capture.refs,
|
||||
this.stacks,
|
||||
reactComponentTreeMap);
|
||||
|
||||
var internedCaptureId=this.strings.intern(captureId);
|
||||
for(var _id3 in capture.refs){
|
||||
var ref=capture.refs[_id3];
|
||||
newData[dataOffset+idField]=parseInt(_id3,16);
|
||||
newData[dataOffset+typeField]=this.strings.intern(ref.type);
|
||||
newData[dataOffset+sizeField]=ref.size;
|
||||
newData[dataOffset+traceField]=internedCaptureId;
|
||||
var pathNode=rootPathMap[_id3];
|
||||
if(pathNode===undefined){
|
||||
throw 'did not find path for ref!';}
|
||||
|
||||
newData[dataOffset+pathField]=pathNode.id;
|
||||
var reactTree=reactComponentTreeMap[_id3];
|
||||
if(reactTree===undefined){
|
||||
newData[dataOffset+reactField]=
|
||||
this.stacks.insert(this.stacks.root,'<not-under-tree>').id;}else
|
||||
{
|
||||
newData[dataOffset+reactField]=reactTree.id;}
|
||||
|
||||
dataOffset+=numFields;}
|
||||
|
||||
this.data=newData;},
|
||||
|
||||
getAggrow:function getAggrow(){
|
||||
var agStrings=this.strings;
|
||||
var agStacks=this.stacks.flatten();
|
||||
var agData=this.data;
|
||||
var agNumRows=agData.length/numFields;
|
||||
var ag=new aggrow(agStrings,agStacks,agNumRows);
|
||||
|
||||
var idExpander=ag.addFieldExpander('Id',
|
||||
function getId(row){
|
||||
var id=agData[row*numFields+idField];
|
||||
if(id<0){
|
||||
id+=0x100000000; // data is int32, id is uint32
|
||||
}
|
||||
return '0x'+id.toString(16);},
|
||||
|
||||
function compareAddress(rowA,rowB){
|
||||
return agData[rowA*numFields+idField]-agData[rowB*numFields+idField];});
|
||||
|
||||
|
||||
var typeExpander=ag.addFieldExpander('Type',
|
||||
function getSize(row){return agStrings.get(agData[row*numFields+typeField]);},
|
||||
function compareSize(rowA,rowB){
|
||||
return agData[rowA*numFields+typeField]-agData[rowB*numFields+typeField];});
|
||||
|
||||
|
||||
ag.addFieldExpander('Size',
|
||||
function getSize(row){return agData[row*numFields+sizeField].toString();},
|
||||
function compareSize(rowA,rowB){
|
||||
return agData[rowA*numFields+sizeField]-agData[rowB*numFields+sizeField];});
|
||||
|
||||
|
||||
var traceExpander=ag.addFieldExpander('Trace',
|
||||
function getSize(row){return agStrings.get(agData[row*numFields+traceField]);},
|
||||
function compareSize(rowA,rowB){
|
||||
return agData[rowA*numFields+traceField]-agData[rowB*numFields+traceField];});
|
||||
|
||||
|
||||
var pathExpander=ag.addCalleeStackExpander('Path',
|
||||
function getStack(row){return agStacks.get(agData[row*numFields+pathField]);});
|
||||
|
||||
var reactExpander=ag.addCalleeStackExpander('React Tree',
|
||||
function getStack(row){return agStacks.get(agData[row*numFields+reactField]);});
|
||||
|
||||
var sizeAggregator=ag.addAggregator('Size',
|
||||
function aggregateSize(indices){
|
||||
var size=0;
|
||||
for(var i=0;i<indices.length;i++){
|
||||
var row=indices[i];
|
||||
size+=agData[row*numFields+sizeField];}
|
||||
|
||||
return size;},
|
||||
|
||||
function formatSize(value){return value.toString();},
|
||||
function sortSize(a,b){return b-a;});
|
||||
|
||||
var countAggregator=ag.addAggregator('Count',
|
||||
function aggregateCount(indices){
|
||||
return indices.length;},
|
||||
|
||||
function formatCount(value){return value.toString();},
|
||||
function sortCount(a,b){return b-a;});
|
||||
|
||||
ag.setActiveExpanders([pathExpander,reactExpander,typeExpander,idExpander,traceExpander]);
|
||||
ag.setActiveAggregators([sizeAggregator,countAggregator]);
|
||||
return ag;}};}
|
||||
|
||||
|
||||
|
||||
|
||||
if(preLoadedCapture){
|
||||
var r=new captureRegistry();
|
||||
r.register('trace',preLoadedCapture);
|
||||
preLoadedCapture=undefined; // let GG clean up the capture
|
||||
ReactDOM.render(React.createElement(Table,{aggrow:r.getAggrow(),__source:{fileName:_jsxFileName,lineNumber:284}}),document.body);}
|
|
@ -0,0 +1,326 @@
|
|||
/**
|
||||
* Copyright (c) 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';
|
||||
/*eslint no-console-disallow: "off"*/
|
||||
/*global React:true*/
|
||||
|
||||
// TODO:
|
||||
// selection and arrow keys for navigating
|
||||
var _jsxFileName='src/table.js';var _createClass=function(){function defineProperties(target,props){for(var i=0;i<props.length;i++){var descriptor=props[i];descriptor.enumerable=descriptor.enumerable||false;descriptor.configurable=true;if("value" in descriptor)descriptor.writable=true;Object.defineProperty(target,descriptor.key,descriptor);}}return function(Constructor,protoProps,staticProps){if(protoProps)defineProperties(Constructor.prototype,protoProps);if(staticProps)defineProperties(Constructor,staticProps);return Constructor;};}();function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor)){throw new TypeError("Cannot call a class as a function");}}function _possibleConstructorReturn(self,call){if(!self){throw new ReferenceError("this hasn't been initialised - super() hasn't been called");}return call&&(typeof call==="object"||typeof call==="function")?call:self;}function _inherits(subClass,superClass){if(typeof superClass!=="function"&&superClass!==null){throw new TypeError("Super expression must either be null or a function, not "+typeof superClass);}subClass.prototype=Object.create(superClass&&superClass.prototype,{constructor:{value:subClass,enumerable:false,writable:true,configurable:true}});if(superClass)Object.setPrototypeOf?Object.setPrototypeOf(subClass,superClass):subClass.__proto__=superClass;}
|
||||
var rowHeight=20;
|
||||
var treeIndent=16;var
|
||||
|
||||
Draggable=function(_React$Component){_inherits(Draggable,_React$Component); // eslint-disable-line no-unused-vars
|
||||
function Draggable(props){_classCallCheck(this,Draggable);return _possibleConstructorReturn(this,Object.getPrototypeOf(Draggable).call(this,
|
||||
props));}_createClass(Draggable,[{key:'render',value:function render()
|
||||
|
||||
|
||||
{
|
||||
var id=this.props.id;
|
||||
function dragStart(e){
|
||||
e.dataTransfer.setData('text/plain',id);}
|
||||
|
||||
return React.cloneElement(
|
||||
this.props.children,
|
||||
{draggable:'true',onDragStart:dragStart});}}]);return Draggable;}(React.Component);
|
||||
|
||||
|
||||
|
||||
Draggable.propTypes={
|
||||
children:React.PropTypes.element.isRequired,
|
||||
id:React.PropTypes.string.isRequired};var
|
||||
|
||||
|
||||
DropTarget=function(_React$Component2){_inherits(DropTarget,_React$Component2); // eslint-disable-line no-unused-vars
|
||||
function DropTarget(props){_classCallCheck(this,DropTarget);return _possibleConstructorReturn(this,Object.getPrototypeOf(DropTarget).call(this,
|
||||
props));}_createClass(DropTarget,[{key:'render',value:function render()
|
||||
|
||||
|
||||
{
|
||||
var thisId=this.props.id;
|
||||
var dropFilter=this.props.dropFilter;
|
||||
var dropAction=this.props.dropAction;
|
||||
return React.cloneElement(
|
||||
this.props.children,
|
||||
{
|
||||
onDragOver:function onDragOver(e){
|
||||
var sourceId=e.dataTransfer.getData('text/plain');
|
||||
if(dropFilter(sourceId)){
|
||||
e.preventDefault();}},
|
||||
|
||||
|
||||
onDrop:function onDrop(e){
|
||||
var sourceId=e.dataTransfer.getData('text/plain');
|
||||
if(dropFilter(sourceId)){
|
||||
e.preventDefault();
|
||||
dropAction(sourceId,thisId);}}});}}]);return DropTarget;}(React.Component);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
DropTarget.propTypes={
|
||||
children:React.PropTypes.element.isRequired,
|
||||
id:React.PropTypes.string.isRequired,
|
||||
dropFilter:React.PropTypes.func.isRequired,
|
||||
dropAction:React.PropTypes.func.isRequired};var
|
||||
|
||||
|
||||
Table=function(_React$Component3){_inherits(Table,_React$Component3); // eslint-disable-line no-unused-vars
|
||||
function Table(props){_classCallCheck(this,Table);var _this3=_possibleConstructorReturn(this,Object.getPrototypeOf(Table).call(this,
|
||||
props));
|
||||
_this3.state={
|
||||
aggrow:props.aggrow,
|
||||
viewport:{top:0,height:100}};return _this3;}_createClass(Table,[{key:'scroll',value:function scroll(
|
||||
|
||||
|
||||
|
||||
e){
|
||||
var viewport=e.target;
|
||||
var top=Math.floor((viewport.scrollTop-viewport.clientHeight*1.0)/rowHeight);
|
||||
var height=Math.ceil(viewport.clientHeight*3.0/rowHeight);
|
||||
this.state.viewport.top=top;
|
||||
this.state.viewport.height=height;
|
||||
this.forceUpdate();}},{key:'dropAggregator',value:function dropAggregator(
|
||||
|
||||
|
||||
s,d){
|
||||
var aggrow=this.state.aggrow;
|
||||
console.log('dropped '+s+' to '+d);
|
||||
if(s.startsWith('aggregate:active:')){
|
||||
var sIndex=parseInt(s.substr(17),10);
|
||||
var dIndex=-1;
|
||||
var active=aggrow.getActiveAggregators();
|
||||
var dragged=active[sIndex];
|
||||
if(d.startsWith('aggregate:insert:')){
|
||||
dIndex=parseInt(d.substr(17),10);}else
|
||||
if(d==='divider:insert'){
|
||||
dIndex=active.length;}else
|
||||
{
|
||||
throw 'not allowed to drag '+s+' to '+d;}
|
||||
|
||||
if(dIndex>sIndex){
|
||||
dIndex--;}
|
||||
|
||||
active.splice(sIndex,1);
|
||||
active.splice(dIndex,0,dragged);
|
||||
aggrow.setActiveAggregators(active);
|
||||
this.forceUpdate();}else
|
||||
if(s.startsWith('expander:active:')){
|
||||
var _sIndex=parseInt(s.substr(16),10);
|
||||
var _dIndex=-1;
|
||||
var _active=aggrow.getActiveExpanders();
|
||||
var _dragged=_active[_sIndex];
|
||||
if(d.startsWith('expander:insert:')){
|
||||
_dIndex=parseInt(d.substr(16),10);}else
|
||||
if(d==='divider:insert'){
|
||||
_dIndex=0;}else
|
||||
{
|
||||
throw 'not allowed to drag '+s+' to '+d;}
|
||||
|
||||
if(_dIndex>_sIndex){
|
||||
_dIndex--;}
|
||||
|
||||
_active.splice(_sIndex,1);
|
||||
_active.splice(_dIndex,0,_dragged);
|
||||
aggrow.setActiveExpanders(_active);
|
||||
this.forceUpdate();}}},{key:'render',value:function render()
|
||||
|
||||
|
||||
|
||||
{var _this4=this;
|
||||
var headers=[];
|
||||
var aggrow=this.state.aggrow;
|
||||
var aggregators=aggrow.getActiveAggregators();
|
||||
var expanders=aggrow.getActiveExpanders();
|
||||
// aggregators
|
||||
for(var i=0;i<aggregators.length;i++){
|
||||
var name=aggrow.getAggregatorName(aggregators[i]);
|
||||
headers.push(
|
||||
React.createElement(DropTarget,{
|
||||
id:'aggregate:insert:'+i.toString(),
|
||||
dropFilter:function dropFilter(){return true;},
|
||||
dropAction:function dropAction(s,d){_this4.dropAggregator(s,d);},__source:{fileName:_jsxFileName,lineNumber:149}},
|
||||
|
||||
React.createElement('div',{style:{
|
||||
width:'16px',
|
||||
height:'inherit',
|
||||
backgroundColor:'darkGray',
|
||||
flexShrink:'0'},__source:{fileName:_jsxFileName,lineNumber:154}})));
|
||||
|
||||
|
||||
headers.push(React.createElement(Draggable,{id:'aggregate:active:'+i.toString(),__source:{fileName:_jsxFileName,lineNumber:161}},
|
||||
React.createElement('div',{style:{width:'128px',textAlign:'center',flexShrink:'0'},__source:{fileName:_jsxFileName,lineNumber:162}},name)));}
|
||||
|
||||
|
||||
headers.push(
|
||||
React.createElement(DropTarget,{
|
||||
id:'divider:insert',
|
||||
dropFilter:function dropFilter(){return true;},
|
||||
dropAction:function dropAction(s,d){_this4.dropAggregator(s,d);},__source:{fileName:_jsxFileName,lineNumber:166}},
|
||||
|
||||
React.createElement('div',{style:{
|
||||
width:'16px',
|
||||
height:'inherit',
|
||||
backgroundColor:'gold',
|
||||
flexShrink:'0'},__source:{fileName:_jsxFileName,lineNumber:171}})));
|
||||
|
||||
|
||||
for(var _i=0;_i<expanders.length;_i++){
|
||||
var _name=aggrow.getExpanderName(expanders[_i]);
|
||||
var bg=_i%2===0?'white':'lightGray';
|
||||
headers.push(React.createElement(Draggable,{id:'expander:active:'+_i.toString(),__source:{fileName:_jsxFileName,lineNumber:181}},
|
||||
React.createElement('div',{style:{
|
||||
width:'128px',
|
||||
textAlign:'center',
|
||||
backgroundColor:bg,
|
||||
flexShrink:'0'},__source:{fileName:_jsxFileName,lineNumber:182}},
|
||||
|
||||
_name)));
|
||||
|
||||
|
||||
var sep=_i+1<expanders.length?'->':'...';
|
||||
headers.push(
|
||||
React.createElement(DropTarget,{
|
||||
id:'expander:insert:'+(_i+1).toString(),
|
||||
dropFilter:function dropFilter(){return true;},
|
||||
dropAction:function dropAction(s,d){_this4.dropAggregator(s,d);},__source:{fileName:_jsxFileName,lineNumber:193}},
|
||||
|
||||
React.createElement('div',{style:{
|
||||
height:'inherit',
|
||||
backgroundColor:'darkGray',
|
||||
flexShrink:'0'},__source:{fileName:_jsxFileName,lineNumber:198}},
|
||||
|
||||
sep)));}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
React.createElement('div',{style:{width:'100%',height:'100%',display:'flex',flexDirection:'column'},__source:{fileName:_jsxFileName,lineNumber:210}},
|
||||
React.createElement('div',{style:{
|
||||
width:'100%',
|
||||
height:'26px',
|
||||
display:'flex',
|
||||
flexDirection:'row',
|
||||
alignItems:'center',
|
||||
borderBottom:'2px solid black'},__source:{fileName:_jsxFileName,lineNumber:211}},
|
||||
|
||||
headers),
|
||||
|
||||
React.createElement('div',{style:{
|
||||
width:'100%',
|
||||
flexGrow:'1',
|
||||
overflow:'scroll'},
|
||||
onScroll:function onScroll(e){return _this4.scroll(e);},__source:{fileName:_jsxFileName,lineNumber:221}},
|
||||
React.createElement('div',{style:{position:'relative'},__source:{fileName:_jsxFileName,lineNumber:226}},
|
||||
this.renderVirtualizedRows()))));}},{key:'renderVirtualizedRows',value:function renderVirtualizedRows()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
{var _this5=this;
|
||||
var aggrow=this.state.aggrow;
|
||||
var viewport=this.state.viewport;
|
||||
var rows=aggrow.getRows(viewport.top,viewport.height);
|
||||
return (
|
||||
React.createElement('div',{style:{
|
||||
position:'absolute',
|
||||
width:'100%',
|
||||
height:(rowHeight*(aggrow.getHeight()+20)).toString()+'px'},__source:{fileName:_jsxFileName,lineNumber:239}},
|
||||
|
||||
rows.map(function(child){return _this5.renderRow(child);})));}},{key:'renderRow',value:function renderRow(
|
||||
|
||||
|
||||
|
||||
|
||||
row){var _this6=this;
|
||||
if(row===null){
|
||||
return null;}
|
||||
|
||||
var bg='lightGray';
|
||||
var aggrow=this.state.aggrow;
|
||||
var columns=[];
|
||||
var rowText='';
|
||||
var indent=4+aggrow.getRowIndent(row)*treeIndent;
|
||||
var aggregates=aggrow.getActiveAggregators();
|
||||
if(row.parent!==null&&row.parent.expander%2===0){
|
||||
bg='white';}
|
||||
|
||||
for(var i=0;i<aggregates.length;i++){
|
||||
var aggregate=aggrow.getRowAggregate(row,i);
|
||||
columns.push(
|
||||
React.createElement('div',{style:{
|
||||
width:'16px',
|
||||
height:'inherit',
|
||||
backgroundColor:'darkGray',
|
||||
flexShrink:'0'},__source:{fileName:_jsxFileName,lineNumber:265}}));
|
||||
|
||||
|
||||
columns.push(
|
||||
React.createElement('div',{style:{
|
||||
width:'128px',
|
||||
textAlign:'right',
|
||||
flexShrink:'0'},__source:{fileName:_jsxFileName,lineNumber:273}},
|
||||
|
||||
aggregate));}
|
||||
|
||||
|
||||
|
||||
columns.push(
|
||||
React.createElement('div',{style:{
|
||||
width:'16px',
|
||||
height:'inherit',
|
||||
backgroundColor:'gold',
|
||||
flexShrink:'0'},__source:{fileName:_jsxFileName,lineNumber:283}}));
|
||||
|
||||
|
||||
if(aggrow.canExpand(row)){
|
||||
rowText+='+';}else
|
||||
if(aggrow.canContract(row)){
|
||||
rowText+='-';}else
|
||||
{
|
||||
rowText+=' ';}
|
||||
|
||||
rowText+=aggrow.getRowLabel(row);
|
||||
columns.push(
|
||||
React.createElement('div',{style:{
|
||||
marginLeft:indent.toString()+'px',
|
||||
flexShrink:'0',
|
||||
whiteSpace:'nowrap'},__source:{fileName:_jsxFileName,lineNumber:299}},
|
||||
|
||||
rowText));
|
||||
|
||||
|
||||
return (
|
||||
React.createElement('div',{style:{
|
||||
position:'absolute',
|
||||
height:(rowHeight-1).toString()+'px',
|
||||
top:(rowHeight*row.top).toString()+'px',
|
||||
display:'flex',
|
||||
flexDirection:'row',
|
||||
backgroundColor:bg,
|
||||
borderBottom:'1px solid gray'},
|
||||
|
||||
onClick:function onClick(){
|
||||
if(aggrow.canExpand(row)){
|
||||
aggrow.expand(row);
|
||||
_this6.forceUpdate();}else
|
||||
if(aggrow.canContract(row)){
|
||||
aggrow.contract(row);
|
||||
_this6.forceUpdate();}},__source:{fileName:_jsxFileName,lineNumber:308}},
|
||||
|
||||
|
||||
columns));}}]);return Table;}(React.Component);
|
|
@ -0,0 +1,619 @@
|
|||
/**
|
||||
* Copyright (c) 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';
|
||||
/*eslint no-bitwise: "off"*/
|
||||
/*eslint no-console-disallow: "off"*/
|
||||
|
||||
// TODO: future features
|
||||
// put in a module.exports
|
||||
// filtering / search
|
||||
// pivot around frames in the middle of a stack by callers / callees
|
||||
// graphing?
|
||||
|
||||
function stringInterner() { // eslint-disable-line no-unused-vars
|
||||
const strings = [];
|
||||
const ids = {};
|
||||
return {
|
||||
intern: function internString(s) {
|
||||
const find = ids[s];
|
||||
if (find === undefined) {
|
||||
const id = strings.length;
|
||||
ids[s] = id;
|
||||
strings.push(s);
|
||||
return id;
|
||||
} else {
|
||||
return find;
|
||||
}
|
||||
},
|
||||
get: function getString(id) {
|
||||
return strings[id];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function stackData(stackIdMap, maxDepth) { // eslint-disable-line no-unused-vars
|
||||
return {
|
||||
maxDepth: maxDepth,
|
||||
get: function getStack(id) {
|
||||
return stackIdMap[id];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function stackRegistry(interner) { // eslint-disable-line no-unused-vars
|
||||
return {
|
||||
root: { id: 0 },
|
||||
nodeCount: 1,
|
||||
insert: function insertNode(parent, label) {
|
||||
const labelId = interner.intern(label);
|
||||
let node = parent[labelId];
|
||||
if (node === undefined) {
|
||||
node = { id: this.nodeCount };
|
||||
this.nodeCount++;
|
||||
parent[labelId] = node;
|
||||
}
|
||||
return node;
|
||||
},
|
||||
flatten: function flattenStacks() {
|
||||
let stackFrameCount = 0;
|
||||
function countStacks(tree, depth) {
|
||||
let leaf = true;
|
||||
for (const frameId in tree) {
|
||||
if (frameId !== 'id') {
|
||||
leaf = countStacks(tree[frameId], depth + 1);
|
||||
}
|
||||
}
|
||||
if (leaf) {
|
||||
stackFrameCount += depth;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
countStacks(this.root, 0);
|
||||
console.log('size needed to store stacks: ' + (stackFrameCount * 4).toString() + 'B');
|
||||
const stackIdMap = new Array(this.nodeCount);
|
||||
const stackArray = new Int32Array(stackFrameCount);
|
||||
let maxStackDepth = 0;
|
||||
stackFrameCount = 0;
|
||||
function flattenStacksImpl(tree, stack) {
|
||||
let childStack;
|
||||
maxStackDepth = Math.max(maxStackDepth, stack.length);
|
||||
for (const frameId in tree) {
|
||||
if (frameId !== 'id') {
|
||||
stack.push(Number(frameId));
|
||||
childStack = flattenStacksImpl(tree[frameId], stack);
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
const id = tree.id;
|
||||
if (id < 0 || id >= stackIdMap.length || stackIdMap[id] !== undefined) {
|
||||
throw 'invalid stack id!';
|
||||
}
|
||||
|
||||
if (childStack !== undefined) {
|
||||
// each child must have our stack as a prefix, so just use that
|
||||
stackIdMap[id] = childStack.subarray(0, stack.length);
|
||||
} else {
|
||||
const newStack = stackArray.subarray(stackFrameCount, stackFrameCount + stack.length);
|
||||
stackFrameCount += stack.length;
|
||||
for (let i = 0; i < stack.length; i++) {
|
||||
newStack[i] = stack[i];
|
||||
}
|
||||
stackIdMap[id] = newStack;
|
||||
}
|
||||
return stackIdMap[id];
|
||||
}
|
||||
flattenStacksImpl(this.root, []);
|
||||
|
||||
return new stackData(stackIdMap, maxStackDepth);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function aggrow(strings, stacks, numRows) { // eslint-disable-line no-unused-vars
|
||||
// expander ID definitions
|
||||
const FIELD_EXPANDER_ID_MIN = 0x0000;
|
||||
const FIELD_EXPANDER_ID_MAX = 0x7fff;
|
||||
const STACK_EXPANDER_ID_MIN = 0x8000;
|
||||
const STACK_EXPANDER_ID_MAX = 0xffff;
|
||||
|
||||
// used for row.expander which reference state.activeExpanders (with frame index masked in)
|
||||
const INVALID_ACTIVE_EXPANDER = -1;
|
||||
const ACTIVE_EXPANDER_MASK = 0xffff;
|
||||
const ACTIVE_EXPANDER_FRAME_SHIFT = 16;
|
||||
|
||||
// aggregator ID definitions
|
||||
const AGGREGATOR_ID_MAX = 0xffff;
|
||||
|
||||
// active aggragators can have sort order changed in the reference
|
||||
const ACTIVE_AGGREGATOR_MASK = 0xffff;
|
||||
const ACTIVE_AGGREGATOR_ASC_BIT = 0x10000;
|
||||
|
||||
// tree node state definitions
|
||||
const NODE_EXPANDED_BIT = 0x0001; // this row is expanded
|
||||
const NODE_REAGGREGATE_BIT = 0x0002; // children need aggregates
|
||||
const NODE_REORDER_BIT = 0x0004; // children need to be sorted
|
||||
const NODE_REPOSITION_BIT = 0x0008; // children need position
|
||||
const NODE_INDENT_SHIFT = 16;
|
||||
|
||||
function calleeFrameGetter(stack, depth) {
|
||||
return stack[depth];
|
||||
}
|
||||
|
||||
function callerFrameGetter(stack, depth) {
|
||||
return stack[stack.length - depth - 1];
|
||||
}
|
||||
|
||||
function createStackComparers(stackGetter, frameGetter) {
|
||||
const comparers = new Array(stacks.maxDepth);
|
||||
for (let depth = 0; depth < stacks.maxDepth; depth++) {
|
||||
const captureDepth = depth; // NB: to capture depth per loop iteration
|
||||
comparers[depth] = function calleeStackComparer(rowA, rowB) {
|
||||
const a = stackGetter(rowA);
|
||||
const b = stackGetter(rowB);
|
||||
// NB: we put the stacks that are too short at the top,
|
||||
// so they can be grouped into the '<exclusive>' bucket
|
||||
if (a.length <= captureDepth && b.length <= captureDepth) {
|
||||
return 0;
|
||||
} else if (a.length <= captureDepth) {
|
||||
return -1;
|
||||
} else if (b.length <= captureDepth) {
|
||||
return 1;
|
||||
}
|
||||
return frameGetter(a, captureDepth) - frameGetter(b, captureDepth);
|
||||
};
|
||||
}
|
||||
return comparers;
|
||||
}
|
||||
|
||||
function createTreeNode(parent, label, indices, expander) {
|
||||
const indent = parent === null ? 0 : (parent.state >>> NODE_INDENT_SHIFT) + 1;
|
||||
const state = NODE_REPOSITION_BIT |
|
||||
NODE_REAGGREGATE_BIT |
|
||||
NODE_REORDER_BIT |
|
||||
(indent << NODE_INDENT_SHIFT);
|
||||
return {
|
||||
parent: parent, // null if root
|
||||
children: null, // array of children nodes
|
||||
label: label, // string to show in UI
|
||||
indices: indices, // row indices under this node
|
||||
aggregates: null, // result of aggregate on indices
|
||||
expander: expander, // index into state.activeExpanders
|
||||
top: 0, // y position of top row (in rows)
|
||||
height: 1, // number of rows including children
|
||||
state: state, // see NODE_* definitions above
|
||||
};
|
||||
}
|
||||
|
||||
function noSortOrder(a, b) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const indices = new Int32Array(numRows);
|
||||
for (let i = 0; i < numRows; i++) {
|
||||
indices[i] = i;
|
||||
}
|
||||
|
||||
const state = {
|
||||
fieldExpanders: [], // tree expanders that expand on simple values
|
||||
stackExpanders: [], // tree expanders that expand stacks
|
||||
activeExpanders: [], // index into field or stack expanders, hierarchy of tree
|
||||
aggregators: [], // all available aggregators, might not be used
|
||||
activeAggregators: [], // index into aggregators, to actually compute
|
||||
sorter: noSortOrder, // compare function that uses sortOrder to sort row.children
|
||||
root: createTreeNode(null, '<root>', indices, INVALID_ACTIVE_EXPANDER),
|
||||
};
|
||||
|
||||
function evaluateAggregate(row) {
|
||||
const activeAggregators = state.activeAggregators;
|
||||
const aggregates = new Array(activeAggregators.length);
|
||||
for (let j = 0; j < activeAggregators.length; j++) {
|
||||
const aggregator = state.aggregators[activeAggregators[j]];
|
||||
aggregates[j] = aggregator.aggregator(row.indices);
|
||||
}
|
||||
row.aggregates = aggregates;
|
||||
row.state |= NODE_REAGGREGATE_BIT;
|
||||
}
|
||||
|
||||
function evaluateAggregates(row) {
|
||||
if ((row.state & NODE_EXPANDED_BIT) !== 0) {
|
||||
const children = row.children;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
evaluateAggregate(children[i]);
|
||||
}
|
||||
row.state |= NODE_REORDER_BIT;
|
||||
}
|
||||
row.state ^= NODE_REAGGREGATE_BIT;
|
||||
}
|
||||
|
||||
function evaluateOrder(row) {
|
||||
if ((row.state & NODE_EXPANDED_BIT) !== 0) {
|
||||
const children = row.children;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
child.state |= NODE_REORDER_BIT;
|
||||
}
|
||||
children.sort(state.sorter);
|
||||
row.state |= NODE_REPOSITION_BIT;
|
||||
}
|
||||
row.state ^= NODE_REORDER_BIT;
|
||||
}
|
||||
|
||||
function evaluatePosition(row) {
|
||||
if ((row.state & NODE_EXPANDED_BIT) !== 0) {
|
||||
const children = row.children;
|
||||
let childTop = row.top + 1;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
if (child.top !== childTop) {
|
||||
child.top = childTop;
|
||||
child.state |= NODE_REPOSITION_BIT;
|
||||
}
|
||||
childTop += child.height;
|
||||
}
|
||||
}
|
||||
row.state ^= NODE_REPOSITION_BIT;
|
||||
}
|
||||
|
||||
function getRowsImpl(row, top, height, result) {
|
||||
if ((row.state & NODE_REAGGREGATE_BIT) !== 0) {
|
||||
evaluateAggregates(row);
|
||||
}
|
||||
if ((row.state & NODE_REORDER_BIT) !== 0) {
|
||||
evaluateOrder(row);
|
||||
}
|
||||
if ((row.state & NODE_REPOSITION_BIT) !== 0) {
|
||||
evaluatePosition(row);
|
||||
}
|
||||
|
||||
if (row.top >= top && row.top < top + height) {
|
||||
if (result[row.top - top] != null) {
|
||||
throw 'getRows put more than one row at position ' + row.top + ' into result';
|
||||
}
|
||||
result[row.top - top] = row;
|
||||
}
|
||||
if ((row.state & NODE_EXPANDED_BIT) !== 0) {
|
||||
const children = row.children;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
if (child.top < top + height && top < child.top + child.height) {
|
||||
getRowsImpl(child, top, height, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateHeight(row, heightChange) {
|
||||
while (row !== null) {
|
||||
row.height += heightChange;
|
||||
row.state |= NODE_REPOSITION_BIT;
|
||||
row = row.parent;
|
||||
}
|
||||
}
|
||||
|
||||
function addChildrenWithFieldExpander(row, expander, nextActiveIndex) {
|
||||
const rowIndices = row.indices;
|
||||
const comparer = expander.comparer;
|
||||
rowIndices.sort(comparer);
|
||||
let begin = 0;
|
||||
let end = 1;
|
||||
row.children = [];
|
||||
while (end < rowIndices.length) {
|
||||
if (comparer(rowIndices[begin], rowIndices[end]) !== 0) {
|
||||
row.children.push(createTreeNode(
|
||||
row,
|
||||
expander.name + ': ' + expander.formatter(rowIndices[begin]),
|
||||
rowIndices.subarray(begin, end),
|
||||
nextActiveIndex));
|
||||
begin = end;
|
||||
}
|
||||
end++;
|
||||
}
|
||||
row.children.push(createTreeNode(
|
||||
row,
|
||||
expander.name + ': ' + expander.formatter(rowIndices[begin]),
|
||||
rowIndices.subarray(begin, end),
|
||||
nextActiveIndex));
|
||||
}
|
||||
|
||||
function addChildrenWithStackExpander(row, expander, activeIndex, depth, nextActiveIndex) {
|
||||
const rowIndices = row.indices;
|
||||
const stackGetter = expander.stackGetter;
|
||||
const frameGetter = expander.frameGetter;
|
||||
const comparer = expander.comparers[depth];
|
||||
const expandNextFrame = activeIndex | ((depth + 1) << ACTIVE_EXPANDER_FRAME_SHIFT);
|
||||
rowIndices.sort(comparer);
|
||||
let columnName = '';
|
||||
if (depth === 0) {
|
||||
columnName = expander.name + ': ';
|
||||
}
|
||||
|
||||
// put all the too-short stacks under <exclusive>
|
||||
let begin = 0;
|
||||
let beginStack = null;
|
||||
row.children = [];
|
||||
while (begin < rowIndices.length) {
|
||||
beginStack = stackGetter(rowIndices[begin]);
|
||||
if (beginStack.length > depth) {
|
||||
break;
|
||||
}
|
||||
begin++;
|
||||
}
|
||||
if (begin > 0) {
|
||||
row.children.push(createTreeNode(
|
||||
row,
|
||||
columnName + '<exclusive>',
|
||||
rowIndices.subarray(0, begin),
|
||||
nextActiveIndex));
|
||||
}
|
||||
// aggregate the rest under frames
|
||||
if (begin < rowIndices.length) {
|
||||
let end = begin + 1;
|
||||
while (end < rowIndices.length) {
|
||||
const endStack = stackGetter(rowIndices[end]);
|
||||
if (frameGetter(beginStack, depth) !== frameGetter(endStack, depth)) {
|
||||
row.children.push(createTreeNode(
|
||||
row,
|
||||
columnName + strings.get(frameGetter(beginStack, depth)),
|
||||
rowIndices.subarray(begin, end),
|
||||
expandNextFrame));
|
||||
begin = end;
|
||||
beginStack = endStack;
|
||||
}
|
||||
end++;
|
||||
}
|
||||
row.children.push(createTreeNode(
|
||||
row,
|
||||
columnName + strings.get(frameGetter(beginStack, depth)),
|
||||
rowIndices.subarray(begin, end),
|
||||
expandNextFrame));
|
||||
}
|
||||
}
|
||||
|
||||
function contractRow(row) {
|
||||
if ((row.state & NODE_EXPANDED_BIT) === 0) {
|
||||
throw 'can not contract row, already contracted';
|
||||
}
|
||||
row.state ^= NODE_EXPANDED_BIT;
|
||||
const heightChange = 1 - row.height;
|
||||
updateHeight(row, heightChange);
|
||||
}
|
||||
|
||||
function pruneExpanders(row, oldExpander, newExpander) {
|
||||
row.state |= NODE_REPOSITION_BIT;
|
||||
if (row.expander === oldExpander) {
|
||||
row.state |= NODE_REAGGREGATE_BIT | NODE_REORDER_BIT | NODE_REPOSITION_BIT;
|
||||
if ((row.state & NODE_EXPANDED_BIT) !== 0) {
|
||||
contractRow(row);
|
||||
}
|
||||
row.children = null;
|
||||
row.expander = newExpander;
|
||||
} else {
|
||||
row.state |= NODE_REPOSITION_BIT;
|
||||
const children = row.children;
|
||||
if (children != null) {
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i];
|
||||
pruneExpanders(child, oldExpander, newExpander);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
addFieldExpander: function addFieldExpander(name, formatter, comparer) {
|
||||
if (FIELD_EXPANDER_ID_MIN + state.fieldExpanders.length >= FIELD_EXPANDER_ID_MAX) {
|
||||
throw 'too many field expanders!';
|
||||
}
|
||||
state.fieldExpanders.push({
|
||||
name: name, // name for column
|
||||
formatter: formatter, // row index -> display string
|
||||
comparer: comparer, // compares by two row indices
|
||||
});
|
||||
return FIELD_EXPANDER_ID_MIN + state.fieldExpanders.length - 1;
|
||||
},
|
||||
addCalleeStackExpander: function addCalleeStackExpander(name, stackGetter) {
|
||||
if (STACK_EXPANDER_ID_MIN + state.fieldExpanders.length >= STACK_EXPANDER_ID_MAX) {
|
||||
throw 'too many stack expanders!';
|
||||
}
|
||||
state.stackExpanders.push({
|
||||
name: name, // name for column
|
||||
stackGetter: stackGetter, // row index -> stack array
|
||||
comparers: createStackComparers(stackGetter, calleeFrameGetter), // depth -> comparer
|
||||
frameGetter: calleeFrameGetter, // (stack, depth) -> string id
|
||||
});
|
||||
return STACK_EXPANDER_ID_MIN + state.stackExpanders.length - 1;
|
||||
},
|
||||
addCallerStackExpander: function addCallerStackExpander(name, stackGetter) {
|
||||
if (STACK_EXPANDER_ID_MIN + state.fieldExpanders.length >= STACK_EXPANDER_ID_MAX) {
|
||||
throw 'too many stack expanders!';
|
||||
}
|
||||
state.stackExpanders.push({
|
||||
name: name,
|
||||
stackGetter: stackGetter,
|
||||
comparers: createStackComparers(stackGetter, callerFrameGetter),
|
||||
frameGetter: callerFrameGetter,
|
||||
});
|
||||
return STACK_EXPANDER_ID_MIN + state.stackExpanders.length - 1;
|
||||
},
|
||||
getExpanders: function getExpanders() {
|
||||
const expanders = [];
|
||||
for (let i = 0; i < state.fieldExpanders.length; i++) {
|
||||
expanders.push(FIELD_EXPANDER_ID_MIN + i);
|
||||
}
|
||||
for (let i = 0; i < state.stackExpanders.length; i++) {
|
||||
expanders.push(STACK_EXPANDER_ID_MIN + i);
|
||||
}
|
||||
return expanders;
|
||||
},
|
||||
getExpanderName: function getExpanderName(id) {
|
||||
if (id >= FIELD_EXPANDER_ID_MIN && id <= FIELD_EXPANDER_ID_MAX) {
|
||||
return state.fieldExpanders[id - FIELD_EXPANDER_ID_MIN].name;
|
||||
} else if (id >= STACK_EXPANDER_ID_MIN && id <= STACK_EXPANDER_ID_MAX) {
|
||||
return state.stackExpanders[id - STACK_EXPANDER_ID_MIN].name;
|
||||
}
|
||||
throw 'Unknown expander ID ' + id.toString();
|
||||
},
|
||||
setActiveExpanders: function setActiveExpanders(ids) {
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
const id = ids[i];
|
||||
if (id >= FIELD_EXPANDER_ID_MIN && id <= FIELD_EXPANDER_ID_MAX) {
|
||||
if (id - FIELD_EXPANDER_ID_MIN >= state.fieldExpanders.length) {
|
||||
throw 'field expander for id ' + id.toString() + ' does not exist!';
|
||||
}
|
||||
} else if (id >= STACK_EXPANDER_ID_MIN && id <= STACK_EXPANDER_ID_MAX) {
|
||||
if (id - STACK_EXPANDER_ID_MIN >= state.stackExpanders.length) {
|
||||
throw 'stack expander for id ' + id.toString() + ' does not exist!';
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
if (state.activeExpanders.length <= i) {
|
||||
pruneExpanders(state.root, INVALID_ACTIVE_EXPANDER, i);
|
||||
break;
|
||||
} else if (ids[i] !== state.activeExpanders[i]) {
|
||||
pruneExpanders(state.root, i, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// TODO: if ids is prefix of activeExpanders, we need to make an expander invalid
|
||||
state.activeExpanders = ids.slice();
|
||||
},
|
||||
getActiveExpanders: function getActiveExpanders() {
|
||||
return state.activeExpanders.slice();
|
||||
},
|
||||
addAggregator: function addAggregator(name, aggregator, formatter, sorter) {
|
||||
if (state.aggregators.length >= AGGREGATOR_ID_MAX) {
|
||||
throw 'too many aggregators!';
|
||||
}
|
||||
state.aggregators.push({
|
||||
name: name, // name for column
|
||||
aggregator: aggregator, // index array -> aggregate value
|
||||
formatter: formatter, // aggregate value -> display string
|
||||
sorter: sorter, // compare two aggregate values
|
||||
});
|
||||
return state.aggregators.length - 1;
|
||||
},
|
||||
getAggregators: function getAggregators() {
|
||||
const aggregators = [];
|
||||
for (let i = 0; i < state.aggregators.length; i++) {
|
||||
aggregators.push(i);
|
||||
}
|
||||
return aggregators;
|
||||
},
|
||||
getAggregatorName: function getAggregatorName(id) {
|
||||
return state.aggregators[id & ACTIVE_AGGREGATOR_MASK].name;
|
||||
},
|
||||
setActiveAggregators: function setActiveAggregators(ids) {
|
||||
for (let i = 0; i < ids.length; i++) {
|
||||
const id = ids[i] & ACTIVE_AGGREGATOR_MASK;
|
||||
if (id < 0 || id > state.aggregators.length) {
|
||||
throw 'aggregator id ' + id.toString() + ' not valid';
|
||||
}
|
||||
}
|
||||
state.activeAggregators = ids.slice();
|
||||
// NB: evaluate root here because dirty bit is for children
|
||||
// so someone has to start with root, and it might as well be right away
|
||||
evaluateAggregate(state.root);
|
||||
let sorter = noSortOrder;
|
||||
for (let i = ids.length - 1; i >= 0; i--) {
|
||||
const ascending = (ids[i] & ACTIVE_AGGREGATOR_ASC_BIT) !== 0;
|
||||
const id = ids[i] & ACTIVE_AGGREGATOR_MASK;
|
||||
const comparer = state.aggregators[id].sorter;
|
||||
const captureSorter = sorter;
|
||||
const captureIndex = i;
|
||||
sorter = function (a, b) {
|
||||
const c = comparer(a.aggregates[captureIndex], b.aggregates[captureIndex]);
|
||||
if (c === 0) {
|
||||
return captureSorter(a, b);
|
||||
}
|
||||
return ascending ? -c : c;
|
||||
};
|
||||
}
|
||||
state.sorter = sorter;
|
||||
state.root.state |= NODE_REORDER_BIT;
|
||||
},
|
||||
getActiveAggregators: function getActiveAggregators() {
|
||||
return state.activeAggregators.slice();
|
||||
},
|
||||
getRows: function getRows(top, height) {
|
||||
const result = new Array(height);
|
||||
for (let i = 0; i < height; i++) {
|
||||
result[i] = null;
|
||||
}
|
||||
getRowsImpl(state.root, top, height, result);
|
||||
return result;
|
||||
},
|
||||
getRowLabel: function getRowLabel(row) {
|
||||
return row.label;
|
||||
},
|
||||
getRowIndent: function getRowIndent(row) {
|
||||
return row.state >>> NODE_INDENT_SHIFT;
|
||||
},
|
||||
getRowAggregate: function getRowAggregate(row, index) {
|
||||
const aggregator = state.aggregators[state.activeAggregators[index]];
|
||||
return aggregator.formatter(row.aggregates[index]);
|
||||
},
|
||||
getHeight: function getHeight() {
|
||||
return state.root.height;
|
||||
},
|
||||
canExpand: function canExpand(row) {
|
||||
return (row.state & NODE_EXPANDED_BIT) === 0 && (row.expander !== INVALID_ACTIVE_EXPANDER);
|
||||
},
|
||||
canContract: function canContract(row) {
|
||||
return (row.state & NODE_EXPANDED_BIT) !== 0;
|
||||
},
|
||||
expand: function expand(row) {
|
||||
if ((row.state & NODE_EXPANDED_BIT) !== 0) {
|
||||
throw 'can not expand row, already expanded';
|
||||
}
|
||||
if (row.height !== 1) {
|
||||
throw 'unexpanded row has height ' + row.height.toString() + ' != 1';
|
||||
}
|
||||
if (row.children === null) { // first expand, generate children
|
||||
const activeIndex = row.expander & ACTIVE_EXPANDER_MASK;
|
||||
let nextActiveIndex = activeIndex + 1; // NB: if next is stack, frame is 0
|
||||
if (nextActiveIndex >= state.activeExpanders.length) {
|
||||
nextActiveIndex = INVALID_ACTIVE_EXPANDER;
|
||||
}
|
||||
if (activeIndex >= state.activeExpanders.length) {
|
||||
throw 'invalid active expander index ' + activeIndex.toString();
|
||||
}
|
||||
const exId = state.activeExpanders[activeIndex];
|
||||
if (exId >= FIELD_EXPANDER_ID_MIN &&
|
||||
exId < FIELD_EXPANDER_ID_MIN + state.fieldExpanders.length) {
|
||||
const expander = state.fieldExpanders[exId - FIELD_EXPANDER_ID_MIN];
|
||||
addChildrenWithFieldExpander(row, expander, nextActiveIndex);
|
||||
} else if (exId >= STACK_EXPANDER_ID_MIN &&
|
||||
exId < STACK_EXPANDER_ID_MIN + state.stackExpanders.length) {
|
||||
const depth = row.expander >>> ACTIVE_EXPANDER_FRAME_SHIFT;
|
||||
const expander = state.stackExpanders[exId - STACK_EXPANDER_ID_MIN];
|
||||
addChildrenWithStackExpander(row, expander, activeIndex, depth, nextActiveIndex);
|
||||
} else {
|
||||
throw 'state.activeIndex ' + activeIndex.toString()
|
||||
+ ' has invalid expander' + exId.toString();
|
||||
}
|
||||
}
|
||||
row.state |= NODE_EXPANDED_BIT
|
||||
| NODE_REAGGREGATE_BIT | NODE_REORDER_BIT | NODE_REPOSITION_BIT;
|
||||
let heightChange = 0;
|
||||
for (let i = 0; i < row.children.length; i++) {
|
||||
heightChange += row.children[i].height;
|
||||
}
|
||||
updateHeight(row, heightChange);
|
||||
// if children only contains one node, then expand it as well
|
||||
if (row.children.length === 1 && this.canExpand(row.children[0])) {
|
||||
this.expand(row.children[0]);
|
||||
}
|
||||
},
|
||||
contract: function contract(row) {
|
||||
contractRow(row);
|
||||
},
|
||||
};
|
||||
}
|
|
@ -0,0 +1,287 @@
|
|||
/**
|
||||
* Copyright (c) 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';
|
||||
/*eslint no-console-disallow: "off"*/
|
||||
/*global React ReactDOM Table stringInterner stackRegistry aggrow preLoadedCapture:true*/
|
||||
|
||||
function registerReactComponentTreeImpl(refs, registry, parents, inEdgeNames, trees, id) {
|
||||
if (parents[id] === undefined) {
|
||||
// not a component
|
||||
} else if (parents[id] === null) {
|
||||
trees[id] = registry.insert(registry.root, '<internalInstance>');
|
||||
} else {
|
||||
const parent = parents[id];
|
||||
const inEdgeName = inEdgeNames[id];
|
||||
let parentTree = trees[parent];
|
||||
if (parentTree === undefined) {
|
||||
parentTree = registerReactComponentTreeImpl(
|
||||
refs,
|
||||
registry,
|
||||
parents,
|
||||
inEdgeNames,
|
||||
trees,
|
||||
parent);
|
||||
}
|
||||
trees[id] = registry.insert(parentTree, inEdgeName);
|
||||
}
|
||||
return trees[id];
|
||||
}
|
||||
|
||||
// TODO: make it easier to query the heap graph, it's super annoying to deal with edges directly
|
||||
function registerReactComponentTree(refs, registry) {
|
||||
// build list of parents for react interal instances, so we can connect a tree
|
||||
const parents = {};
|
||||
const inEdgeNames = {};
|
||||
for (const id in refs) {
|
||||
const ref = refs[id];
|
||||
for (const linkId in ref.edges) {
|
||||
if (linkId !== '0x0') {
|
||||
const name = ref.edges[linkId];
|
||||
if (name === '_renderedChildren') {
|
||||
if (parents[id] === undefined) {
|
||||
// mark that we are a react component, even if we don't have a parent
|
||||
parents[id] = null;
|
||||
}
|
||||
const childrenRef = refs[linkId];
|
||||
for (const childId in childrenRef.edges) {
|
||||
const linkName = childrenRef.edges[childId];
|
||||
if (linkName.startsWith('.')) {
|
||||
parents[childId] = id;
|
||||
inEdgeNames[childId] = linkName;
|
||||
}
|
||||
}
|
||||
} else if (name === '_renderedComponent') {
|
||||
if (parents[id] === undefined) {
|
||||
parents[id] = null;
|
||||
}
|
||||
parents[linkId] = id;
|
||||
inEdgeNames[linkId] = '_renderedComponent';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// build tree of react internal instances (since that's what has the structure)
|
||||
const trees = {};
|
||||
for (const id in refs) {
|
||||
registerReactComponentTreeImpl(refs, registry, parents, inEdgeNames, trees, id);
|
||||
}
|
||||
// hook in components by looking at their _reactInternalInstance fields
|
||||
for (const id in refs) {
|
||||
const ref = refs[id];
|
||||
for (const linkId in ref.edges) {
|
||||
const name = ref.edges[linkId];
|
||||
if (name === '_reactInternalInstance') {
|
||||
if (trees[linkId] !== undefined) {
|
||||
trees[id] = registry.insert(trees[linkId], '<component>');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return trees;
|
||||
}
|
||||
|
||||
function registerPathToRoot(roots, refs, registry, reactComponentTree) {
|
||||
const visited = {};
|
||||
let breadth = [];
|
||||
for (let i = 0; i < roots.length; i++) {
|
||||
const id = roots[i];
|
||||
if (visited[id] === undefined) {
|
||||
const ref = refs[id];
|
||||
visited[id] = registry.insert(registry.root, ref.type);
|
||||
breadth.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
while (breadth.length > 0) {
|
||||
const nextBreadth = [];
|
||||
for (let i = 0; i < breadth.length; i++) {
|
||||
const id = breadth[i];
|
||||
const ref = refs[id];
|
||||
const node = visited[id];
|
||||
// TODO: make edges map id -> name, (empty for none) seems that would be better
|
||||
|
||||
const edges = Object.getOwnPropertyNames(ref.edges);
|
||||
edges.sort(function putUnknownLast(a, b) {
|
||||
const aName = ref.edges[a];
|
||||
const bName = ref.edges[b];
|
||||
if (aName === null && bName !== null) {
|
||||
return 1;
|
||||
} else if (aName !== null && bName === null) {
|
||||
return -1;
|
||||
} else if (aName === null && bName === null) {
|
||||
return 0;
|
||||
} else {
|
||||
return a.localeCompare(b);
|
||||
}
|
||||
});
|
||||
|
||||
for (let j = 0; j < edges.length; j++) {
|
||||
const edgeId = edges[j];
|
||||
let edgeName = '';
|
||||
if (ref.edges[edgeId]) {
|
||||
edgeName = ref.edges[edgeId] + ': ';
|
||||
}
|
||||
if (visited[edgeId] === undefined) {
|
||||
const edgeRef = refs[edgeId];
|
||||
if (edgeRef === undefined) {
|
||||
// TODO: figure out why we have edges that point to things not JSCell
|
||||
//console.log('registerPathToRoot unable to follow edge from ' + id + ' to ' + edgeId);
|
||||
} else {
|
||||
visited[edgeId] = registry.insert(node, edgeName + edgeRef.type);
|
||||
nextBreadth.push(edgeId);
|
||||
if (reactComponentTree[edgeId] === undefined) {
|
||||
reactComponentTree[edgeId] = reactComponentTree[id];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
breadth = nextBreadth;
|
||||
}
|
||||
return visited;
|
||||
}
|
||||
|
||||
function captureRegistry() {
|
||||
const strings = stringInterner();
|
||||
const stacks = stackRegistry(strings);
|
||||
const data = new Int32Array(0);
|
||||
|
||||
const idField = 0;
|
||||
const typeField = 1;
|
||||
const sizeField = 2;
|
||||
const traceField = 3;
|
||||
const pathField = 4;
|
||||
const reactField = 5;
|
||||
const numFields = 6;
|
||||
|
||||
return {
|
||||
strings: strings,
|
||||
stacks: stacks,
|
||||
data: data,
|
||||
register: function registerCapture(captureId, capture) {
|
||||
// NB: capture.refs is potentially VERY large, so we try to avoid making
|
||||
// copies, even of iteration is a bit more annoying.
|
||||
let rowCount = 0;
|
||||
for (const id in capture.refs) { // eslint-disable-line no-unused-vars
|
||||
rowCount++;
|
||||
}
|
||||
console.log(
|
||||
'increasing row data from ' + (this.data.length * 4).toString() + 'B to ' +
|
||||
(this.data.length * 4 + rowCount * numFields * 4).toString() + 'B'
|
||||
);
|
||||
const newData = new Int32Array(this.data.length + rowCount * numFields);
|
||||
newData.set(data);
|
||||
let dataOffset = this.data.length;
|
||||
this.data = null;
|
||||
|
||||
const reactComponentTreeMap = registerReactComponentTree(capture.refs, this.stacks);
|
||||
const rootPathMap = registerPathToRoot(
|
||||
capture.roots,
|
||||
capture.refs,
|
||||
this.stacks,
|
||||
reactComponentTreeMap
|
||||
);
|
||||
const internedCaptureId = this.strings.intern(captureId);
|
||||
for (const id in capture.refs) {
|
||||
const ref = capture.refs[id];
|
||||
newData[dataOffset + idField] = parseInt(id, 16);
|
||||
newData[dataOffset + typeField] = this.strings.intern(ref.type);
|
||||
newData[dataOffset + sizeField] = ref.size;
|
||||
newData[dataOffset + traceField] = internedCaptureId;
|
||||
const pathNode = rootPathMap[id];
|
||||
if (pathNode === undefined) {
|
||||
throw 'did not find path for ref!';
|
||||
}
|
||||
newData[dataOffset + pathField] = pathNode.id;
|
||||
const reactTree = reactComponentTreeMap[id];
|
||||
if (reactTree === undefined) {
|
||||
newData[dataOffset + reactField] =
|
||||
this.stacks.insert(this.stacks.root, '<not-under-tree>').id;
|
||||
} else {
|
||||
newData[dataOffset + reactField] = reactTree.id;
|
||||
}
|
||||
dataOffset += numFields;
|
||||
}
|
||||
this.data = newData;
|
||||
},
|
||||
getAggrow: function getAggrow() {
|
||||
const agStrings = this.strings;
|
||||
const agStacks = this.stacks.flatten();
|
||||
const agData = this.data;
|
||||
const agNumRows = agData.length / numFields;
|
||||
const ag = new aggrow(agStrings, agStacks, agNumRows);
|
||||
|
||||
const idExpander = ag.addFieldExpander('Id',
|
||||
function getId(row) {
|
||||
let id = agData[row * numFields + idField];
|
||||
if (id < 0) {
|
||||
id += 0x100000000; // data is int32, id is uint32
|
||||
}
|
||||
return '0x' + id.toString(16);
|
||||
},
|
||||
function compareAddress(rowA, rowB) {
|
||||
return agData[rowA * numFields + idField] - agData[rowB * numFields + idField];
|
||||
});
|
||||
|
||||
const typeExpander = ag.addFieldExpander('Type',
|
||||
function getSize(row) { return agStrings.get(agData[row * numFields + typeField]); },
|
||||
function compareSize(rowA, rowB) {
|
||||
return agData[rowA * numFields + typeField] - agData[rowB * numFields + typeField];
|
||||
});
|
||||
|
||||
ag.addFieldExpander('Size',
|
||||
function getSize(row) { return agData[row * numFields + sizeField].toString(); },
|
||||
function compareSize(rowA, rowB) {
|
||||
return agData[rowA * numFields + sizeField] - agData[rowB * numFields + sizeField];
|
||||
});
|
||||
|
||||
const traceExpander = ag.addFieldExpander('Trace',
|
||||
function getSize(row) { return agStrings.get(agData[row * numFields + traceField]); },
|
||||
function compareSize(rowA, rowB) {
|
||||
return agData[rowA * numFields + traceField] - agData[rowB * numFields + traceField];
|
||||
});
|
||||
|
||||
const pathExpander = ag.addCalleeStackExpander('Path',
|
||||
function getStack(row) { return agStacks.get(agData[row * numFields + pathField]); });
|
||||
|
||||
const reactExpander = ag.addCalleeStackExpander('React Tree',
|
||||
function getStack(row) { return agStacks.get(agData[row * numFields + reactField]); });
|
||||
|
||||
const sizeAggregator = ag.addAggregator('Size',
|
||||
function aggregateSize(indices) {
|
||||
let size = 0;
|
||||
for (let i = 0; i < indices.length; i++) {
|
||||
const row = indices[i];
|
||||
size += agData[row * numFields + sizeField];
|
||||
}
|
||||
return size;
|
||||
},
|
||||
function formatSize(value) { return value.toString(); },
|
||||
function sortSize(a, b) { return b - a; } );
|
||||
|
||||
const countAggregator = ag.addAggregator('Count',
|
||||
function aggregateCount(indices) {
|
||||
return indices.length;
|
||||
},
|
||||
function formatCount(value) { return value.toString(); },
|
||||
function sortCount(a, b) { return b - a; } );
|
||||
|
||||
ag.setActiveExpanders([pathExpander, reactExpander, typeExpander, idExpander, traceExpander]);
|
||||
ag.setActiveAggregators([sizeAggregator, countAggregator]);
|
||||
return ag;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (preLoadedCapture) {
|
||||
const r = new captureRegistry();
|
||||
r.register('trace', preLoadedCapture);
|
||||
preLoadedCapture = undefined; // let GG clean up the capture
|
||||
ReactDOM.render(<Table aggrow={r.getAggrow()} />, document.body);
|
||||
}
|
|
@ -0,0 +1,330 @@
|
|||
/**
|
||||
* Copyright (c) 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';
|
||||
/*eslint no-console-disallow: "off"*/
|
||||
/*global React:true*/
|
||||
|
||||
// TODO:
|
||||
// selection and arrow keys for navigating
|
||||
|
||||
const rowHeight = 20;
|
||||
const treeIndent = 16;
|
||||
|
||||
class Draggable extends React.Component { // eslint-disable-line no-unused-vars
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const id = this.props.id;
|
||||
function dragStart(e) {
|
||||
e.dataTransfer.setData('text/plain', id);
|
||||
}
|
||||
return React.cloneElement(
|
||||
this.props.children,
|
||||
{ draggable: 'true', onDragStart: dragStart }
|
||||
);
|
||||
}
|
||||
}
|
||||
Draggable.propTypes = {
|
||||
children: React.PropTypes.element.isRequired,
|
||||
id: React.PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
class DropTarget extends React.Component { // eslint-disable-line no-unused-vars
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const thisId = this.props.id;
|
||||
const dropFilter = this.props.dropFilter;
|
||||
const dropAction = this.props.dropAction;
|
||||
return React.cloneElement(
|
||||
this.props.children,
|
||||
{
|
||||
onDragOver: (e) => {
|
||||
const sourceId = e.dataTransfer.getData('text/plain');
|
||||
if (dropFilter(sourceId)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
onDrop: (e) => {
|
||||
const sourceId = e.dataTransfer.getData('text/plain');
|
||||
if (dropFilter(sourceId)) {
|
||||
e.preventDefault();
|
||||
dropAction(sourceId, thisId);
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DropTarget.propTypes = {
|
||||
children: React.PropTypes.element.isRequired,
|
||||
id: React.PropTypes.string.isRequired,
|
||||
dropFilter: React.PropTypes.func.isRequired,
|
||||
dropAction: React.PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class Table extends React.Component { // eslint-disable-line no-unused-vars
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
aggrow: props.aggrow,
|
||||
viewport: { top: 0, height: 100 },
|
||||
};
|
||||
}
|
||||
|
||||
scroll(e) {
|
||||
const viewport = e.target;
|
||||
const top = Math.floor((viewport.scrollTop - viewport.clientHeight * 1.0) / rowHeight);
|
||||
const height = Math.ceil(viewport.clientHeight * 3.0 / rowHeight);
|
||||
this.state.viewport.top = top;
|
||||
this.state.viewport.height = height;
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
dropAggregator(s, d) {
|
||||
const aggrow = this.state.aggrow;
|
||||
console.log('dropped ' + s + ' to ' + d);
|
||||
if (s.startsWith('aggregate:active:')) {
|
||||
const sIndex = parseInt(s.substr(17), 10);
|
||||
let dIndex = -1;
|
||||
const active = aggrow.getActiveAggregators();
|
||||
const dragged = active[sIndex];
|
||||
if (d.startsWith('aggregate:insert:')) {
|
||||
dIndex = parseInt(d.substr(17), 10);
|
||||
} else if (d === 'divider:insert') {
|
||||
dIndex = active.length;
|
||||
} else {
|
||||
throw 'not allowed to drag ' + s + ' to ' + d;
|
||||
}
|
||||
if (dIndex > sIndex) {
|
||||
dIndex--;
|
||||
}
|
||||
active.splice(sIndex, 1);
|
||||
active.splice(dIndex, 0, dragged);
|
||||
aggrow.setActiveAggregators(active);
|
||||
this.forceUpdate();
|
||||
} else if (s.startsWith('expander:active:')) {
|
||||
const sIndex = parseInt(s.substr(16), 10);
|
||||
let dIndex = -1;
|
||||
const active = aggrow.getActiveExpanders();
|
||||
const dragged = active[sIndex];
|
||||
if (d.startsWith('expander:insert:')) {
|
||||
dIndex = parseInt(d.substr(16), 10);
|
||||
} else if (d === 'divider:insert') {
|
||||
dIndex = 0;
|
||||
} else {
|
||||
throw 'not allowed to drag ' + s + ' to ' + d;
|
||||
}
|
||||
if (dIndex > sIndex) {
|
||||
dIndex--;
|
||||
}
|
||||
active.splice(sIndex, 1);
|
||||
active.splice(dIndex, 0, dragged);
|
||||
aggrow.setActiveExpanders(active);
|
||||
this.forceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const headers = [];
|
||||
const aggrow = this.state.aggrow;
|
||||
const aggregators = aggrow.getActiveAggregators();
|
||||
const expanders = aggrow.getActiveExpanders();
|
||||
// aggregators
|
||||
for (let i = 0; i < aggregators.length; i++) {
|
||||
const name = aggrow.getAggregatorName(aggregators[i]);
|
||||
headers.push((
|
||||
<DropTarget
|
||||
id={'aggregate:insert:' + i.toString()}
|
||||
dropFilter={()=>{return true; }}
|
||||
dropAction={(s, d)=>{ this.dropAggregator(s, d); }}
|
||||
>
|
||||
<div style={{
|
||||
width: '16px',
|
||||
height: 'inherit',
|
||||
backgroundColor: 'darkGray',
|
||||
flexShrink: '0' }}
|
||||
></div>
|
||||
</DropTarget>));
|
||||
headers.push((<Draggable id={'aggregate:active:' + i.toString()}>
|
||||
<div style={{ width: '128px', textAlign: 'center', flexShrink: '0' }}>{name}</div>
|
||||
</Draggable>));
|
||||
}
|
||||
headers.push((
|
||||
<DropTarget
|
||||
id="divider:insert"
|
||||
dropFilter={()=>{return true; }}
|
||||
dropAction={(s, d)=>{ this.dropAggregator(s, d); }}
|
||||
>
|
||||
<div style={{
|
||||
width: '16px',
|
||||
height: 'inherit',
|
||||
backgroundColor: 'gold',
|
||||
flexShrink: '0'
|
||||
}}></div>
|
||||
</DropTarget>));
|
||||
for (let i = 0; i < expanders.length; i++) {
|
||||
const name = aggrow.getExpanderName(expanders[i]);
|
||||
const bg = (i % 2 === 0) ? 'white' : 'lightGray';
|
||||
headers.push((<Draggable id={'expander:active:' + i.toString()}>
|
||||
<div style={{
|
||||
width: '128px',
|
||||
textAlign: 'center',
|
||||
backgroundColor: bg,
|
||||
flexShrink: '0'
|
||||
}}>
|
||||
{name}
|
||||
</div>
|
||||
</Draggable>));
|
||||
const sep = i + 1 < expanders.length ? '->' : '...';
|
||||
headers.push((
|
||||
<DropTarget
|
||||
id={'expander:insert:' + (i + 1).toString()}
|
||||
dropFilter={()=>{return true; }}
|
||||
dropAction={(s, d)=>{ this.dropAggregator(s, d);}}
|
||||
>
|
||||
<div style={{
|
||||
height: 'inherit',
|
||||
backgroundColor: 'darkGray',
|
||||
flexShrink: '0'
|
||||
}}>
|
||||
{sep}
|
||||
</div>
|
||||
</DropTarget>)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||
<div style={{
|
||||
width: '100%',
|
||||
height: '26px',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
borderBottom: '2px solid black',
|
||||
}}>
|
||||
{headers}
|
||||
</div>
|
||||
<div style={{
|
||||
width: '100%',
|
||||
flexGrow: '1',
|
||||
overflow: 'scroll'
|
||||
}} onScroll={ (e) => this.scroll(e) }>
|
||||
<div style={{ position: 'relative' }}>
|
||||
{ this.renderVirtualizedRows() }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderVirtualizedRows() {
|
||||
const aggrow = this.state.aggrow;
|
||||
const viewport = this.state.viewport;
|
||||
const rows = aggrow.getRows(viewport.top, viewport.height);
|
||||
return (
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
height: (rowHeight * (aggrow.getHeight() + 20)).toString() + 'px'
|
||||
}}>
|
||||
{ rows.map(child => this.renderRow(child)) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderRow(row) {
|
||||
if (row === null) {
|
||||
return null;
|
||||
}
|
||||
let bg = 'lightGray';
|
||||
const aggrow = this.state.aggrow;
|
||||
const columns = [];
|
||||
let rowText = '';
|
||||
const indent = 4 + aggrow.getRowIndent(row) * treeIndent;
|
||||
const aggregates = aggrow.getActiveAggregators();
|
||||
if (row.parent !== null && (row.parent.expander % 2 === 0)) {
|
||||
bg = 'white';
|
||||
}
|
||||
for (let i = 0; i < aggregates.length; i++) {
|
||||
var aggregate = aggrow.getRowAggregate(row, i);
|
||||
columns.push((
|
||||
<div style={{
|
||||
width: '16px',
|
||||
height: 'inherit',
|
||||
backgroundColor: 'darkGray',
|
||||
flexShrink: '0'
|
||||
}}></div>
|
||||
));
|
||||
columns.push((
|
||||
<div style={{
|
||||
width: '128px',
|
||||
textAlign: 'right',
|
||||
flexShrink: '0'
|
||||
}}>
|
||||
{aggregate}
|
||||
</div>
|
||||
));
|
||||
}
|
||||
columns.push((
|
||||
<div style={{
|
||||
width: '16px',
|
||||
height: 'inherit',
|
||||
backgroundColor: 'gold',
|
||||
flexShrink: '0'
|
||||
}}></div>
|
||||
));
|
||||
if (aggrow.canExpand(row)) {
|
||||
rowText += '+';
|
||||
} else if (aggrow.canContract(row)) {
|
||||
rowText += '-';
|
||||
} else {
|
||||
rowText += ' ';
|
||||
}
|
||||
rowText += aggrow.getRowLabel(row);
|
||||
columns.push((
|
||||
<div style={{
|
||||
marginLeft: indent.toString() + 'px',
|
||||
flexShrink: '0',
|
||||
whiteSpace: 'nowrap'
|
||||
}}>
|
||||
{rowText}
|
||||
</div>
|
||||
));
|
||||
return (
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
height: (rowHeight - 1).toString() + 'px',
|
||||
top: (rowHeight * row.top).toString() + 'px',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
backgroundColor: bg,
|
||||
borderBottom: '1px solid gray',
|
||||
}}
|
||||
onClick={ () => {
|
||||
if (aggrow.canExpand(row)) {
|
||||
aggrow.expand(row);
|
||||
this.forceUpdate();
|
||||
} else if (aggrow.canContract(row)) {
|
||||
aggrow.contract(row);
|
||||
this.forceUpdate();
|
||||
}
|
||||
}}>
|
||||
{columns}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -7,8 +7,9 @@
|
|||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
'use strict';
|
||||
/*eslint no-console-disallow: "off"*/
|
||||
|
||||
const exec = require('child_process').exec;
|
||||
const spawn = require('child_process').spawn;
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
|
@ -18,9 +19,27 @@ module.exports = function(req, res, next) {
|
|||
return;
|
||||
}
|
||||
|
||||
console.log('Receiving heap capture...');
|
||||
var captureName = '/tmp/capture_' + Date.now() + '.json';
|
||||
fs.writeFileSync(captureName, req.rawBody);
|
||||
console.log('Capture written to ' + captureName);
|
||||
console.log('Downloading Heap Capture');
|
||||
var preload = path.join(__dirname, 'heapCapture/preLoadedCapture.js');
|
||||
fs.writeFileSync(preload, 'var preLoadedCapture = ');
|
||||
fs.appendFileSync(preload, req.rawBody);
|
||||
fs.appendFileSync(preload, ';');
|
||||
res.end();
|
||||
console.log('Packaging Trace');
|
||||
var captureHtml = path.join(__dirname, 'heapCapture/captures/capture_' + Date.now() + '.html');
|
||||
var capture = fs.createWriteStream(captureHtml);
|
||||
var inliner = spawn(
|
||||
'inliner',
|
||||
['--nocompress', 'heapCapture.html'],
|
||||
{ cwd: path.join(__dirname, '/heapCapture/'),
|
||||
stdio: [ process.stdin, 'pipe', process.stderr ],
|
||||
});
|
||||
inliner.stdout.pipe(capture);
|
||||
inliner.on('exit', (code, signal) => {
|
||||
if (code === 0) {
|
||||
console.log('Heap capture written to: ' + captureHtml);
|
||||
} else {
|
||||
console.error('Error processing heap capture, inliner returned code: ' + code);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue