From 8ee921addae3d24e316d3707bb203df49db32183 Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Sat, 21 Oct 2023 22:20:07 -0400 Subject: [PATCH] Move call node path/index conversion functions into CallNodeInfo. This makes CallNodeInfo an interface, and puts the conversion functions between call node indexes and call node paths onto this new interface. It also provides accessor methods for the traditional members. This reduces the number of direct uses of the call node table. It will allow us to have two implementations in the future: One for the non-inverted call tree, and one for the inverted call tree. --- src/actions/profile-view.js | 28 +- src/components/calltree/CallTree.js | 10 +- src/components/flame-graph/Canvas.js | 12 +- src/components/flame-graph/FlameGraph.js | 35 +-- src/components/shared/CallNodeContextMenu.js | 29 +- src/components/shared/thread/StackGraph.js | 6 +- src/components/stack-chart/Canvas.js | 10 +- src/components/stack-chart/index.js | 16 +- src/components/tooltip/CallNode.js | 3 +- src/profile-logic/address-timings.js | 10 +- src/profile-logic/call-node-info.js | 180 +++++++++++++ src/profile-logic/call-tree.js | 14 +- src/profile-logic/line-timings.js | 10 +- src/profile-logic/profile-data.js | 249 +++++------------- src/profile-logic/stack-timing.js | 2 +- src/profile-logic/transforms.js | 15 +- src/reducers/profile-view.js | 7 +- src/selectors/per-thread/index.js | 4 +- src/selectors/per-thread/stack-sample.js | 37 ++- src/test/fixtures/utils.js | 2 +- .../__snapshots__/profile-view.test.js.snap | 18 +- src/test/store/actions.test.js | 6 +- src/test/store/bottom-box.test.js | 19 +- src/test/store/profile-view.test.js | 6 +- src/test/unit/address-timings.test.js | 3 +- src/test/unit/line-timings.test.js | 3 +- src/test/unit/profile-data.test.js | 59 ++--- src/test/unit/profile-tree.test.js | 47 ++-- src/types/actions.js | 4 +- src/types/profile-derived.js | 44 +++- 30 files changed, 464 insertions(+), 424 deletions(-) create mode 100644 src/profile-logic/call-node-info.js 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;