diff --git a/src/actions/profile-view.js b/src/actions/profile-view.js index 76e9c32098..65ad672115 100644 --- a/src/actions/profile-view.js +++ b/src/actions/profile-view.js @@ -148,77 +148,64 @@ export function changeRightClickedCallNode( } /** - * Given a threadIndex and a sampleIndex, select the call node at the top ("leaf") - * of that sample's stack. + * Given a threadIndex and a sampleIndex, select the call node which carries the + * sample's self time. In the inverted tree, this will be a root node. */ -export function selectLeafCallNode( +export function selectSelfCallNode( threadsKey: ThreadsKey, sampleIndex: IndexIntoSamplesTable | null ): ThunkAction { return (dispatch, getState) => { const threadSelectors = getThreadSelectorsFromThreadsKey(threadsKey); const sampleCallNodes = - threadSelectors.getSampleIndexToCallNodeIndexForFilteredThread( + threadSelectors.getSampleIndexToNonInvertedCallNodeIndexForFilteredThread( getState() ); - const callNodeInfo = threadSelectors.getCallNodeInfo(getState()); - let newSelectedCallNode = -1; if ( - sampleIndex !== null && - sampleIndex >= 0 && - sampleIndex < sampleCallNodes.length + sampleIndex === null || + sampleIndex < 0 || + sampleIndex >= sampleCallNodes.length ) { - newSelectedCallNode = sampleCallNodes[sampleIndex] ?? -1; - } - - dispatch( - changeSelectedCallNode( - threadsKey, - callNodeInfo.getCallNodePathFromIndex(newSelectedCallNode) - ) - ); - }; -} - -/** - * Given a threadIndex and a sampleIndex, select the call node at the bottom ("root") - * of that sample's stack. - */ -export function selectRootCallNode( - threadsKey: ThreadsKey, - sampleIndex: IndexIntoSamplesTable | null -): ThunkAction { - return (dispatch, getState) => { - const threadSelectors = getThreadSelectorsFromThreadsKey(threadsKey); - const filteredThread = threadSelectors.getFilteredThread(getState()); - const callNodeInfo = threadSelectors.getCallNodeInfo(getState()); - - if (sampleIndex === null) { dispatch(changeSelectedCallNode(threadsKey, [])); return; } - const newSelectedStack = filteredThread.samples.stack[sampleIndex]; - if (newSelectedStack === null || newSelectedStack === undefined) { + + const nonInvertedSelfCallNode = sampleCallNodes[sampleIndex]; + if (nonInvertedSelfCallNode === null) { dispatch(changeSelectedCallNode(threadsKey, [])); return; } - const stackIndexToCallNodeIndex = - callNodeInfo.getStackIndexToCallNodeIndex(); - const newSelectedCallNode = stackIndexToCallNodeIndex[newSelectedStack]; - const selectedCallNodePath = - callNodeInfo.getCallNodePathFromIndex(newSelectedCallNode); - const rootCallNodePath = [selectedCallNodePath[0]]; + const callNodeInfo = threadSelectors.getCallNodeInfo(getState()); - dispatch( - changeSelectedCallNode( - threadsKey, - rootCallNodePath, - { source: 'auto' }, - selectedCallNodePath - ) - ); + // Compute the call path based on the non-inverted call node table. + // We're not calling callNodeInfo.getCallNodePathFromIndex here because we + // only have a non-inverted call node index, which wouldn't be accepted by + // the inverted call node info. + const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + const callNodePath = []; + let cni = nonInvertedSelfCallNode; + while (cni !== -1) { + callNodePath.push(callNodeTable.func[cni]); + cni = callNodeTable.prefix[cni]; + } + + if (callNodeInfo.isInverted()) { + // In the inverted tree, we want to select the inverted tree root node + // with the "self" function, and also expand the path to the non-inverted root. + dispatch( + changeSelectedCallNode( + threadsKey, + callNodePath.slice(0, 1), // Select a root node + { source: 'auto' }, + callNodePath // Expand the full path + ) + ); + } else { + // In the non-inverted tree, we want to select the self node. + dispatch(changeSelectedCallNode(threadsKey, callNodePath.reverse())); + } }; } diff --git a/src/components/flame-graph/Canvas.js b/src/components/flame-graph/Canvas.js index d730d71313..b89490658e 100644 --- a/src/components/flame-graph/Canvas.js +++ b/src/components/flame-graph/Canvas.js @@ -163,7 +163,7 @@ class FlameGraphCanvasImpl extends React.PureComponent { return; } - const callNodeTable = callNodeInfo.getCallNodeTable(); + const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); const depth = callNodeTable.depth[selectedCallNodeIndex]; const y = (maxStackDepthPlusOne - depth - 1) * ROW_HEIGHT; @@ -237,7 +237,7 @@ class FlameGraphCanvasImpl extends React.PureComponent { fastFillStyle.set('#ffffff'); ctx.fillRect(0, 0, deviceContainerWidth, deviceContainerHeight); - const callNodeTable = callNodeInfo.getCallNodeTable(); + const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); const startDepth = Math.floor( maxStackDepthPlusOne - viewportBottom / stackFrameHeight @@ -367,7 +367,6 @@ class FlameGraphCanvasImpl extends React.PureComponent { shouldDisplayTooltips, categories, interval, - isInverted, callTreeSummaryStrategy, innerWindowIDToPageMap, weightType, @@ -426,7 +425,6 @@ class FlameGraphCanvasImpl extends React.PureComponent { callNodeIndex, callNodeInfo, interval, - isInverted, thread, unfilteredThread, sampleIndexOffset, diff --git a/src/components/flame-graph/FlameGraph.js b/src/components/flame-graph/FlameGraph.js index f80531e8fd..2d221359a9 100644 --- a/src/components/flame-graph/FlameGraph.js +++ b/src/components/flame-graph/FlameGraph.js @@ -163,7 +163,7 @@ class FlameGraphImpl extends React.PureComponent { _wideEnough = (callNodeIndex: IndexIntoCallNodeTable): boolean => { const { flameGraphTiming, callNodeInfo } = this.props; - const callNodeTable = callNodeInfo.getCallNodeTable(); + const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); const depth = callNodeTable.depth[callNodeIndex]; const row = flameGraphTiming[depth]; const columnIndex = row.callNode.indexOf(callNodeIndex); @@ -188,7 +188,7 @@ class FlameGraphImpl extends React.PureComponent { let callNodeIndex = startingCallNodeIndex; - const callNodeTable = callNodeInfo.getCallNodeTable(); + const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); const depth = callNodeTable.depth[callNodeIndex]; const row = flameGraphTiming[depth]; let columnIndex = row.callNode.indexOf(callNodeIndex); @@ -219,7 +219,7 @@ class FlameGraphImpl extends React.PureComponent { changeSelectedCallNode, handleCallNodeTransformShortcut, } = this.props; - const callNodeTable = callNodeInfo.getCallNodeTable(); + const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); if ( // Please do not forget to update the switch/case below if changing the array to allow more keys. @@ -305,7 +305,7 @@ class FlameGraphImpl extends React.PureComponent { if (document.activeElement === this._viewport) { event.preventDefault(); const { callNodeInfo, selectedCallNodeIndex, thread } = this.props; - const callNodeTable = callNodeInfo.getCallNodeTable(); + const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); if (selectedCallNodeIndex !== null) { const funcIndex = callNodeTable.func[selectedCallNodeIndex]; const funcName = thread.stringTable.getString( diff --git a/src/components/shared/thread/StackGraph.js b/src/components/shared/thread/StackGraph.js index b605584edd..f56efd68f7 100644 --- a/src/components/shared/thread/StackGraph.js +++ b/src/components/shared/thread/StackGraph.js @@ -21,7 +21,7 @@ type Props = {| +className: string, +thread: Thread, +samplesSelectedStates: null | SelectedState[], - +sampleCallNodes: Array, + +sampleNonInvertedCallNodes: Array, +interval: Milliseconds, +rangeStart: Milliseconds, +rangeEnd: Milliseconds, @@ -38,13 +38,14 @@ type Props = {| export class ThreadStackGraph extends PureComponent { _heightFunction = (sampleIndex: IndexIntoSamplesTable): number | null => { - const { callNodeInfo, sampleCallNodes } = this.props; - const callNodeIndex = sampleCallNodes[sampleIndex]; - if (callNodeIndex === null) { + const { callNodeInfo, sampleNonInvertedCallNodes } = this.props; + const nonInvertedCallNodeIndex = sampleNonInvertedCallNodes[sampleIndex]; + if (nonInvertedCallNodeIndex === null) { return null; } - const callNodeTable = callNodeInfo.getCallNodeTable(); - return callNodeTable.depth[callNodeIndex]; + + const nonInvertedCallNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + return nonInvertedCallNodeTable.depth[nonInvertedCallNodeIndex]; }; render() { @@ -60,12 +61,12 @@ export class ThreadStackGraph extends PureComponent { trackName, onSampleClick, } = this.props; - const callNodeTable = callNodeInfo.getCallNodeTable(); + const nonInvertedCallNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); let maxDepth = 0; - for (let i = 0; i < callNodeTable.depth.length; i++) { - if (callNodeTable.depth[i] > maxDepth) { - maxDepth = callNodeTable.depth[i]; + for (let i = 0; i < nonInvertedCallNodeTable.depth.length; i++) { + if (nonInvertedCallNodeTable.depth[i] > maxDepth) { + maxDepth = nonInvertedCallNodeTable.depth[i]; } } diff --git a/src/components/timeline/TrackThread.js b/src/components/timeline/TrackThread.js index 9ed1cdc09c..0ad7c5f392 100644 --- a/src/components/timeline/TrackThread.js +++ b/src/components/timeline/TrackThread.js @@ -22,7 +22,6 @@ import { getCategories, getSelectedThreadIndexes, getTimelineType, - getInvertCallstack, getThreadSelectorsFromThreadsKey, getMaxThreadCPUDeltaPerMs, getIsExperimentalCPUGraphsEnabled, @@ -38,8 +37,7 @@ import { updatePreviewSelection, changeSelectedCallNode, focusCallTree, - selectLeafCallNode, - selectRootCallNode, + selectSelfCallNode, } from 'firefox-profiler/actions/profile-view'; import { reportTrackThreadHeight } from 'firefox-profiler/actions/app'; import { EmptyThreadIndicator } from './EmptyThreadIndicator'; @@ -85,8 +83,7 @@ type StateProps = {| +timelineType: TimelineType, +hasFileIoMarkers: boolean, +samplesSelectedStates: null | SelectedState[], - +sampleCallNodes: Array, - +invertCallstack: boolean, + +sampleNonInvertedCallNodes: Array, +treeOrderSampleComparator: ( IndexIntoSamplesTable, IndexIntoSamplesTable @@ -103,8 +100,7 @@ type DispatchProps = {| +updatePreviewSelection: typeof updatePreviewSelection, +changeSelectedCallNode: typeof changeSelectedCallNode, +focusCallTree: typeof focusCallTree, - +selectLeafCallNode: typeof selectLeafCallNode, - +selectRootCallNode: typeof selectRootCallNode, + +selectSelfCallNode: typeof selectSelfCallNode, +reportTrackThreadHeight: typeof reportTrackThreadHeight, |}; @@ -130,23 +126,15 @@ class TimelineTrackThreadImpl extends PureComponent { const { threadsKey, - selectLeafCallNode, - selectRootCallNode, + selectSelfCallNode, focusCallTree, - invertCallstack, selectedThreadIndexes, callTreeVisible, } = this.props; // Sample clicking only works for one thread. See issue #2709 if (selectedThreadIndexes.size === 1) { - if (invertCallstack) { - // When we're displaying the inverted call stack, the "leaf" call node we're - // interested in is actually displayed as the "root" of the tree. - selectRootCallNode(threadsKey, sampleIndex); - } else { - selectLeafCallNode(threadsKey, sampleIndex); - } + selectSelfCallNode(threadsKey, sampleIndex); if (sampleIndex !== null && callTreeVisible) { // If the user clicked outside of the activity graph (sampleIndex === null), @@ -198,7 +186,7 @@ class TimelineTrackThreadImpl extends PureComponent { timelineType, hasFileIoMarkers, showMemoryMarkers, - sampleCallNodes, + sampleNonInvertedCallNodes, samplesSelectedStates, treeOrderSampleComparator, trackType, @@ -317,7 +305,7 @@ class TimelineTrackThreadImpl extends PureComponent { rangeStart={rangeStart} rangeEnd={rangeEnd} callNodeInfo={callNodeInfo} - sampleCallNodes={sampleCallNodes} + sampleNonInvertedCallNodes={sampleNonInvertedCallNodes} samplesSelectedStates={samplesSelectedStates} categories={categories} onSampleClick={this._onSampleClick} @@ -352,13 +340,14 @@ export const TimelineTrackThread = explicitConnect< fullThread.samples.threadCPUDelta !== undefined; return { - invertCallstack: getInvertCallstack(state), fullThread, filteredThread: selectors.getFilteredThread(state), rangeFilteredThread: selectors.getRangeFilteredThread(state), callNodeInfo: selectors.getCallNodeInfo(state), - sampleCallNodes: - selectors.getSampleIndexToCallNodeIndexForFilteredThread(state), + sampleNonInvertedCallNodes: + selectors.getSampleIndexToNonInvertedCallNodeIndexForFilteredThread( + state + ), unfilteredSamplesRange: selectors.unfilteredSamplesRange(state), interval: getProfileInterval(state), rangeStart: committedRange.start, @@ -385,8 +374,7 @@ export const TimelineTrackThread = explicitConnect< updatePreviewSelection, changeSelectedCallNode, focusCallTree, - selectLeafCallNode, - selectRootCallNode, + selectSelfCallNode, reportTrackThreadHeight, }, component: withSize(TimelineTrackThreadImpl), diff --git a/src/profile-logic/address-timings.js b/src/profile-logic/address-timings.js index 3ff041777b..00080a3f3d 100644 --- a/src/profile-logic/address-timings.js +++ b/src/profile-logic/address-timings.js @@ -350,7 +350,8 @@ export function getStackAddressInfoForCallNodeNonInverted( callNodeInfo: CallNodeInfo, nativeSymbol: IndexIntoNativeSymbolTable ): StackAddressInfo { - const stackIndexToCallNodeIndex = callNodeInfo.getStackIndexToCallNodeIndex(); + const stackIndexToCallNodeIndex = + callNodeInfo.getStackIndexToNonInvertedCallNodeIndex(); // "self address" == "the address which a stack's self time is contributed to" const callNodeSelfAddressForAllStacks = []; diff --git a/src/profile-logic/call-node-info.js b/src/profile-logic/call-node-info.js index 3d85522a0a..cf28cd6747 100644 --- a/src/profile-logic/call-node-info.js +++ b/src/profile-logic/call-node-info.js @@ -21,12 +21,23 @@ export class CallNodeInfoImpl implements CallNodeInfo { // If true, call node indexes describe nodes in the inverted call tree. _isInverted: boolean; - // The call node table. + // The call node table. This is either the inverted or the non-inverted call + // node table, depending on _isInverted. _callNodeTable: CallNodeTable; - // The mapping of stack index to corresponding call node index. + // The non-inverted call node table, regardless of _isInverted. + _nonInvertedCallNodeTable: CallNodeTable; + + // The mapping of stack index to corresponding call node index. This maps to + // either the inverted or the non-inverted call node table, depending on + // _isInverted. _stackIndexToCallNodeIndex: Int32Array; + // The mapping of stack index to corresponding non-inverted call node index. + // This always maps to the non-inverted call node table, regardless of + // _isInverted. + _stackIndexToNonInvertedCallNodeIndex: 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. @@ -34,11 +45,16 @@ export class CallNodeInfoImpl implements CallNodeInfo { constructor( callNodeTable: CallNodeTable, + nonInvertedCallNodeTable: CallNodeTable, stackIndexToCallNodeIndex: Int32Array, + stackIndexToNonInvertedCallNodeIndex: Int32Array, isInverted: boolean ) { this._callNodeTable = callNodeTable; + this._nonInvertedCallNodeTable = nonInvertedCallNodeTable; this._stackIndexToCallNodeIndex = stackIndexToCallNodeIndex; + this._stackIndexToNonInvertedCallNodeIndex = + stackIndexToNonInvertedCallNodeIndex; this._isInverted = isInverted; } @@ -54,6 +70,14 @@ export class CallNodeInfoImpl implements CallNodeInfo { return this._stackIndexToCallNodeIndex; } + getNonInvertedCallNodeTable(): CallNodeTable { + return this._nonInvertedCallNodeTable; + } + + getStackIndexToNonInvertedCallNodeIndex(): Int32Array { + return this._stackIndexToNonInvertedCallNodeIndex; + } + getCallNodePathFromIndex( callNodeIndex: IndexIntoCallNodeTable | null ): CallNodePath { diff --git a/src/profile-logic/line-timings.js b/src/profile-logic/line-timings.js index 91ab961117..0d0065f25c 100644 --- a/src/profile-logic/line-timings.js +++ b/src/profile-logic/line-timings.js @@ -209,7 +209,8 @@ export function getStackLineInfoForCallNodeNonInverted( callNodeIndex: IndexIntoCallNodeTable, callNodeInfo: CallNodeInfo ): StackLineInfo { - const stackIndexToCallNodeIndex = callNodeInfo.getStackIndexToCallNodeIndex(); + const stackIndexToCallNodeIndex = + callNodeInfo.getStackIndexToNonInvertedCallNodeIndex(); // "self line" == "the line which a stack's self time is contributed to" const callNodeSelfLineForAllStacks = []; diff --git a/src/profile-logic/profile-data.js b/src/profile-logic/profile-data.js index 6a8226a40f..42c287c43c 100644 --- a/src/profile-logic/profile-data.js +++ b/src/profile-logic/profile-data.js @@ -105,7 +105,13 @@ export function getCallNodeInfo( funcTable, defaultCategory ); - return new CallNodeInfoImpl(callNodeTable, stackIndexToCallNodeIndex, false); + return new CallNodeInfoImpl( + callNodeTable, + callNodeTable, + stackIndexToCallNodeIndex, + stackIndexToCallNodeIndex, + false + ); } type CallNodeTableAndStackMap = { @@ -423,6 +429,8 @@ function _createCallNodeTableFromUnorderedComponents( */ export function getInvertedCallNodeInfo( thread: Thread, + nonInvertedCallNodeTable: CallNodeTable, + stackIndexToNonInvertedCallNodeIndex: Int32Array, defaultCategory: IndexIntoCategoryList ): CallNodeInfo { // We compute an inverted stack table, but we don't let it escape this function. @@ -468,7 +476,9 @@ export function getInvertedCallNodeInfo( } return new CallNodeInfoImpl( callNodeTable, + nonInvertedCallNodeTable, nonInvertedStackIndexToCallNodeIndex, + stackIndexToNonInvertedCallNodeIndex, true ); } @@ -776,7 +786,6 @@ export function getTimingsForPath( needlePath: CallNodePath, callNodeInfo: CallNodeInfo, interval: Milliseconds, - isInvertedTree: boolean, thread: Thread, unfilteredThread: Thread, sampleIndexOffset: number, @@ -789,7 +798,6 @@ export function getTimingsForPath( callNodeInfo.getCallNodeIndexFromPath(needlePath), callNodeInfo, interval, - isInvertedTree, thread, unfilteredThread, sampleIndexOffset, @@ -812,7 +820,6 @@ export function getTimingsForCallNodeIndex( needleNodeIndex: IndexIntoCallNodeTable | null, callNodeInfo: CallNodeInfo, interval: Milliseconds, - isInvertedTree: boolean, thread: Thread, unfilteredThread: Thread, sampleIndexOffset: number, @@ -1009,6 +1016,7 @@ export function getTimingsForCallNodeIndex( const needleDescendantsEndIndex = callNodeTable.subtreeRangeEnd[needleNodeIndex]; + const isInvertedTree = callNodeInfo.isInverted(); const needleNodeIsRootOfInvertedTree = isInvertedTree && callNodeTable.prefix[needleNodeIndex] === -1; @@ -2011,8 +2019,10 @@ 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 = callNodeInfo.getCallNodeTable(); - const stackIndexToCallNodeIndex = callNodeInfo.getStackIndexToCallNodeIndex(); + const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); + // TODO: Use sampleCallNodes instead + const stackIndexToCallNodeIndex = + callNodeInfo.getStackIndexToNonInvertedCallNodeIndex(); for (let sampleIndex = 0; sampleIndex < samples.length; sampleIndex++) { const stackIndex = samples.stack[sampleIndex]; if (stackIndex === null) { @@ -3548,7 +3558,8 @@ export function getNativeSymbolsForCallNodeNonInverted( stackTable: StackTable, frameTable: FrameTable ): IndexIntoNativeSymbolTable[] { - const stackIndexToCallNodeIndex = callNodeInfo.getStackIndexToCallNodeIndex(); + const stackIndexToCallNodeIndex = + callNodeInfo.getStackIndexToNonInvertedCallNodeIndex(); const set = new Set(); for (let stackIndex = 0; stackIndex < stackTable.length; stackIndex++) { if (stackIndexToCallNodeIndex[stackIndex] === callNodeIndex) { diff --git a/src/selectors/per-thread/index.js b/src/selectors/per-thread/index.js index fa734d9565..91ce053c85 100644 --- a/src/selectors/per-thread/index.js +++ b/src/selectors/per-thread/index.js @@ -263,7 +263,6 @@ export const selectedNodeSelectors: NodeSelectors = (() => { selectedThreadSelectors.getSelectedCallNodePath, selectedThreadSelectors.getCallNodeInfo, ProfileSelectors.getProfileInterval, - UrlState.getInvertCallstack, selectedThreadSelectors.getPreviewFilteredThread, selectedThreadSelectors.getThread, selectedThreadSelectors.getSampleIndexOffsetFromPreviewRange, diff --git a/src/selectors/per-thread/stack-sample.js b/src/selectors/per-thread/stack-sample.js index d10525c03e..bd897ea1ad 100644 --- a/src/selectors/per-thread/stack-sample.js +++ b/src/selectors/per-thread/stack-sample.js @@ -116,8 +116,16 @@ export function getStackAndSampleSelectorsPerThread( const _getInvertedCallNodeInfo: Selector = createSelectorWithTwoCacheSlots( threadSelectors.getFilteredThread, + _getNonInvertedCallNodeInfo, ProfileSelectors.getDefaultCategory, - ProfileData.getInvertedCallNodeInfo + (thread, nonInvertedCallNodeInfo, defaultCategory) => { + return ProfileData.getInvertedCallNodeInfo( + thread, + nonInvertedCallNodeInfo.getNonInvertedCallNodeTable(), + nonInvertedCallNodeInfo.getStackIndexToNonInvertedCallNodeIndex(), + defaultCategory + ); + } ); const getCallNodeInfo: Selector = (state) => { @@ -243,6 +251,18 @@ export function getStackAndSampleSelectorsPerThread( ProfileData.getSampleIndexToCallNodeIndex ); + const getSampleIndexToNonInvertedCallNodeIndexForFilteredThread: Selector< + Array, + > = createSelector( + (state) => threadSelectors.getFilteredThread(state).samples.stack, + (state) => getCallNodeInfo(state).getStackIndexToNonInvertedCallNodeIndex(), + (filteredThreadSampleStacks, stackIndexToNonInvertedCallNodeIndex) => + ProfileData.getSampleIndexToCallNodeIndex( + filteredThreadSampleStacks, + stackIndexToNonInvertedCallNodeIndex + ) + ); + const getSampleIndexToCallNodeIndexForTabFilteredThread: Selector< Array, > = createSelector( @@ -397,7 +417,7 @@ export function getStackAndSampleSelectorsPerThread( ); const getFlameGraphRows: Selector = createSelector( - (state) => getCallNodeInfo(state).getCallNodeTable(), + (state) => getCallNodeInfo(state).getNonInvertedCallNodeTable(), (state) => threadSelectors.getFilteredThread(state).funcTable, (state) => threadSelectors.getFilteredThread(state).stringTable, FlameGraph.computeFlameGraphRows @@ -406,7 +426,7 @@ export function getStackAndSampleSelectorsPerThread( const getFlameGraphTiming: Selector = createSelector( getFlameGraphRows, - (state) => getCallNodeInfo(state).getCallNodeTable(), + (state) => getCallNodeInfo(state).getNonInvertedCallNodeTable(), getCallTreeTimings, FlameGraph.getFlameGraphTiming ); @@ -441,6 +461,7 @@ export function getStackAndSampleSelectorsPerThread( getExpandedCallNodePaths, getExpandedCallNodeIndexes, getSampleIndexToCallNodeIndexForFilteredThread, + getSampleIndexToNonInvertedCallNodeIndexForFilteredThread, getSampleIndexToCallNodeIndexForTabFilteredThread, getSamplesSelectedStatesInFilteredThread, getTreeOrderComparatorInFilteredThread, diff --git a/src/test/store/__snapshots__/profile-view.test.js.snap b/src/test/store/__snapshots__/profile-view.test.js.snap index a2a9958e8e..e72d94552a 100644 --- a/src/test/store/__snapshots__/profile-view.test.js.snap +++ b/src/test/store/__snapshots__/profile-view.test.js.snap @@ -2288,6 +2288,109 @@ CallNodeInfoImpl { ], }, "_isInverted": false, + "_nonInvertedCallNodeTable": Object { + "category": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + "depth": Array [ + 0, + 1, + 2, + 2, + 3, + 2, + 3, + 2, + 3, + ], + "func": Int32Array [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + ], + "innerWindowID": Float64Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 2, + 0, + 0, + ], + "length": 9, + "maxDepth": 3, + "nextSibling": Int32Array [ + -1, + -1, + 3, + 5, + -1, + 7, + -1, + -1, + -1, + ], + "prefix": Int32Array [ + -1, + 0, + 1, + 1, + 3, + 1, + 5, + 1, + 7, + ], + "sourceFramesInlinedIntoSymbol": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], + "subcategory": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + "subtreeRangeEnd": Uint32Array [ + 9, + 9, + 3, + 5, + 5, + 7, + 7, + 9, + 9, + ], + }, "_stackIndexToCallNodeIndex": Int32Array [ 0, 1, @@ -2299,6 +2402,17 @@ CallNodeInfoImpl { 7, 8, ], + "_stackIndexToNonInvertedCallNodeIndex": Int32Array [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + ], } `; @@ -2410,6 +2524,109 @@ CallTree { ], }, "_isInverted": false, + "_nonInvertedCallNodeTable": Object { + "category": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + "depth": Array [ + 0, + 1, + 2, + 2, + 3, + 2, + 3, + 2, + 3, + ], + "func": Int32Array [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + ], + "innerWindowID": Float64Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 2, + 0, + 0, + ], + "length": 9, + "maxDepth": 3, + "nextSibling": Int32Array [ + -1, + -1, + 3, + 5, + -1, + 7, + -1, + -1, + -1, + ], + "prefix": Int32Array [ + -1, + 0, + 1, + 1, + 3, + 1, + 5, + 1, + 7, + ], + "sourceFramesInlinedIntoSymbol": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], + "subcategory": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + "subtreeRangeEnd": Uint32Array [ + 9, + 9, + 3, + 5, + 5, + 7, + 7, + 9, + 9, + ], + }, "_stackIndexToCallNodeIndex": Int32Array [ 0, 1, @@ -2421,6 +2638,17 @@ CallTree { 7, 8, ], + "_stackIndexToNonInvertedCallNodeIndex": Int32Array [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + ], }, "_callNodeTable": Object { "category": Int32Array [ @@ -2703,6 +2931,109 @@ CallTree { ], }, "_isInverted": false, + "_nonInvertedCallNodeTable": Object { + "category": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + "depth": Array [ + 0, + 1, + 2, + 2, + 3, + 2, + 3, + 2, + 3, + ], + "func": Int32Array [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + ], + "innerWindowID": Float64Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 2, + 0, + 0, + ], + "length": 9, + "maxDepth": 3, + "nextSibling": Int32Array [ + -1, + -1, + 3, + 5, + -1, + 7, + -1, + -1, + -1, + ], + "prefix": Int32Array [ + -1, + 0, + 1, + 1, + 3, + 1, + 5, + 1, + 7, + ], + "sourceFramesInlinedIntoSymbol": Array [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + ], + "subcategory": Int32Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ], + "subtreeRangeEnd": Uint32Array [ + 9, + 9, + 3, + 5, + 5, + 7, + 7, + 9, + 9, + ], + }, "_stackIndexToCallNodeIndex": Int32Array [ 0, 1, @@ -2714,6 +3045,17 @@ CallTree { 7, 8, ], + "_stackIndexToNonInvertedCallNodeIndex": Int32Array [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + ], }, "_callNodeTable": Object { "category": Int32Array [ diff --git a/src/test/store/actions.test.js b/src/test/store/actions.test.js index cddcf82e47..3e8fe7d1eb 100644 --- a/src/test/store/actions.test.js +++ b/src/test/store/actions.test.js @@ -149,7 +149,7 @@ describe('selectors/getFlameGraphTiming', function () { const callNodeInfo = selectedThreadSelectors.getCallNodeInfo( store.getState() ); - const callNodeTable = callNodeInfo.getCallNodeTable(); + const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); const flameGraphTiming = selectedThreadSelectors.getFlameGraphTiming( store.getState() ); @@ -180,7 +180,7 @@ describe('selectors/getFlameGraphTiming', function () { const callNodeInfo = selectedThreadSelectors.getCallNodeInfo( store.getState() ); - const callNodeTable = callNodeInfo.getCallNodeTable(); + const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); const flameGraphTiming = selectedThreadSelectors.getFlameGraphTiming( store.getState() ); diff --git a/src/test/unit/address-timings.test.js b/src/test/unit/address-timings.test.js index 101da31661..ecf594a7c6 100644 --- a/src/test/unit/address-timings.test.js +++ b/src/test/unit/address-timings.test.js @@ -158,9 +158,20 @@ describe('getAddressTimings for getStackAddressInfoForCallNode', function () { isInverted: boolean ) { const { stackTable, frameTable, funcTable, samples } = thread; + const nonInvertedCallNodeInfo = getCallNodeInfo( + stackTable, + frameTable, + funcTable, + defaultCat + ); const callNodeInfo = isInverted - ? getInvertedCallNodeInfo(thread, defaultCat) - : getCallNodeInfo(stackTable, frameTable, funcTable, defaultCat); + ? getInvertedCallNodeInfo( + thread, + nonInvertedCallNodeInfo.getNonInvertedCallNodeTable(), + nonInvertedCallNodeInfo.getStackIndexToNonInvertedCallNodeIndex(), + defaultCat + ) + : nonInvertedCallNodeInfo; const callNodeIndex = ensureExists( callNodeInfo.getCallNodeIndexFromPath(callNodePath), 'invalid call node path' diff --git a/src/test/unit/line-timings.test.js b/src/test/unit/line-timings.test.js index 63ef358c6c..aec1d1ade2 100644 --- a/src/test/unit/line-timings.test.js +++ b/src/test/unit/line-timings.test.js @@ -121,9 +121,20 @@ describe('getLineTimings for getStackLineInfoForCallNode', function () { isInverted: boolean ) { const { stackTable, frameTable, funcTable, samples } = thread; + const nonInvertedCallNodeInfo = getCallNodeInfo( + stackTable, + frameTable, + funcTable, + defaultCat + ); const callNodeInfo = isInverted - ? getInvertedCallNodeInfo(thread, defaultCat) - : getCallNodeInfo(stackTable, frameTable, funcTable, defaultCat); + ? getInvertedCallNodeInfo( + thread, + nonInvertedCallNodeInfo.getNonInvertedCallNodeTable(), + nonInvertedCallNodeInfo.getStackIndexToNonInvertedCallNodeIndex(), + defaultCat + ) + : nonInvertedCallNodeInfo; const callNodeIndex = ensureExists( callNodeInfo.getCallNodeIndexFromPath(callNodePath), 'invalid call node path' diff --git a/src/test/unit/profile-data.test.js b/src/test/unit/profile-data.test.js index 32a7f67cd9..5eff9f333f 100644 --- a/src/test/unit/profile-data.test.js +++ b/src/test/unit/profile-data.test.js @@ -450,7 +450,7 @@ describe('profile-data', function () { thread.funcTable, defaultCategory ); - const callNodeTable = callNodeInfo.getCallNodeTable(); + const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); it('should create one callNode per original stack', function () { // After nudgeReturnAddresses, the stack table now has 8 entries. @@ -505,9 +505,9 @@ describe('profile-data', function () { thread.funcTable, defaultCategory ); - const callNodeTable = callNodeInfo.getCallNodeTable(); + const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable(); const stackIndexToCallNodeIndex = - callNodeInfo.getStackIndexToCallNodeIndex(); + callNodeInfo.getStackIndexToNonInvertedCallNodeIndex(); const stack0 = thread.samples.stack[0]; const stack1 = thread.samples.stack[1]; if (stack0 === null || stack1 === null) { @@ -874,7 +874,8 @@ describe('getSamplesSelectedStates', function () { thread.funcTable, 0 ); - const stackIndexToCallNodeIndex = callNodeInfo.getStackIndexToCallNodeIndex(); + const stackIndexToCallNodeIndex = + callNodeInfo.getStackIndexToNonInvertedCallNodeIndex(); const sampleCallNodes = getSampleIndexToCallNodeIndex( thread.samples.stack, stackIndexToCallNodeIndex @@ -1195,7 +1196,18 @@ describe('getNativeSymbolsForCallNode', function () { 'Expected to find categories' ); const defaultCategory = categories.findIndex((c) => c.name === 'Other'); - const callNodeInfo = getInvertedCallNodeInfo(thread, defaultCategory); + const nonInvertedCallNodeInfo = getCallNodeInfo( + thread.stackTable, + thread.frameTable, + thread.funcTable, + defaultCategory + ); + const callNodeInfo = getInvertedCallNodeInfo( + thread, + nonInvertedCallNodeInfo.getNonInvertedCallNodeTable(), + nonInvertedCallNodeInfo.getStackIndexToNonInvertedCallNodeIndex(), + defaultCategory + ); const c = callNodeInfo.getCallNodeIndexFromPath([funC]); expect(c).not.toBeNull(); diff --git a/src/test/unit/profile-tree.test.js b/src/test/unit/profile-tree.test.js index eb81d70663..4ba030b5d8 100644 --- a/src/test/unit/profile-tree.test.js +++ b/src/test/unit/profile-tree.test.js @@ -127,7 +127,7 @@ describe('unfiltered call tree', function () { const cnKN = callNodeInfo.getCallNodeIndexFromPath([K, N]); const rows = computeFlameGraphRows( - callNodeInfo.getCallNodeTable(), + callNodeInfo.getNonInvertedCallNodeTable(), thread.funcTable, thread.stringTable ); @@ -470,6 +470,8 @@ describe('inverted call tree', function () { // Now compute the inverted tree and check it. const invertedCallNodeInfo = getInvertedCallNodeInfo( thread, + callNodeInfo.getNonInvertedCallNodeTable(), + callNodeInfo.getStackIndexToNonInvertedCallNodeIndex(), defaultCategory ); const invertedCallTreeTimings = computeCallTreeTimings( diff --git a/src/types/profile-derived.js b/src/types/profile-derived.js index 956ad7a456..5f792168bf 100644 --- a/src/types/profile-derived.js +++ b/src/types/profile-derived.js @@ -132,6 +132,10 @@ export interface CallNodeInfo { // call node table, otherwise this is the non-inverted call node table. getCallNodeTable(): CallNodeTable; + // Returns the non-inverted call node table. + // This is always the non-inverted call node table, regardless of isInverted(). + getNonInvertedCallNodeTable(): CallNodeTable; + // Returns a mapping from the stack table to the call node table. // The Int32Array should be used as if it were a // Map. @@ -150,6 +154,10 @@ export interface CallNodeInfo { // C <- B <- A in the inverted call node table. getStackIndexToCallNodeIndex(): Int32Array; + // Returns a mapping from the stack table to the non-inverted call node table. + // This always maps to the non-inverted call node table, regardless of isInverted(). + getStackIndexToNonInvertedCallNodeIndex(): Int32Array; + // Converts a call node index into a call node path. getCallNodePathFromIndex( callNodeIndex: IndexIntoCallNodeTable | null