Charles Dick 1199c5ade9 Add function name, url, line, column to heap capture
Reviewed By: bnham

Differential Revision: D3703012

fbshipit-source-id: 8e15deeeabe15da2a87a71c2baf0fa72d5bc6568
2016-08-16 06:59:06 -07:00

620 lines
17 KiB
JavaScript

/**
* 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};
}
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});
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});
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});
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);
}};
}// @generated