Skip to content

Commit 47ee093

Browse files
committed
Allow exporting argument values in profiles
This adds a checkbox which will allow the user to include argument values in exported profiles. The export also respects the checkbox to only export the specified timerange by reconstructing the values buffer to only include arguments referenced by included samples.
1 parent eeb50fa commit 47ee093

15 files changed

Lines changed: 156 additions & 13 deletions

File tree

locales/en-US/app.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,7 @@ MenuButtons--publish--renderCheckbox-label-preference = Include preference value
724724
MenuButtons--publish--renderCheckbox-label-private-browsing = Include the data from private browsing windows
725725
MenuButtons--publish--renderCheckbox-label-private-browsing-warning-image =
726726
.title = This profile contains private browsing data
727+
MenuButtons--publish--renderCheckbox-label-argument-values = Include JavaScript execution tracing function argument values
727728
MenuButtons--publish--reupload-performance-profile = Re-upload Performance Profile
728729
MenuButtons--publish--share-performance-profile = Share Performance Profile
729730
MenuButtons--publish--info-description = Upload your profile and make it accessible to anyone with the link.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@
7979
"common-tags": "^1.8.2",
8080
"copy-to-clipboard": "^3.3.3",
8181
"core-js": "^3.48.0",
82-
"devtools-reps": "^0.27.4",
82+
"devtools-reps": "^0.27.6",
8383
"escape-string-regexp": "^4.0.0",
8484
"gecko-profiler-demangle": "^0.4.0",
8585
"idb": "^8.0.3",

