Skip to content

Commit 9069714

Browse files
committed
Add "Collapse source" transform
This is going to be very useful when we have the source map symbolication support. This way, we will be able to callapse things like react.min.js that is polluting the call tree otherwise.
1 parent 3448088 commit 9069714

17 files changed

Lines changed: 534 additions & 18 deletions

locales/en-US/app.ftl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,14 @@ CallNodeContextMenu--transform-collapse-resource =
139139
.title =
140140
Collapsing a resource will flatten out all the calls to that
141141
resource into a single collapsed call node.
142+
# This is used as the context menu item to apply the "Collapse source" transform.
143+
# Variables:
144+
# $nameForSource (String) - Name of the source file to collapse.
145+
CallNodeContextMenu--transform-collapse-source =
146+
Collapse source <strong>{ $nameForSource }</strong>
147+
.title =
148+
Collapsing a source file will flatten out all the calls from that
149+
source file into a single collapsed call node.
142150
CallNodeContextMenu--transform-collapse-recursion = Collapse recursion
143151
.title =
144152
Collapsing recursion removes calls that repeatedly recurse into
@@ -1155,6 +1163,11 @@ TransformNavigator--complete = Complete “{ $item }”
11551163
# $item (String) - Name of the resource that collapsed. E.g.: libxul.so.
11561164
TransformNavigator--collapse-resource = Collapse: { $item }
11571165
1166+
# "Collapse source" transform.
1167+
# Variables:
1168+
# $item (String) - Name of the source file that was collapsed. E.g.: foo.js.
1169+
TransformNavigator--collapse-source = Collapse source: { $item }
1170+
11581171
# "Focus subtree" transform.
11591172
# See: https://profiler.firefox.com/docs/#/./guide-filtering-call-trees?id=focus
11601173
# Variables:

src/actions/profile-view.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
getThreads,
1717
getLastNonShiftClick,
1818
getReservedFunctionsForResources,
19+
getReservedFunctionsForSources,
1920
} from 'firefox-profiler/selectors/profile';
2021
import {
2122
getThreadSelectors,
@@ -63,6 +64,7 @@ import type {
6364
CallNodePath,
6465
IndexIntoCallNodeTable,
6566
IndexIntoResourceTable,
67+
IndexIntoSourceTable,
6668
TrackIndex,
6769
MarkerIndex,
6870
Transform,
@@ -1847,6 +1849,29 @@ export function addCollapseResourceTransformToStack(
18471849
};
18481850
}
18491851

1852+
export function addCollapseSourceTransformToStack(
1853+
threadsKey: ThreadsKey,
1854+
sourceIndex: IndexIntoSourceTable,
1855+
implementation: ImplementationFilter
1856+
): ThunkAction<void> {
1857+
return (dispatch, getState) => {
1858+
const reservedFunctionsForSources =
1859+
getReservedFunctionsForSources(getState());
1860+
const collapsedFuncIndex = ensureExists(
1861+
ensureExists(reservedFunctionsForSources).get(sourceIndex)
1862+
);
1863+
1864+
dispatch(
1865+
addTransformToStack(threadsKey, {
1866+
type: 'collapse-source',
1867+
sourceIndex,
1868+
collapsedFuncIndex,
1869+
implementation,
1870+
})
1871+
);
1872+
};
1873+
}
1874+
18501875
export function popTransformsFromStack(
18511876
firstPoppedFilterIndex: number
18521877
): ThunkAction<void> {
@@ -2103,6 +2128,20 @@ export function handleCallNodeTransformShortcut(
21032128
})
21042129
);
21052130
break;
2131+
case 'X': {
2132+
const { funcTable } = unfilteredThread;
2133+
const sourceIndex = funcTable.source[funcIndex];
2134+
if (sourceIndex !== null) {
2135+
dispatch(
2136+
addCollapseSourceTransformToStack(
2137+
threadsKey,
2138+
sourceIndex,
2139+
implementation
2140+
)
2141+
);
2142+
}
2143+
break;
2144+
}
21062145
default:
21072146
// This did not match a call tree transform.
21082147
}

