mirror of
https://github.com/status-im/react-native.git
synced 2025-02-26 08:05:34 +00:00
remove old heap capture in preparation for pull from tracery-prerelease
Reviewed By: bnham Differential Revision: D4250926 fbshipit-source-id: 7cfbb8d8aae5ef150f0260c92e946d569e41f7fb
This commit is contained in:
parent
bed9087191
commit
3094c36c81
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,760 +0,0 @@
|
||||
/**
|
||||
* 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 StackRegistry() { // eslint-disable-line no-unused-vars
|
||||
return {
|
||||
root: { id: 0 },
|
||||
nodeCount: 1,
|
||||
maxDepth: -1,
|
||||
stackIdMap: null,
|
||||
insert: function insertNode(parent, frameId) {
|
||||
if (this.stackIdMap !== null) {
|
||||
throw 'stacks already flattened';
|
||||
}
|
||||
let node = parent[frameId];
|
||||
if (node === undefined) {
|
||||
node = { id: this.nodeCount };
|
||||
this.nodeCount++;
|
||||
parent[frameId] = node;
|
||||
}
|
||||
return node;
|
||||
},
|
||||
get: function getStackArray(id) {
|
||||
return this.stackIdMap[id];
|
||||
},
|
||||
flatten: function flattenStacks() {
|
||||
if (this.stackIdMap !== null) {
|
||||
return;
|
||||
}
|
||||
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, []);
|
||||
this.root = null;
|
||||
this.stackIdMap = stackIdMap;
|
||||
this.maxDepth = maxStackDepth;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function AggrowData(columns) { // eslint-disable-line no-unused-vars
|
||||
const columnCount = columns.length;
|
||||
const columnConverter = columns.map(c => {
|
||||
switch (c.type) {
|
||||
case 'int': // stores raw value
|
||||
return (i) => i;
|
||||
case 'string': // stores interned id of string
|
||||
return (s) => c.strings.intern(s);
|
||||
case 'stack': // stores id of stack node
|
||||
return (s) => s.id;
|
||||
default:
|
||||
throw 'unknown AggrowData column type';
|
||||
}
|
||||
});
|
||||
return {
|
||||
data: new Int32Array(0),
|
||||
columns: columns,
|
||||
rowCount: 0,
|
||||
rowInserter: function rowInserter(numRows) {
|
||||
console.log(
|
||||
'increasing row data from ' + (this.data.length * 4).toLocaleString() + ' B to ' +
|
||||
(this.data.length * 4 + numRows * columnCount * 4).toLocaleString() + ' B'
|
||||
);
|
||||
const newData = new Int32Array(this.data.length + numRows * columnCount);
|
||||
newData.set(this.data);
|
||||
let currOffset = this.data.length;
|
||||
const endOffset = newData.length;
|
||||
this.data = newData;
|
||||
this.rowCount = newData.length / columnCount;
|
||||
return {
|
||||
insertRow: function insertRow() {
|
||||
if (currOffset >= endOffset) {
|
||||
throw 'tried to insert data off end of added range';
|
||||
}
|
||||
if (arguments.length !== columnCount) {
|
||||
throw 'expected data for ' + columnCount.toString() + ' columns, got' +
|
||||
arguments.length.toString() + ' columns';
|
||||
}
|
||||
for (let i = 0; i < arguments.length; i++) {
|
||||
newData[currOffset + i] = columnConverter[i](arguments[i]);
|
||||
}
|
||||
currOffset += columnCount;
|
||||
},
|
||||
done: function done() {
|
||||
if (currOffset !== endOffset) {
|
||||
throw 'unfilled rows';
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function Aggrow(aggrowData) {
|
||||
const columns = aggrowData.columns;
|
||||
const columnCount = columns.length;
|
||||
const data = aggrowData.data;
|
||||
function columnIndex(columnName, columnType) {
|
||||
const index = columns.findIndex(c => c.name === columnName && c.type === columnType);
|
||||
if (index < 0) {
|
||||
throw 'did not find data column ' + columnName + ' with type ' + columnType;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
if (columns[i].type === 'stack') {
|
||||
columns[i].stacks.flatten();
|
||||
}
|
||||
}
|
||||
return {
|
||||
expander: new AggrowExpander(aggrowData.rowCount),
|
||||
addSumAggregator: function addSumAggregator(aggregatorName, columnName) {
|
||||
const index = columnIndex(columnName, 'int');
|
||||
return this.expander.addAggregator(
|
||||
aggregatorName,
|
||||
function aggregateSize(indices) {
|
||||
let size = 0;
|
||||
for (let i = 0; i < indices.length; i++) {
|
||||
const row = indices[i];
|
||||
size += data[row * columnCount + index];
|
||||
}
|
||||
return size;
|
||||
},
|
||||
(value) => value.toLocaleString(),
|
||||
(a, b) => b - a,
|
||||
);
|
||||
},
|
||||
addCountAggregator: function addCountAggregator(aggregatorName) {
|
||||
return this.expander.addAggregator(
|
||||
aggregatorName,
|
||||
function aggregateCount(indices) {
|
||||
return indices.length;
|
||||
},
|
||||
(value) => value.toLocaleString(),
|
||||
(a, b) => b - a,
|
||||
);
|
||||
},
|
||||
addStringExpander: function addStringExpander(expanderName, columnName) {
|
||||
const index = columnIndex(columnName, 'string');
|
||||
const strings = columns[index].strings;
|
||||
return this.expander.addFieldExpander(
|
||||
expanderName,
|
||||
(row) => strings.get(data[row * columnCount + index]),
|
||||
(rowA, rowB) => data[rowA * columnCount + index] - data[rowB * columnCount + index],
|
||||
);
|
||||
},
|
||||
addNumberExpander: function addNumberExpander(expanderName, columnName) {
|
||||
const index = columnIndex(columnName, 'int');
|
||||
return this.expander.addFieldExpander(
|
||||
expanderName,
|
||||
(row) => data[row * columnCount + index].toLocaleString(),
|
||||
(rowA, rowB) => data[rowA * columnCount + index] - data[rowB * columnCount + index],
|
||||
);
|
||||
},
|
||||
addPointerExpander: function addPointerExpander(expanderName, columnName) {
|
||||
const index = columnIndex(columnName, 'int');
|
||||
return this.expander.addFieldExpander(
|
||||
expanderName,
|
||||
(row) => '0x' + (data[row * columnCount + index] >>> 0).toString(),
|
||||
(rowA, rowB) => data[rowA * columnCount + index] - data[rowB * columnCount + index],
|
||||
);
|
||||
},
|
||||
addStackExpander: function addStackExpander(expanderName, columnName, formatter) {
|
||||
// TODO: options for caller/callee, pivoting
|
||||
const index = columnIndex(columnName, 'stack');
|
||||
const stacks = columns[index].stacks;
|
||||
return this.expander.addCalleeStackExpander(
|
||||
expanderName,
|
||||
stacks.maxDepth,
|
||||
(row) => stacks.get(data[row * columnCount + index]),
|
||||
formatter,
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function AggrowExpander(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 calleeFrameIdGetter(stack, depth) {
|
||||
return stack[depth];
|
||||
}
|
||||
|
||||
function callerFrameIdGetter(stack, depth) {
|
||||
return stack[stack.length - depth - 1];
|
||||
}
|
||||
|
||||
function createStackComparers(stackGetter, frameIdGetter, maxStackDepth) {
|
||||
const comparers = new Array(maxStackDepth);
|
||||
for (let depth = 0; depth < maxStackDepth; 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 frameIdGetter(a, captureDepth) - frameIdGetter(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 frameIdGetter = expander.frameIdGetter;
|
||||
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 (frameIdGetter(beginStack, depth) !== frameIdGetter(endStack, depth)) {
|
||||
row.children.push(createTreeNode(
|
||||
row,
|
||||
columnName + frameGetter(frameIdGetter(beginStack, depth)),
|
||||
rowIndices.subarray(begin, end),
|
||||
expandNextFrame));
|
||||
begin = end;
|
||||
beginStack = endStack;
|
||||
}
|
||||
end++;
|
||||
}
|
||||
row.children.push(createTreeNode(
|
||||
row,
|
||||
columnName + frameGetter(frameIdGetter(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, maxStackDepth, stackGetter, frameGetter) {
|
||||
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, calleeFrameIdGetter, maxStackDepth), // depth -> comparer
|
||||
frameIdGetter: calleeFrameIdGetter, // (stack, depth) -> string id
|
||||
frameGetter: frameGetter,
|
||||
});
|
||||
return STACK_EXPANDER_ID_MIN + state.stackExpanders.length - 1;
|
||||
},
|
||||
addCallerStackExpander: function addCallerStackExpander(name, maxStackDepth, stackGetter, frameGetter) {
|
||||
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, callerFrameIdGetter, maxStackDepth),
|
||||
frameIdGetter: callerFrameIdGetter,
|
||||
frameGetter: frameGetter,
|
||||
});
|
||||
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);
|
||||
},
|
||||
};
|
||||
}
|
@ -1,379 +0,0 @@
|
||||
/**
|
||||
* 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 AggrowData Aggrow preLoadedCapture:true*/
|
||||
|
||||
function RefVisitor(refs, id) {
|
||||
this.refs = refs;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
RefVisitor.prototype = {
|
||||
moveToEdge: function moveToEdge(name) {
|
||||
const ref = this.refs[this.id];
|
||||
if (ref && ref.edges) {
|
||||
const edges = ref.edges;
|
||||
for (const edgeId in edges) {
|
||||
if (edges[edgeId] === name) {
|
||||
this.id = edgeId;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.id = undefined;
|
||||
return this;
|
||||
},
|
||||
moveToFirst: function moveToFirst(callback) {
|
||||
const ref = this.refs[this.id];
|
||||
if (ref && ref.edges) {
|
||||
const edges = ref.edges;
|
||||
for (const edgeId in edges) {
|
||||
this.id = edgeId;
|
||||
if (callback(edges[edgeId], this)) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.id = undefined;
|
||||
return this;
|
||||
},
|
||||
forEachEdge: function forEachEdge(callback) {
|
||||
const ref = this.refs[this.id];
|
||||
if (ref && ref.edges) {
|
||||
const edges = ref.edges;
|
||||
const visitor = new RefVisitor(this.refs, undefined);
|
||||
for (const edgeId in edges) {
|
||||
visitor.id = edgeId;
|
||||
callback(edges[edgeId], visitor);
|
||||
}
|
||||
}
|
||||
},
|
||||
getType: function getType() {
|
||||
const ref = this.refs[this.id];
|
||||
if (ref) {
|
||||
return ref.type;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
getRef: function getRef() {
|
||||
return this.refs[this.id];
|
||||
},
|
||||
clone: function clone() {
|
||||
return new RefVisitor(this.refs, this.id);
|
||||
},
|
||||
isDefined: function isDefined() {
|
||||
return !!this.id;
|
||||
},
|
||||
getValue: function getValue() {
|
||||
const ref = this.refs[this.id];
|
||||
if (ref) {
|
||||
if (ref.type === 'string') {
|
||||
if (ref.value) {
|
||||
return ref.value;
|
||||
} else {
|
||||
const rope = [];
|
||||
this.forEachEdge((name, visitor) => {
|
||||
if (name && name.startsWith('[') && name.endsWith(']')) {
|
||||
const index = parseInt(name.substring(1, name.length - 1), 10);
|
||||
rope[index] = visitor.getValue();
|
||||
}
|
||||
});
|
||||
return rope.join('');
|
||||
}
|
||||
} else if (ref.type === 'ScriptExecutable'
|
||||
|| ref.type === 'EvalExecutable'
|
||||
|| ref.type === 'ProgramExecutable') {
|
||||
return ref.value.url + ':' + ref.value.line + ':' + ref.value.col;
|
||||
} else if (ref.type === 'FunctionExecutable') {
|
||||
return ref.value.name + '@' + ref.value.url + ':' + ref.value.line + ':' + ref.value.col;
|
||||
} else if (ref.type === 'NativeExecutable') {
|
||||
return ref.value.function + ' ' + ref.value.constructor + ' ' + ref.value.name;
|
||||
} else if (ref.type === 'Function') {
|
||||
const executable = this.clone().moveToEdge('@Executable');
|
||||
if (executable.id) {
|
||||
return executable.getRef().type + ' ' + executable.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
return '#none';
|
||||
}
|
||||
};
|
||||
|
||||
function forEachRef(refs, callback) {
|
||||
const visitor = new RefVisitor(refs, undefined);
|
||||
for (const id in refs) {
|
||||
visitor.id = id;
|
||||
callback(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
function firstRef(refs, callback) {
|
||||
for (const id in refs) {
|
||||
const ref = refs[id];
|
||||
if (callback(id, ref)) {
|
||||
return new RefVisitor(refs, id);
|
||||
}
|
||||
}
|
||||
return new RefVisitor(refs, undefined);
|
||||
}
|
||||
|
||||
function getInternalInstanceName(visitor) {
|
||||
const type = visitor.clone().moveToEdge('_currentElement').moveToEdge('type');
|
||||
if (type.getType() === 'string') { // element.type is string
|
||||
return type.getValue();
|
||||
} else if (type.getType() === 'Function') { // element.type is function
|
||||
const displayName = type.clone().moveToEdge('displayName');
|
||||
if (displayName.isDefined()) {
|
||||
return displayName.getValue(); // element.type.displayName
|
||||
}
|
||||
const name = type.clone().moveToEdge('name');
|
||||
if (name.isDefined()) {
|
||||
return name.getValue(); // element.type.name
|
||||
}
|
||||
type.moveToEdge('@Executable');
|
||||
if (type.getType() === 'FunctionExecutable') {
|
||||
return type.getRef().value.name; // element.type symbolicated name
|
||||
}
|
||||
}
|
||||
return '#unknown';
|
||||
}
|
||||
|
||||
function buildReactComponentTree(visitor, registry, strings) {
|
||||
const ref = visitor.getRef();
|
||||
if (ref.reactTree || ref.reactParent === undefined) {
|
||||
return; // has one or doesn't need one
|
||||
}
|
||||
const parentVisitor = ref.reactParent;
|
||||
if (parentVisitor === null) {
|
||||
ref.reactTree = registry.insert(registry.root, strings.intern(getInternalInstanceName(visitor)));
|
||||
} else if (parentVisitor) {
|
||||
const parentRef = parentVisitor.getRef();
|
||||
buildReactComponentTree(parentVisitor, registry, strings);
|
||||
let relativeName = getInternalInstanceName(visitor);
|
||||
if (ref.reactKey) {
|
||||
relativeName = ref.reactKey + ': ' + relativeName;
|
||||
}
|
||||
ref.reactTree = registry.insert(parentRef.reactTree, strings.intern(relativeName));
|
||||
} else {
|
||||
throw 'non react instance parent of react instance';
|
||||
}
|
||||
}
|
||||
|
||||
function markReactComponentTree(refs, registry, strings) {
|
||||
// annotate all refs that are react internal instances with their parent and name
|
||||
// ref.reactParent = visitor that points to parent instance,
|
||||
// null if we know it's an instance, but don't have a parent yet
|
||||
// ref.reactKey = if a key is used to distinguish siblings
|
||||
forEachRef(refs, (visitor) => {
|
||||
const visitorClone = visitor.clone(); // visitor will get stomped on next iteration
|
||||
const ref = visitor.getRef();
|
||||
visitor.forEachEdge((edgeName, edgeVisitor) => {
|
||||
const edgeRef = edgeVisitor.getRef();
|
||||
if (edgeRef) {
|
||||
if (edgeName === '_renderedChildren') {
|
||||
if (ref.reactParent === undefined) {
|
||||
// ref is react component, even if we don't have a parent yet
|
||||
ref.reactParent = null;
|
||||
}
|
||||
edgeVisitor.forEachEdge((childName, childVisitor) => {
|
||||
const childRef = childVisitor.getRef();
|
||||
if (childRef && childName.startsWith('.')) {
|
||||
childRef.reactParent = visitorClone;
|
||||
childRef.reactKey = childName;
|
||||
}
|
||||
});
|
||||
} else if (edgeName === '_renderedComponent') {
|
||||
if (ref.reactParent === undefined) {
|
||||
ref.reactParent = null;
|
||||
}
|
||||
edgeRef.reactParent = visitorClone;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
// build tree of react internal instances (since that's what has the structure)
|
||||
// fill in ref.reactTree = path registry node
|
||||
forEachRef(refs, (visitor) => {
|
||||
buildReactComponentTree(visitor, registry, strings);
|
||||
});
|
||||
// hook in components by looking at their _reactInternalInstance fields
|
||||
forEachRef(refs, (visitor) => {
|
||||
const ref = visitor.getRef();
|
||||
const instanceRef = visitor.moveToEdge('_reactInternalInstance').getRef();
|
||||
if (instanceRef) {
|
||||
ref.reactTree = instanceRef.reactTree;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function functionUrlFileName(visitor) {
|
||||
const executable = visitor.clone().moveToEdge('@Executable');
|
||||
const ref = executable.getRef();
|
||||
if (ref && ref.value && ref.value.url) {
|
||||
const url = ref.value.url;
|
||||
let file = url.substring(url.lastIndexOf('/') + 1);
|
||||
if (file.endsWith('.js')) {
|
||||
file = file.substring(0, file.length - 3);
|
||||
}
|
||||
return file;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function markModules(refs) {
|
||||
const modules = firstRef(refs, (id, ref) => ref.type === 'CallbackGlobalObject');
|
||||
modules.moveToEdge('require');
|
||||
modules.moveToFirst((name, visitor) => visitor.getType() === 'JSActivation');
|
||||
modules.moveToEdge('modules');
|
||||
modules.forEachEdge((name, visitor) => {
|
||||
const ref = visitor.getRef();
|
||||
visitor.moveToEdge('exports');
|
||||
if (visitor.getType() === 'Object') {
|
||||
visitor.moveToFirst((memberName, member) => member.getType() === 'Function');
|
||||
if (visitor.isDefined()) {
|
||||
ref.module = functionUrlFileName(visitor);
|
||||
}
|
||||
} else if (visitor.getType() === 'Function') {
|
||||
const displayName = visitor.clone().moveToEdge('displayName');
|
||||
if (displayName.isDefined()) {
|
||||
ref.module = displayName.getValue();
|
||||
}
|
||||
ref.module = functionUrlFileName(visitor);
|
||||
}
|
||||
if (ref && !ref.module) {
|
||||
ref.module = '#unknown ' + name;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function registerPathToRoot(refs, registry, strings) {
|
||||
markReactComponentTree(refs, registry, strings);
|
||||
markModules(refs);
|
||||
let breadth = [];
|
||||
forEachRef(refs, (visitor) => {
|
||||
const ref = visitor.getRef();
|
||||
if (ref.type === 'CallbackGlobalObject') {
|
||||
ref.rootPath = registry.insert(registry.root, strings.intern(ref.type));
|
||||
breadth.push(visitor.clone());
|
||||
}
|
||||
});
|
||||
while (breadth.length > 0) {
|
||||
const nextBreadth = [];
|
||||
for (let i = 0; i < breadth.length; i++) {
|
||||
const visitor = breadth[i];
|
||||
const ref = visitor.getRef();
|
||||
visitor.forEachEdge((edgeName, edgeVisitor) => {
|
||||
const edgeRef = edgeVisitor.getRef();
|
||||
if (edgeRef && edgeRef.rootPath === undefined) {
|
||||
let pathName = edgeRef.type;
|
||||
if (edgeName) {
|
||||
pathName = edgeName + ': ' + pathName;
|
||||
}
|
||||
edgeRef.rootPath = registry.insert(ref.rootPath, strings.intern(pathName));
|
||||
nextBreadth.push(edgeVisitor.clone());
|
||||
// copy module and react tree forward
|
||||
if (edgeRef.module === undefined) {
|
||||
edgeRef.module = ref.module;
|
||||
}
|
||||
if (edgeRef.reactTree === undefined) {
|
||||
edgeRef.reactTree = ref.reactTree;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
breadth = nextBreadth;
|
||||
}
|
||||
}
|
||||
|
||||
function registerCapture(data, captureId, capture, stacks, strings) {
|
||||
// NB: capture.refs is potentially VERY large, so we try to avoid making
|
||||
// copies, even if iteration is a bit more annoying.
|
||||
let rowCount = 0;
|
||||
for (const id in capture.refs) { // eslint-disable-line no-unused-vars
|
||||
rowCount++;
|
||||
}
|
||||
for (const id in capture.markedBlocks) { // eslint-disable-line no-unused-vars
|
||||
rowCount++;
|
||||
}
|
||||
const inserter = data.rowInserter(rowCount);
|
||||
registerPathToRoot(capture.refs, stacks, strings);
|
||||
const noneString = strings.intern('#none');
|
||||
const noneStack = stacks.insert(stacks.root, noneString);
|
||||
forEachRef(capture.refs, (visitor) => {
|
||||
// want to data.append(value, value, value), not IDs
|
||||
const ref = visitor.getRef();
|
||||
const id = visitor.id;
|
||||
inserter.insertRow(
|
||||
parseInt(id, 16),
|
||||
ref.type,
|
||||
ref.size,
|
||||
captureId,
|
||||
ref.rootPath === undefined ? noneStack : ref.rootPath,
|
||||
ref.reactTree === undefined ? noneStack : ref.reactTree,
|
||||
visitor.getValue(),
|
||||
ref.module === undefined ? '#none' : ref.module,
|
||||
);
|
||||
});
|
||||
for (const id in capture.markedBlocks) {
|
||||
const block = capture.markedBlocks[id];
|
||||
inserter.insertRow(
|
||||
parseInt(id, 16),
|
||||
'Marked Block Overhead',
|
||||
block.capacity - block.size,
|
||||
captureId,
|
||||
noneStack,
|
||||
noneStack,
|
||||
'capacity: ' + block.capacity + ', size: ' + block.size + ', granularity: ' + block.cellSize,
|
||||
'#none',
|
||||
);
|
||||
}
|
||||
inserter.done();
|
||||
}
|
||||
|
||||
if (preLoadedCapture) {
|
||||
const strings = StringInterner();
|
||||
const stacks = new StackRegistry();
|
||||
const columns = [
|
||||
{ name: 'id', type: 'int' },
|
||||
{ name: 'type', type: 'string', strings: strings },
|
||||
{ name: 'size', type: 'int' },
|
||||
{ name: 'trace', type: 'string', strings: strings },
|
||||
{ name: 'path', type: 'stack', stacks: stacks },
|
||||
{ name: 'react', type: 'stack', stacks: stacks },
|
||||
{ name: 'value', type: 'string', strings: strings },
|
||||
{ name: 'module', type: 'string', strings: strings },
|
||||
];
|
||||
const data = new AggrowData(columns);
|
||||
registerCapture(data, 'trace', preLoadedCapture, stacks, strings);
|
||||
preLoadedCapture = undefined; // let GG clean up the capture
|
||||
const aggrow = new Aggrow(data);
|
||||
aggrow.addPointerExpander('Id', 'id');
|
||||
const typeExpander = aggrow.addStringExpander('Type', 'type');
|
||||
aggrow.addNumberExpander('Size', 'size');
|
||||
aggrow.addStringExpander('Trace', 'trace');
|
||||
const pathExpander = aggrow.addStackExpander('Path', 'path', strings.get);
|
||||
const reactExpander = aggrow.addStackExpander('React Tree', 'react', strings.get);
|
||||
const valueExpander = aggrow.addStringExpander('Value', 'value');
|
||||
const moduleExpander = aggrow.addStringExpander('Module', 'module');
|
||||
aggrow.expander.setActiveExpanders([
|
||||
pathExpander,
|
||||
reactExpander,
|
||||
moduleExpander,
|
||||
typeExpander,
|
||||
valueExpander,
|
||||
]);
|
||||
const sizeAggregator = aggrow.addSumAggregator('Size', 'size');
|
||||
const countAggregator = aggrow.addCountAggregator('Count');
|
||||
aggrow.expander.setActiveAggregators([
|
||||
sizeAggregator,
|
||||
countAggregator,
|
||||
]);
|
||||
ReactDOM.render(<Table aggrow={aggrow.expander} />, document.body);
|
||||
}
|
@ -1,540 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
return React.cloneElement(
|
||||
this.props.children,
|
||||
{
|
||||
draggable: 'true',
|
||||
onDragStart: (e) => {
|
||||
e.dataTransfer.setData('text', id);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
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 dropAction = this.props.dropAction;
|
||||
return React.cloneElement(
|
||||
this.props.children,
|
||||
{
|
||||
onDragOver: (e) => e.preventDefault(),
|
||||
onDrop: (e) => {
|
||||
const sourceId = e.dataTransfer.getData('text');
|
||||
e.preventDefault();
|
||||
dropAction(sourceId, thisId);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DropTarget.propTypes = {
|
||||
children: React.PropTypes.element.isRequired,
|
||||
id: React.PropTypes.string.isRequired,
|
||||
dropAction: React.PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
class ExpanderConfiguration extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
const aggrow = this.props.aggrow;
|
||||
const expander = this.props.expander;
|
||||
return (
|
||||
<Draggable id={'expander:add:' + expander.toString()}>
|
||||
<div
|
||||
style={{
|
||||
width: 'auto',
|
||||
height: '26px',
|
||||
border: '1px solid darkGray',
|
||||
margin: '2px',
|
||||
}}>
|
||||
{aggrow.getExpanderName(expander)}
|
||||
</div>
|
||||
</Draggable>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TableConfiguration extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
expanded: false,
|
||||
};
|
||||
}
|
||||
renderExpander(ex) {
|
||||
return (<ExpanderConfiguration aggrow={this.props.aggrow} expander={ex} />);
|
||||
}
|
||||
render() {
|
||||
const expanderText = this.state.expanded ? '>>' : '<<';
|
||||
const aggrow = this.props.aggrow;
|
||||
let config = [];
|
||||
if (this.state.expanded) {
|
||||
config = aggrow.getExpanders().map((ex) => this.renderExpander(ex));
|
||||
}
|
||||
return (
|
||||
<div style={{
|
||||
width: this.state.expanded ? '512px' : '26px',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
borderLeft: '2px solid black',
|
||||
}}>
|
||||
<div style={{
|
||||
width: '100%',
|
||||
height: '26px',
|
||||
border: '1px solid darkGray',
|
||||
}}
|
||||
onClick={ (e) => this.setState({expanded: !this.state.expanded}) }>
|
||||
{ expanderText }
|
||||
</div>
|
||||
{ config }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TableConfiguration.propTypes = {
|
||||
aggrow: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
class TableHeader extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
render() {
|
||||
const aggrow = this.props.aggrow;
|
||||
const aggregators = aggrow.getActiveAggregators();
|
||||
const expanders = aggrow.getActiveExpanders();
|
||||
const headers = [];
|
||||
for (let i = 0; i < aggregators.length; i++) {
|
||||
const name = aggrow.getAggregatorName(aggregators[i]);
|
||||
headers.push((
|
||||
<DropTarget
|
||||
id={'aggregate:insert:' + i.toString()}
|
||||
dropAction={this.props.dropAction}
|
||||
>
|
||||
<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"
|
||||
dropAction={this.props.dropAction}
|
||||
>
|
||||
<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()}
|
||||
dropAction={this.props.dropAction}
|
||||
>
|
||||
<div style={{
|
||||
height: 'inherit',
|
||||
backgroundColor: 'darkGray',
|
||||
flexShrink: '0'
|
||||
}}>
|
||||
{sep}
|
||||
</div>
|
||||
</DropTarget>)
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div style={{
|
||||
width: '100%',
|
||||
height: '26px',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
borderBottom: '2px solid black',
|
||||
}}>
|
||||
{headers}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TableHeader.propTypes = {
|
||||
aggrow: React.PropTypes.object.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 },
|
||||
cursor: 0,
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
if (top !== this.state.viewport.top || height !== this.state.viewport.height) {
|
||||
this.setState({viewport: {top, height}});
|
||||
}
|
||||
}
|
||||
|
||||
_contractRow(row) {
|
||||
let newCursor = this.state.cursor;
|
||||
if (newCursor > row.top && newCursor < row.top + row.height) { // in contracted section
|
||||
newCursor = row.top;
|
||||
} else if (newCursor >= row.top + row.height) { // below contracted section
|
||||
newCursor -= row.height - 1;
|
||||
}
|
||||
this.state.aggrow.contract(row);
|
||||
this.setState({cursor: newCursor});
|
||||
console.log('-' + row.top);
|
||||
}
|
||||
|
||||
_expandRow(row) {
|
||||
let newCursor = this.state.cursor;
|
||||
this.state.aggrow.expand(row);
|
||||
if (newCursor > row.top) { // below expanded section
|
||||
newCursor += row.height - 1;
|
||||
}
|
||||
this.setState({cursor: newCursor});
|
||||
console.log('+' + row.top);
|
||||
}
|
||||
|
||||
_scrollDiv: null;
|
||||
|
||||
_keepCursorInViewport() {
|
||||
if (this._scrollDiv) {
|
||||
const cursor = this.state.cursor;
|
||||
const scrollDiv = this._scrollDiv;
|
||||
if (cursor * rowHeight < scrollDiv.scrollTop + scrollDiv.clientHeight * 0.1) {
|
||||
scrollDiv.scrollTop = cursor * rowHeight - scrollDiv.clientHeight * 0.1;
|
||||
} else if ((cursor + 1) * rowHeight > scrollDiv.scrollTop + scrollDiv.clientHeight * 0.9) {
|
||||
scrollDiv.scrollTop = (cursor + 1) * rowHeight - scrollDiv.clientHeight * 0.9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keydown(e) {
|
||||
const aggrow = this.state.aggrow;
|
||||
let cursor = this.state.cursor;
|
||||
let row = aggrow.getRows(cursor, 1)[0];
|
||||
switch (e.keyCode) {
|
||||
case 38: // up
|
||||
if (cursor > 0) {
|
||||
this.setState({cursor: cursor - 1});
|
||||
this._keepCursorInViewport();
|
||||
}
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 40: // down
|
||||
if (cursor < aggrow.getHeight() - 1) {
|
||||
this.setState({cursor: cursor + 1});
|
||||
this._keepCursorInViewport();
|
||||
}
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 37: // left
|
||||
if (aggrow.canContract(row)) {
|
||||
this._contractRow(row);
|
||||
} else if (aggrow.getRowIndent(row) > 0) {
|
||||
const indent = aggrow.getRowIndent(row) - 1;
|
||||
while (aggrow.getRowIndent(row) > indent) {
|
||||
cursor--;
|
||||
row = aggrow.getRows(cursor, 1)[0];
|
||||
}
|
||||
this.setState({cursor: cursor});
|
||||
this._keepCursorInViewport();
|
||||
}
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 39: // right
|
||||
if (aggrow.canExpand(row)) {
|
||||
this._expandRow(row);
|
||||
} else if (cursor < aggrow.getHeight() - 1) {
|
||||
this.setState({cursor: cursor + 1});
|
||||
this._keepCursorInViewport();
|
||||
}
|
||||
e.preventDefault();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dropAction(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.setState({cursor:0});
|
||||
} 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.setState({cursor:0});
|
||||
} else if (s.startsWith('expander:add:')) {
|
||||
let dIndex = -1;
|
||||
const sExpander = parseInt(s.substring(13), 10);
|
||||
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;
|
||||
}
|
||||
const active = aggrow.getActiveExpanders();
|
||||
active.splice(dIndex, 0, sExpander);
|
||||
aggrow.setActiveExpanders(active);
|
||||
this.setState({cursor:0});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'row' }}>
|
||||
<div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column', overflow: 'hidden', }}>
|
||||
<TableHeader aggrow={this.state.aggrow} dropAction={(s, d) => this.dropAction(s, d)} />
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
flexGrow: '1',
|
||||
overflow: 'scroll'
|
||||
}}
|
||||
onScroll={ (e) => this.scroll(e) }
|
||||
ref={(div) => { this._scrollDiv = div; } }>
|
||||
<div style={{ position: 'relative' }}>
|
||||
{ this.renderVirtualizedRows() }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<TableConfiguration aggrow={this.state.aggrow} />
|
||||
</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';
|
||||
}
|
||||
if (row.top === this.state.cursor) {
|
||||
bg = 'lightblue';
|
||||
}
|
||||
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)) {
|
||||
columns.push((
|
||||
<div
|
||||
style={{
|
||||
marginLeft: indent.toString() + 'px',
|
||||
flexShrink: '0',
|
||||
width: '12px',
|
||||
textAlign: 'center',
|
||||
border: '1px solid gray',
|
||||
}}
|
||||
onClick={ () => this._expandRow(row) }
|
||||
>+</div>
|
||||
));
|
||||
} else if (aggrow.canContract(row)) {
|
||||
columns.push((
|
||||
<div
|
||||
style={{
|
||||
marginLeft: indent.toString() + 'px',
|
||||
flexShrink: '0',
|
||||
width: '12px',
|
||||
textAlign: 'center',
|
||||
border: '1px solid gray',
|
||||
}}
|
||||
onClick={ () => this._contractRow(row) }
|
||||
>-</div>
|
||||
));
|
||||
} else {
|
||||
columns.push((
|
||||
<div
|
||||
style={{
|
||||
marginLeft: indent.toString() + 'px',
|
||||
}}
|
||||
></div>
|
||||
));
|
||||
}
|
||||
rowText += aggrow.getRowLabel(row);
|
||||
columns.push((
|
||||
<div style={{
|
||||
flexShrink: '0',
|
||||
whiteSpace: 'nowrap',
|
||||
marginRight: '20px'
|
||||
}}>
|
||||
{rowText}
|
||||
</div>
|
||||
));
|
||||
return (
|
||||
<div
|
||||
key={row.top}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
height: (rowHeight - 1).toString() + 'px',
|
||||
top: (rowHeight * row.top).toString() + 'px',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
backgroundColor: bg,
|
||||
borderBottom: '1px solid gray',
|
||||
}}
|
||||
onClick={ () => {
|
||||
this.setState({cursor: row.top});
|
||||
}}>
|
||||
{columns}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.keydown = this.keydown.bind(this);
|
||||
document.body.addEventListener('keydown', this.keydown);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
document.body.removeEventListener('keydown', this.keydown);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user