src/components/app/MenuButtons/Publish.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
getProfileRootRange,
1717
getHasPreferenceMarkers,
1818
getContainsPrivateBrowsingInformation,
19+
getHasJSTracingArgumentValues,
1920
} from 'firefox-profiler/selectors/profile';
2021
import {
2122
getAbortFunction,
@@ -58,6 +59,7 @@ type StateProps = {
5859
readonly rootRange: StartEndRange;
5960
readonly shouldShowPreferenceOption: boolean;
6061
readonly profileContainsPrivateBrowsingInformation: boolean;
62+
readonly profileHasJSTracingArgumentValues: boolean;
6163
readonly checkedSharingOptions: CheckedSharingOptions;
6264
readonly sanitizedProfileEncodingState: SanitizedProfileEncodingState;
6365
readonly downloadFileName: string;
@@ -123,6 +125,7 @@ class PublishPanelImpl extends React.PureComponent<PublishProps, {}> {
123125
const {
124126
shouldShowPreferenceOption,
125127
profileContainsPrivateBrowsingInformation,
128+
profileHasJSTracingArgumentValues,
126129
sanitizedProfileEncodingState,
127130
downloadFileName,
128131
shouldSanitizeByDefault,
@@ -210,6 +213,12 @@ class PublishPanelImpl extends React.PureComponent<PublishProps, {}> {
210213
</Localized>
211214
)
212215
: null}
216+
{profileHasJSTracingArgumentValues
217+
? this._renderCheckbox(
218+
'includeArgumentValues',
219+
'MenuButtons--publish--renderCheckbox-label-argument-values'
220+
)
221+
: null}
213222
</div>
214223
{sanitizedProfileEncodingState.phase === 'ERROR' ? (
215224
<div className="photon-message-bar photon-message-bar-error photon-message-bar-inner-content">
@@ -361,6 +370,7 @@ export const PublishPanel = explicitConnect<
361370
shouldShowPreferenceOption: getHasPreferenceMarkers(state),
362371
profileContainsPrivateBrowsingInformation:
363372
getContainsPrivateBrowsingInformation(state),
373+
profileHasJSTracingArgumentValues: getHasJSTracingArgumentValues(state),
364374
checkedSharingOptions: getCheckedSharingOptions(state),
365375
downloadFileName: getFilenameString(state),
366376
sanitizedProfileEncodingState: getSanitizedProfileEncodingState(state),

src/profile-logic/profile-data.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ import type {
102102
} from 'firefox-profiler/types';
103103
import { SelectedState, ResourceType } from 'firefox-profiler/types';
104104
import type { CallNodeInfo, SuffixOrderIndex } from './call-node-info';
105+
import { bytesToBase64 } from 'firefox-profiler/utils/base64';
106+
import { ValueSummaryReader } from 'devtools-reps';
105107

106108
/**
107109
* Various helpers for dealing with the profile as a data structure.
@@ -2262,12 +2264,50 @@ export function filterCounterSamplesToRange(
22622264
count: samples.count.slice(beginSampleIndex, endSampleIndex),
22632265
number: samples.number
22642266
? samples.number.slice(beginSampleIndex, endSampleIndex)
2265-
: undefined
2267+
: undefined,
22662268
};
22672269

22682270
return newCounter;
22692271
}
22702272

2273+
/**
2274+
* Filter a traced values buffer to only include entries that are referenced
2275+
* by the given argument values array. This is used during sanitization when
2276+
* filtering to a committed time range.
2277+
*/
2278+
export function filterTracedValuesBufferToEntries(
2279+
tracedValuesBuffer: ArrayBuffer,
2280+
thread: RawThread
2281+
): RawThread {
2282+
if (
2283+
!thread.samples.argumentValues ||
2284+
!thread.tracedValuesBuffer ||
2285+
!thread.tracedObjectShapes
2286+
) {
2287+
throw new Error(
2288+
'filterTracedValuesBufferToEntries should only be called with JS Execution Tracer profiles'
2289+
);
2290+
}
2291+
2292+
const newThread: RawThread = { ...thread };
2293+
const argumentValues: Array<number | null> = [
2294+
...thread.samples.argumentValues,
2295+
];
2296+
2297+
const filtered = ValueSummaryReader.filterValuesBufferToEntries(
2298+
tracedValuesBuffer,
2299+
argumentValues
2300+
);
2301+
2302+
newThread.tracedValuesBuffer = bytesToBase64(filtered.valuesBuffer);
2303+
newThread.samples = {
2304+
...newThread.samples,
2305+
argumentValues: filtered.entryIndices,
2306+
};
2307+
2308+
return newThread;
2309+
}
2310+
22712311
/**
22722312
* Process the samples in the counter.
22732313
*/

src/profile-logic/sanitize.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { getSchemaFromMarker } from './marker-schema';
2222
import {
2323
filterRawThreadSamplesToRange,
2424
filterCounterSamplesToRange,
25+
filterTracedValuesBufferToEntries,
2526
} from './profile-data';
2627
import type {
2728
Profile,
@@ -58,6 +59,7 @@ const PRIVATE_BROWSING_STACK = 1;
5859
export function sanitizePII(
5960
profile: Profile,
6061
derivedMarkerInfoForAllThreads: DerivedMarkerInfo[],
62+
tracedValuesBuffers: Array<ArrayBuffer | undefined>,
6163
maybePIIToBeRemoved: RemoveProfileInformation | null,
6264
markerSchemaByName: MarkerSchemaByName
6365
): SanitizeProfileResult {
@@ -306,6 +308,7 @@ export function sanitizePII(
306308
thread,
307309
stringTable,
308310
derivedMarkerInfoForAllThreads[threadIndex],
311+
tracedValuesBuffers[threadIndex],
309312
threadIndex,
310313
PIIToBeRemoved,
311314
windowIdFromPrivateBrowsing,
@@ -420,6 +423,7 @@ function sanitizeThreadPII(
420423
thread: RawThread,
421424
stringTable: StringTable,
422425
derivedMarkerInfo: DerivedMarkerInfo,
426+
tracedValuesBuffer: ArrayBuffer | undefined,
423427
threadIndex: number,
424428
PIIToBeRemoved: RemoveProfileInformation,
425429
windowIdFromPrivateBrowsing: Set<InnerWindowID>,
@@ -592,8 +596,22 @@ function sanitizeThreadPII(
592596
delete newThread['eTLD+1'];
593597
}
594598

595-
delete newThread.tracedValuesBuffer;
596-
delete newThread.tracedObjectShapes;
599+
if (
600+
newThread.samples.argumentValues &&
601+
tracedValuesBuffer &&
602+
newThread.tracedObjectShapes &&
603+
!PIIToBeRemoved.shouldRemoveArgumentValues
604+
) {
605+
newThread = filterTracedValuesBufferToEntries(
606+
tracedValuesBuffer,
607+
newThread
608+
);
609+
} else {
610+
delete newThread.tracedValuesBuffer;
611+
delete newThread.tracedObjectShapes;
612+
newThread.samples = { ...newThread.samples };
613+
delete newThread.samples.argumentValues;
614+
}
597615

598616
const { samples } = newThread;
599617
if (stackFlags !== null && windowIdFromPrivateBrowsing.size > 0) {

src/reducers/publish.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ function _getSanitizingSharingOptions(): CheckedSharingOptions {
2525
includeExtension: false,
2626
includePreferenceValues: false,
2727
includePrivateBrowsingData: false,
28+
includeArgumentValues: false,
2829
};
2930
}
3031

@@ -39,6 +40,8 @@ function _getMostlyNonSanitizingSharingOptions(): CheckedSharingOptions {
3940
includePreferenceValues: true,
4041
// We always want to sanitize the private browsing data by default
4142
includePrivateBrowsingData: false,
43+
// We always want to sanitize the argument values by default since they may contain PII
44+
includeArgumentValues: false,
4245
};
4346
}
4447

src/selectors/profile.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,13 @@ export const getContainsPrivateBrowsingInformation: Selector<boolean> =
912912
return hasPrivateThreads;
913913
});
914914

915+
// Gets whether this profile contains argument values from JS execution tracing.
916+
export const getHasJSTracingArgumentValues: Selector<boolean> = createSelector(
917+
getThreads,
918+
(threads) =>
919+
threads.some((thread) => thread.samples.argumentValues !== undefined)
920+
);
921+
915922
/**
916923
* Returns the TIDs of the threads that are profiled.
917924
*/

src/selectors/publish.ts

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
getLocalTracksByPid,
1414
getHasPreferenceMarkers,
1515
getContainsPrivateBrowsingInformation,
16+
getHasJSTracingArgumentValues,
1617
getThreads,
1718
getMarkerSchemaByName,
1819
} from './profile';
@@ -80,6 +81,7 @@ export const getRemoveProfileInformation: Selector<RemoveProfileInformation | nu
8081
getLocalTracksByPid,
8182
getHasPreferenceMarkers,
8283
getContainsPrivateBrowsingInformation,
84+
getHasJSTracingArgumentValues,
8385
(
8486
checkedSharingOptions,
8587
profile,
@@ -89,14 +91,15 @@ export const getRemoveProfileInformation: Selector<RemoveProfileInformation | nu
8991
globalTracks,
9092
localTracksByPid,
9193
hasPreferenceMarkers,
92-
containsPrivateBrowsingInformation
94+
containsPrivateBrowsingInformation,
95+
hasArgumentValues
9396
) => {
9497
let isIncludingEverything = true;
9598
for (const [prop, value] of Object.entries(checkedSharingOptions)) {
96-
// Do not include preference values or private browsing checkboxes if
97-
// they're hidden. Even though `includePreferenceValues` is not taken
98-
// into account, it is false, if the profile updateChannel is not
99-
// nightly or custom build.
99+
// Do not include preference values, private browsing, or argument
100+
// values checkboxes if they're hidden. Even though
101+
// `includePreferenceValues` is not taken into account, it is false, if
102+
// the profile updateChannel is not nightly or custom build.
100103
if (prop === 'includePreferenceValues' && !hasPreferenceMarkers) {
101104
continue;
102105
}
@@ -106,6 +109,9 @@ export const getRemoveProfileInformation: Selector<RemoveProfileInformation | nu
106109
) {
107110
continue;
108111
}
112+
if (prop === 'includeArgumentValues' && !hasArgumentValues) {
113+
continue;
114+
}
109115
isIncludingEverything = isIncludingEverything && value;
110116
}
111117
if (isIncludingEverything) {
@@ -177,6 +183,8 @@ export const getRemoveProfileInformation: Selector<RemoveProfileInformation | nu
177183
!checkedSharingOptions.includePreferenceValues,
178184
shouldRemovePrivateBrowsingData:
179185
!checkedSharingOptions.includePrivateBrowsingData,
186+
shouldRemoveArgumentValues:
187+
!checkedSharingOptions.includeArgumentValues,
180188
};
181189
}
182190
);
@@ -202,6 +210,25 @@ function getDerivedMarkerInfoForAllThreads(state: State): DerivedMarkerInfo[] {
202210
return _derivedMarkerInfo;
203211
}
204212

213+
/**
214+
* The traced values buffers are needed for profile sanitization when filtering
215+
* argument values to a time range. Similar memoization approach as above.
216+
*/
217+
let _threadsForBuffers: any = null;
218+
let _tracedValuesBuffers: Array<ArrayBuffer | undefined> | null = null;
219+
function getTracedValuesBuffersForAllThreads(
220+
state: State
221+
): Array<ArrayBuffer | undefined> {
222+
const threads = getThreads(state);
223+
if (_threadsForBuffers !== threads || _tracedValuesBuffers === null) {
224+
_threadsForBuffers = threads;
225+
_tracedValuesBuffers = threads.map((_: any, threadIndex: ThreadIndex) =>
226+
getThreadSelectors(threadIndex).getTracedValuesBuffer(state)
227+
);
228+
}
229+
return _tracedValuesBuffers;
230+
}
231+
205232
/**
206233
* Run the profile sanitization step, and also get information about how any
207234
* UrlState needs to be updated, with things like mapping thread indexes,
@@ -211,6 +238,7 @@ export const getSanitizedProfile: Selector<SanitizeProfileResult> =
211238
createSelector(
212239
getProfile,
213240
getDerivedMarkerInfoForAllThreads,
241+
getTracedValuesBuffersForAllThreads,
214242
getRemoveProfileInformation,
215243
getMarkerSchemaByName,
216244
sanitizePII

src/test/store/publish.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ describe('getCheckedSharingOptions', function () {
7373
describe('default filtering by channel', function () {
7474
const isFiltering = {
7575
includeExtension: false,
76+
includeArgumentValues: false,
7677
includeFullTimeRange: false,
7778
includeHiddenThreads: false,
7879
includeAllTabs: false,
@@ -83,6 +84,7 @@ describe('getCheckedSharingOptions', function () {
8384
};
8485
const isNotFiltering = {
8586
includeExtension: true,
87+
includeArgumentValues: false,
8688
includeFullTimeRange: true,
8789
includeHiddenThreads: true,
8890
includeAllTabs: true,

src/test/unit/sanitize.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ describe('sanitizePII', function () {
4646
shouldRemoveExtensions: false,
4747
shouldRemovePreferenceValues: false,
4848
shouldRemovePrivateBrowsingData: false,
49+
shouldRemoveArgumentValues: false,
4950
};
5051

5152
const PIIToRemove: RemoveProfileInformation = {
@@ -132,9 +133,12 @@ describe('sanitizePII', function () {
132133
},
133134
};
134135

136+
const tracedValuesBuffers = originalProfile.threads.map(() => undefined);
137+
135138
const sanitizedProfile = sanitizePII(
136139
originalProfile,
137140
derivedMarkerInfoForAllThreads,
141+
tracedValuesBuffers,
138142
PIIToRemove,
139143
markerSchemaByName
140144
).profile;

0 commit comments

Comments
 (0)