src/app-logic/url-handling.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ import { StringTable } from 'firefox-profiler/utils/string-table';
5353
import type { ProfileUpgradeInfo } from 'firefox-profiler/profile-logic/processed-profile-versioning';
5454
import type { ProfileAndProfileUpgradeInfo } from 'firefox-profiler/actions/receive-profile';
5555

56-
export const CURRENT_URL_VERSION = 15;
56+
export const CURRENT_URL_VERSION = 16;
5757

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

13561361
for (let destVersion = 1; destVersion <= CURRENT_URL_VERSION; destVersion++) {

src/components/shared/CallNodeContextMenu.tsx

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import copy from 'copy-to-clipboard';
2121
import {
2222
addTransformToStack,
2323
addCollapseResourceTransformToStack,
24+
addCollapseSourceTransformToStack,
2425
expandAllCallNodeDescendants,
2526
updateBottomBoxContentsAndMaybeOpen,
2627
setContextMenuVisibility,
@@ -80,6 +81,7 @@ type StateProps = {
8081
type DispatchProps = {
8182
readonly addTransformToStack: typeof addTransformToStack;
8283
readonly addCollapseResourceTransformToStack: typeof addCollapseResourceTransformToStack;
84+
readonly addCollapseSourceTransformToStack: typeof addCollapseSourceTransformToStack;
8385
readonly expandAllCallNodeDescendants: typeof expandAllCallNodeDescendants;
8486
readonly updateBottomBoxContentsAndMaybeOpen: typeof updateBottomBoxContentsAndMaybeOpen;
8587
readonly setContextMenuVisibility: typeof setContextMenuVisibility;
@@ -326,6 +328,7 @@ class CallNodeContextMenuImpl extends React.PureComponent<Props> {
326328
const {
327329
addTransformToStack,
328330
addCollapseResourceTransformToStack,
331+
addCollapseSourceTransformToStack,
329332
implementation,
330333
inverted,
331334
} = this.props;
@@ -393,6 +396,21 @@ class CallNodeContextMenuImpl extends React.PureComponent<Props> {
393396
);
394397
break;
395398
}
399+
case 'collapse-source': {
400+
const { funcTable } = thread;
401+
const sourceIndex = funcTable.source[selectedFunc];
402+
if (sourceIndex === null) {
403+
throw new Error(
404+
'collapse-source was triggered on a function without a source'
405+
);
406+
}
407+
addCollapseSourceTransformToStack(
408+
threadsKey,
409+
sourceIndex,
410+
implementation
411+
);
412+
break;
413+
}
396414
case 'collapse-direct-recursion': {
397415
addTransformToStack(threadsKey, {
398416
type: 'collapse-direct-recursion',
@@ -539,6 +557,36 @@ class CallNodeContextMenuImpl extends React.PureComponent<Props> {
539557
return stringTable.getString(resNameStringIndex);
540558
}
541559

560+
getNameForSelectedSource(): string | null {
561+
const rightClickedCallNodeInfo = this.getRightClickedCallNodeInfo();
562+
563+
if (rightClickedCallNodeInfo === null) {
564+
throw new Error(
565+
"The context menu assumes there is a selected call node and there wasn't one."
566+
);
567+
}
568+
569+
const {
570+
callNodeInfo,
571+
callNodeIndex,
572+
thread: { funcTable, stringTable, sources },
573+
} = rightClickedCallNodeInfo;
574+
575+
const funcIndex = callNodeInfo.funcForNode(callNodeIndex);
576+
if (funcIndex === undefined) {
577+
return null;
578+
}
579+
if (!funcTable.isJS[funcIndex]) {
580+
return null;
581+
}
582+
const sourceIndex = funcTable.source[funcIndex];
583+
if (sourceIndex === null) {
584+
return null;
585+
}
586+
const fileNameIndex = sources.filename[sourceIndex];
587+
return stringTable.getString(fileNameIndex);
588+
}
589+
542590
getRightClickedCallNodeInfo(): null | {
543591
readonly thread: Thread;
544592
readonly threadsKey: ThreadsKey;
@@ -602,6 +650,7 @@ class CallNodeContextMenuImpl extends React.PureComponent<Props> {
602650
const hasCategory = categoryIndex !== -1;
603651
// This could be the C++ library, or the JS filename.
604652
const nameForResource = this.getNameForSelectedResource();
653+
const nameForSource = this.getNameForSelectedSource();
605654
const categoryName: string = hasCategory
606655
? categories[categoryIndex].name
607656
: '';
@@ -740,6 +789,22 @@ class CallNodeContextMenuImpl extends React.PureComponent<Props> {
740789
})
741790
: null}
742791

792+
{nameForSource
793+
? this.renderTransformMenuItem({
794+
l10nId: 'CallNodeContextMenu--transform-collapse-source',
795+
l10nProps: {
796+
vars: { nameForSource: nameForSource },
797+
elems: { strong: <strong /> },
798+
},
799+
shortcut: 'X',
800+
icon: 'Collapse',
801+
onClick: this._handleClick,
802+
transform: 'collapse-source',
803+
title: '',
804+
content: `Collapse source <strong>${nameForSource}</strong>`,
805+
})
806+
: null}
807+
743808
{funcHasRecursiveCall(callNodeTable, funcIndex)
744809
? this.renderTransformMenuItem({
745810
l10nId: 'CallNodeContextMenu--transform-collapse-recursion',
@@ -951,6 +1016,7 @@ export const CallNodeContextMenu = explicitConnect<
9511016
mapDispatchToProps: {
9521017
addTransformToStack,
9531018
addCollapseResourceTransformToStack,
1019+
addCollapseSourceTransformToStack,
9541020
expandAllCallNodeDescendants,
9551021
updateBottomBoxContentsAndMaybeOpen,
9561022
setContextMenuVisibility,

src/profile-logic/index-translation.ts

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
CallNodePath,
77
IndexIntoFuncTable,
88
IndexIntoResourceTable,
9+
IndexIntoSourceTable,
910
ProfileIndexTranslationMaps,
1011
} from 'firefox-profiler/types';
1112

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

25+
// Returns the new source index for the given old source index.
26+
// Returns null if the index has no new index equivalent.
27+
export function translateSourceIndex(
28+
sourceIndex: IndexIntoSourceTable,
29+
translationMaps: ProfileIndexTranslationMaps
30+
): IndexIntoSourceTable | null {
31+
const newSourceIndexPlusOne =
32+
translationMaps.oldSourceToNewSourcePlusOne[sourceIndex];
33+
return newSourceIndexPlusOne !== 0 ? newSourceIndexPlusOne - 1 : null;
34+
}
35+
2436
// Returns the new func index for the given old func index.
25-
// This handles indexes for "reserved funcs" for collapsed resources, which
26-
// are located after the regular funcTable.
37+
// This handles indexes for "reserved funcs" for collapsed resources and
38+
// collapsed sources, which are located after the regular funcTable.
2739
// Returns null if the index has no new index equivalent.
2840
export function translateFuncIndex(
2941
funcIndex: IndexIntoFuncTable,
@@ -35,14 +47,26 @@ export function translateFuncIndex(
3547
translationMaps.oldFuncToNewFuncPlusOne[funcIndex];
3648
return newFuncIndexPlusOne !== 0 ? newFuncIndexPlusOne - 1 : null;
3749
}
38-
// This must be a funcIndex from the "func table with reserved functions for collapsed resources".
39-
const resourceIndex = funcIndex - oldFuncCount;
40-
const newResourceIndex = translateResourceIndex(
41-
resourceIndex,
42-
translationMaps
43-
);
44-
return newResourceIndex !== null
45-
? translationMaps.newFuncCount + newResourceIndex
50+
const reservedOffset = funcIndex - oldFuncCount;
51+
const oldResourceCount = translationMaps.oldResourceCount;
52+
if (reservedOffset < oldResourceCount) {
53+
// This is a reserved func for a collapsed resource.
54+
const resourceIndex = reservedOffset;
55+
const newResourceIndex = translateResourceIndex(
56+
resourceIndex,
57+
translationMaps
58+
);
59+
return newResourceIndex !== null
60+
? translationMaps.newFuncCount + newResourceIndex
61+
: null;
62+
}
63+
// This is a reserved func for a collapsed source.
64+
const sourceIndex = reservedOffset - oldResourceCount;
65+
const newSourceIndex = translateSourceIndex(sourceIndex, translationMaps);
66+
return newSourceIndex !== null
67+
? translationMaps.newFuncCount +
68+
translationMaps.newResourceCount +
69+
newSourceIndex
4670
: null;
4771
}
4872

src/profile-logic/merge-compare.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,8 @@ export function mergeSharedData(profiles: Profile[]): {
503503
oldThreadIndexToNew: null,
504504
oldFuncCount: profile.shared.funcTable.length,
505505
newFuncCount: newFuncTable.length,
506+
oldResourceCount: profile.shared.resourceTable.length,
507+
newResourceCount: newResourceTable.length,
506508
oldLibToNewLibPlusOne,
507509
oldStringToNewStringPlusOne,
508510
oldSourceToNewSourcePlusOne,

src/profile-logic/profile-data.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3281,18 +3281,27 @@ export function getOriginAnnotationForFunc(
32813281
*
32823282
* This returns a new thread with an extended funcTable.
32833283
*
3284-
* At the moment, the only functions we reserve are "collapsed resource" functions.
3285-
* These are used by the "collapse resource" transform.
3284+
* We reserve two sets of functions:
3285+
* - "collapsed resource" functions (used by the "collapse-resource" transform)
3286+
* - "collapsed source" functions (used by the "collapse-source" transform)
3287+
*
3288+
* Resource reserved funcs occupy indices [funcCount, funcCount + resourceCount).
3289+
* Source reserved funcs occupy indices [funcCount + resourceCount, funcCount + resourceCount + sourceCount).
32863290
*/
32873291
export function reserveFunctionsForCollapsedResources(
32883292
originalFuncTable: FuncTable,
3289-
resourceTable: ResourceTable
3293+
resourceTable: ResourceTable,
3294+
sourceTable: SourceTable
32903295
): FuncTableWithReservedFunctions {
32913296
const funcTable = shallowCloneFuncTable(originalFuncTable);
32923297
const reservedFunctionsForResources = new Map<
32933298
IndexIntoResourceTable,
32943299
IndexIntoFuncTable
32953300
>();
3301+
const reservedFunctionsForSources = new Map<
3302+
IndexIntoSourceTable,
3303+
IndexIntoFuncTable
3304+
>();
32963305
const jsResourceTypes = [
32973306
ResourceType.Addon,
32983307
ResourceType.Url,
@@ -3318,9 +3327,23 @@ export function reserveFunctionsForCollapsedResources(
33183327
funcTable.length++;
33193328
reservedFunctionsForResources.set(resourceIndex, funcIndex);
33203329
}
3330+
for (let sourceIndex = 0; sourceIndex < sourceTable.length; sourceIndex++) {
3331+
const name = sourceTable.filename[sourceIndex];
3332+
const funcIndex = funcTable.length;
3333+
funcTable.isJS.push(true);
3334+
funcTable.relevantForJS.push(true);
3335+
funcTable.name.push(name);
3336+
funcTable.resource.push(-1);
3337+
funcTable.source.push(sourceIndex);
3338+
funcTable.lineNumber.push(null);
3339+
funcTable.columnNumber.push(null);
3340+
funcTable.length++;
3341+
reservedFunctionsForSources.set(sourceIndex, funcIndex);
3342+
}
33213343
return {
33223344
funcTable,
33233345
reservedFunctionsForResources,
3346+
reservedFunctionsForSources,
33243347
};
33253348
}
33263349

src/profile-logic/sanitize.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,9 @@ export function sanitizePII(
390390
oldFuncCount: profile.shared.funcTable.length,
391391
newFuncCount:
392392
compactedProfileWithTranslationMaps.profile.shared.funcTable.length,
393+
oldResourceCount: profile.shared.resourceTable.length,
394+
newResourceCount:
395+
compactedProfileWithTranslationMaps.profile.shared.resourceTable.length,
393396
...compactedProfileWithTranslationMaps.translationMaps,
394397
},
395398
};

0 commit comments

Comments
 (0)