From 439932989414be459320bb6772cc08da244812ef Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Sat, 1 Mar 2025 00:51:33 -0500 Subject: [PATCH 1/2] Don't assume that sanitization keeps string indexes unchanged in this test. --- src/test/unit/sanitize.test.js | 60 +++++++++++++--------------------- 1 file changed, 22 insertions(+), 38 deletions(-) diff --git a/src/test/unit/sanitize.test.js b/src/test/unit/sanitize.test.js index 15ecaf3462..7f13fb5e56 100644 --- a/src/test/unit/sanitize.test.js +++ b/src/test/unit/sanitize.test.js @@ -1349,27 +1349,13 @@ describe('sanitizePII', function () { }) ); - const indexForGCMinor = - originalProfile.threads[0].stringArray.indexOf('GCMinor'); - expect(indexForGCMinor).not.toBe(-1); - expect(originalProfile.threads[0].markers.name).toContain( - indexForGCMinor + const originalMarkerNames = originalProfile.threads[0].markers.name.map( + (stringIndex) => originalProfile.threads[0].stringArray[stringIndex] ); - const indexForScreenshot = originalProfile.threads[0].stringArray.indexOf( - 'CompositorScreenshot' - ); - expect(indexForScreenshot).not.toBe(-1); - expect(originalProfile.threads[0].markers.name).toContain( - indexForScreenshot - ); - - const indexForTextOnlyMarker = - originalProfile.threads[0].stringArray.indexOf('TextOnlyMarker'); - expect(indexForTextOnlyMarker).not.toBe(-1); - expect(originalProfile.threads[0].markers.name).toContain( - indexForTextOnlyMarker - ); + expect(originalMarkerNames).toContain('GCMinor'); + expect(originalMarkerNames).toContain('CompositorScreenshot'); + expect(originalMarkerNames).toContain('TextOnlyMarker'); // 2. An unsanitized profile also has all the initial markers. expect(unsanitizedProfile.threads[0].markers.data).toContainEqual( @@ -1387,18 +1373,23 @@ describe('sanitizePII', function () { innerWindowID: unknownInnerWindowID, }) ); - expect(unsanitizedProfile.threads[0].markers.name).toContain( - indexForGCMinor - ); - expect(unsanitizedProfile.threads[0].markers.name).toContain( - indexForScreenshot - ); - expect(unsanitizedProfile.threads[0].markers.name).toContain( - indexForTextOnlyMarker - ); + + const unsanitizedMarkerNames = + unsanitizedProfile.threads[0].markers.name.map( + (stringIndex) => + unsanitizedProfile.threads[0].stringArray[stringIndex] + ); + expect(unsanitizedMarkerNames).toContain('GCMinor'); + expect(unsanitizedMarkerNames).toContain('CompositorScreenshot'); + expect(unsanitizedMarkerNames).toContain('TextOnlyMarker'); // 3. Finally check the innerWindowID property of remaining markers in the // sanitized profile. + + const sanitizedMarkerNames = sanitizedProfile.threads[0].markers.name.map( + (stringIndex) => sanitizedProfile.threads[0].stringArray[stringIndex] + ); + // We don't have the markers coming from the first tab. expect(sanitizedProfile.threads[0].markers.data).not.toContainEqual( expect.objectContaining({ @@ -1412,13 +1403,8 @@ describe('sanitizePII', function () { ); // Nor the markers that aren't tied to a tab - expect(sanitizedProfile.threads[0].markers.name).not.toContain( - indexForGCMinor - ); - - expect(sanitizedProfile.threads[0].markers.name).not.toContain( - indexForTextOnlyMarker - ); + expect(sanitizedMarkerNames).not.toContain('GCMinor'); + expect(sanitizedMarkerNames).not.toContain('TextOnlyMarker'); // But we still have the others. expect(sanitizedProfile.threads[0].markers.data).toContainEqual( @@ -1428,9 +1414,7 @@ describe('sanitizePII', function () { ); // Including the screenshots - expect(originalProfile.threads[0].markers.name).toContain( - indexForScreenshot - ); + expect(sanitizedMarkerNames).toContain('CompositorScreenshot'); }); it('removes samples coming from other tabs', () => { From 28d93092faef18b7a4fdecea7b171f6956a4421e Mon Sep 17 00:00:00 2001 From: Markus Stange Date: Sun, 26 Jan 2025 16:42:51 -0500 Subject: [PATCH 2/2] Add a compacting step at the end of profile sanitization. In the future we're planning to share more tables across threads, starting with the string table. If a user wants to upload only visible threads, there's the risk of keeping data from other threads in the profile if more of that data is shared across threads. So this commit adds a "compacting garbage collection" step before uploading, so that only data which is referenced within the sanitized profile is kept in the profile. --- src/profile-logic/profile-compacting.js | 779 ++++++++++++++++++ src/profile-logic/sanitize.js | 3 +- .../__snapshots__/MenuButtons.test.js.snap | 4 +- 3 files changed, 783 insertions(+), 3 deletions(-) create mode 100644 src/profile-logic/profile-compacting.js diff --git a/src/profile-logic/profile-compacting.js b/src/profile-logic/profile-compacting.js new file mode 100644 index 0000000000..061048f93f --- /dev/null +++ b/src/profile-logic/profile-compacting.js @@ -0,0 +1,779 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// @flow + +import { + getEmptyRawStackTable, + getEmptyFrameTable, + getEmptyFuncTable, + getEmptyResourceTable, + getEmptyNativeSymbolTable, +} from './data-structures'; +import { computeStringIndexMarkerFieldsByDataType } from './marker-schema'; + +import type { + Profile, + RawThread, + RawMarkerTable, + IndexIntoStackTable, + RawStackTable, + FrameTable, + FuncTable, + ResourceTable, + NativeSymbolTable, + RawSamplesTable, + NativeAllocationsTable, + JsAllocationsTable, + Lib, +} from 'firefox-profiler/types'; + +export type CompactedProfileWithTranslationMaps = {| + profile: Profile, + sharedDataTranslationMaps: SharedDataTranslationMaps, + threadDataTranslationMapsByThread: ThreadTranslationMaps[], +|}; + +type CompactedThreadWithTranslationMaps = { + compactedThread: RawThread, + translationMaps: ThreadTranslationMaps, +}; + +type ReferencedProfileData = {| + referencedSharedData: ReferencedSharedData, + referencedThreadDataPerThread: ReferencedThreadData[], +|}; + +type ReferencedSharedData = {| + referencedLibs: Uint8Array, +|}; + +type ReferencedThreadData = {| + referencedStacks: Uint8Array, + referencedFrames: Uint8Array, + referencedFuncs: Uint8Array, + referencedResources: Uint8Array, + referencedNativeSymbols: Uint8Array, + referencedStrings: Uint8Array, + referencedLibs: Uint8Array, +|}; + +type SharedDataTranslationMaps = {| + oldLibToNewLibPlusOne: Int32Array, +|}; + +type ThreadTranslationMaps = {| + oldStackToNewStackPlusOne: Int32Array, + oldFrameToNewFramePlusOne: Int32Array, + oldFuncToNewFuncPlusOne: Int32Array, + oldResourceToNewResourcePlusOne: Int32Array, + oldNativeSymbolToNewNativeSymbolPlusOne: Int32Array, + oldStringToNewStringPlusOne: Int32Array, + oldLibToNewLibPlusOne: Int32Array, +|}; + +/** + * Returns a new profile with all unreferenced data removed. + * + * The markers and samples in the profile are the "GC roots". All other data + * tables exist only to make the marker and sample data meaningful. + * (Here, sample data includes allocation samples from thread.jsAllocations and + * thread.nativeAllocations.) + * + * When a profile is uploaded, we allow removing parts of the uploaded data, + * for example by restricting to a time range (which removes samples and markers + * outside of the time range) or by removing entire threads. + * + * computeCompactedProfile makes it so that, once those threads / samples / markers + * are removed, we don't keep around any stacks / frames / strings / etc. which + * were only used by the removed threads / samples / markers. + */ +export function computeCompactedProfile( + profile: Profile +): CompactedProfileWithTranslationMaps { + const stringIndexMarkerFieldsByDataType = + computeStringIndexMarkerFieldsByDataType(profile.meta.markerSchema); + + // Step 1: Gather all references. + const referencedData = _gatherReferencesInProfile( + profile, + stringIndexMarkerFieldsByDataType + ); + + // Step 2: Create new tables for everything, skipping unreferenced entries. + return _createCompactedProfile( + profile, + referencedData, + stringIndexMarkerFieldsByDataType + ); +} + +function _gatherReferencesInProfile( + profile: Profile, + stringIndexMarkerFieldsByDataType: Map +): ReferencedProfileData { + const referencedSharedData: ReferencedSharedData = { + referencedLibs: new Uint8Array(profile.libs.length), + }; + + const referencedThreadDataPerThread = profile.threads.map((thread) => + _gatherReferencesInThread( + thread, + referencedSharedData, + stringIndexMarkerFieldsByDataType + ) + ); + + return { referencedSharedData, referencedThreadDataPerThread }; +} + +function _createCompactedProfile( + profile: Profile, + referencedData: ReferencedProfileData, + stringIndexMarkerFieldsByDataType: Map +): CompactedProfileWithTranslationMaps { + const sharedDataTranslationMaps: SharedDataTranslationMaps = { + oldLibToNewLibPlusOne: new Int32Array(profile.libs.length), + }; + + const newLibs = _createCompactedLibs( + profile.libs, + referencedData.referencedSharedData, + sharedDataTranslationMaps + ); + + const threadDataTranslationMapsByThread = []; + const newThreads = profile.threads.map((thread, threadIndex): RawThread => { + const { compactedThread, translationMaps } = _createCompactedThread( + thread, + referencedData.referencedThreadDataPerThread[threadIndex], + sharedDataTranslationMaps, + stringIndexMarkerFieldsByDataType + ); + threadDataTranslationMapsByThread[threadIndex] = translationMaps; + return compactedThread; + }); + + const newProfile: Profile = { + ...profile, + libs: newLibs, + threads: newThreads, + }; + + return { + profile: newProfile, + sharedDataTranslationMaps, + threadDataTranslationMapsByThread, + }; +} + +function _gatherReferencesInThread( + thread: RawThread, + referencedSharedData: ReferencedSharedData, + stringIndexMarkerFieldsByDataType: Map +): ReferencedThreadData { + const referencedThreadData: ReferencedThreadData = { + referencedStacks: new Uint8Array(thread.stackTable.length), + referencedFrames: new Uint8Array(thread.frameTable.length), + referencedFuncs: new Uint8Array(thread.funcTable.length), + referencedResources: new Uint8Array(thread.resourceTable.length), + referencedNativeSymbols: new Uint8Array(thread.nativeSymbols.length), + referencedStrings: new Uint8Array(thread.stringArray.length), + ...referencedSharedData, + }; + _gatherReferencesInSamples(thread.samples, referencedThreadData); + if (thread.jsAllocations) { + _gatherReferencesInJsAllocations( + thread.jsAllocations, + referencedThreadData + ); + } + if (thread.nativeAllocations) { + _gatherReferencesInNativeAllocations( + thread.nativeAllocations, + referencedThreadData + ); + } + _gatherReferencesInMarkers( + thread.markers, + stringIndexMarkerFieldsByDataType, + referencedThreadData + ); + + _gatherReferencesInStackTable(thread.stackTable, referencedThreadData); + _gatherReferencesInFrameTable(thread.frameTable, referencedThreadData); + _gatherReferencesInFuncTable(thread.funcTable, referencedThreadData); + _gatherReferencesInResourceTable(thread.resourceTable, referencedThreadData); + _gatherReferencesInNativeSymbols(thread.nativeSymbols, referencedThreadData); + return referencedThreadData; +} + +function _createCompactedThread( + thread: RawThread, + references: ReferencedThreadData, + sharedDataTranslationMaps: SharedDataTranslationMaps, + stringIndexMarkerFieldsByDataType: Map +): CompactedThreadWithTranslationMaps { + const translationMaps = { + oldStackToNewStackPlusOne: new Int32Array(thread.stackTable.length), + oldFrameToNewFramePlusOne: new Int32Array(thread.frameTable.length), + oldFuncToNewFuncPlusOne: new Int32Array(thread.funcTable.length), + oldResourceToNewResourcePlusOne: new Int32Array( + thread.resourceTable.length + ), + oldNativeSymbolToNewNativeSymbolPlusOne: new Int32Array( + thread.nativeSymbols.length + ), + oldStringToNewStringPlusOne: new Int32Array(thread.stringArray.length), + ...sharedDataTranslationMaps, + }; + const newStringArray = _createCompactedStringArray( + thread.stringArray, + references, + translationMaps + ); + const newNativeSymbols = _createCompactedNativeSymbols( + thread.nativeSymbols, + references, + translationMaps + ); + const newResourceTable = _createCompactedResourceTable( + thread.resourceTable, + references, + translationMaps + ); + const newFuncTable = _createCompactedFuncTable( + thread.funcTable, + references, + translationMaps + ); + const newFrameTable = _createCompactedFrameTable( + thread.frameTable, + references, + translationMaps + ); + const newStackTable = _createCompactedStackTable( + thread.stackTable, + references, + translationMaps + ); + const newSamples = _createCompactedSamples(thread.samples, translationMaps); + const newJsAllocations = thread.jsAllocations + ? _createCompactedJsAllocations(thread.jsAllocations, translationMaps) + : undefined; + const newNativeAllocations = thread.nativeAllocations + ? _createCompactedNativeAllocations( + thread.nativeAllocations, + translationMaps + ) + : undefined; + const newMarkers = _createCompactedMarkers( + thread.markers, + translationMaps, + stringIndexMarkerFieldsByDataType + ); + const newThread: RawThread = { + ...thread, + stringArray: newStringArray, + nativeSymbols: newNativeSymbols, + resourceTable: newResourceTable, + funcTable: newFuncTable, + frameTable: newFrameTable, + stackTable: newStackTable, + samples: newSamples, + jsAllocations: newJsAllocations, + nativeAllocations: newNativeAllocations, + markers: newMarkers, + }; + + return { compactedThread: newThread, translationMaps }; +} + +function _gatherReferencesInSamples( + samples: RawSamplesTable, + references: ReferencedThreadData +) { + _gatherReferencesInStackCol(samples.stack, references); +} + +function _createCompactedSamples( + samples: RawSamplesTable, + translationMaps: ThreadTranslationMaps +): RawSamplesTable { + return { + ...samples, + stack: _translateStackCol(samples.stack, translationMaps), + }; +} + +function _gatherReferencesInJsAllocations( + jsAllocations: JsAllocationsTable, + references: ReferencedThreadData +) { + _gatherReferencesInStackCol(jsAllocations.stack, references); +} + +function _createCompactedJsAllocations( + jsAllocations: JsAllocationsTable, + translationMaps: ThreadTranslationMaps +): JsAllocationsTable { + return { + ...jsAllocations, + stack: _translateStackCol(jsAllocations.stack, translationMaps), + }; +} + +function _gatherReferencesInNativeAllocations( + nativeAllocations: NativeAllocationsTable, + references: ReferencedThreadData +) { + _gatherReferencesInStackCol(nativeAllocations.stack, references); +} + +function _createCompactedNativeAllocations( + nativeAllocations: NativeAllocationsTable, + translationMaps: ThreadTranslationMaps +): NativeAllocationsTable { + return { + ...nativeAllocations, + stack: _translateStackCol(nativeAllocations.stack, translationMaps), + }; +} + +function _gatherReferencesInStackCol( + stackCol: Array, + references: ReferencedThreadData +) { + const { referencedStacks } = references; + for (let i = 0; i < stackCol.length; i++) { + const stack = stackCol[i]; + if (stack !== null) { + referencedStacks[stack] = 1; + } + } +} + +function _translateStackCol( + stackCol: Array, + translationMaps: ThreadTranslationMaps +): Array { + const { oldStackToNewStackPlusOne } = translationMaps; + const newStackCol = stackCol.slice(); + + for (let i = 0; i < stackCol.length; i++) { + const stack = stackCol[i]; + newStackCol[i] = + stack !== null ? oldStackToNewStackPlusOne[stack] - 1 : null; + } + + return newStackCol; +} + +function _gatherReferencesInMarkers( + markers: RawMarkerTable, + stringIndexMarkerFieldsByDataType: Map, + references: ReferencedThreadData +) { + const { referencedStacks, referencedStrings } = references; + for (let i = 0; i < markers.length; i++) { + referencedStrings[markers.name[i]] = 1; + + const data = markers.data[i]; + if (!data) { + continue; + } + + if (data.cause) { + const stack = data.cause.stack; + if (stack !== null) { + referencedStacks[stack] = 1; + } + } + + if (data.type) { + const stringIndexMarkerFields = stringIndexMarkerFieldsByDataType.get( + data.type + ); + if (stringIndexMarkerFields !== undefined) { + for (const fieldKey of stringIndexMarkerFields) { + const stringIndex = data[fieldKey]; + if (typeof stringIndex === 'number') { + referencedStrings[stringIndex] = 1; + } + } + } + } + } +} + +function _createCompactedMarkers( + markers: RawMarkerTable, + translationMaps: ThreadTranslationMaps, + stringIndexMarkerFieldsByDataType: Map +): RawMarkerTable { + const { oldStackToNewStackPlusOne, oldStringToNewStringPlusOne } = + translationMaps; + const newDataCol = markers.data.slice(); + const newNameCol = markers.name.slice(); + for (let i = 0; i < markers.length; i++) { + newNameCol[i] = oldStringToNewStringPlusOne[markers.name[i]] - 1; + + const data = markers.data[i]; + if (!data) { + continue; + } + + let newData = data; + if (newData.cause) { + const stack = newData.cause.stack; + if (stack !== null) { + newData = { + ...newData, + cause: { + ...newData.cause, + stack: oldStackToNewStackPlusOne[stack] - 1, + }, + }; + } + } + + if (data.type) { + const stringIndexMarkerFields = stringIndexMarkerFieldsByDataType.get( + data.type + ); + if (stringIndexMarkerFields !== undefined) { + for (const fieldKey of stringIndexMarkerFields) { + const stringIndex = data[fieldKey]; + if (typeof stringIndex === 'number') { + newData = { + ...newData, + [fieldKey]: oldStringToNewStringPlusOne[stringIndex] - 1, + }; + } + } + } + } + + newDataCol[i] = (newData: any); + } + + return { + ...markers, + name: newNameCol, + data: newDataCol, + }; +} + +function _gatherReferencesInStackTable( + stackTable: RawStackTable, + references: ReferencedThreadData +) { + const { referencedStacks, referencedFrames } = references; + for (let i = stackTable.length - 1; i >= 0; i--) { + if (referencedStacks[i] === 0) { + continue; + } + + const prefix = stackTable.prefix[i]; + if (prefix !== null) { + referencedStacks[prefix] = 1; + } + referencedFrames[stackTable.frame[i]] = 1; + } +} + +function _createCompactedStackTable( + stackTable: RawStackTable, + { referencedStacks }: ReferencedThreadData, + translationMaps: ThreadTranslationMaps +): RawStackTable { + const { oldStackToNewStackPlusOne, oldFrameToNewFramePlusOne } = + translationMaps; + const newStackTable = getEmptyRawStackTable(); + for (let i = 0; i < stackTable.length; i++) { + if (referencedStacks[i] === 0) { + continue; + } + + const prefix = stackTable.prefix[i]; + + const newIndex = newStackTable.length++; + newStackTable.prefix[newIndex] = + prefix !== null ? oldStackToNewStackPlusOne[prefix] - 1 : null; + newStackTable.frame[newIndex] = + oldFrameToNewFramePlusOne[stackTable.frame[i]] - 1; + + oldStackToNewStackPlusOne[i] = newIndex + 1; + } + + return newStackTable; +} + +function _gatherReferencesInFrameTable( + frameTable: FrameTable, + references: ReferencedThreadData +) { + const { referencedFrames, referencedFuncs, referencedNativeSymbols } = + references; + for (let i = 0; i < frameTable.length; i++) { + if (referencedFrames[i] === 0) { + continue; + } + + referencedFuncs[frameTable.func[i]] = 1; + + const nativeSymbol = frameTable.nativeSymbol[i]; + if (nativeSymbol !== null) { + referencedNativeSymbols[nativeSymbol] = 1; + } + } +} + +function _createCompactedFrameTable( + frameTable: FrameTable, + { referencedFrames }: ReferencedThreadData, + translationMaps: ThreadTranslationMaps +): FrameTable { + const { + oldFrameToNewFramePlusOne, + oldFuncToNewFuncPlusOne, + oldNativeSymbolToNewNativeSymbolPlusOne, + } = translationMaps; + const newFrameTable = getEmptyFrameTable(); + for (let i = 0; i < frameTable.length; i++) { + if (referencedFrames[i] === 0) { + continue; + } + + const nativeSymbol = frameTable.nativeSymbol[i]; + + const newIndex = newFrameTable.length++; + newFrameTable.address[newIndex] = frameTable.address[i]; + newFrameTable.inlineDepth[newIndex] = frameTable.inlineDepth[i]; + newFrameTable.category[newIndex] = frameTable.category[i]; + newFrameTable.subcategory[newIndex] = frameTable.subcategory[i]; + newFrameTable.func[newIndex] = + oldFuncToNewFuncPlusOne[frameTable.func[i]] - 1; + newFrameTable.nativeSymbol[newIndex] = + nativeSymbol !== null + ? oldNativeSymbolToNewNativeSymbolPlusOne[nativeSymbol] - 1 + : null; + newFrameTable.innerWindowID[newIndex] = frameTable.innerWindowID[i]; + newFrameTable.line[newIndex] = frameTable.line[i]; + newFrameTable.column[newIndex] = frameTable.column[i]; + + oldFrameToNewFramePlusOne[i] = newIndex + 1; + } + + return newFrameTable; +} + +function _gatherReferencesInFuncTable( + funcTable: FuncTable, + references: ReferencedThreadData +) { + const { referencedFuncs, referencedStrings, referencedResources } = + references; + for (let i = 0; i < funcTable.length; i++) { + if (referencedFuncs[i] === 0) { + continue; + } + + referencedStrings[funcTable.name[i]] = 1; + + const fileNameIndex = funcTable.fileName[i]; + if (fileNameIndex !== null) { + referencedStrings[fileNameIndex] = 1; + } + + const resource = funcTable.resource[i]; + if (resource !== -1) { + referencedResources[resource] = 1; + } + } +} + +function _createCompactedFuncTable( + funcTable: FuncTable, + { referencedFuncs }: ReferencedThreadData, + translationMaps: ThreadTranslationMaps +): FuncTable { + const { + oldFuncToNewFuncPlusOne, + oldResourceToNewResourcePlusOne, + oldStringToNewStringPlusOne, + } = translationMaps; + const newFuncTable = getEmptyFuncTable(); + for (let i = 0; i < funcTable.length; i++) { + if (referencedFuncs[i] === 0) { + continue; + } + + const resource = funcTable.resource[i]; + const fileName = funcTable.fileName[i]; + + const newIndex = newFuncTable.length++; + newFuncTable.name[newIndex] = + oldStringToNewStringPlusOne[funcTable.name[i]] - 1; + newFuncTable.isJS[newIndex] = funcTable.isJS[i]; + newFuncTable.relevantForJS[newIndex] = funcTable.relevantForJS[i]; + newFuncTable.resource[newIndex] = + resource !== -1 ? oldResourceToNewResourcePlusOne[resource] - 1 : -1; + newFuncTable.fileName[newIndex] = + fileName !== null ? oldStringToNewStringPlusOne[fileName] - 1 : null; + newFuncTable.lineNumber[newIndex] = funcTable.lineNumber[i]; + newFuncTable.columnNumber[newIndex] = funcTable.columnNumber[i]; + + oldFuncToNewFuncPlusOne[i] = newIndex + 1; + } + + return newFuncTable; +} + +function _gatherReferencesInResourceTable( + resourceTable: ResourceTable, + references: ReferencedThreadData +) { + const { referencedResources, referencedStrings, referencedLibs } = references; + for (let i = 0; i < resourceTable.length; i++) { + if (referencedResources[i] === 0) { + continue; + } + + referencedStrings[resourceTable.name[i]] = 1; + + const host = resourceTable.host[i]; + if (host !== null) { + referencedStrings[host] = 1; + } + + const lib = resourceTable.lib[i]; + if (lib !== null) { + referencedLibs[lib] = 1; + } + } +} + +function _createCompactedResourceTable( + resourceTable: ResourceTable, + { referencedResources }: ReferencedThreadData, + translationMaps: ThreadTranslationMaps +): ResourceTable { + const { + oldResourceToNewResourcePlusOne, + oldStringToNewStringPlusOne, + oldLibToNewLibPlusOne, + } = translationMaps; + const newResourceTable = getEmptyResourceTable(); + for (let i = 0; i < resourceTable.length; i++) { + if (referencedResources[i] === 0) { + continue; + } + + const host = resourceTable.host[i]; + const lib = resourceTable.lib[i]; + + const newIndex = newResourceTable.length++; + newResourceTable.name[newIndex] = + oldStringToNewStringPlusOne[resourceTable.name[i]] - 1; + newResourceTable.host[newIndex] = + host !== null ? oldStringToNewStringPlusOne[host] - 1 : null; + newResourceTable.lib[newIndex] = + lib !== null ? oldLibToNewLibPlusOne[lib] - 1 : null; + newResourceTable.type[newIndex] = resourceTable.type[i]; + + oldResourceToNewResourcePlusOne[i] = newIndex + 1; + } + + return newResourceTable; +} + +function _gatherReferencesInNativeSymbols( + nativeSymbols: NativeSymbolTable, + references: ReferencedThreadData +) { + const { referencedNativeSymbols, referencedStrings, referencedLibs } = + references; + for (let i = 0; i < nativeSymbols.length; i++) { + if (referencedNativeSymbols[i] === 0) { + continue; + } + + referencedStrings[nativeSymbols.name[i]] = 1; + referencedLibs[nativeSymbols.libIndex[i]] = 1; + } +} + +function _createCompactedNativeSymbols( + nativeSymbols: NativeSymbolTable, + { referencedNativeSymbols }: ReferencedThreadData, + translationMaps: ThreadTranslationMaps +): NativeSymbolTable { + const { + oldNativeSymbolToNewNativeSymbolPlusOne, + oldStringToNewStringPlusOne, + oldLibToNewLibPlusOne, + } = translationMaps; + const newNativeSymbols = getEmptyNativeSymbolTable(); + for (let i = 0; i < nativeSymbols.length; i++) { + if (referencedNativeSymbols[i] === 0) { + continue; + } + + const newIndex = newNativeSymbols.length++; + newNativeSymbols.name[newIndex] = + oldStringToNewStringPlusOne[nativeSymbols.name[i]] - 1; + newNativeSymbols.libIndex[newIndex] = + oldLibToNewLibPlusOne[nativeSymbols.libIndex[i]] - 1; + newNativeSymbols.address[newIndex] = nativeSymbols.address[i]; + newNativeSymbols.functionSize[newIndex] = nativeSymbols.functionSize[i]; + + oldNativeSymbolToNewNativeSymbolPlusOne[i] = newIndex + 1; + } + + return newNativeSymbols; +} + +function _createCompactedStringArray( + stringArray: string[], + { referencedStrings }: ReferencedThreadData, + translationMaps: ThreadTranslationMaps +): string[] { + const { oldStringToNewStringPlusOne } = translationMaps; + let nextIndex = 0; + const newStringArray = []; + for (let i = 0; i < stringArray.length; i++) { + if (referencedStrings[i] === 0) { + continue; + } + + const newIndex = nextIndex++; + newStringArray[newIndex] = stringArray[i]; + oldStringToNewStringPlusOne[i] = newIndex + 1; + } + + return newStringArray; +} + +function _createCompactedLibs( + libs: Lib[], + referencedSharedData: ReferencedSharedData, + sharedDataTranslationMaps: SharedDataTranslationMaps +): Lib[] { + const { referencedLibs } = referencedSharedData; + const { oldLibToNewLibPlusOne } = sharedDataTranslationMaps; + let nextIndex = 0; + const newLibs = []; + for (let i = 0; i < libs.length; i++) { + if (referencedLibs[i] === 0) { + continue; + } + + const newIndex = nextIndex++; + newLibs[newIndex] = libs[i]; + oldLibToNewLibPlusOne[i] = newIndex + 1; + } + + return newLibs; +} diff --git a/src/profile-logic/sanitize.js b/src/profile-logic/sanitize.js index 0eba33752e..0f2369f4be 100644 --- a/src/profile-logic/sanitize.js +++ b/src/profile-logic/sanitize.js @@ -9,6 +9,7 @@ import { shallowCloneRawMarkerTable, shallowCloneFuncTable, } from './data-structures'; +import { computeCompactedProfile } from './profile-compacting'; import { removeURLs } from '../utils/string'; import { removeNetworkMarkerURLs, @@ -205,7 +206,7 @@ export function sanitizePII( } return { - profile: newProfile, + profile: computeCompactedProfile(newProfile).profile, // Note that the profile was sanitized. isSanitized: true, // Provide a new empty committed range if needed. diff --git a/src/test/components/__snapshots__/MenuButtons.test.js.snap b/src/test/components/__snapshots__/MenuButtons.test.js.snap index 0e79a8879f..d027b2501b 100644 --- a/src/test/components/__snapshots__/MenuButtons.test.js.snap +++ b/src/test/components/__snapshots__/MenuButtons.test.js.snap @@ -2211,7 +2211,7 @@ exports[`app/MenuButtons matches the snapshot for the menu buttons and class="menuButtonsDownloadSize" > ( - 1.63 kB + 1.62 kB ) @@ -2442,7 +2442,7 @@ exports[`app/MenuButtons matches the snapshot for the opened panel for class="menuButtonsDownloadSize" > ( - 1.63 kB + 1.62 kB )