Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make inverting the call tree fast, by computing inverted call nodes lazily #4900

Merged
merged 32 commits into from
Feb 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
db716a5
Add CallNodeInfoInverted.
mstange Jan 23, 2024
68bbf6b
Implement the "suffix order".
mstange Aug 6, 2024
2a3c888
Use the suffix order in getMatchingAncestorStackForInvertedCallNode.
mstange Jan 19, 2024
f5e3ad1
Implement getSamplesSelectedStates for inverted trees with the suffix…
mstange Jan 19, 2024
3ac08b9
Use suffix order in getTimingsForCallNodeIndex.
mstange Jan 20, 2024
9f1c8f3
Implement getTreeOrderComparator for the inverted tree with non-inver…
mstange Jan 20, 2024
cb7a9ce
Use SampleIndexToNonInvertedCallNodeIndex for the stack chart.
mstange Jan 20, 2024
2259f60
Remove now-unused stack-to-inverted-call-node mapping.
mstange Jan 20, 2024
166342c
Call getNonInvertedCallNodeTable() in more stack-chart-related places.
mstange Jan 20, 2024
f2a32cf
Compute inverted call tree timings differently.
mstange Aug 6, 2024
724ed13
Rename leaf to self in many places.
mstange Jan 19, 2024
2da3e88
Use the non-inverted call node table to check for recursion.
mstange Aug 7, 2024
3fc5886
Replace all remaining callers of getCallNodeTable() with xyzForNode()…
mstange Nov 13, 2023
1e9c8ba
Remove now-unused getCallNodeTable().
mstange Jan 20, 2024
a0db0ec
Create inverted call nodes lazily.
mstange Jan 20, 2024
e44ec8f
Optimize handling of roots.
mstange Jan 20, 2024
a891ee2
Make sourceFramesInlinedIntoSymbol an Int32Array.
mstange Jan 20, 2024
b25d6b5
Refine the suffix order incrementally, as new inverted nodes are crea…
mstange Nov 24, 2024
9440725
Fold the CallNodeInfoInverted interface into the implementation class.
mstange Nov 24, 2024
e1a726b
Remove unused methods from CallNodeInfoNonInverted.
mstange Nov 25, 2024
0cd70fa
Add a test for focus-category on an inverted call tree.
mstange Nov 25, 2024
f942782
Merge fast-invert4 with main.
mstange Feb 13, 2025
b8a472d
Be more explicit about the expectation that, if we have traced timing…
mstange Feb 13, 2025
3462954
Expand comment about inverted call node indexes (mention that the ord…
mstange Feb 13, 2025
9ddbf1c
Adjust CallNodeInfoInverted documentation based on julien's feedback.
mstange Feb 13, 2025
3c79aff
Clarify that the CallNodePaths used with CallNodeInfoInverted are inv…
mstange Feb 13, 2025
e5f0b4c
Rename _findDeepestKnownAncestor to _findDeepestExistingInvertedAnces…
mstange Feb 13, 2025
9252918
Improve comments in getCallNodeIndexFromPath.
mstange Feb 13, 2025
034244c
Move _getChildWithFunc and improve its comments.
mstange Feb 13, 2025
0331c6b
Adjust the comment above _createNonRootNode as requested by julien.
mstange Feb 13, 2025
3f4e927
Clarify the comment about ordering the inverted children by func.
mstange Feb 13, 2025
c80f34a
Fix parentDoopNode typo.
mstange Feb 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions src/actions/profile-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ import type {
Pid,
IndexIntoSamplesTable,
CallNodePath,
CallNodeInfo,
IndexIntoCallNodeTable,
IndexIntoResourceTable,
TrackIndex,
Expand All @@ -86,6 +85,7 @@ import {
} from '../profile-logic/transforms';
import { changeStoredProfileNameInDb } from 'firefox-profiler/app-logic/uploaded-profiles-db';
import type { TabSlug } from '../app-logic/tabs-handling';
import type { CallNodeInfo } from '../profile-logic/call-node-info';
import { intersectSets } from 'firefox-profiler/utils/set';

/**
Expand Down Expand Up @@ -2035,12 +2035,13 @@ export function handleCallNodeTransformShortcut(
const threadSelectors = getThreadSelectorsFromThreadsKey(threadsKey);
const unfilteredThread = threadSelectors.getThread(getState());
const callNodeInfo = threadSelectors.getCallNodeInfo(getState());
const callNodeTable = callNodeInfo.getCallNodeTable();
const implementation = getImplementationFilter(getState());
const inverted = getInvertCallstack(getState());
const callNodePath = callNodeInfo.getCallNodePathFromIndex(callNodeIndex);
const funcIndex = callNodeTable.func[callNodeIndex];
const category = callNodeTable.category[callNodeIndex];
const funcIndex = callNodeInfo.funcForNode(callNodeIndex);
const category = callNodeInfo.categoryForNode(callNodeIndex);

const nonInvertedCallNodeTable = callNodeInfo.getNonInvertedCallNodeTable();

switch (event.key) {
case 'F':
Expand Down Expand Up @@ -2099,7 +2100,7 @@ export function handleCallNodeTransformShortcut(
break;
}
case 'r': {
if (funcHasRecursiveCall(callNodeTable, funcIndex)) {
if (funcHasRecursiveCall(nonInvertedCallNodeTable, funcIndex)) {
dispatch(
addTransformToStack(threadsKey, {
type: 'collapse-recursion',
Expand All @@ -2110,7 +2111,7 @@ export function handleCallNodeTransformShortcut(
break;
}
case 'R': {
if (funcHasDirectRecursiveCall(callNodeTable, funcIndex)) {
if (funcHasDirectRecursiveCall(nonInvertedCallNodeTable, funcIndex)) {
dispatch(
addTransformToStack(threadsKey, {
type: 'collapse-direct-recursion',
Expand Down
8 changes: 4 additions & 4 deletions src/components/calltree/CallTree.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import type {
State,
ImplementationFilter,
ThreadsKey,
CallNodeInfo,
CategoryList,
IndexIntoCallNodeTable,
CallNodeDisplayData,
Expand All @@ -47,6 +46,7 @@ import type {
SelectionContext,
} from 'firefox-profiler/types';
import type { CallTree as CallTreeType } from 'firefox-profiler/profile-logic/call-tree';
import type { CallNodeInfo } from 'firefox-profiler/profile-logic/call-node-info';

import type {
Column,
Expand Down Expand Up @@ -320,7 +320,6 @@ class CallTreeImpl extends PureComponent<Props> {
// This tree is empty.
return;
}
const callNodeTable = callNodeInfo.getCallNodeTable();
newExpandedCallNodeIndexes.push(currentCallNodeIndex);
for (let i = 0; i < maxInterestingDepth; i++) {
const children = tree.getChildren(currentCallNodeIndex);
Expand All @@ -330,7 +329,8 @@ class CallTreeImpl extends PureComponent<Props> {

// Let's find if there's a non idle children.
const firstNonIdleNode = children.find(
(nodeIndex) => callNodeTable.category[nodeIndex] !== idleCategoryIndex
(nodeIndex) =>
callNodeInfo.categoryForNode(nodeIndex) !== idleCategoryIndex
);

// If there's a non idle children, use it; otherwise use the first
Expand All @@ -341,7 +341,7 @@ class CallTreeImpl extends PureComponent<Props> {
}
this._onExpandedCallNodesChange(newExpandedCallNodeIndexes);

const categoryIndex = callNodeTable.category[currentCallNodeIndex];
const categoryIndex = callNodeInfo.categoryForNode(currentCallNodeIndex);
if (categoryIndex !== idleCategoryIndex) {
// If we selected the call node with a "idle" category, we'd have a
// completely dimmed activity graph because idle stacks are not drawn in
Expand Down
6 changes: 3 additions & 3 deletions src/components/flame-graph/Canvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import type {
CssPixels,
DevicePixels,
Milliseconds,
CallNodeInfo,
IndexIntoCallNodeTable,
CallTreeSummaryStrategy,
WeightType,
Expand All @@ -42,6 +41,7 @@ import type {
FlameGraphDepth,
IndexIntoFlameGraphTiming,
} from 'firefox-profiler/profile-logic/flame-graph';
import type { CallNodeInfo } from 'firefox-profiler/profile-logic/call-node-info';

import type {
ChartCanvasScale,
Expand All @@ -50,7 +50,7 @@ import type {

import type {
CallTree,
CallTreeTimings,
CallTreeTimingsNonInverted,
} from 'firefox-profiler/profile-logic/call-tree';

export type OwnProps = {|
Expand All @@ -77,7 +77,7 @@ export type OwnProps = {|
+callTreeSummaryStrategy: CallTreeSummaryStrategy,
+ctssSamples: SamplesLikeTable,
+unfilteredCtssSamples: SamplesLikeTable,
+tracedTiming: CallTreeTimings | null,
+tracedTiming: CallTreeTimingsNonInverted | null,
+displayImplementation: boolean,
+displayStackType: boolean,
|};
Expand Down
19 changes: 17 additions & 2 deletions src/components/flame-graph/FlameGraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import {
handleCallNodeTransformShortcut,
updateBottomBoxContentsAndMaybeOpen,
} from 'firefox-profiler/actions/profile-view';
import { extractNonInvertedCallTreeTimings } from 'firefox-profiler/profile-logic/call-tree';
import { ensureExists } from 'firefox-profiler/utils/flow';

import type {
Thread,
Expand All @@ -40,14 +42,14 @@ import type {
SamplesLikeTable,
PreviewSelection,
CallTreeSummaryStrategy,
CallNodeInfo,
IndexIntoCallNodeTable,
ThreadsKey,
InnerWindowID,
Page,
} from 'firefox-profiler/types';

import type { FlameGraphTiming } from 'firefox-profiler/profile-logic/flame-graph';
import type { CallNodeInfo } from 'firefox-profiler/profile-logic/call-node-info';

import type {
CallTree,
Expand Down Expand Up @@ -344,6 +346,19 @@ class FlameGraphImpl extends React.PureComponent<Props> {
displayStackType,
} = this.props;

// Get the CallTreeTimingsNonInverted out of tracedTiming. We pass this
// along rather than the more generic CallTreeTimings type so that the
// FlameGraphCanvas component can operate on the more specialized type.
// (CallTreeTimingsNonInverted and CallTreeTimingsInverted are very
// different, and the flame graph is only used with non-inverted timings.)
const tracedTimingNonInverted =
tracedTiming !== null
? ensureExists(
extractNonInvertedCallTreeTimings(tracedTiming),
'The flame graph should only ever see non-inverted timings, see UrlState.getInvertCallstack'
)
: null;

const maxViewportHeight = maxStackDepthPlusOne * STACK_FRAME_HEIGHT;

return (
Expand Down Expand Up @@ -394,7 +409,7 @@ class FlameGraphImpl extends React.PureComponent<Props> {
isInverted,
ctssSamples,
unfilteredCtssSamples,
tracedTiming,
tracedTiming: tracedTimingNonInverted,
displayImplementation,
displayStackType,
}}
Expand Down
26 changes: 12 additions & 14 deletions src/components/shared/CallNodeContextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ import type {
TransformType,
ImplementationFilter,
IndexIntoCallNodeTable,
CallNodeInfo,
CallNodePath,
Thread,
ThreadsKey,
Expand All @@ -64,6 +63,7 @@ import type {

import type { TabSlug } from 'firefox-profiler/app-logic/tabs-handling';
import type { ConnectedProps } from 'firefox-profiler/utils/connect';
import type { CallNodeInfo } from 'firefox-profiler/profile-logic/call-node-info';
import type { BrowserConnectionStatus } from 'firefox-profiler/app-logic/browser-connection';

type StateProps = {|
Expand Down Expand Up @@ -147,8 +147,7 @@ class CallNodeContextMenuImpl extends React.PureComponent<Props> {
callNodeInfo,
} = rightClickedCallNodeInfo;

const callNodeTable = callNodeInfo.getCallNodeTable();
const funcIndex = callNodeTable.func[callNodeIndex];
const funcIndex = callNodeInfo.funcForNode(callNodeIndex);
const isJS = funcTable.isJS[funcIndex];
const stringIndex = funcTable.name[funcIndex];
const functionCall = stringTable.getString(stringIndex);
Expand Down Expand Up @@ -185,8 +184,7 @@ class CallNodeContextMenuImpl extends React.PureComponent<Props> {
callNodeInfo,
} = rightClickedCallNodeInfo;

const callNodeTable = callNodeInfo.getCallNodeTable();
const funcIndex = callNodeTable.func[callNodeIndex];
const funcIndex = callNodeInfo.funcForNode(callNodeIndex);
const stringIndex = funcTable.fileName[funcIndex];
if (stringIndex === null) {
return null;
Expand All @@ -209,8 +207,7 @@ class CallNodeContextMenuImpl extends React.PureComponent<Props> {
callNodeInfo,
} = rightClickedCallNodeInfo;

const callNodeTable = callNodeInfo.getCallNodeTable();
const funcIndex = callNodeTable.func[callNodeIndex];
const funcIndex = callNodeInfo.funcForNode(callNodeIndex);
const line = funcTable.lineNumber[funcIndex];
const column = funcTable.columnNumber[funcIndex];
return { line, column };
Expand Down Expand Up @@ -337,8 +334,7 @@ class CallNodeContextMenuImpl extends React.PureComponent<Props> {
const { threadsKey, callNodePath, thread, callNodeIndex, callNodeInfo } =
rightClickedCallNodeInfo;
const selectedFunc = callNodePath[callNodePath.length - 1];
const callNodeTable = callNodeInfo.getCallNodeTable();
const category = callNodeTable.category[callNodeIndex];
const category = callNodeInfo.categoryForNode(callNodeIndex);
switch (type) {
case 'focus-subtree':
addTransformToStack(threadsKey, {
Expand Down Expand Up @@ -451,8 +447,7 @@ class CallNodeContextMenuImpl extends React.PureComponent<Props> {
const { callNodeInfo, callNodeIndex } = rightClickedCallNodeInfo;
const { innerWindowIDToPageMap } = this.props;

const callNodeTable = callNodeInfo.getCallNodeTable();
const innerWindowID = callNodeTable.innerWindowID[callNodeIndex];
const innerWindowID = callNodeInfo.innerWindowIDForNode(callNodeIndex);

if (innerWindowID && innerWindowIDToPageMap) {
const page = innerWindowIDToPageMap.get(innerWindowID);
Expand Down Expand Up @@ -583,9 +578,8 @@ class CallNodeContextMenuImpl extends React.PureComponent<Props> {
callNodeInfo,
} = rightClickedCallNodeInfo;

const callNodeTable = callNodeInfo.getCallNodeTable();
const categoryIndex = callNodeTable.category[callNodeIndex];
const funcIndex = callNodeTable.func[callNodeIndex];
const categoryIndex = callNodeInfo.categoryForNode(callNodeIndex);
const funcIndex = callNodeInfo.funcForNode(callNodeIndex);
const isJS = funcTable.isJS[funcIndex];
const hasCategory = categoryIndex !== -1;
// This could be the C++ library, or the JS filename.
Expand All @@ -599,6 +593,9 @@ class CallNodeContextMenuImpl extends React.PureComponent<Props> {
const fileName =
filePath &&
parseFileNameFromSymbolication(filePath).path.match(/[^\\/]+$/)?.[0];

const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note to self, this change could have been in the previous commit (use the non inverted table for the recursive function transform)


const showOpenDebuggerItem =
isJS &&
filePath &&
Expand All @@ -607,6 +604,7 @@ class CallNodeContextMenuImpl extends React.PureComponent<Props> {
filePath !== 'self-hosted' &&
browserConnectionStatus.status === 'ESTABLISHED' &&
this._getTabID();

return (
<>
{fileName ? (
Expand Down
2 changes: 1 addition & 1 deletion src/components/shared/thread/CPUGraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import type {
CategoryList,
IndexIntoSamplesTable,
Milliseconds,
CallNodeInfo,
SelectedState,
} from 'firefox-profiler/types';
import type { CallNodeInfo } from 'firefox-profiler/profile-logic/call-node-info';

type Props = {|
+className: string,
Expand Down
2 changes: 1 addition & 1 deletion src/components/shared/thread/StackGraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import type {
CategoryList,
IndexIntoSamplesTable,
Milliseconds,
CallNodeInfo,
IndexIntoCallNodeTable,
SelectedState,
} from 'firefox-profiler/types';
import type { CallNodeInfo } from 'firefox-profiler/profile-logic/call-node-info';

type Props = {|
+className: string,
Expand Down
7 changes: 3 additions & 4 deletions src/components/stack-chart/Canvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import type {
ThreadsKey,
UserTimingMarkerPayload,
WeightType,
CallNodeInfo,
IndexIntoCallNodeTable,
CombinedTimingRows,
Milliseconds,
Expand All @@ -41,6 +40,7 @@ import type {
InnerWindowID,
Page,
} from 'firefox-profiler/types';
import type { CallNodeInfo } from 'firefox-profiler/profile-logic/call-node-info';

import type {
ChartCanvasScale,
Expand Down Expand Up @@ -128,8 +128,7 @@ class StackChartCanvasImpl extends React.PureComponent<Props> {
return;
}

const callNodeTable = callNodeInfo.getCallNodeTable();
const depth = callNodeTable.depth[selectedCallNodeIndex];
const depth = callNodeInfo.depthForNode(selectedCallNodeIndex);
const y = depth * ROW_CSS_PIXELS_HEIGHT;

if (y < this.props.viewport.viewportTop) {
Expand Down Expand Up @@ -262,7 +261,7 @@ class StackChartCanvasImpl extends React.PureComponent<Props> {
categoryForUserTiming = 0;
}

const callNodeTable = callNodeInfo.getCallNodeTable();
const callNodeTable = callNodeInfo.getNonInvertedCallNodeTable();

// Only draw the stack frames that are vertically within view.
for (let depth = startDepth; depth < endDepth; depth++) {
Expand Down
5 changes: 2 additions & 3 deletions src/components/stack-chart/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ import { getBottomBoxInfoForCallNode } from '../../profile-logic/profile-data';
import type {
Thread,
CategoryList,
CallNodeInfo,
IndexIntoCallNodeTable,
CombinedTimingRows,
MarkerIndex,
Expand All @@ -58,6 +57,7 @@ import type {
InnerWindowID,
Page,
} from 'firefox-profiler/types';
import type { CallNodeInfo } from 'firefox-profiler/profile-logic/call-node-info';

import type { ConnectedProps } from '../../utils/connect';

Expand Down Expand Up @@ -181,8 +181,7 @@ class StackChartImpl extends React.PureComponent<Props> {
event.preventDefault();
const { callNodeInfo, selectedCallNodeIndex, thread } = this.props;
if (selectedCallNodeIndex !== null) {
const callNodeTable = callNodeInfo.getCallNodeTable();
const funcIndex = callNodeTable.func[selectedCallNodeIndex];
const funcIndex = callNodeInfo.funcForNode(selectedCallNodeIndex);
const funcName = thread.stringTable.getString(
thread.funcTable.name[funcIndex]
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/timeline/TrackThread.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ import type {
IndexIntoSamplesTable,
Milliseconds,
StartEndRange,
CallNodeInfo,
ImplementationFilter,
IndexIntoCallNodeTable,
SelectedState,
State,
ThreadsKey,
} from 'firefox-profiler/types';
import type { CallNodeInfo } from 'firefox-profiler/profile-logic/call-node-info';

import type { ConnectedProps } from 'firefox-profiler/utils/connect';

Expand Down
Loading