diff --git a/src/actions/profile-view.js b/src/actions/profile-view.js index 5f5a443bfb..76e9c32098 100644 --- a/src/actions/profile-view.js +++ b/src/actions/profile-view.js @@ -36,7 +36,6 @@ import { getInvertCallstack, getHash, } from 'firefox-profiler/selectors/url-state'; -import { getCallNodePathFromIndex } from 'firefox-profiler/profile-logic/profile-data'; import { assertExhaustiveCheck, getFirstItemFromSet, @@ -176,10 +175,7 @@ export function selectLeafCallNode( dispatch( changeSelectedCallNode( threadsKey, - getCallNodePathFromIndex( - newSelectedCallNode, - callNodeInfo.callNodeTable - ) + callNodeInfo.getCallNodePathFromIndex(newSelectedCallNode) ) ); }; @@ -207,13 +203,12 @@ export function selectRootCallNode( dispatch(changeSelectedCallNode(threadsKey, [])); return; } - const newSelectedCallNode = - callNodeInfo.stackIndexToCallNodeIndex[newSelectedStack]; + const stackIndexToCallNodeIndex = + callNodeInfo.getStackIndexToCallNodeIndex(); + const newSelectedCallNode = stackIndexToCallNodeIndex[newSelectedStack]; - const selectedCallNodePath = getCallNodePathFromIndex( - newSelectedCallNode, - callNodeInfo.callNodeTable - ); + const selectedCallNodePath = + callNodeInfo.getCallNodePathFromIndex(newSelectedCallNode); const rootCallNodePath = [selectedCallNodePath[0]]; dispatch( @@ -1634,7 +1629,7 @@ export function expandAllCallNodeDescendants( }); const expandedCallNodePaths = [...descendants].map((callNodeIndex) => - getCallNodePathFromIndex(callNodeIndex, callNodeInfo.callNodeTable) + callNodeInfo.getCallNodePathFromIndex(callNodeIndex) ); dispatch(changeExpandedCallNodes(threadsKey, expandedCallNodePaths)); }; @@ -1878,13 +1873,13 @@ export function addTransformToStack( const transformedThread = threadSelectors.getRangeAndTransformFilteredThread(getState()); - const { callNodeTable } = threadSelectors.getCallNodeInfo(getState()); + const callNodeInfo = threadSelectors.getCallNodeInfo(getState()); dispatch({ type: 'ADD_TRANSFORM_TO_STACK', threadsKey, transform, transformedThread, - callNodeTable, + callNodeInfo, }); sendAnalytics({ hitType: 'event', @@ -2041,10 +2036,11 @@ export function handleCallNodeTransformShortcut( } const threadSelectors = getThreadSelectorsFromThreadsKey(threadsKey); const unfilteredThread = threadSelectors.getThread(getState()); - const { callNodeTable } = threadSelectors.getCallNodeInfo(getState()); + const callNodeInfo = threadSelectors.getCallNodeInfo(getState()); + const callNodeTable = callNodeInfo.getCallNodeTable(); const implementation = getImplementationFilter(getState()); const inverted = getInvertCallstack(getState()); - const callNodePath = getCallNodePathFromIndex(callNodeIndex, callNodeTable); + const callNodePath = callNodeInfo.getCallNodePathFromIndex(callNodeIndex); const funcIndex = callNodeTable.func[callNodeIndex]; const category = callNodeTable.category[callNodeIndex]; diff --git a/src/components/calltree/CallTree.js b/src/components/calltree/CallTree.js index 2b6feceb95..4ad5c7110d 100644 --- a/src/components/calltree/CallTree.js +++ b/src/components/calltree/CallTree.js @@ -9,7 +9,6 @@ import explicitConnect from 'firefox-profiler/utils/connect'; import { TreeView } from 'firefox-profiler/components/shared/TreeView'; import { CallTreeEmptyReasons } from './CallTreeEmptyReasons'; import { Icon } from 'firefox-profiler/components/shared/Icon'; -import { getCallNodePathFromIndex } from 'firefox-profiler/profile-logic/profile-data'; import { getInvertCallstack, getImplementationFilter, @@ -246,7 +245,7 @@ class CallTreeImpl extends PureComponent { const { callNodeInfo, threadsKey, changeSelectedCallNode } = this.props; changeSelectedCallNode( threadsKey, - getCallNodePathFromIndex(newSelectedCallNode, callNodeInfo.callNodeTable), + callNodeInfo.getCallNodePathFromIndex(newSelectedCallNode), context ); }; @@ -255,7 +254,7 @@ class CallTreeImpl extends PureComponent { const { callNodeInfo, threadsKey, changeRightClickedCallNode } = this.props; changeRightClickedCallNode( threadsKey, - getCallNodePathFromIndex(newSelectedCallNode, callNodeInfo.callNodeTable) + callNodeInfo.getCallNodePathFromIndex(newSelectedCallNode) ); }; @@ -266,7 +265,7 @@ class CallTreeImpl extends PureComponent { changeExpandedCallNodes( threadsKey, newExpandedCallNodeIndexes.map((callNodeIndex) => - getCallNodePathFromIndex(callNodeIndex, callNodeInfo.callNodeTable) + callNodeInfo.getCallNodePathFromIndex(callNodeIndex) ) ); }; @@ -301,7 +300,7 @@ class CallTreeImpl extends PureComponent { tree, expandedCallNodeIndexes, selectedCallNodeIndex, - callNodeInfo: { callNodeTable }, + callNodeInfo, categories, } = this.props; @@ -321,6 +320,7 @@ class CallTreeImpl extends PureComponent { // This tree is empty. return; } + const callNodeTable = callNodeInfo.getCallNodeTable(); newExpandedCallNodeIndexes.push(currentCallNodeIndex); for (let i = 0; i < maxInterestingDepth; i++) { const children = tree.getChildren(currentCallNodeIndex); diff --git a/src/components/flame-graph/Canvas.js b/src/components/flame-graph/Canvas.js index 445479b554..22b1c54226 100644 --- a/src/components/flame-graph/Canvas.js +++ b/src/components/flame-graph/Canvas.js @@ -154,16 +154,14 @@ class FlameGraphCanvasImpl extends React.PureComponent { } _scrollSelectionIntoView = () => { - const { - selectedCallNodeIndex, - maxStackDepthPlusOne, - callNodeInfo: { callNodeTable }, - } = this.props; + const { selectedCallNodeIndex, maxStackDepthPlusOne, callNodeInfo } = + this.props; if (selectedCallNodeIndex === null) { return; } + const callNodeTable = callNodeInfo.getCallNodeTable(); const depth = callNodeTable.depth[selectedCallNodeIndex]; const y = (maxStackDepthPlusOne - depth - 1) * ROW_HEIGHT; @@ -185,7 +183,7 @@ class FlameGraphCanvasImpl extends React.PureComponent { const { thread, flameGraphTiming, - callNodeInfo: { callNodeTable }, + callNodeInfo, stackFrameHeight, maxStackDepthPlusOne, rightClickedCallNodeIndex, @@ -237,6 +235,8 @@ class FlameGraphCanvasImpl extends React.PureComponent { fastFillStyle.set('#ffffff'); ctx.fillRect(0, 0, deviceContainerWidth, deviceContainerHeight); + const callNodeTable = callNodeInfo.getCallNodeTable(); + const startDepth = Math.floor( maxStackDepthPlusOne - viewportBottom / stackFrameHeight ); diff --git a/src/components/flame-graph/FlameGraph.js b/src/components/flame-graph/FlameGraph.js index 64210dc728..35769154e3 100644 --- a/src/components/flame-graph/FlameGraph.js +++ b/src/components/flame-graph/FlameGraph.js @@ -24,7 +24,6 @@ import { getInvertCallstack, } from '../../selectors/url-state'; import { ContextMenuTrigger } from 'firefox-profiler/components/shared/ContextMenuTrigger'; -import { getCallNodePathFromIndex } from 'firefox-profiler/profile-logic/profile-data'; import { changeSelectedCallNode, changeRightClickedCallNode, @@ -117,7 +116,7 @@ class FlameGraphImpl extends React.PureComponent { const { callNodeInfo, threadsKey, changeSelectedCallNode } = this.props; changeSelectedCallNode( threadsKey, - getCallNodePathFromIndex(callNodeIndex, callNodeInfo.callNodeTable) + callNodeInfo.getCallNodePathFromIndex(callNodeIndex) ); }; @@ -127,7 +126,7 @@ class FlameGraphImpl extends React.PureComponent { const { callNodeInfo, threadsKey, changeRightClickedCallNode } = this.props; changeRightClickedCallNode( threadsKey, - getCallNodePathFromIndex(callNodeIndex, callNodeInfo.callNodeTable) + callNodeInfo.getCallNodePathFromIndex(callNodeIndex) ); }; @@ -160,11 +159,9 @@ class FlameGraphImpl extends React.PureComponent { * Is the box for this call node wide enough to be selected? */ _wideEnough = (callNodeIndex: IndexIntoCallNodeTable): boolean => { - const { - flameGraphTiming, - callNodeInfo: { callNodeTable }, - } = this.props; + const { flameGraphTiming, callNodeInfo } = this.props; + const callNodeTable = callNodeInfo.getCallNodeTable(); const depth = callNodeTable.depth[callNodeIndex]; const row = flameGraphTiming[depth]; const columnIndex = row.callNode.indexOf(callNodeIndex); @@ -185,13 +182,11 @@ class FlameGraphImpl extends React.PureComponent { startingCallNodeIndex: IndexIntoCallNodeTable, direction: 1 | -1 ): IndexIntoCallNodeTable | void => { - const { - flameGraphTiming, - callNodeInfo: { callNodeTable }, - } = this.props; + const { flameGraphTiming, callNodeInfo } = this.props; let callNodeIndex = startingCallNodeIndex; + const callNodeTable = callNodeInfo.getCallNodeTable(); const depth = callNodeTable.depth[callNodeIndex]; const row = flameGraphTiming[depth]; let columnIndex = row.callNode.indexOf(callNodeIndex); @@ -216,12 +211,13 @@ class FlameGraphImpl extends React.PureComponent { const { threadsKey, callTree, - callNodeInfo: { callNodeTable }, + callNodeInfo, selectedCallNodeIndex, rightClickedCallNodeIndex, changeSelectedCallNode, handleCallNodeTransformShortcut, } = this.props; + const callNodeTable = callNodeInfo.getCallNodeTable(); if ( // Please do not forget to update the switch/case below if changing the array to allow more keys. @@ -231,7 +227,7 @@ class FlameGraphImpl extends React.PureComponent { // Just select the "root" node if we've got no prior selection. changeSelectedCallNode( threadsKey, - getCallNodePathFromIndex(0, callNodeTable) + callNodeInfo.getCallNodePathFromIndex(0) ); return; } @@ -242,7 +238,7 @@ class FlameGraphImpl extends React.PureComponent { if (prefix !== -1) { changeSelectedCallNode( threadsKey, - getCallNodePathFromIndex(prefix, callNodeTable) + callNodeInfo.getCallNodePathFromIndex(prefix) ); } break; @@ -257,7 +253,7 @@ class FlameGraphImpl extends React.PureComponent { if (callNodeIndex !== undefined && this._wideEnough(callNodeIndex)) { changeSelectedCallNode( threadsKey, - getCallNodePathFromIndex(callNodeIndex, callNodeTable) + callNodeInfo.getCallNodePathFromIndex(callNodeIndex) ); } break; @@ -272,7 +268,7 @@ class FlameGraphImpl extends React.PureComponent { if (callNodeIndex !== undefined) { changeSelectedCallNode( threadsKey, - getCallNodePathFromIndex(callNodeIndex, callNodeTable) + callNodeInfo.getCallNodePathFromIndex(callNodeIndex) ); } break; @@ -306,11 +302,8 @@ class FlameGraphImpl extends React.PureComponent { _onCopy = (event: ClipboardEvent) => { if (document.activeElement === this._viewport) { event.preventDefault(); - const { - callNodeInfo: { callNodeTable }, - selectedCallNodeIndex, - thread, - } = this.props; + const { callNodeInfo, selectedCallNodeIndex, thread } = this.props; + const callNodeTable = callNodeInfo.getCallNodeTable(); if (selectedCallNodeIndex !== null) { const funcIndex = callNodeTable.func[selectedCallNodeIndex]; const funcName = thread.stringTable.getString( diff --git a/src/components/shared/CallNodeContextMenu.js b/src/components/shared/CallNodeContextMenu.js index d62b303e37..2a9dbbc1bb 100644 --- a/src/components/shared/CallNodeContextMenu.js +++ b/src/components/shared/CallNodeContextMenu.js @@ -18,7 +18,6 @@ import { getFunctionName } from 'firefox-profiler/profile-logic/function-info'; import { getBottomBoxInfoForCallNode, getOriginAnnotationForFunc, - getCallNodePathFromIndex, } from 'firefox-profiler/profile-logic/profile-data'; import { getCategories } from 'firefox-profiler/selectors'; @@ -136,9 +135,10 @@ class CallNodeContextMenuImpl extends React.PureComponent { const { callNodeIndex, thread: { stringTable, funcTable }, - callNodeInfo: { callNodeTable }, + callNodeInfo, } = rightClickedCallNodeInfo; + const callNodeTable = callNodeInfo.getCallNodeTable(); const funcIndex = callNodeTable.func[callNodeIndex]; const isJS = funcTable.isJS[funcIndex]; const stringIndex = funcTable.name[funcIndex]; @@ -173,9 +173,10 @@ class CallNodeContextMenuImpl extends React.PureComponent { const { callNodeIndex, thread: { stringTable, funcTable }, - callNodeInfo: { callNodeTable }, + callNodeInfo, } = rightClickedCallNodeInfo; + const callNodeTable = callNodeInfo.getCallNodeTable(); const funcIndex = callNodeTable.func[callNodeIndex]; const stringIndex = funcTable.fileName[funcIndex]; if (stringIndex === null) { @@ -223,13 +224,12 @@ class CallNodeContextMenuImpl extends React.PureComponent { const { callNodeIndex, thread: { funcTable, resourceTable, stringTable }, - callNodeInfo: { callNodeTable }, + callNodeInfo, } = rightClickedCallNodeInfo; - const callPath = getCallNodePathFromIndex( - callNodeIndex, - callNodeTable - ).reverse(); + const callPath = callNodeInfo + .getCallNodePathFromIndex(callNodeIndex) + .reverse(); const stack = callPath .map((funcIndex) => { @@ -300,14 +300,10 @@ class CallNodeContextMenuImpl extends React.PureComponent { ); } - const { - threadsKey, - callNodePath, - thread, - callNodeIndex, - callNodeInfo: { callNodeTable }, - } = rightClickedCallNodeInfo; + const { threadsKey, callNodePath, thread, callNodeIndex, callNodeInfo } = + rightClickedCallNodeInfo; const selectedFunc = callNodePath[callNodePath.length - 1]; + const callNodeTable = callNodeInfo.getCallNodeTable(); const category = callNodeTable.category[callNodeIndex]; switch (type) { case 'focus-subtree': @@ -489,9 +485,10 @@ class CallNodeContextMenuImpl extends React.PureComponent { const { callNodeIndex, thread: { funcTable }, - callNodeInfo: { callNodeTable }, + callNodeInfo, } = rightClickedCallNodeInfo; + const callNodeTable = callNodeInfo.getCallNodeTable(); const categoryIndex = callNodeTable.category[callNodeIndex]; const funcIndex = callNodeTable.func[callNodeIndex]; const isJS = funcTable.isJS[funcIndex]; diff --git a/src/components/shared/thread/StackGraph.js b/src/components/shared/thread/StackGraph.js index 381546da6b..b605584edd 100644 --- a/src/components/shared/thread/StackGraph.js +++ b/src/components/shared/thread/StackGraph.js @@ -43,8 +43,8 @@ export class ThreadStackGraph extends PureComponent { if (callNodeIndex === null) { return null; } - - return callNodeInfo.callNodeTable.depth[callNodeIndex]; + const callNodeTable = callNodeInfo.getCallNodeTable(); + return callNodeTable.depth[callNodeIndex]; }; render() { @@ -60,7 +60,7 @@ export class ThreadStackGraph extends PureComponent { trackName, onSampleClick, } = this.props; - const { callNodeTable } = callNodeInfo; + const callNodeTable = callNodeInfo.getCallNodeTable(); let maxDepth = 0; for (let i = 0; i < callNodeTable.depth.length; i++) { diff --git a/src/components/stack-chart/Canvas.js b/src/components/stack-chart/Canvas.js index e4cc672fab..da20f1acce 100644 --- a/src/components/stack-chart/Canvas.js +++ b/src/components/stack-chart/Canvas.js @@ -122,15 +122,13 @@ class StackChartCanvasImpl extends React.PureComponent { } _scrollSelectionIntoView = () => { - const { - selectedCallNodeIndex, - callNodeInfo: { callNodeTable }, - } = this.props; + const { selectedCallNodeIndex, callNodeInfo } = this.props; if (selectedCallNodeIndex === null) { return; } + const callNodeTable = callNodeInfo.getCallNodeTable(); const depth = callNodeTable.depth[selectedCallNodeIndex]; const y = depth * ROW_CSS_PIXELS_HEIGHT; @@ -166,7 +164,7 @@ class StackChartCanvasImpl extends React.PureComponent { stackFrameHeight, selectedCallNodeIndex, categories, - callNodeInfo: { callNodeTable }, + callNodeInfo, getMarker, marginLeft, viewport: { @@ -264,6 +262,8 @@ class StackChartCanvasImpl extends React.PureComponent { categoryForUserTiming = 0; } + const callNodeTable = callNodeInfo.getCallNodeTable(); + // Only draw the stack frames that are vertically within view. for (let depth = startDepth; depth < endDepth; depth++) { // Get the timing information for a row of stack frames. diff --git a/src/components/stack-chart/index.js b/src/components/stack-chart/index.js index 368fe0d699..90120e458e 100644 --- a/src/components/stack-chart/index.js +++ b/src/components/stack-chart/index.js @@ -38,10 +38,7 @@ import { changeMouseTimePosition, } from '../../actions/profile-view'; -import { - getCallNodePathFromIndex, - getBottomBoxInfoForCallNode, -} from '../../profile-logic/profile-data'; +import { getBottomBoxInfoForCallNode } from '../../profile-logic/profile-data'; import type { Thread, @@ -122,7 +119,7 @@ class StackChartImpl extends React.PureComponent { const { callNodeInfo, threadsKey, changeSelectedCallNode } = this.props; changeSelectedCallNode( threadsKey, - getCallNodePathFromIndex(callNodeIndex, callNodeInfo.callNodeTable) + callNodeInfo.getCallNodePathFromIndex(callNodeIndex) ); }; @@ -131,7 +128,7 @@ class StackChartImpl extends React.PureComponent { changeRightClickedCallNode( threadsKey, - getCallNodePathFromIndex(callNodeIndex, callNodeInfo.callNodeTable) + callNodeInfo.getCallNodePathFromIndex(callNodeIndex) ); }; @@ -182,12 +179,9 @@ class StackChartImpl extends React.PureComponent { _onCopy = (event: ClipboardEvent) => { if (document.activeElement === this._viewport) { event.preventDefault(); - const { - callNodeInfo: { callNodeTable }, - selectedCallNodeIndex, - thread, - } = this.props; + const { callNodeInfo, selectedCallNodeIndex, thread } = this.props; if (selectedCallNodeIndex !== null) { + const callNodeTable = callNodeInfo.getCallNodeTable(); const funcIndex = callNodeTable.func[selectedCallNodeIndex]; const funcName = thread.stringTable.getString( thread.funcTable.name[funcIndex] diff --git a/src/components/tooltip/CallNode.js b/src/components/tooltip/CallNode.js index 79effb53a9..8ae5979040 100644 --- a/src/components/tooltip/CallNode.js +++ b/src/components/tooltip/CallNode.js @@ -430,9 +430,10 @@ export class TooltipCallNode extends React.PureComponent { timings, callTreeSummaryStrategy, innerWindowIDToPageMap, - callNodeInfo: { callNodeTable }, + callNodeInfo, displayStackType, } = this.props; + const callNodeTable = callNodeInfo.getCallNodeTable(); const categoryIndex = callNodeTable.category[callNodeIndex]; const categoryColor = categories[categoryIndex].color; const subcategoryIndex = callNodeTable.subcategory[callNodeIndex]; diff --git a/src/profile-logic/address-timings.js b/src/profile-logic/address-timings.js index d409e62a50..3ff041777b 100644 --- a/src/profile-logic/address-timings.js +++ b/src/profile-logic/address-timings.js @@ -202,7 +202,7 @@ export function getStackAddressInfoForCallNode( callNodeInfo: CallNodeInfo, nativeSymbol: IndexIntoNativeSymbolTable ): StackAddressInfo { - return callNodeInfo.isInverted + return callNodeInfo.isInverted() ? getStackAddressInfoForCallNodeInverted( stackTable, frameTable, @@ -347,9 +347,11 @@ export function getStackAddressInfoForCallNodeNonInverted( stackTable: StackTable, frameTable: FrameTable, callNodeIndex: IndexIntoCallNodeTable, - { stackIndexToCallNodeIndex }: CallNodeInfo, + callNodeInfo: CallNodeInfo, nativeSymbol: IndexIntoNativeSymbolTable ): StackAddressInfo { + const stackIndexToCallNodeIndex = callNodeInfo.getStackIndexToCallNodeIndex(); + // "self address" == "the address which a stack's self time is contributed to" const callNodeSelfAddressForAllStacks = []; // "total addresses" == "the set of addresses whose total time this stack contributes to" @@ -426,12 +428,12 @@ export function getStackAddressInfoForCallNodeInverted( callNodeInfo: CallNodeInfo, nativeSymbol: IndexIntoNativeSymbolTable ): StackAddressInfo { - const invertedCallNodeTable = callNodeInfo.callNodeTable; + const invertedCallNodeTable = callNodeInfo.getCallNodeTable(); const depth = invertedCallNodeTable.depth[callNodeIndex]; const endIndex = invertedCallNodeTable.subtreeRangeEnd[callNodeIndex]; const callNodeIsRootOfInvertedTree = invertedCallNodeTable.prefix[callNodeIndex] === -1; - const { stackIndexToCallNodeIndex } = callNodeInfo; + const stackIndexToCallNodeIndex = callNodeInfo.getStackIndexToCallNodeIndex(); const stackTablePrefixCol = stackTable.prefix; // "self address" == "the address which a stack's self time is contributed to" diff --git a/src/profile-logic/call-node-info.js b/src/profile-logic/call-node-info.js new file mode 100644 index 0000000000..3d85522a0a --- /dev/null +++ b/src/profile-logic/call-node-info.js @@ -0,0 +1,180 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// @flow + +import { hashPath } from 'firefox-profiler/utils/path'; + +import type { + IndexIntoFuncTable, + CallNodeInfo, + CallNodeTable, + CallNodePath, + IndexIntoCallNodeTable, +} from 'firefox-profiler/types'; + +/** + * The implementation of the CallNodeInfo interface. + */ +export class CallNodeInfoImpl implements CallNodeInfo { + // If true, call node indexes describe nodes in the inverted call tree. + _isInverted: boolean; + + // The call node table. + _callNodeTable: CallNodeTable; + + // The mapping of stack index to corresponding call node index. + _stackIndexToCallNodeIndex: Int32Array; + + // This is a Map. This map speeds up + // the look-up process by caching every CallNodePath we handle which avoids + // looking up parents again and again. + _cache: Map = new Map(); + + constructor( + callNodeTable: CallNodeTable, + stackIndexToCallNodeIndex: Int32Array, + isInverted: boolean + ) { + this._callNodeTable = callNodeTable; + this._stackIndexToCallNodeIndex = stackIndexToCallNodeIndex; + this._isInverted = isInverted; + } + + isInverted(): boolean { + return this._isInverted; + } + + getCallNodeTable(): CallNodeTable { + return this._callNodeTable; + } + + getStackIndexToCallNodeIndex(): Int32Array { + return this._stackIndexToCallNodeIndex; + } + + getCallNodePathFromIndex( + callNodeIndex: IndexIntoCallNodeTable | null + ): CallNodePath { + if (callNodeIndex === null || callNodeIndex === -1) { + return []; + } + + const callNodePath = []; + let cni = callNodeIndex; + while (cni !== -1) { + callNodePath.push(this._callNodeTable.func[cni]); + cni = this._callNodeTable.prefix[cni]; + } + callNodePath.reverse(); + return callNodePath; + } + + getCallNodeIndexFromPath( + callNodePath: CallNodePath + ): IndexIntoCallNodeTable | null { + const cache = this._cache; + const hashFullPath = hashPath(callNodePath); + const result = cache.get(hashFullPath); + if (result !== undefined) { + // The cache already has the result for the full path. + return result; + } + + // This array serves as a map and stores the hashes of callNodePath's + // parents to speed up the algorithm. First we'll follow the tree from the + // bottom towards the top, pushing hashes as we compute them, and then we'll + // move back towards the bottom popping hashes from this array. + const sliceHashes = [hashFullPath]; + + // Step 1: find whether we already computed the index for one of the path's + // parents, starting from the closest parent and looping towards the "top" of + // the tree. + // If we find it for one of the parents, we'll be able to start at this point + // in the following look up. + let i = callNodePath.length; + let index; + while (--i > 0) { + // Looking up each parent for this call node, starting from the deepest node. + // If we find a parent this makes it possible to start the look up from this location. + const subPath = callNodePath.slice(0, i); + const hash = hashPath(subPath); + index = cache.get(hash); + if (index !== undefined) { + // Yay, we already have the result for a parent! + break; + } + // Cache the hashed value because we'll need it later, after resolving this path. + // Note we don't add the hash if we found the parent in the cache, so the + // last added element here will accordingly be the first popped in the next + // algorithm. + sliceHashes.push(hash); + } + + // Step 2: look for the requested path using the call node table, starting at + // the parent we already know if we found one, and looping down the tree. + // We're contributing to the cache at the same time. + + // `index` is undefined if no parent was found in the cache. In that case we + // start from the start, and use `-1` which is the prefix we use to indicate + // the root node. + if (index === undefined) { + // assert(i === 0); + index = -1; + } + + while (i < callNodePath.length) { + // Resolving the index for subpath `callNodePath.slice(0, i+1)` given we + // know the index for the subpath `callNodePath.slice(0, i)` (its parent). + const func = callNodePath[i]; + const nextNodeIndex = this.getCallNodeIndexFromParentAndFunc(index, func); + + // We couldn't find this path into the call node table. This shouldn't + // normally happen. + if (nextNodeIndex === null) { + return null; + } + + // Contributing to the shared cache + const hash = sliceHashes.pop(); + cache.set(hash, nextNodeIndex); + + index = nextNodeIndex; + i++; + } + + return index < 0 ? null : index; + } + + getCallNodeIndexFromParentAndFunc( + parent: IndexIntoCallNodeTable | -1, + func: IndexIntoFuncTable + ): IndexIntoCallNodeTable | null { + const callNodeTable = this._callNodeTable; + if (parent === -1) { + if (callNodeTable.length === 0) { + return null; + } + } else if (callNodeTable.subtreeRangeEnd[parent] === parent + 1) { + // parent has no children. + return null; + } + // Node children always come after their parents in the call node table, + // that's why we start looping at `parent + 1`. + // Note that because the root parent is `-1`, we correctly start at `0` when + // we look for a root. + const firstChild = parent + 1; + for ( + let callNodeIndex = firstChild; + callNodeIndex !== -1; + callNodeIndex = callNodeTable.nextSibling[callNodeIndex] + ) { + if (callNodeTable.func[callNodeIndex] === func) { + return callNodeIndex; + } + } + + return null; + } +} diff --git a/src/profile-logic/call-tree.js b/src/profile-logic/call-tree.js index af553d6686..6ac2174eac 100644 --- a/src/profile-logic/call-tree.js +++ b/src/profile-logic/call-tree.js @@ -10,7 +10,6 @@ import { getOriginAnnotationForFunc, getCategoryPairLabel, getBottomBoxInfoForCallNode, - getCallNodePathFromIndex, } from './profile-data'; import { resourceTypes } from './data-structures'; import { getFunctionName } from './function-info'; @@ -98,7 +97,7 @@ export class CallTree { ) { this._categories = categories; this._callNodeInfo = callNodeInfo; - this._callNodeTable = callNodeInfo.callNodeTable; + this._callNodeTable = callNodeInfo.getCallNodeTable(); this._callNodeSummary = callNodeSummary; this._callNodeHasChildren = callNodeHasChildren; this._thread = thread; @@ -415,7 +414,7 @@ export class CallTree { } } - return getCallNodePathFromIndex(maxNode, this._callNodeTable); + return this._callNodeInfo.getCallNodePathFromIndex(maxNode); } } @@ -515,9 +514,11 @@ function _getStackSelf( export function computeCallTreeTimings( samples: SamplesLikeTable, sampleIndexToCallNodeIndex: Array, - { callNodeTable }: CallNodeInfo, + callNodeInfo: CallNodeInfo, invertCallstack: boolean ): CallTreeTimings { + const callNodeTable = callNodeInfo.getCallNodeTable(); + // Inverted trees need a different method for computing the timing. const { callNodeSelf, callNodeLeaf } = invertCallstack ? _getInvertedStackSelf(samples, callNodeTable, sampleIndexToCallNodeIndex) @@ -686,10 +687,13 @@ export function extractSamplesLikeTable( */ export function computeTracedTiming( samples: SamplesLikeTable, - { callNodeTable, stackIndexToCallNodeIndex }: CallNodeInfo, + callNodeInfo: CallNodeInfo, interval: Milliseconds, invertCallstack: boolean ): TracedTiming | null { + const callNodeTable = callNodeInfo.getCallNodeTable(); + const stackIndexToCallNodeIndex = callNodeInfo.getStackIndexToCallNodeIndex(); + if (samples.weightType !== 'samples' || samples.weight) { // Only compute for the samples weight types that have no weights. If a samples // table has weights then it's a diff profile. Currently, we aren't calculating diff --git a/src/profile-logic/line-timings.js b/src/profile-logic/line-timings.js index 293f26968e..91ab961117 100644 --- a/src/profile-logic/line-timings.js +++ b/src/profile-logic/line-timings.js @@ -125,7 +125,7 @@ export function getStackLineInfoForCallNode( callNodeIndex: IndexIntoCallNodeTable, callNodeInfo: CallNodeInfo ): StackLineInfo { - return callNodeInfo.isInverted + return callNodeInfo.isInverted() ? getStackLineInfoForCallNodeInverted( stackTable, frameTable, @@ -207,8 +207,10 @@ export function getStackLineInfoForCallNodeNonInverted( stackTable: StackTable, frameTable: FrameTable, callNodeIndex: IndexIntoCallNodeTable, - { stackIndexToCallNodeIndex }: CallNodeInfo + callNodeInfo: CallNodeInfo ): StackLineInfo { + const stackIndexToCallNodeIndex = callNodeInfo.getStackIndexToCallNodeIndex(); + // "self line" == "the line which a stack's self time is contributed to" const callNodeSelfLineForAllStacks = []; // "total lines" == "the set of lines whose total time this stack contributes to" @@ -281,12 +283,12 @@ export function getStackLineInfoForCallNodeInverted( callNodeIndex: IndexIntoCallNodeTable, callNodeInfo: CallNodeInfo ): StackLineInfo { - const invertedCallNodeTable = callNodeInfo.callNodeTable; + const invertedCallNodeTable = callNodeInfo.getCallNodeTable(); const depth = invertedCallNodeTable.depth[callNodeIndex]; const endIndex = invertedCallNodeTable.subtreeRangeEnd[callNodeIndex]; const callNodeIsRootOfInvertedTree = invertedCallNodeTable.prefix[callNodeIndex] === -1; - const { stackIndexToCallNodeIndex } = callNodeInfo; + const stackIndexToCallNodeIndex = callNodeInfo.getStackIndexToCallNodeIndex(); const stackTablePrefixCol = stackTable.prefix; // "self line" == "the line which a stack's self time is contributed to" diff --git a/src/profile-logic/profile-data.js b/src/profile-logic/profile-data.js index eb216df9e6..6a8226a40f 100644 --- a/src/profile-logic/profile-data.js +++ b/src/profile-logic/profile-data.js @@ -16,6 +16,7 @@ import { shallowCloneFrameTable, shallowCloneFuncTable, } from './data-structures'; +import { CallNodeInfoImpl } from './call-node-info'; import { INSTANT, INTERVAL, @@ -23,7 +24,6 @@ import { INTERVAL_END, } from 'firefox-profiler/app-logic/constants'; import { timeCode } from 'firefox-profiler/utils/time-code'; -import { hashPath } from 'firefox-profiler/utils/path'; import { bisectionRight, bisectionLeft } from 'firefox-profiler/utils/bisect'; import { parseFileNameFromSymbolication } from 'firefox-profiler/utils/special-paths'; import { @@ -91,20 +91,43 @@ import type { UniqueStringArray } from 'firefox-profiler/utils/unique-string-arr */ /** - * Generate the CallNodeInfo which contains the CallNodeTable, and a map to convert - * an IndexIntoStackTable to a IndexIntoCallNodeTable. This function runs through - * a stackTable, and de-duplicates stacks that have frames that point to the same - * function. + * Generate the non-inverted CallNodeInfo for a thread. + */ +export function getCallNodeInfo( + stackTable: StackTable, + frameTable: FrameTable, + funcTable: FuncTable, + defaultCategory: IndexIntoCategoryList +): CallNodeInfo { + const { callNodeTable, stackIndexToCallNodeIndex } = computeCallNodeTable( + stackTable, + frameTable, + funcTable, + defaultCategory + ); + return new CallNodeInfoImpl(callNodeTable, stackIndexToCallNodeIndex, false); +} + +type CallNodeTableAndStackMap = { + callNodeTable: CallNodeTable, + // IndexIntoStackTable -> IndexIntoCallNodeTable + stackIndexToCallNodeIndex: Int32Array, +}; + +/** + * Generate the CallNodeTable, and a map to convert an IndexIntoStackTable to a + * IndexIntoCallNodeTable. This function runs through a stackTable, and + * de-duplicates stacks that have frames that point to the same function. * * See `src/types/profile-derived.js` for the type definitions. * See `docs-developer/call-trees.md` for a detailed explanation of CallNodes. */ -export function getCallNodeInfo( +export function computeCallNodeTable( stackTable: StackTable, frameTable: FrameTable, funcTable: FuncTable, defaultCategory: IndexIntoCategoryList -): CallNodeInfo { +): CallNodeTableAndStackMap { return timeCode('getCallNodeInfo', () => { const stackIndexToCallNodeIndex = new Int32Array(stackTable.length); @@ -254,7 +277,7 @@ export function getCallNodeInfo( } stackIndexToCallNodeIndex[stackIndex] = callNodeIndex; } - return _createCallNodeInfoFromUnorderedComponents( + return _createCallNodeTableFromUnorderedComponents( prefix, firstChild, nextSibling, @@ -270,8 +293,8 @@ export function getCallNodeInfo( } /** - * Create a CallNodeInfo with an ordered call node table based on the pieces of - * an unordered call node table. + * Create a CallNodeTableAndStackMap with an ordered call node table based on + * the pieces of an unordered call node table. * * The order of siblings is maintained. * If a node A has children, its first child B directly follows A. @@ -279,7 +302,7 @@ export function getCallNodeInfo( * next sibling of the closest ancestor which has a next sibling. * This means that any node and all its descendants are laid out contiguously. */ -function _createCallNodeInfoFromUnorderedComponents( +function _createCallNodeTableFromUnorderedComponents( prefix: Array, firstChild: Array, nextSibling: Array, @@ -290,13 +313,12 @@ function _createCallNodeInfoFromUnorderedComponents( sourceFramesInlinedIntoSymbol: Array, length: number, stackIndexToCallNodeIndex: Int32Array -): CallNodeInfo { +): CallNodeTableAndStackMap { return timeCode('createCallNodeInfoFromUnorderedComponents', () => { if (length === 0) { return { callNodeTable: getEmptyCallNodeTable(), stackIndexToCallNodeIndex: new Int32Array(0), - isInverted: false, }; } @@ -392,11 +414,13 @@ function _createCallNodeInfoFromUnorderedComponents( stackIndexToCallNodeIndex: stackIndexToCallNodeIndex.map( (oldIndex) => oldIndexToNewIndex[oldIndex] ), - isInverted: false, }; }); } +/** + * Generate the inverted CallNodeInfo for a thread. + */ export function getInvertedCallNodeInfo( thread: Thread, defaultCategory: IndexIntoCategoryList @@ -411,7 +435,7 @@ export function getInvertedCallNodeInfo( const { callNodeTable, stackIndexToCallNodeIndex: invertedStackIndexToCallNodeIndex, - } = getCallNodeInfo( + } = computeCallNodeTable( invertedThread.stackTable, invertedThread.frameTable, invertedThread.funcTable, @@ -442,11 +466,11 @@ export function getInvertedCallNodeInfo( invertedStackIndexToCallNodeIndex[invertedStackIndex]; } } - return { + return new CallNodeInfoImpl( callNodeTable, - stackIndexToCallNodeIndex: nonInvertedStackIndexToCallNodeIndex, - isInverted: true, - }; + nonInvertedStackIndexToCallNodeIndex, + true + ); } // Given a stack index `needleStack` and a call node in the inverted tree @@ -677,7 +701,7 @@ function mapCallNodeSelectedStatesToSamples( * from the same subtree (in the call tree) "clump together" in the graph. */ export function getSamplesSelectedStates( - callNodeTable: CallNodeTable, + callNodeInfo: CallNodeInfo, sampleCallNodes: Array, activeTabFilteredCallNodes: Array, selectedCallNodeIndex: IndexIntoCallNodeTable | null @@ -689,6 +713,7 @@ export function getSamplesSelectedStates( ); } + const callNodeTable = callNodeInfo.getCallNodeTable(); return mapCallNodeSelectedStatesToSamples( sampleCallNodes, activeTabFilteredCallNodes, @@ -761,7 +786,7 @@ export function getTimingsForPath( displayImplementation: boolean ) { return getTimingsForCallNodeIndex( - getCallNodeIndexFromPath(needlePath, callNodeInfo.callNodeTable), + callNodeInfo.getCallNodeIndexFromPath(needlePath), callNodeInfo, interval, isInvertedTree, @@ -785,7 +810,7 @@ export function getTimingsForPath( */ export function getTimingsForCallNodeIndex( needleNodeIndex: IndexIntoCallNodeTable | null, - { callNodeTable, stackIndexToCallNodeIndex }: CallNodeInfo, + callNodeInfo: CallNodeInfo, interval: Milliseconds, isInvertedTree: boolean, thread: Thread, @@ -978,6 +1003,9 @@ export function getTimingsForCallNodeIndex( return { forPath: pathTimings, rootTime }; } + const callNodeTable = callNodeInfo.getCallNodeTable(); + const stackIndexToCallNodeIndex = callNodeInfo.getStackIndexToCallNodeIndex(); + const needleDescendantsEndIndex = callNodeTable.subtreeRangeEnd[needleNodeIndex]; @@ -1940,168 +1968,6 @@ export function processEventDelays( }; } -// --------------- CallNodePath and CallNodeIndex manipulations --------------- - -// Returns a list of CallNodeIndex from CallNodePaths. This function uses a map -// to speed up the look-up process. -export function getCallNodeIndicesFromPaths( - callNodePaths: CallNodePath[], - callNodeTable: CallNodeTable -): Array { - // This is a Map. This map speeds up - // the look-up process by caching every CallNodePath we handle which avoids - // looking up parents again and again. - const cache = new Map(); - return callNodePaths.map((path) => - _getCallNodeIndexFromPathWithCache(path, callNodeTable, cache) - ); -} - -// Returns a CallNodeIndex from a CallNodePath, using and contributing to the -// cache parameter. -function _getCallNodeIndexFromPathWithCache( - callNodePath: CallNodePath, - callNodeTable: CallNodeTable, - cache: Map -): IndexIntoCallNodeTable | null { - const hashFullPath = hashPath(callNodePath); - const result = cache.get(hashFullPath); - if (result !== undefined) { - // The cache already has the result for the full path. - return result; - } - - // This array serves as a map and stores the hashes of callNodePath's - // parents to speed up the algorithm. First we'll follow the tree from the - // bottom towards the top, pushing hashes as we compute them, and then we'll - // move back towards the bottom popping hashes from this array. - const sliceHashes = [hashFullPath]; - - // Step 1: find whether we already computed the index for one of the path's - // parents, starting from the closest parent and looping towards the "top" of - // the tree. - // If we find it for one of the parents, we'll be able to start at this point - // in the following look up. - let i = callNodePath.length; - let index; - while (--i > 0) { - // Looking up each parent for this call node, starting from the deepest node. - // If we find a parent this makes it possible to start the look up from this location. - const subPath = callNodePath.slice(0, i); - const hash = hashPath(subPath); - index = cache.get(hash); - if (index !== undefined) { - // Yay, we already have the result for a parent! - break; - } - // Cache the hashed value because we'll need it later, after resolving this path. - // Note we don't add the hash if we found the parent in the cache, so the - // last added element here will accordingly be the first popped in the next - // algorithm. - sliceHashes.push(hash); - } - - // Step 2: look for the requested path using the call node table, starting at - // the parent we already know if we found one, and looping down the tree. - // We're contributing to the cache at the same time. - - // `index` is undefined if no parent was found in the cache. In that case we - // start from the start, and use `-1` which is the prefix we use to indicate - // the root node. - if (index === undefined) { - // assert(i === 0); - index = -1; - } - - while (i < callNodePath.length) { - // Resolving the index for subpath `callNodePath.slice(0, i+1)` given we - // know the index for the subpath `callNodePath.slice(0, i)` (its parent). - const func = callNodePath[i]; - const nextNodeIndex = getCallNodeIndexFromParentAndFunc( - index, - func, - callNodeTable - ); - - // We couldn't find this path into the call node table. This shouldn't - // normally happen. - if (nextNodeIndex === null) { - return null; - } - - // Contributing to the shared cache - const hash = sliceHashes.pop(); - cache.set(hash, nextNodeIndex); - - index = nextNodeIndex; - i++; - } - - return index < 0 ? null : index; -} - -// Returns the CallNodeIndex that matches the function `func` and whose parent's -// CallNodeIndex is `parent`. -export function getCallNodeIndexFromParentAndFunc( - parent: IndexIntoCallNodeTable | -1, - func: IndexIntoFuncTable, - callNodeTable: CallNodeTable -): IndexIntoCallNodeTable | null { - if (parent === -1) { - if (callNodeTable.length === 0) { - return null; - } - } else if (callNodeTable.subtreeRangeEnd[parent] === parent + 1) { - // parent has no children. - return null; - } - // Node children always come after their parents in the call node table, - // that's why we start looping at `parent + 1`. - // Note that because the root parent is `-1`, we correctly start at `0` when - // we look for a root. - const firstChild = parent + 1; - for ( - let callNodeIndex = firstChild; - callNodeIndex !== -1; - callNodeIndex = callNodeTable.nextSibling[callNodeIndex] - ) { - if (callNodeTable.func[callNodeIndex] === func) { - return callNodeIndex; - } - } - - return null; -} - -// This function returns a CallNodeIndex from a CallNodePath, using the -// specified `callNodeTable`. -export function getCallNodeIndexFromPath( - callNodePath: CallNodePath, - callNodeTable: CallNodeTable -): IndexIntoCallNodeTable | null { - const [result] = getCallNodeIndicesFromPaths([callNodePath], callNodeTable); - return result; -} - -// This function returns a CallNodePath from a CallNodeIndex. -export function getCallNodePathFromIndex( - callNodeIndex: IndexIntoCallNodeTable | null, - callNodeTable: CallNodeTable -): CallNodePath { - if (callNodeIndex === null || callNodeIndex === -1) { - return []; - } - - const callNodePath = []; - let fs = callNodeIndex; - while (fs !== -1) { - callNodePath.push(callNodeTable.func[fs]); - fs = callNodeTable.prefix[fs]; - } - callNodePath.reverse(); - return callNodePath; -} - /** * This function converts a stack information into a call node and * category path structure. @@ -2145,7 +2011,8 @@ export function computeCallNodeMaxDepthPlusOne( // computed for the filtered thread, but a samples-like table can use the preview // filtered thread, which involves a subset of the total call nodes. let maxDepth = -1; - const { callNodeTable, stackIndexToCallNodeIndex } = callNodeInfo; + const callNodeTable = callNodeInfo.getCallNodeTable(); + const stackIndexToCallNodeIndex = callNodeInfo.getStackIndexToCallNodeIndex(); for (let sampleIndex = 0; sampleIndex < samples.length; sampleIndex++) { const stackIndex = samples.stack[sampleIndex]; if (stackIndex === null) { @@ -3659,7 +3526,7 @@ export function getNativeSymbolsForCallNode( stackTable: StackTable, frameTable: FrameTable ): IndexIntoNativeSymbolTable[] { - if (callNodeInfo.isInverted) { + if (callNodeInfo.isInverted()) { return getNativeSymbolsForCallNodeInverted( callNodeIndex, callNodeInfo, @@ -3677,10 +3544,11 @@ export function getNativeSymbolsForCallNode( export function getNativeSymbolsForCallNodeNonInverted( callNodeIndex: IndexIntoCallNodeTable, - { stackIndexToCallNodeIndex }: CallNodeInfo, + callNodeInfo: CallNodeInfo, stackTable: StackTable, frameTable: FrameTable ): IndexIntoNativeSymbolTable[] { + const stackIndexToCallNodeIndex = callNodeInfo.getStackIndexToCallNodeIndex(); const set = new Set(); for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) { if (stackIndexToCallNodeIndex[stackIndex] === callNodeIndex) { @@ -3700,11 +3568,11 @@ export function getNativeSymbolsForCallNodeInverted( stackTable: StackTable, frameTable: FrameTable ): IndexIntoNativeSymbolTable[] { - const invertedCallNodeTable = callNodeInfo.callNodeTable; + const invertedCallNodeTable = callNodeInfo.getCallNodeTable(); const depth = invertedCallNodeTable.depth[callNodeIndex]; const endIndex = invertedCallNodeTable.subtreeRangeEnd[callNodeIndex]; const stackTablePrefixCol = stackTable.prefix; - const { stackIndexToCallNodeIndex } = callNodeInfo; + const stackIndexToCallNodeIndex = callNodeInfo.getStackIndexToCallNodeIndex(); const set = new Set(); for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) { const stackForNode = getMatchingAncestorStackForInvertedCallNode( @@ -3775,7 +3643,8 @@ export function getBottomBoxInfoForCallNode( nativeSymbols, } = thread; - const funcIndex = callNodeInfo.callNodeTable.func[callNodeIndex]; + const callNodeTable = callNodeInfo.getCallNodeTable(); + const funcIndex = callNodeTable.func[callNodeIndex]; const fileName = funcTable.fileName[funcIndex]; const sourceFile = fileName !== null ? stringTable.getString(fileName) : null; const resource = funcTable.resource[funcIndex]; diff --git a/src/profile-logic/stack-timing.js b/src/profile-logic/stack-timing.js index 37acb201cf..fc8c728a69 100644 --- a/src/profile-logic/stack-timing.js +++ b/src/profile-logic/stack-timing.js @@ -66,7 +66,7 @@ export function getStackTimingByDepth( maxDepthPlusOne: number, interval: Milliseconds ): StackTimingByDepth { - const { callNodeTable } = callNodeInfo; + const callNodeTable = callNodeInfo.getCallNodeTable(); const { prefix: callNodeTablePrefixColumn, subtreeRangeEnd: callNodeTableSubtreeRangeEndColumn, diff --git a/src/profile-logic/transforms.js b/src/profile-logic/transforms.js index 5f0ec3d565..dc95c8cd2a 100644 --- a/src/profile-logic/transforms.js +++ b/src/profile-logic/transforms.js @@ -12,7 +12,6 @@ import { updateThreadStacks, updateThreadStacksByGeneratingNewStackColumns, getMapStackUpdater, - getCallNodeIndexFromParentAndFunc, } from './profile-data'; import { timeCode } from '../utils/time-code'; import { assertExhaustiveCheck, convertToTransformType } from '../utils/flow'; @@ -32,6 +31,7 @@ import type { CallNodePath, CallNodeAndCategoryPath, CallNodeTable, + CallNodeInfo, StackType, ImplementationFilter, Transform, @@ -493,7 +493,7 @@ export function applyTransformToCallNodePath( callNodePath: CallNodePath, transform: Transform, transformedThread: Thread, - callNodeTable: CallNodeTable + callNodeInfo: CallNodeInfo ): CallNodePath { switch (transform.type) { case 'focus-subtree': @@ -507,7 +507,7 @@ export function applyTransformToCallNodePath( return _removeOtherCategoryFunctionsInNodePathWithFunction( transform.category, callNodePath, - callNodeTable + callNodeInfo ); case 'merge-call-node': return _mergeNodeInCallNodePath(transform.callNodePath, callNodePath); @@ -596,16 +596,17 @@ function _dropFunctionInCallNodePath( function _removeOtherCategoryFunctionsInNodePathWithFunction( category: IndexIntoCategoryList, callNodePath: CallNodePath, - callNodeTable: CallNodeTable + callNodeInfo: CallNodeInfo ): CallNodePath { + const callNodeTable = callNodeInfo.getCallNodeTable(); + const newCallNodePath = []; let prefix = -1; for (const funcIndex of callNodePath) { - const callNodeIndex = getCallNodeIndexFromParentAndFunc( + const callNodeIndex = callNodeInfo.getCallNodeIndexFromParentAndFunc( prefix, - funcIndex, - callNodeTable + funcIndex ); if (callNodeIndex === null) { throw new Error( diff --git a/src/reducers/profile-view.js b/src/reducers/profile-view.js index 139774e456..db968f64c8 100644 --- a/src/reducers/profile-view.js +++ b/src/reducers/profile-view.js @@ -346,8 +346,7 @@ const viewOptionsPerThread: Reducer = ( }); } case 'ADD_TRANSFORM_TO_STACK': { - const { threadsKey, transform, transformedThread, callNodeTable } = - action; + const { threadsKey, transform, transformedThread, callNodeInfo } = action; const getFilteredPathSet = function (pathSet: PathSet): PathSet { return new PathSet( @@ -357,7 +356,7 @@ const viewOptionsPerThread: Reducer = ( path, transform, transformedThread, - callNodeTable + callNodeInfo ) ) .filter((path) => path.length > 0) @@ -369,7 +368,7 @@ const viewOptionsPerThread: Reducer = ( path, transform, transformedThread, - callNodeTable + callNodeInfo ); }; diff --git a/src/selectors/per-thread/index.js b/src/selectors/per-thread/index.js index dae455e7e8..fa734d9565 100644 --- a/src/selectors/per-thread/index.js +++ b/src/selectors/per-thread/index.js @@ -289,8 +289,8 @@ export const selectedNodeSelectors: NodeSelectors = (() => { if (sourceViewFile === null || selectedCallNodeIndex === null) { return null; } - const selectedFunc = - callNodeInfo.callNodeTable.func[selectedCallNodeIndex]; + const callNodeTable = callNodeInfo.getCallNodeTable(); + const selectedFunc = callNodeTable.func[selectedCallNodeIndex]; const selectedFuncFile = funcTable.fileName[selectedFunc]; if ( selectedFuncFile === null || diff --git a/src/selectors/per-thread/stack-sample.js b/src/selectors/per-thread/stack-sample.js index 6f10bbd650..0bac1067a5 100644 --- a/src/selectors/per-thread/stack-sample.js +++ b/src/selectors/per-thread/stack-sample.js @@ -198,10 +198,7 @@ export function getStackAndSampleSelectorsPerThread( getCallNodeInfo, getSelectedCallNodePath, (callNodeInfo, callNodePath) => { - return ProfileData.getCallNodeIndexFromPath( - callNodePath, - callNodeInfo.callNodeTable - ); + return callNodeInfo.getCallNodeIndexFromPath(callNodePath); } ); @@ -219,10 +216,9 @@ export function getStackAndSampleSelectorsPerThread( > = createSelector( getCallNodeInfo, getExpandedCallNodePaths, - ({ callNodeTable }, callNodePaths) => - ProfileData.getCallNodeIndicesFromPaths( - Array.from(callNodePaths), - callNodeTable + (callNodeInfo, callNodePaths) => + Array.from(callNodePaths).map((path) => + callNodeInfo.getCallNodeIndexFromPath(path) ) ); @@ -230,8 +226,8 @@ export function getStackAndSampleSelectorsPerThread( Array, > = createSelector( (state) => threadSelectors.getFilteredThread(state).samples.stack, - getCallNodeInfo, - (filteredThreadSampleStacks, { stackIndexToCallNodeIndex }) => + (state) => getCallNodeInfo(state).getStackIndexToCallNodeIndex(), + (filteredThreadSampleStacks, stackIndexToCallNodeIndex) => ProfileData.getSampleIndexToCallNodeIndex( filteredThreadSampleStacks, stackIndexToCallNodeIndex @@ -242,8 +238,8 @@ export function getStackAndSampleSelectorsPerThread( Array, > = createSelector( (state) => threadSelectors.getTabFilteredThread(state).samples.stack, - getCallNodeInfo, - (tabFilteredThreadSampleStacks, { stackIndexToCallNodeIndex }) => + (state) => getCallNodeInfo(state).getStackIndexToCallNodeIndex(), + (tabFilteredThreadSampleStacks, stackIndexToCallNodeIndex) => ProfileData.getSampleIndexToCallNodeIndex( tabFilteredThreadSampleStacks, stackIndexToCallNodeIndex @@ -260,11 +256,11 @@ export function getStackAndSampleSelectorsPerThread( ( sampleIndexToCallNodeIndex, activeTabFilteredCallNodeIndex, - { callNodeTable }, + callNodeInfo, selectedCallNode ) => { return ProfileData.getSamplesSelectedStates( - callNodeTable, + callNodeInfo, sampleIndexToCallNodeIndex, activeTabFilteredCallNodeIndex, selectedCallNode @@ -313,7 +309,7 @@ export function getStackAndSampleSelectorsPerThread( const sampleIndexToCallNodeIndex = ProfileData.getSampleIndexToCallNodeIndex( samples.stack, - callNodeInfo.stackIndexToCallNodeIndex + callNodeInfo.getStackIndexToCallNodeIndex() ); return CallTree.computeCallTreeTimings( samples, @@ -365,7 +361,7 @@ export function getStackAndSampleSelectorsPerThread( ); const getFlameGraphRows: Selector = createSelector( - (state) => getCallNodeInfo(state).callNodeTable, + (state) => getCallNodeInfo(state).getCallNodeTable(), (state) => threadSelectors.getFilteredThread(state).funcTable, (state) => threadSelectors.getFilteredThread(state).stringTable, FlameGraph.computeFlameGraphRows @@ -374,7 +370,7 @@ export function getStackAndSampleSelectorsPerThread( const getFlameGraphTiming: Selector = createSelector( getFlameGraphRows, - (state) => getCallNodeInfo(state).callNodeTable, + (state) => getCallNodeInfo(state).getCallNodeTable(), getCallTreeTimings, FlameGraph.getFlameGraphTiming ); @@ -383,14 +379,13 @@ export function getStackAndSampleSelectorsPerThread( createSelector( getRightClickedCallNodeInfo, getCallNodeInfo, - (rightClickedCallNodeInfo, { callNodeTable }) => { + (rightClickedCallNodeInfo, callNodeInfo) => { if ( rightClickedCallNodeInfo !== null && threadsKey === rightClickedCallNodeInfo.threadsKey ) { - return ProfileData.getCallNodeIndexFromPath( - rightClickedCallNodeInfo.callNodePath, - callNodeTable + return callNodeInfo.getCallNodeIndexFromPath( + rightClickedCallNodeInfo.callNodePath ); } diff --git a/src/test/fixtures/utils.js b/src/test/fixtures/utils.js index 9a3367ce6d..8b329e7ad4 100644 --- a/src/test/fixtures/utils.js +++ b/src/test/fixtures/utils.js @@ -131,7 +131,7 @@ export function callTreeFromProfile( thread.samples, getSampleIndexToCallNodeIndex( thread.samples.stack, - callNodeInfo.stackIndexToCallNodeIndex + callNodeInfo.getStackIndexToCallNodeIndex() ), callNodeInfo, false diff --git a/src/test/store/__snapshots__/profile-view.test.js.snap b/src/test/store/__snapshots__/profile-view.test.js.snap index 04a4821036..cfd72bf73c 100644 --- a/src/test/store/__snapshots__/profile-view.test.js.snap +++ b/src/test/store/__snapshots__/profile-view.test.js.snap @@ -2182,8 +2182,9 @@ Object { `; exports[`snapshots of selectors/profile matches the last stored run of selectedThreadSelector.getCallNodeInfo 1`] = ` -Object { - "callNodeTable": Object { +CallNodeInfoImpl { + "_cache": Map {}, + "_callNodeTable": Object { "category": Int32Array [ 0, 0, @@ -2286,8 +2287,8 @@ Object { 9, ], }, - "isInverted": false, - "stackIndexToCallNodeIndex": Int32Array [ + "_isInverted": false, + "_stackIndexToCallNodeIndex": Int32Array [ 0, 1, 2, @@ -2314,8 +2315,9 @@ CallTree { 0, 0, ], - "_callNodeInfo": Object { - "callNodeTable": Object { + "_callNodeInfo": CallNodeInfoImpl { + "_cache": Map {}, + "_callNodeTable": Object { "category": Int32Array [ 0, 0, @@ -2418,8 +2420,8 @@ CallTree { 9, ], }, - "isInverted": false, - "stackIndexToCallNodeIndex": Int32Array [ + "_isInverted": false, + "_stackIndexToCallNodeIndex": Int32Array [ 0, 1, 2, diff --git a/src/test/store/actions.test.js b/src/test/store/actions.test.js index 4527f8d77c..cddcf82e47 100644 --- a/src/test/store/actions.test.js +++ b/src/test/store/actions.test.js @@ -146,9 +146,10 @@ describe('selectors/getFlameGraphTiming', function () { * "FunctionName1 (StartTime:EndTime) | FunctionName2 (StartTime:EndTime)" */ function getHumanReadableFlameGraphRanges(store, funcNames) { - const { callNodeTable } = selectedThreadSelectors.getCallNodeInfo( + const callNodeInfo = selectedThreadSelectors.getCallNodeInfo( store.getState() ); + const callNodeTable = callNodeInfo.getCallNodeTable(); const flameGraphTiming = selectedThreadSelectors.getFlameGraphTiming( store.getState() ); @@ -176,9 +177,10 @@ describe('selectors/getFlameGraphTiming', function () { * "FunctionName1 (SelfTimeRelative) | ..." */ function getHumanReadableFlameGraphTimings(store, funcNames) { - const { callNodeTable } = selectedThreadSelectors.getCallNodeInfo( + const callNodeInfo = selectedThreadSelectors.getCallNodeInfo( store.getState() ); + const callNodeTable = callNodeInfo.getCallNodeTable(); const flameGraphTiming = selectedThreadSelectors.getFlameGraphTiming( store.getState() ); diff --git a/src/test/store/bottom-box.test.js b/src/test/store/bottom-box.test.js index 90439b28ac..cb847c5b91 100644 --- a/src/test/store/bottom-box.test.js +++ b/src/test/store/bottom-box.test.js @@ -10,10 +10,7 @@ import { selectedNodeSelectors, } from '../../selectors/per-thread'; import { emptyAddressTimings } from '../../profile-logic/address-timings'; -import { - getBottomBoxInfoForCallNode, - getCallNodeIndexFromPath, -} from '../../profile-logic/profile-data'; +import { getBottomBoxInfoForCallNode } from '../../profile-logic/profile-data'; import { changeSelectedCallNode, updateBottomBoxContentsAndMaybeOpen, @@ -83,7 +80,7 @@ describe('bottom box', function () { // Simulate double-clicking the call node at [A, B, C, D]. const abcd = ensureExists( - getCallNodeIndexFromPath([A, B, C, D], callNodeInfo.callNodeTable) + callNodeInfo.getCallNodeIndexFromPath([A, B, C, D]) ); const nativeSymbolInfoD = { libIndex: 0, @@ -171,9 +168,7 @@ describe('bottom box', function () { // [selected tab: calltree] [calltree: bottombox open] [flame-graph: bottombox open] [assembly view closed] // Double-click a call node which doesn't have source file information. - const aef = ensureExists( - getCallNodeIndexFromPath([A, E, F], callNodeInfo.callNodeTable) - ); + const aef = ensureExists(callNodeInfo.getCallNodeIndexFromPath([A, E, F])); const nativeSymbolInfoF = { libIndex: 1, address: 0x12, @@ -216,9 +211,7 @@ describe('bottom box', function () { // Simulate double-clicking the call node at [A, B, C]. // This call node has been inlined into B and into A. - const abc = ensureExists( - getCallNodeIndexFromPath([A, B, C], callNodeInfo.callNodeTable) - ); + const abc = ensureExists(callNodeInfo.getCallNodeIndexFromPath([A, B, C])); const nativeSymbolInfoA = { libIndex: 0, address: 0x20, @@ -268,9 +261,7 @@ describe('bottom box', function () { // box with that info. dispatch(changeSelectedCallNode(threadsKey, [A, B, C, D])); const bottomBoxInfoABC = getBottomBoxInfoForCallNode( - ensureExists( - getCallNodeIndexFromPath([A, B, C, D], callNodeInfo.callNodeTable) - ), + ensureExists(callNodeInfo.getCallNodeIndexFromPath([A, B, C, D])), callNodeInfo, thread ); diff --git a/src/test/store/profile-view.test.js b/src/test/store/profile-view.test.js index 03598c9b53..f2b6bd6e76 100644 --- a/src/test/store/profile-view.test.js +++ b/src/test/store/profile-view.test.js @@ -48,7 +48,6 @@ import { } from '../../selectors/per-thread'; import { ensureExists } from '../../utils/flow'; import { - getCallNodeIndexFromPath, processCounter, type BreakdownByCategory, } from '../../profile-logic/profile-data'; @@ -3286,8 +3285,7 @@ describe('traced timing', function () { ); } - const { callNodeTable } = - selectedThreadSelectors.getCallNodeInfo(getState()); + const callNodeInfo = selectedThreadSelectors.getCallNodeInfo(getState()); const { running, self } = ensureExists( selectedThreadSelectors.getTracedTiming(getState()), @@ -3297,7 +3295,7 @@ describe('traced timing', function () { return { funcNames: funcNamesDictPerThread[0], getCallNode: (...callNodePath) => - ensureExists(getCallNodeIndexFromPath(callNodePath, callNodeTable)), + ensureExists(callNodeInfo.getCallNodeIndexFromPath(callNodePath)), running, self, profile, diff --git a/src/test/unit/address-timings.test.js b/src/test/unit/address-timings.test.js index adf2c69476..101da31661 100644 --- a/src/test/unit/address-timings.test.js +++ b/src/test/unit/address-timings.test.js @@ -13,7 +13,6 @@ import { import { getCallNodeInfo, getInvertedCallNodeInfo, - getCallNodeIndexFromPath, } from '../../profile-logic/profile-data'; import { ensureExists } from 'firefox-profiler/utils/flow'; import type { @@ -163,7 +162,7 @@ describe('getAddressTimings for getStackAddressInfoForCallNode', function () { ? getInvertedCallNodeInfo(thread, defaultCat) : getCallNodeInfo(stackTable, frameTable, funcTable, defaultCat); const callNodeIndex = ensureExists( - getCallNodeIndexFromPath(callNodePath, callNodeInfo.callNodeTable), + callNodeInfo.getCallNodeIndexFromPath(callNodePath), 'invalid call node path' ); const stackLineInfo = getStackAddressInfoForCallNode( diff --git a/src/test/unit/line-timings.test.js b/src/test/unit/line-timings.test.js index be8eda4659..63ef358c6c 100644 --- a/src/test/unit/line-timings.test.js +++ b/src/test/unit/line-timings.test.js @@ -13,7 +13,6 @@ import { import { getCallNodeInfo, getInvertedCallNodeInfo, - getCallNodeIndexFromPath, } from '../../profile-logic/profile-data'; import { ensureExists } from 'firefox-profiler/utils/flow'; import type { @@ -126,7 +125,7 @@ describe('getLineTimings for getStackLineInfoForCallNode', function () { ? getInvertedCallNodeInfo(thread, defaultCat) : getCallNodeInfo(stackTable, frameTable, funcTable, defaultCat); const callNodeIndex = ensureExists( - getCallNodeIndexFromPath(callNodePath, callNodeInfo.callNodeTable), + callNodeInfo.getCallNodeIndexFromPath(callNodePath), 'invalid call node path' ); const stackLineInfo = getStackLineInfoForCallNode( diff --git a/src/test/unit/profile-data.test.js b/src/test/unit/profile-data.test.js index b28d1c83cb..32a7f67cd9 100644 --- a/src/test/unit/profile-data.test.js +++ b/src/test/unit/profile-data.test.js @@ -15,11 +15,9 @@ import { getCallNodeInfo, getInvertedCallNodeInfo, filterThreadByImplementation, - getCallNodePathFromIndex, getSampleIndexClosestToStartTime, convertStackToCallNodeAndCategoryPath, getSampleIndexToCallNodeIndex, - getCallNodeIndexFromPath, getTreeOrderComparator, getSamplesSelectedStates, extractProfileFilterPageData, @@ -446,12 +444,13 @@ describe('profile-data', function () { 'Expected to find categories' ).findIndex((c) => c.name === 'Other'); const thread = profile.threads[0]; - const { callNodeTable } = getCallNodeInfo( + const callNodeInfo = getCallNodeInfo( thread.stackTable, thread.frameTable, thread.funcTable, defaultCategory ); + const callNodeTable = callNodeInfo.getCallNodeTable(); it('should create one callNode per original stack', function () { // After nudgeReturnAddresses, the stack table now has 8 entries. @@ -500,12 +499,15 @@ describe('profile-data', function () { meta.categories, 'Expected to find categories' ).findIndex((c) => c.name === 'Other'); - const { callNodeTable, stackIndexToCallNodeIndex } = getCallNodeInfo( + const callNodeInfo = getCallNodeInfo( thread.stackTable, thread.frameTable, thread.funcTable, defaultCategory ); + const callNodeTable = callNodeInfo.getCallNodeTable(); + const stackIndexToCallNodeIndex = + callNodeInfo.getStackIndexToCallNodeIndex(); const stack0 = thread.samples.stack[0]; const stack1 = thread.samples.stack[1]; if (stack0 === null || stack1 === null) { @@ -513,13 +515,11 @@ describe('profile-data', function () { } const originalStackListA = _getStackList(thread, stack0); const originalStackListB = _getStackList(thread, stack1); - const mergedFuncListA = getCallNodePathFromIndex( - stackIndexToCallNodeIndex[stack0], - callNodeTable + const mergedFuncListA = callNodeInfo.getCallNodePathFromIndex( + stackIndexToCallNodeIndex[stack0] ); - const mergedFuncListB = getCallNodePathFromIndex( - stackIndexToCallNodeIndex[stack1], - callNodeTable + const mergedFuncListB = callNodeInfo.getCallNodePathFromIndex( + stackIndexToCallNodeIndex[stack1] ); it('starts with a fully unduplicated set stack frames', function () { @@ -785,19 +785,19 @@ describe('funcHasDirectRecursiveCall and funcHasRecursiveCall', function () { profile.meta.categories, 'Expected to find categories' ).findIndex((c) => c.name === 'Other'); - const { callNodeTable } = getCallNodeInfo( + const callNodeTable = getCallNodeInfo( thread.stackTable, thread.frameTable, thread.funcTable, defaultCategory - ); + ).getCallNodeTable(); const jsOnlyThread = filterThreadByImplementation(thread, 'js'); - const { callNodeTable: jsOnlyCallNodeTable } = getCallNodeInfo( + const jsOnlyCallNodeTable = getCallNodeInfo( jsOnlyThread.stackTable, jsOnlyThread.frameTable, jsOnlyThread.funcTable, defaultCategory - ); + ).getCallNodeTable(); return { callNodeTable, jsOnlyCallNodeTable, funcNames }; } @@ -868,26 +868,27 @@ describe('getSamplesSelectedStates', function () { C E F G `); const thread = profile.threads[0]; - const { callNodeTable, stackIndexToCallNodeIndex } = getCallNodeInfo( + const callNodeInfo = getCallNodeInfo( thread.stackTable, thread.frameTable, thread.funcTable, 0 ); + const stackIndexToCallNodeIndex = callNodeInfo.getStackIndexToCallNodeIndex(); const sampleCallNodes = getSampleIndexToCallNodeIndex( thread.samples.stack, stackIndexToCallNodeIndex ); - const A_B = getCallNodeIndexFromPath([A, B], callNodeTable); - const A_B_F = getCallNodeIndexFromPath([A, B, F], callNodeTable); - const A_D = getCallNodeIndexFromPath([A, D], callNodeTable); - const A_D_E = getCallNodeIndexFromPath([A, D, E], callNodeTable); + const A_B = callNodeInfo.getCallNodeIndexFromPath([A, B]); + const A_B_F = callNodeInfo.getCallNodeIndexFromPath([A, B, F]); + const A_D = callNodeInfo.getCallNodeIndexFromPath([A, D]); + const A_D_E = callNodeInfo.getCallNodeIndexFromPath([A, D, E]); it('determines the selection status of all the samples', function () { expect( getSamplesSelectedStates( - callNodeTable, + callNodeInfo, sampleCallNodes, sampleCallNodes, A_B @@ -901,7 +902,7 @@ describe('getSamplesSelectedStates', function () { ]); expect( getSamplesSelectedStates( - callNodeTable, + callNodeInfo, sampleCallNodes, sampleCallNodes, A_D @@ -915,7 +916,7 @@ describe('getSamplesSelectedStates', function () { ]); expect( getSamplesSelectedStates( - callNodeTable, + callNodeInfo, sampleCallNodes, sampleCallNodes, A_B_F @@ -929,7 +930,7 @@ describe('getSamplesSelectedStates', function () { ]); expect( getSamplesSelectedStates( - callNodeTable, + callNodeInfo, sampleCallNodes, sampleCallNodes, A_D_E @@ -1153,15 +1154,9 @@ describe('getNativeSymbolsForCallNode', function () { thread.funcTable, defaultCategory ); - const ab = getCallNodeIndexFromPath( - [funA, funB], - callNodeInfo.callNodeTable - ); + const ab = callNodeInfo.getCallNodeIndexFromPath([funA, funB]); expect(ab).not.toBeNull(); - const abc = getCallNodeIndexFromPath( - [funA, funB, funC], - callNodeInfo.callNodeTable - ); + const abc = callNodeInfo.getCallNodeIndexFromPath([funA, funB, funC]); expect(abc).not.toBeNull(); // Both the call path [funA, funB] and the call path [funA, funB, funC] end @@ -1201,7 +1196,7 @@ describe('getNativeSymbolsForCallNode', function () { ); const defaultCategory = categories.findIndex((c) => c.name === 'Other'); const callNodeInfo = getInvertedCallNodeInfo(thread, defaultCategory); - const c = getCallNodeIndexFromPath([funC], callNodeInfo.callNodeTable); + const c = callNodeInfo.getCallNodeIndexFromPath([funC]); expect(c).not.toBeNull(); // The call node for funC in the inverted thread has one sample where funC diff --git a/src/test/unit/profile-tree.test.js b/src/test/unit/profile-tree.test.js index 65041d4456..2c14121fa1 100644 --- a/src/test/unit/profile-tree.test.js +++ b/src/test/unit/profile-tree.test.js @@ -14,7 +14,6 @@ import { computeFlameGraphRows } from '../../profile-logic/flame-graph'; import { getCallNodeInfo, getInvertedCallNodeInfo, - getCallNodeIndexFromPath, getOriginAnnotationForFunc, filterThreadSamplesToRange, getSampleIndexToCallNodeIndex, @@ -75,7 +74,7 @@ describe('unfiltered call tree', function () { thread.samples, getSampleIndexToCallNodeIndex( thread.samples.stack, - callNodeInfo.stackIndexToCallNodeIndex + callNodeInfo.getStackIndexToCallNodeIndex() ), callNodeInfo, false @@ -110,26 +109,26 @@ describe('unfiltered call tree', function () { profile.meta.categories, 'Expected to find categories' ).findIndex((c) => c.name === 'Other'); - const { callNodeTable } = getCallNodeInfo( + const callNodeInfo = getCallNodeInfo( thread.stackTable, thread.frameTable, thread.funcTable, defaultCategory ); - const cnZ = getCallNodeIndexFromPath([Z], callNodeTable); - const cnZX = getCallNodeIndexFromPath([Z, X], callNodeTable); - const cnZXY = getCallNodeIndexFromPath([Z, X, Y], callNodeTable); - const cnZXW = getCallNodeIndexFromPath([Z, X, W], callNodeTable); - const cnG = getCallNodeIndexFromPath([G], callNodeTable); - const cnGH = getCallNodeIndexFromPath([G, H], callNodeTable); - const cnGHI = getCallNodeIndexFromPath([G, H, I], callNodeTable); - const cnGHJ = getCallNodeIndexFromPath([G, H, J], callNodeTable); - const cnK = getCallNodeIndexFromPath([K], callNodeTable); - const cnKM = getCallNodeIndexFromPath([K, M], callNodeTable); - const cnKN = getCallNodeIndexFromPath([K, N], callNodeTable); + const cnZ = callNodeInfo.getCallNodeIndexFromPath([Z]); + const cnZX = callNodeInfo.getCallNodeIndexFromPath([Z, X]); + const cnZXY = callNodeInfo.getCallNodeIndexFromPath([Z, X, Y]); + const cnZXW = callNodeInfo.getCallNodeIndexFromPath([Z, X, W]); + const cnG = callNodeInfo.getCallNodeIndexFromPath([G]); + const cnGH = callNodeInfo.getCallNodeIndexFromPath([G, H]); + const cnGHI = callNodeInfo.getCallNodeIndexFromPath([G, H, I]); + const cnGHJ = callNodeInfo.getCallNodeIndexFromPath([G, H, J]); + const cnK = callNodeInfo.getCallNodeIndexFromPath([K]); + const cnKM = callNodeInfo.getCallNodeIndexFromPath([K, M]); + const cnKN = callNodeInfo.getCallNodeIndexFromPath([K, N]); const rows = computeFlameGraphRows( - callNodeTable, + callNodeInfo.getCallNodeTable(), thread.funcTable, thread.stringTable ); @@ -373,7 +372,7 @@ describe('unfiltered call tree', function () { profile.meta.categories, 'Expected to find categories' ).findIndex((c) => c.name === 'Other'); - const { callNodeTable } = getCallNodeInfo( + const callNodeInfo = getCallNodeInfo( thread.stackTable, thread.frameTable, thread.funcTable, @@ -383,9 +382,7 @@ describe('unfiltered call tree', function () { // Helper to make the assertions a little less verbose. function checkStack(callNodePath, index, name) { it(`finds stack that ends in ${name}`, function () { - expect(getCallNodeIndexFromPath(callNodePath, callNodeTable)).toBe( - index - ); + expect(callNodeInfo.getCallNodeIndexFromPath(callNodePath)).toBe(index); }); } @@ -402,9 +399,9 @@ describe('unfiltered call tree', function () { checkStack([A, B, H, I], I, 'I'); it(`doesn't find a non-existent stack`, function () { - expect( - getCallNodeIndexFromPath([A, B, C, D, E, F, G], callNodeTable) - ).toBe(null); + expect(callNodeInfo.getCallNodeIndexFromPath([A, B, C, D, E, F, G])).toBe( + null + ); }); }); }); @@ -440,7 +437,7 @@ describe('inverted call tree', function () { thread.samples, getSampleIndexToCallNodeIndex( thread.samples.stack, - callNodeInfo.stackIndexToCallNodeIndex + callNodeInfo.getStackIndexToCallNodeIndex() ), callNodeInfo, false @@ -478,7 +475,7 @@ describe('inverted call tree', function () { thread.samples, getSampleIndexToCallNodeIndex( thread.samples.stack, - invertedCallNodeInfo.stackIndexToCallNodeIndex + invertedCallNodeInfo.getStackIndexToCallNodeIndex() ), invertedCallNodeInfo, true @@ -628,7 +625,7 @@ describe('diffing trees', function () { thread.samples, getSampleIndexToCallNodeIndex( thread.samples.stack, - callNodeInfo.stackIndexToCallNodeIndex + callNodeInfo.getStackIndexToCallNodeIndex() ), callNodeInfo, false diff --git a/src/types/actions.js b/src/types/actions.js index 7dbe06b600..b9c81364d9 100644 --- a/src/types/actions.js +++ b/src/types/actions.js @@ -16,7 +16,7 @@ import type { } from './profile'; import type { CallNodePath, - CallNodeTable, + CallNodeInfo, GlobalTrack, LocalTrack, TrackIndex, @@ -478,7 +478,7 @@ type UrlStateAction = +threadsKey: ThreadsKey, +transform: Transform, +transformedThread: Thread, - +callNodeTable: CallNodeTable, + +callNodeInfo: CallNodeInfo, |} | {| +type: 'POP_TRANSFORMS_FROM_STACK', diff --git a/src/types/profile-derived.js b/src/types/profile-derived.js index 2be2336875..841af62a39 100644 --- a/src/types/profile-derived.js +++ b/src/types/profile-derived.js @@ -122,12 +122,20 @@ export type CallNodeTable = { }; /** - * Both the callNodeTable and a map that converts an IndexIntoStackTable - * into an IndexIntoCallNodeTable. + * Wraps the call node table and provides associated functionality. */ -export type CallNodeInfo = { - callNodeTable: CallNodeTable, - // IndexIntoStackTable -> IndexIntoCallNodeTable | -1 +export interface CallNodeInfo { + // If true, call node indexes describe nodes in the inverted call tree. + isInverted(): boolean; + + // Returns the call node table. If isInverted() is true, this is an inverted + // call node table, otherwise this is the non-inverted call node table. + getCallNodeTable(): CallNodeTable; + + // Returns a mapping from the stack table to the call node table. + // The Int32Array should be used as if it were a + // Map. + // // If this CallNodeInfo is for the non-inverted tree, this maps the stack index // to its corresponding call node index, and all entries are >= 0. // If this CallNodeInfo is for the inverted tree, this maps the non-inverted @@ -140,11 +148,27 @@ export type CallNodeInfo = { // of the A -> B -> C -> D stack and no sample / marker / allocation has // A -> B -> C as its stack, then there is no need to have a call node // C <- B <- A in the inverted call node table. - stackIndexToCallNodeIndex: Int32Array, - // Whether the call node table in this call node info describes the inverted - // call tree. - isInverted: boolean, -}; + getStackIndexToCallNodeIndex(): Int32Array; + + // Converts a call node index into a call node path. + getCallNodePathFromIndex( + callNodeIndex: IndexIntoCallNodeTable | null + ): CallNodePath; + + // Converts a call node path into a call node index. + getCallNodeIndexFromPath( + callNodePath: CallNodePath + ): IndexIntoCallNodeTable | null; + + // Returns the call node index that matches the function `func` and whose + // parent's index is `parent`. If `parent` is -1, this returns the index of + // the root node with function `func`. + // Returns null if the described call node doesn't exist. + getCallNodeIndexFromParentAndFunc( + parent: IndexIntoCallNodeTable | -1, + func: IndexIntoFuncTable + ): IndexIntoCallNodeTable | null; +} export type LineNumber = number;