Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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: 13 additions & 0 deletions locales/en-US/app.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,14 @@ CallNodeContextMenu--transform-collapse-resource =
.title =
Collapsing a resource will flatten out all the calls to that
resource into a single collapsed call node.
# This is used as the context menu item to apply the "Collapse source" transform.
# Variables:
# $nameForSource (String) - Name of the source file to collapse.
CallNodeContextMenu--transform-collapse-source =
Collapse source <strong>{ $nameForSource }</strong>
.title =
Collapsing a source file will flatten out all the calls from that
source file into a single collapsed call node.
CallNodeContextMenu--transform-collapse-recursion = Collapse recursion
.title =
Collapsing recursion removes calls that repeatedly recurse into
Expand Down Expand Up @@ -1155,6 +1163,11 @@ TransformNavigator--complete = Complete “{ $item }”
# $item (String) - Name of the resource that collapsed. E.g.: libxul.so.
TransformNavigator--collapse-resource = Collapse: { $item }

# "Collapse source" transform.
# Variables:
# $item (String) - Name of the source file that was collapsed. E.g.: foo.js.
TransformNavigator--collapse-source = Collapse source: { $item }

# "Focus subtree" transform.
# See: https://profiler.firefox.com/docs/#/./guide-filtering-call-trees?id=focus
# Variables:
Expand Down
39 changes: 39 additions & 0 deletions src/actions/profile-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
getThreads,
getLastNonShiftClick,
getReservedFunctionsForResources,
getReservedFunctionsForSources,
} from 'firefox-profiler/selectors/profile';
import {
getThreadSelectors,
Expand Down Expand Up @@ -63,6 +64,7 @@ import type {
CallNodePath,
IndexIntoCallNodeTable,
IndexIntoResourceTable,
IndexIntoSourceTable,
TrackIndex,
MarkerIndex,
Transform,
Expand Down Expand Up @@ -1847,6 +1849,29 @@ export function addCollapseResourceTransformToStack(
};
}

export function addCollapseSourceTransformToStack(
threadsKey: ThreadsKey,
sourceIndex: IndexIntoSourceTable,
implementation: ImplementationFilter
): ThunkAction<void> {
return (dispatch, getState) => {
const reservedFunctionsForSources =
getReservedFunctionsForSources(getState());
const collapsedFuncIndex = ensureExists(
ensureExists(reservedFunctionsForSources).get(sourceIndex)
);

dispatch(
addTransformToStack(threadsKey, {
type: 'collapse-source',
sourceIndex,
collapsedFuncIndex,
implementation,
})
);
};
}

export function popTransformsFromStack(
firstPoppedFilterIndex: number
): ThunkAction<void> {
Expand Down Expand Up @@ -2103,6 +2128,20 @@ export function handleCallNodeTransformShortcut(
})
);
break;
case 'X': {
const { funcTable } = unfilteredThread;
const sourceIndex = funcTable.source[funcIndex];
if (sourceIndex !== null) {
dispatch(
addCollapseSourceTransformToStack(
threadsKey,
sourceIndex,
implementation
)
);
}
break;
}
default:
// This did not match a call tree transform.
}
Expand Down
7 changes: 6 additions & 1 deletion src/app-logic/url-handling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import { StringTable } from 'firefox-profiler/utils/string-table';
import type { ProfileUpgradeInfo } from 'firefox-profiler/profile-logic/processed-profile-versioning';
import type { ProfileAndProfileUpgradeInfo } from 'firefox-profiler/actions/receive-profile';

export const CURRENT_URL_VERSION = 15;
export const CURRENT_URL_VERSION = 16;

/**
* This static piece of state might look like an anti-pattern, but it's a relatively
Expand Down Expand Up @@ -1351,6 +1351,11 @@ const _upgraders: {
.map(mapIndexesInTransform)
.join('~');
},
[16]: (_processedLocation: ProcessedLocationBeforeUpgrade) => {
// Version 16 introduced the 'collapse-source' transform ('cs' short key).
// No URL structure changes are needed; this version bump ensures older
// versions of the profiler do not silently ignore the new transform.
},
};

for (let destVersion = 1; destVersion <= CURRENT_URL_VERSION; destVersion++) {
Expand Down
66 changes: 66 additions & 0 deletions src/components/shared/CallNodeContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import copy from 'copy-to-clipboard';
import {
addTransformToStack,
addCollapseResourceTransformToStack,
addCollapseSourceTransformToStack,
expandAllCallNodeDescendants,
updateBottomBoxContentsAndMaybeOpen,
setContextMenuVisibility,
Expand Down Expand Up @@ -80,6 +81,7 @@ type StateProps = {
type DispatchProps = {
readonly addTransformToStack: typeof addTransformToStack;
readonly addCollapseResourceTransformToStack: typeof addCollapseResourceTransformToStack;
readonly addCollapseSourceTransformToStack: typeof addCollapseSourceTransformToStack;
readonly expandAllCallNodeDescendants: typeof expandAllCallNodeDescendants;
readonly updateBottomBoxContentsAndMaybeOpen: typeof updateBottomBoxContentsAndMaybeOpen;
readonly setContextMenuVisibility: typeof setContextMenuVisibility;
Expand Down Expand Up @@ -326,6 +328,7 @@ class CallNodeContextMenuImpl extends React.PureComponent<Props> {
const {
addTransformToStack,
addCollapseResourceTransformToStack,
addCollapseSourceTransformToStack,
implementation,
inverted,
} = this.props;
Expand Down Expand Up @@ -393,6 +396,21 @@ class CallNodeContextMenuImpl extends React.PureComponent<Props> {
);
break;
}
case 'collapse-source': {
const { funcTable } = thread;
const sourceIndex = funcTable.source[selectedFunc];
if (sourceIndex === null) {
throw new Error(
'collapse-source was triggered on a function without a source'
);
}
addCollapseSourceTransformToStack(
threadsKey,
sourceIndex,
implementation
);
break;
}
case 'collapse-direct-recursion': {
addTransformToStack(threadsKey, {
type: 'collapse-direct-recursion',
Expand Down Expand Up @@ -539,6 +557,36 @@ class CallNodeContextMenuImpl extends React.PureComponent<Props> {
return stringTable.getString(resNameStringIndex);
}

getNameForSelectedSource(): string | null {
const rightClickedCallNodeInfo = this.getRightClickedCallNodeInfo();

if (rightClickedCallNodeInfo === null) {
throw new Error(
"The context menu assumes there is a selected call node and there wasn't one."
);
}

const {
callNodeInfo,
callNodeIndex,
thread: { funcTable, stringTable, sources },
} = rightClickedCallNodeInfo;

const funcIndex = callNodeInfo.funcForNode(callNodeIndex);
if (funcIndex === undefined) {
return null;
}
if (!funcTable.isJS[funcIndex]) {
return null;
}
const sourceIndex = funcTable.source[funcIndex];
if (sourceIndex === null) {
return null;
}
const fileNameIndex = sources.filename[sourceIndex];
return stringTable.getString(fileNameIndex);
}

getRightClickedCallNodeInfo(): null | {
readonly thread: Thread;
readonly threadsKey: ThreadsKey;
Expand Down Expand Up @@ -602,6 +650,7 @@ class CallNodeContextMenuImpl extends React.PureComponent<Props> {
const hasCategory = categoryIndex !== -1;
// This could be the C++ library, or the JS filename.
const nameForResource = this.getNameForSelectedResource();
const nameForSource = this.getNameForSelectedSource();
const categoryName: string = hasCategory
? categories[categoryIndex].name
: '';
Expand Down Expand Up @@ -740,6 +789,22 @@ class CallNodeContextMenuImpl extends React.PureComponent<Props> {
})
: null}

{nameForSource
? this.renderTransformMenuItem({
l10nId: 'CallNodeContextMenu--transform-collapse-source',
l10nProps: {
vars: { nameForSource: nameForSource },
elems: { strong: <strong /> },
},
shortcut: 'X',
icon: 'Collapse',
onClick: this._handleClick,
transform: 'collapse-source',
title: '',
content: `Collapse source <strong>${nameForSource}</strong>`,
})
: null}

{funcHasRecursiveCall(callNodeTable, funcIndex)
? this.renderTransformMenuItem({
l10nId: 'CallNodeContextMenu--transform-collapse-recursion',
Expand Down Expand Up @@ -951,6 +1016,7 @@ export const CallNodeContextMenu = explicitConnect<
mapDispatchToProps: {
addTransformToStack,
addCollapseResourceTransformToStack,
addCollapseSourceTransformToStack,
expandAllCallNodeDescendants,
updateBottomBoxContentsAndMaybeOpen,
setContextMenuVisibility,
Expand Down
44 changes: 34 additions & 10 deletions src/profile-logic/index-translation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type {
CallNodePath,
IndexIntoFuncTable,
IndexIntoResourceTable,
IndexIntoSourceTable,
ProfileIndexTranslationMaps,
} from 'firefox-profiler/types';

Expand All @@ -21,9 +22,20 @@ export function translateResourceIndex(
return newResourceIndexPlusOne !== 0 ? newResourceIndexPlusOne - 1 : null;
}

// Returns the new source index for the given old source index.
// Returns null if the index has no new index equivalent.
export function translateSourceIndex(
sourceIndex: IndexIntoSourceTable,
translationMaps: ProfileIndexTranslationMaps
): IndexIntoSourceTable | null {
const newSourceIndexPlusOne =
translationMaps.oldSourceToNewSourcePlusOne[sourceIndex];
return newSourceIndexPlusOne !== 0 ? newSourceIndexPlusOne - 1 : null;
}

// Returns the new func index for the given old func index.
// This handles indexes for "reserved funcs" for collapsed resources, which
// are located after the regular funcTable.
// This handles indexes for "reserved funcs" for collapsed resources and
// collapsed sources, which are located after the regular funcTable.
// Returns null if the index has no new index equivalent.
export function translateFuncIndex(
funcIndex: IndexIntoFuncTable,
Expand All @@ -35,14 +47,26 @@ export function translateFuncIndex(
translationMaps.oldFuncToNewFuncPlusOne[funcIndex];
return newFuncIndexPlusOne !== 0 ? newFuncIndexPlusOne - 1 : null;
}
// This must be a funcIndex from the "func table with reserved functions for collapsed resources".
const resourceIndex = funcIndex - oldFuncCount;
const newResourceIndex = translateResourceIndex(
resourceIndex,
translationMaps
);
return newResourceIndex !== null
? translationMaps.newFuncCount + newResourceIndex
const reservedOffset = funcIndex - oldFuncCount;
const oldResourceCount = translationMaps.oldResourceCount;
if (reservedOffset < oldResourceCount) {
// This is a reserved func for a collapsed resource.
const resourceIndex = reservedOffset;
const newResourceIndex = translateResourceIndex(
resourceIndex,
translationMaps
);
return newResourceIndex !== null
? translationMaps.newFuncCount + newResourceIndex
: null;
}
// This is a reserved func for a collapsed source.
const sourceIndex = reservedOffset - oldResourceCount;
const newSourceIndex = translateSourceIndex(sourceIndex, translationMaps);
return newSourceIndex !== null
? translationMaps.newFuncCount +
translationMaps.newResourceCount +
newSourceIndex
: null;
}

Expand Down
2 changes: 2 additions & 0 deletions src/profile-logic/merge-compare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,8 @@ export function mergeSharedData(profiles: Profile[]): {
oldThreadIndexToNew: null,
oldFuncCount: profile.shared.funcTable.length,
newFuncCount: newFuncTable.length,
oldResourceCount: profile.shared.resourceTable.length,
newResourceCount: newResourceTable.length,
oldLibToNewLibPlusOne,
oldStringToNewStringPlusOne,
oldSourceToNewSourcePlusOne,
Expand Down
29 changes: 26 additions & 3 deletions src/profile-logic/profile-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3281,18 +3281,27 @@ export function getOriginAnnotationForFunc(
*
* This returns a new thread with an extended funcTable.
*
* At the moment, the only functions we reserve are "collapsed resource" functions.
* These are used by the "collapse resource" transform.
* We reserve two sets of functions:
* - "collapsed resource" functions (used by the "collapse-resource" transform)
* - "collapsed source" functions (used by the "collapse-source" transform)
*
* Resource reserved funcs occupy indices [funcCount, funcCount + resourceCount).
* Source reserved funcs occupy indices [funcCount + resourceCount, funcCount + resourceCount + sourceCount).
*/
export function reserveFunctionsForCollapsedResources(
originalFuncTable: FuncTable,
resourceTable: ResourceTable
resourceTable: ResourceTable,
sourceTable: SourceTable
): FuncTableWithReservedFunctions {
const funcTable = shallowCloneFuncTable(originalFuncTable);
const reservedFunctionsForResources = new Map<
IndexIntoResourceTable,
IndexIntoFuncTable
>();
const reservedFunctionsForSources = new Map<
IndexIntoSourceTable,
IndexIntoFuncTable
>();
const jsResourceTypes = [
ResourceType.Addon,
ResourceType.Url,
Expand All @@ -3318,9 +3327,23 @@ export function reserveFunctionsForCollapsedResources(
funcTable.length++;
reservedFunctionsForResources.set(resourceIndex, funcIndex);
}
for (let sourceIndex = 0; sourceIndex < sourceTable.length; sourceIndex++) {
const name = sourceTable.filename[sourceIndex];
const funcIndex = funcTable.length;
funcTable.isJS.push(true);
funcTable.relevantForJS.push(true);
funcTable.name.push(name);
funcTable.resource.push(-1);
funcTable.source.push(sourceIndex);
funcTable.lineNumber.push(null);
funcTable.columnNumber.push(null);
funcTable.length++;
reservedFunctionsForSources.set(sourceIndex, funcIndex);
}
return {
funcTable,
reservedFunctionsForResources,
reservedFunctionsForSources,
};
}

Expand Down
3 changes: 3 additions & 0 deletions src/profile-logic/sanitize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,9 @@ export function sanitizePII(
oldFuncCount: profile.shared.funcTable.length,
newFuncCount:
compactedProfileWithTranslationMaps.profile.shared.funcTable.length,
oldResourceCount: profile.shared.resourceTable.length,
newResourceCount:
compactedProfileWithTranslationMaps.profile.shared.resourceTable.length,
...compactedProfileWithTranslationMaps.translationMaps,
},
};
Expand Down
Loading
Loading