diff --git a/docs-developer/CHANGELOG-formats.md b/docs-developer/CHANGELOG-formats.md index 6d1ed2b5c8..ecd794b08c 100644 --- a/docs-developer/CHANGELOG-formats.md +++ b/docs-developer/CHANGELOG-formats.md @@ -6,6 +6,14 @@ Note that this is not an exhaustive list. Processed profile format upgraders can ## Processed profile format +### Version 70 + +The `lib` column moved off the `resourceTable` and onto the `frameTable`. Previously a frame's library was reached indirectly, via `frame -> func -> resource -> lib`; now the frame points at its library directly, and `resourceTable` has no `lib` column at all. Frames with no library have their lib set to the `-1` sentinel. + +This decouples resources from libraries. + +The `lib` column can optionally be stored as an `Int32Array`, for profiles loaded from [JsonSlabs](https://github.com/mstange/json-slabs/) files (.jslb, .jslb.gz). Regular JS / JSON arrays are still accepted - but note that `-1` (not `null`) must be used regardless of format. + ### Version 69 A new marker schema display location, `timeline-network`, was added. A marker schema can list `timeline-network` in its `display` array to have markers of that type surfaced in the Network track. diff --git a/profiler-cli/src/test/unit/call-tree-formatting.test.ts b/profiler-cli/src/test/unit/call-tree-formatting.test.ts index db77920db4..28f0da1343 100644 --- a/profiler-cli/src/test/unit/call-tree-formatting.test.ts +++ b/profiler-cli/src/test/unit/call-tree-formatting.test.ts @@ -52,9 +52,8 @@ function buildTopDownResult( const state = store.getState(); const threadSelectors = getThreadSelectors(0); const callTree = threadSelectors.getCallTree(state); - const libs = profile.libs; - const regularCallTree = collectCallTree(callTree, libs, options); + const regularCallTree = collectCallTree(callTree, options); return { type: 'thread-samples-top-down', @@ -76,7 +75,6 @@ function buildBottomUpResult( const store = storeWithProfile(profile); const state = store.getState(); const threadSelectors = getThreadSelectors(0); - const libs = profile.libs; // Build inverted call tree (bottom-up view) let collectedInvertedTree = null; @@ -118,7 +116,7 @@ function buildBottomUpResult( weightType ); - collectedInvertedTree = collectCallTree(invertedTree, libs, options); + collectedInvertedTree = collectCallTree(invertedTree, options); } catch (e) { // Failed to create inverted tree console.error('Failed to create inverted call tree:', e); diff --git a/src/actions/profile-view.ts b/src/actions/profile-view.ts index 80c350cfd9..6ef3391213 100644 --- a/src/actions/profile-view.ts +++ b/src/actions/profile-view.ts @@ -1949,7 +1949,6 @@ export function updateBottomBoxContentsAndMaybeOpen( bottomBoxInfo: BottomBoxInfo ): Action { const { - libIndex, sourceIndex, nativeSymbols, initialNativeSymbol, @@ -1970,7 +1969,6 @@ export function updateBottomBoxContentsAndMaybeOpen( return { type: 'UPDATE_BOTTOM_BOX', - libIndex, sourceIndex, nativeSymbols, currentNativeSymbol: initialNativeSymbol, diff --git a/src/app-logic/constants.ts b/src/app-logic/constants.ts index 1f6b47f2d5..f8a4d98330 100644 --- a/src/app-logic/constants.ts +++ b/src/app-logic/constants.ts @@ -12,7 +12,7 @@ export const GECKO_PROFILE_VERSION = 36; // The current version of the "processed" profile format. // Please don't forget to update the processed profile format changelog in // `docs-developer/CHANGELOG-formats.md`. -export const PROCESSED_PROFILE_VERSION = 69; +export const PROCESSED_PROFILE_VERSION = 70; // The following are the margin sizes for the left and right of the timeline. Independent // components need to share these values. diff --git a/src/app-logic/url-handling.ts b/src/app-logic/url-handling.ts index 85a0b35e88..76475bf370 100644 --- a/src/app-logic/url-handling.ts +++ b/src/app-logic/url-handling.ts @@ -565,7 +565,6 @@ export function stateFromLocation( toValidTabSlug(pathParts[selectedTabPathPart]) || 'calltree'; const sourceView: SourceViewState = { scrollGeneration: 0, - libIndex: null, sourceIndex: null, highlightedLine: null, }; diff --git a/src/profile-logic/bottom-box.ts b/src/profile-logic/bottom-box.ts index cbd935ed8f..9ecc043399 100644 --- a/src/profile-logic/bottom-box.ts +++ b/src/profile-logic/bottom-box.ts @@ -2,8 +2,6 @@ * 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/. */ -import { ResourceType } from 'firefox-profiler/types'; - import type { Thread, IndexIntoStackTable, @@ -37,14 +35,8 @@ export function getBottomBoxInfoForCallNode( thread: Thread, samples: SamplesLikeTable ): BottomBoxInfo { - const { - stackTable, - frameTable, - funcTable, - stringTable, - resourceTable, - nativeSymbols, - } = thread; + const { stackTable, frameTable, funcTable, stringTable, nativeSymbols } = + thread; const funcIndex = callNodeInfo.funcForNode(callNodeIndex); const { source: sourceIndex, line: funcLine } = getOriginalPositionForFrame( @@ -54,11 +46,6 @@ export function getBottomBoxInfoForCallNode( funcTable, thread.sourceLocationTable ); - const resource = funcTable.resource[funcIndex]; - const libIndex = - resource !== -1 && resourceTable.type[resource] === ResourceType.Library - ? resourceTable.lib[resource] - : null; const callNodeFramePerStack = getCallNodeFramePerStack( callNodeIndex, callNodeInfo, @@ -126,7 +113,6 @@ export function getBottomBoxInfoForCallNode( const hottestInstructionAddress = mapGetKeyWithMaxValue(addressTimings); return { - libIndex, sourceIndex, nativeSymbols: nativeSymbolInfosForCallNode, initialNativeSymbol: @@ -148,14 +134,8 @@ export function getBottomBoxInfoForStackFrame( stackIndex: IndexIntoStackTable, thread: Thread ): BottomBoxInfo { - const { - stackTable, - frameTable, - funcTable, - resourceTable, - nativeSymbols, - stringTable, - } = thread; + const { stackTable, frameTable, funcTable, nativeSymbols, stringTable } = + thread; const frameIndex = stackTable.frame[stackIndex]; const funcIndex = frameTable.func[frameIndex]; @@ -166,12 +146,6 @@ export function getBottomBoxInfoForStackFrame( funcTable, thread.sourceLocationTable ); - const resource = funcTable.resource[funcIndex]; - const libIndex = - resource !== -1 && resourceTable.type[resource] === ResourceType.Library - ? resourceTable.lib[resource] - : null; - // Get native symbol for this frame const nativeSymbol = frameTable.nativeSymbol[frameIndex]; const nativeSymbolInfos = @@ -190,7 +164,6 @@ export function getBottomBoxInfoForStackFrame( nativeSymbol !== null ? frameTable.address[frameIndex] : -1; return { - libIndex, sourceIndex, nativeSymbols: nativeSymbolInfos, initialNativeSymbol: 0, diff --git a/src/profile-logic/data-structures.ts b/src/profile-logic/data-structures.ts index 1aa38d66fd..fa574aa539 100644 --- a/src/profile-logic/data-structures.ts +++ b/src/profile-logic/data-structures.ts @@ -35,6 +35,7 @@ import type { IndexIntoSubcategoryListForCategory, IndexIntoNativeSymbolTable, IndexIntoSourceLocationTable, + IndexIntoLibs, InnerWindowID, Address, Bytes, @@ -117,6 +118,7 @@ export type RawFrameTableBuilder = { category: (IndexIntoCategoryList | null)[]; subcategory: (IndexIntoSubcategoryListForCategory | null)[]; func: IndexIntoFuncTable[]; + lib: Array; nativeSymbol: (IndexIntoNativeSymbolTable | null)[]; innerWindowID: (InnerWindowID | null)[]; line: (number | null)[]; @@ -289,6 +291,7 @@ export function getRawFrameTableBuilder(): RawFrameTableBuilder { category: [], subcategory: [], func: [], + lib: [], nativeSymbol: [], innerWindowID: [], line: [], @@ -311,6 +314,7 @@ export function getRawFrameTableBuilderWithExistingContents( category: frameTable.category.slice(), subcategory: frameTable.subcategory.slice(), func: Array.from(frameTable.func), + lib: Array.from(frameTable.lib), nativeSymbol: frameTable.nativeSymbol.slice(), innerWindowID: frameTable.innerWindowID.slice(), line: frameTable.line.slice(), @@ -328,6 +332,7 @@ export function finishRawFrameTableBuilder( address: new Int32Array(builder.address), inlineDepth: new Uint8Array(builder.inlineDepth), func: new Int32Array(builder.func), + lib: new Int32Array(builder.lib), }; } @@ -409,7 +414,6 @@ export function getEmptyResourceTable(): ResourceTable { // If modifying this structure, please update all callers of this function to ensure // that they are pushing on correctly to the data structure. These pushes may not // be caught by the type system. - lib: [], name: [], host: [], type: [], diff --git a/src/profile-logic/global-data-collector.ts b/src/profile-logic/global-data-collector.ts index 03333b58c7..01ae67b233 100644 --- a/src/profile-logic/global-data-collector.ts +++ b/src/profile-logic/global-data-collector.ts @@ -61,8 +61,6 @@ export class GlobalDataCollector { _funcKeyToFuncIndex: Map = new Map(); _nativeSymbolKeyToNativeSymbolIndex: Map = new Map(); - _libIndexToResourceIndex: Map = - new Map(); _libNameToResourceIndex: Map = new Map(); _originToResourceIndex: Map = new Map(); @@ -179,7 +177,6 @@ export class GlobalDataCollector { const idIndex = this._stringTable.indexForString(extensions.id[i]); - resourceTable.lib[resourceIndex] = null; resourceTable.name[resourceIndex] = this._stringTable.indexForString(name); resourceTable.host[resourceIndex] = idIndex; @@ -223,7 +220,6 @@ export class GlobalDataCollector { this._originToResourceIndex.set(origin, resourceIndex); if (host) { // This is a webhost URL. - resourceTable.lib[resourceIndex] = null; resourceTable.name[resourceIndex] = this._stringTable.indexForString(origin); resourceTable.host[resourceIndex] = @@ -232,7 +228,6 @@ export class GlobalDataCollector { } else { // This is a URL, but it doesn't point to something on the web, e.g. a // chrome url. - resourceTable.lib[resourceIndex] = null; resourceTable.name[resourceIndex] = this._stringTable.indexForString(scriptURI); resourceTable.host[resourceIndex] = null; @@ -241,26 +236,7 @@ export class GlobalDataCollector { return resourceIndex; } - indexForLibResource(libIndex: IndexIntoLibs): IndexIntoResourceTable { - let resourceIndex = this._libIndexToResourceIndex.get(libIndex); - if (resourceIndex !== undefined) { - return resourceIndex; - } - - const resourceTable = this._resourceTable; - - resourceIndex = this._resourceTable.length++; - this._libIndexToResourceIndex.set(libIndex, resourceIndex); - resourceTable.lib[resourceIndex] = libIndex; - resourceTable.name[resourceIndex] = this._stringTable.indexForString( - this._libs[libIndex].name - ); - resourceTable.host[resourceIndex] = null; - resourceTable.type[resourceIndex] = ResourceType.Library; - return resourceIndex; - } - - indexForNameOnlyLibResource( + indexForLibResourceByName( libNameStringIndex: IndexIntoStringTable ): IndexIntoResourceTable { let resourceIndex = this._libNameToResourceIndex.get(libNameStringIndex); @@ -272,13 +248,18 @@ export class GlobalDataCollector { resourceIndex = this._resourceTable.length++; this._libNameToResourceIndex.set(libNameStringIndex, resourceIndex); - resourceTable.lib[resourceIndex] = null; resourceTable.name[resourceIndex] = libNameStringIndex; resourceTable.host[resourceIndex] = null; resourceTable.type[resourceIndex] = ResourceType.Library; return resourceIndex; } + indexForLibResourceByLib(libIndex: IndexIntoLibs): IndexIntoResourceTable { + return this.indexForLibResourceByName( + this._stringTable.indexForString(this._libs[libIndex].name) + ); + } + indexForNativeSymbol( libIndex: IndexIntoLibs, address: Address, diff --git a/src/profile-logic/import/chrome.ts b/src/profile-logic/import/chrome.ts index 0a448666e0..eba5e6879c 100644 --- a/src/profile-logic/import/chrome.ts +++ b/src/profile-logic/import/chrome.ts @@ -699,6 +699,7 @@ async function processTracingEvents( frameTable.category[frameIndex] = category; frameTable.subcategory[frameIndex] = 0; frameTable.func[frameIndex] = funcId; + frameTable.lib[frameIndex] = -1; frameTable.nativeSymbol[frameIndex] = null; frameTable.innerWindowID[frameIndex] = 0; frameTable.line[frameIndex] = diff --git a/src/profile-logic/import/dhat.ts b/src/profile-logic/import/dhat.ts index e0dd3f240c..94c9cc859e 100644 --- a/src/profile-logic/import/dhat.ts +++ b/src/profile-logic/import/dhat.ts @@ -212,6 +212,7 @@ export function attemptToConvertDhat(json: unknown): Profile | null { frameTable.category.push(otherCategory); frameTable.subcategory.push(otherSubCategory); frameTable.innerWindowID.push(null); + frameTable.lib.push(-1); frameTable.nativeSymbol.push(null); frameTable.inlineDepth.push(0); frameTable.func.push(rootFuncIndex); @@ -279,6 +280,7 @@ export function attemptToConvertDhat(json: unknown): Profile | null { frameTable.category.push(otherCategory); frameTable.subcategory.push(otherSubCategory); frameTable.innerWindowID.push(null); + frameTable.lib.push(-1); frameTable.nativeSymbol.push(null); frameTable.inlineDepth.push(0); frameTable.func.push(funcIndex); diff --git a/src/profile-logic/import/flame-graph.ts b/src/profile-logic/import/flame-graph.ts index 6a7e4a1fff..71504573b1 100644 --- a/src/profile-logic/import/flame-graph.ts +++ b/src/profile-logic/import/flame-graph.ts @@ -120,6 +120,7 @@ export function convertFlameGraphProfile(profileText: string): Profile { frameTable.category.push(category); frameTable.subcategory.push(0); frameTable.func.push(funcIndex); + frameTable.lib.push(-1); frameTable.nativeSymbol.push(null); frameTable.innerWindowID.push(null); frameTable.line.push(null); diff --git a/src/profile-logic/import/simpleperf.ts b/src/profile-logic/import/simpleperf.ts index 75fce1f0d9..39be0d9fb2 100644 --- a/src/profile-logic/import/simpleperf.ts +++ b/src/profile-logic/import/simpleperf.ts @@ -99,7 +99,6 @@ class FirefoxResourceTable { findOrAddResource(file: report.IFile): IndexIntoResourceTable { let resourceIndex = this.resourcesMap.get(file.id!); if (!resourceIndex) { - this.resourceTable.lib.push(null); this.resourceTable.name.push(this.strings.indexForString(file.path!)); this.resourceTable.host.push(null); this.resourceTable.type.push(1); // Library @@ -177,6 +176,7 @@ class FirefoxFrameTable { this.frameTable.category.push(category); this.frameTable.subcategory.push(0); this.frameTable.func.push(funcIndex); + this.frameTable.lib.push(-1); this.frameTable.nativeSymbol.push(null); this.frameTable.innerWindowID.push(null); this.frameTable.line.push(null); diff --git a/src/profile-logic/insert-stack-labels.ts b/src/profile-logic/insert-stack-labels.ts index e614569e82..988a30cb23 100644 --- a/src/profile-logic/insert-stack-labels.ts +++ b/src/profile-logic/insert-stack-labels.ts @@ -136,6 +136,7 @@ export function insertStackLabels( frameTable.func[frameIndex] = funcIndex; frameTable.category[frameIndex] = labelCategoryIndex; frameTable.subcategory[frameIndex] = 0; + frameTable.lib[frameIndex] = -1; frameTable.nativeSymbol[frameIndex] = null; frameTable.address[frameIndex] = 0; frameTable.inlineDepth[frameIndex] = 0; diff --git a/src/profile-logic/js-tracer.ts b/src/profile-logic/js-tracer.ts index 92ef451f82..2ad48571c2 100644 --- a/src/profile-logic/js-tracer.ts +++ b/src/profile-logic/js-tracer.ts @@ -610,6 +610,7 @@ export function convertJsTracerToThreadWithoutSamples( frameTable.inlineDepth.push(0); frameTable.category.push(otherCategory); frameTable.func.push(funcIndex); + frameTable.lib.push(-1); frameTable.nativeSymbol.push(null); frameTable.innerWindowID.push(0); frameTable.line.push(line); diff --git a/src/profile-logic/merge-compare.ts b/src/profile-logic/merge-compare.ts index 10e6a3fd14..18e8e7518a 100644 --- a/src/profile-logic/merge-compare.ts +++ b/src/profile-logic/merge-compare.ts @@ -461,11 +461,7 @@ export function mergeSharedData(profiles: Profile[]): { const { resourceTable: newResourceTable, translationMaps: translationMapsForResources, - } = mergeResourceTables( - profiles, - translationMapsForStrings, - translationMapsForLibs - ); + } = mergeResourceTables(profiles, translationMapsForStrings); const { nativeSymbols: newNativeSymbols, translationMaps: translationMapsForNativeSymbols, @@ -490,7 +486,8 @@ export function mergeSharedData(profiles: Profile[]): { translationMapsForFuncs, translationMapsForNativeSymbols, translationMapsForOriginalLocation, - translationMapsForCategories + translationMapsForCategories, + translationMapsForLibs ); const { stackTable: newStackTable, @@ -721,7 +718,7 @@ function mergeLibs(libsPerProfile: Lib[][]): { const oldLibToNewLibPlusOne = new Int32Array(libs.length); libs.forEach((lib, i) => { - const insertedLibKey = [lib.name, lib.debugName].join('#'); + const insertedLibKey = [lib.debugName, lib.breakpadId].join('#'); const insertedLibIndex = mapOfInsertedLibs.get(insertedLibKey); if (insertedLibIndex !== undefined) { oldLibToNewLibPlusOne[i] = insertedLibIndex + 1; @@ -747,11 +744,14 @@ function _mapLib( return oldLibToNewLibPlusOne[libIndex] - 1; } -function _mapNullableLib( - libIndex: IndexIntoLibs | null, +function _mapFrameLib( + libIndex: IndexIntoLibs | -1, oldLibToNewLibPlusOne: TranslationMapForLibs -): IndexIntoLibs | null { - return libIndex !== null ? oldLibToNewLibPlusOne[libIndex] - 1 : null; +): IndexIntoLibs | -1 { + if (libIndex === -1) { + return -1; + } + return oldLibToNewLibPlusOne[libIndex] - 1; } function _mapString( @@ -846,8 +846,7 @@ function _mapNullableStack( */ function mergeResourceTables( profiles: ReadonlyArray, - translationMapsForStrings: TranslationMapForStrings[], - translationMapsForLibs: TranslationMapForLibs[] + translationMapsForStrings: TranslationMapForStrings[] ): { resourceTable: ResourceTable; translationMaps: TranslationMapForResources[]; @@ -857,7 +856,6 @@ function mergeResourceTables( const newResourceTable = getEmptyResourceTable(); profiles.forEach((profile, profileIndex) => { - const oldLibToNewLibPlusOne = translationMapsForLibs[profileIndex]; const oldStringToNewStringPlusOne = translationMapsForStrings[profileIndex]; const { resourceTable } = profile.shared; const oldResourceToNewResourcePlusOne = new Int32Array( @@ -865,10 +863,6 @@ function mergeResourceTables( ); for (let i = 0; i < resourceTable.length; i++) { - const libIndex = _mapNullableLib( - resourceTable.lib[i], - oldLibToNewLibPlusOne - ); const nameIndex = _mapString( resourceTable.name[i], oldStringToNewStringPlusOne @@ -890,7 +884,6 @@ function mergeResourceTables( oldResourceToNewResourcePlusOne[i] = newResourceTable.length + 1; mapOfInsertedResources.set(resourceKey, newResourceTable.length); - newResourceTable.lib.push(libIndex); newResourceTable.name.push(nameIndex); newResourceTable.host.push(hostIndex); newResourceTable.type.push(type); @@ -1059,7 +1052,8 @@ function mergeFrameTables( translationMapsForFuncs: TranslationMapForFuncs[], translationMapsForNativeSymbols: TranslationMapForNativeSymbols[], translationMapsForOriginalLocation: TranslationMapForOriginalLocation[], - translationMapsForCategories: TranslationMapForCategories[] + translationMapsForCategories: TranslationMapForCategories[], + translationMapsForLibs: TranslationMapForLibs[] ): { frameTable: RawFrameTable; translationMaps: TranslationMapForFrames[] } { const translationMaps: TranslationMapForFrames[] = []; const newFrameTable = getRawFrameTableBuilder(); @@ -1073,6 +1067,7 @@ function mergeFrameTables( translationMapsForOriginalLocation[profileIndex]; const oldCategoryToNewCategoryPlusOne = translationMapsForCategories[profileIndex]; + const oldLibToNewLibPlusOne = translationMapsForLibs[profileIndex]; const oldFrameToNewFramePlusOne = new Int32Array(frameTable.length); for (let i = 0; i < frameTable.length; i++) { @@ -1094,6 +1089,9 @@ function mergeFrameTables( newFrameTable.subcategory.push(subcategory); newFrameTable.nativeSymbol.push(nativeSymbol); newFrameTable.func.push(func); + newFrameTable.lib.push( + _mapFrameLib(frameTable.lib[i], oldLibToNewLibPlusOne) + ); newFrameTable.innerWindowID.push(frameTable.innerWindowID[i]); newFrameTable.line.push(frameTable.line[i]); newFrameTable.column.push(frameTable.column[i]); diff --git a/src/profile-logic/process-profile.ts b/src/profile-logic/process-profile.ts index 69c4712e7a..a5ad586acb 100644 --- a/src/profile-logic/process-profile.ts +++ b/src/profile-logic/process-profile.ts @@ -116,6 +116,7 @@ import type { GeckoSourceTable, IndexIntoCategoryList, IndexIntoFrameTable, + IndexIntoLibs, CounterDisplayConfig, RawProfileSharedData, } from 'firefox-profiler/types'; @@ -202,7 +203,11 @@ type ExtractionInfo = { addressLocator: AddressLocator; stringToNewFuncIndexAndFrameAddress: Map< string, - { funcIndex: IndexIntoFuncTable; frameAddress: Address | null } + { + funcIndex: IndexIntoFuncTable; + frameAddress: Address | null; + libIndex: IndexIntoLibs | null; + } >; globalDataCollector: GlobalDataCollector; geckoSourceTable: GeckoSourceTable; @@ -226,6 +231,7 @@ export function extractFuncsAndResourcesFromFrameLocations( ): { frameFuncs: IndexIntoFuncTable[]; frameAddresses: (Address | null)[]; + frameLibs: (IndexIntoLibs | null)[]; } { const stringTable = globalDataCollector.getStringTable(); @@ -242,6 +248,7 @@ export function extractFuncsAndResourcesFromFrameLocations( // information by applying various string matching heuristics. const frameFuncs = []; const frameAddresses = []; + const frameLibs: (IndexIntoLibs | null)[] = []; for (let frameIndex = 0; frameIndex < frameLocations.length; frameIndex++) { const originalLocationIndex = frameLocations[frameIndex]; const locationString = ensureExists( @@ -253,9 +260,10 @@ export function extractFuncsAndResourcesFromFrameLocations( extractionInfo.stringToNewFuncIndexAndFrameAddress.get(locationString); if (info !== undefined) { // The location string was already processed. - const { funcIndex, frameAddress } = info; + const { funcIndex, frameAddress, libIndex } = info; frameFuncs.push(funcIndex); frameAddresses.push(frameAddress); + frameLibs.push(libIndex); continue; } @@ -263,6 +271,7 @@ export function extractFuncsAndResourcesFromFrameLocations( // resource information. let funcIndex = null; let frameAddress = null; + let libIndex: IndexIntoLibs | null = null; const unsymbolicatedInfo = _extractUnsymbolicatedFunction( extractionInfo, locationString, @@ -271,6 +280,7 @@ export function extractFuncsAndResourcesFromFrameLocations( if (unsymbolicatedInfo !== null) { funcIndex = unsymbolicatedInfo.funcIndex; frameAddress = unsymbolicatedInfo.frameAddress; + libIndex = unsymbolicatedInfo.libIndex; } else { funcIndex = _extractCppFunction(extractionInfo, locationString); if (funcIndex === null) { @@ -289,15 +299,18 @@ export function extractFuncsAndResourcesFromFrameLocations( extractionInfo.stringToNewFuncIndexAndFrameAddress.set(locationString, { funcIndex, frameAddress, + libIndex, }); frameFuncs.push(funcIndex); frameAddresses.push(frameAddress); + frameLibs.push(libIndex); } return { frameFuncs, frameAddresses, + frameLibs, }; } @@ -308,23 +321,25 @@ export function extractFuncsAndResourcesFromFrameLocations( * into the same function, so cannot do any function grouping. So we get one "function" per * address. * We also associate the address with the library that contains it, and convert the address - * into a library-relative offset. This association is established via the function's - * "resource": The function points to the resource (of type ResourceType.Library), and the - * resource has the index to the library in thread.libs. - * We return the index of the newly-added function, and the address as a library-relative - * offset. + * into a library-relative offset. The library index goes onto the frame (frameTable.lib); + * and the function additionally gets a "resource" of type ResourceType.Library. */ function _extractUnsymbolicatedFunction( extractionInfo: ExtractionInfo, locationString: string, locationIndex: IndexIntoStringTable -): { funcIndex: IndexIntoFuncTable; frameAddress: Address } | null { +): { + funcIndex: IndexIntoFuncTable; + frameAddress: Address; + libIndex: IndexIntoLibs | null; +} | null { if (!locationString.startsWith('0x')) { return null; } const { addressLocator, globalDataCollector } = extractionInfo; let resourceIndex = -1; + let libIndex: IndexIntoLibs | null = null; let addressRelativeToLib: Address = -1; try { @@ -343,8 +358,8 @@ function _extractUnsymbolicatedFunction( // Yes, we found the library whose mapping covers this address! addressRelativeToLib = relativeAddress; - const libIndex = globalDataCollector.indexForLib(lib); - resourceIndex = globalDataCollector.indexForLibResource(libIndex); + libIndex = globalDataCollector.indexForLib(lib); + resourceIndex = globalDataCollector.indexForLibResourceByLib(libIndex); } } catch (_e) { // Probably a hex parse error. Ignore. @@ -359,7 +374,7 @@ function _extractUnsymbolicatedFunction( null, null ); - return { funcIndex, frameAddress: addressRelativeToLib }; + return { funcIndex, frameAddress: addressRelativeToLib, libIndex }; } /** @@ -401,7 +416,7 @@ function _extractCppFunction( return frameInfo.funcIndex; } - const resourceIndex = globalDataCollector.indexForNameOnlyLibResource( + const resourceIndex = globalDataCollector.indexForLibResourceByName( libraryNameStringIndex ); return globalDataCollector.indexForFunc( @@ -535,7 +550,8 @@ function _processFrameTable( geckoFrameStruct: GeckoFrameStruct, sharedFrameTable: RawFrameTableBuilder, frameFuncs: IndexIntoFuncTable[], - frameAddresses: (Address | null)[] + frameAddresses: (Address | null)[], + frameLibs: (IndexIntoLibs | null)[] ): IndexIntoFrameTable { const frameIndexOffset = sharedFrameTable.length; for (let i = 0; i < geckoFrameStruct.length; i++) { @@ -545,6 +561,7 @@ function _processFrameTable( sharedFrameTable.category[newIndex] = geckoFrameStruct.category[i]; sharedFrameTable.subcategory[newIndex] = geckoFrameStruct.subcategory[i]; sharedFrameTable.func[newIndex] = frameFuncs[i]; + sharedFrameTable.lib[newIndex] = frameLibs[i] ?? -1; sharedFrameTable.nativeSymbol[newIndex] = null; sharedFrameTable.innerWindowID[newIndex] = geckoFrameStruct.innerWindowID[i]; @@ -1327,7 +1344,7 @@ function _processThread( const { libs, pausedRanges, meta, sources } = processProfile; const { shutdownTime } = meta; - const { frameFuncs, frameAddresses } = + const { frameFuncs, frameAddresses, frameLibs } = extractFuncsAndResourcesFromFrameLocations( geckoFrameStruct.location, geckoFrameStruct.relevantForJS, @@ -1341,7 +1358,8 @@ function _processThread( geckoFrameStruct, globalDataCollector.getFrameTable(), frameFuncs, - frameAddresses + frameAddresses, + frameLibs ); const stackIndexOffset = _processStackTable( geckoStackTable, @@ -2113,6 +2131,7 @@ function convertSharedTablesEligibleColumns( address: toInt32Array(frameTable.address), inlineDepth: toUint8Array(frameTable.inlineDepth), func: toInt32Array(frameTable.func), + lib: toInt32Array(frameTable.lib), }, }; } diff --git a/src/profile-logic/processed-profile-versioning.ts b/src/profile-logic/processed-profile-versioning.ts index 0999f5c078..89d5e4e2f9 100644 --- a/src/profile-logic/processed-profile-versioning.ts +++ b/src/profile-logic/processed-profile-versioning.ts @@ -3310,6 +3310,29 @@ const _upgraders: { } } }, + [70]: (profile: any) => { + // The `lib` column moved off the resourceTable and onto the frameTable, so + // that resources and libraries can vary independently: multiple libs can + // now share one resource. + // + // Before: frame -> func -> resource -> lib + // After: frame -> lib + // Frames with no library use the sentinel value -1. + const { frameTable, funcTable, resourceTable } = profile.shared; + const libForFunc = new Int32Array(funcTable.length).fill(-1); + for (let funcIndex = 0; funcIndex < funcTable.length; funcIndex++) { + const resourceIndex = funcTable.resource[funcIndex]; + if (resourceIndex !== -1) { + libForFunc[funcIndex] = resourceTable.lib[resourceIndex] ?? -1; + } + } + const lib = new Array(frameTable.length).fill(-1); + for (let frameIndex = 0; frameIndex < frameTable.length; frameIndex++) { + lib[frameIndex] = libForFunc[frameTable.func[frameIndex]]; + } + frameTable.lib = lib; + delete resourceTable.lib; + }, // If you add a new upgrader here, please document the change in // `docs-developer/CHANGELOG-formats.md`. }; diff --git a/src/profile-logic/profile-compacting.ts b/src/profile-logic/profile-compacting.ts index c7c59ce82b..90eb76dfc7 100644 --- a/src/profile-logic/profile-compacting.ts +++ b/src/profile-logic/profile-compacting.ts @@ -50,6 +50,10 @@ type ColumnDescription = null extends ( : Int32Array extends TCol ? | { type: 'INDEX_REF_INT32'; referencedTable: TableCompactionState } + | { + type: 'INDEX_REF_OR_NEG_ONE_INT32'; + referencedTable: TableCompactionState; + } | { type: 'SELF_RELATIVE_PARENT' } | { type: 'NO_REF' } : @@ -88,6 +92,10 @@ const ColDesc = { type: 'INDEX_REF_OR_NEG_ONE' as const, referencedTable, }), + indexRefOrNegOneInt32: (referencedTable: TableCompactionState) => ({ + type: 'INDEX_REF_OR_NEG_ONE_INT32' as const, + referencedTable, + }), selfPrefixOffset: () => ({ type: 'SELF_RELATIVE_PARENT' as const }), noRef: () => ({ type: 'NO_REF' as const }), }; @@ -170,6 +178,7 @@ export function computeCompactedProfile( category: ColDesc.noRef(), subcategory: ColDesc.noRef(), func: ColDesc.indexRefInt32(tcs.funcTable), + lib: ColDesc.indexRefOrNegOneInt32(tcs.libs), nativeSymbol: ColDesc.indexRefOrNull(tcs.nativeSymbols), innerWindowID: ColDesc.noRef(), line: ColDesc.noRef(), @@ -194,7 +203,6 @@ export function computeCompactedProfile( const resourceTableDesc: TableDescription = { name: ColDesc.indexRef(tcs.stringArray), host: ColDesc.indexRefOrNull(tcs.stringArray), - lib: ColDesc.indexRefOrNull(tcs.libs), type: ColDesc.noRef(), }; const nativeSymbolsDesc: TableDescription = { @@ -355,6 +363,7 @@ function _markTableAndComputeTranslation( case 'SELF_RELATIVE_PARENT': break; // already handled in the first pass case 'INDEX_REF_OR_NEG_ONE': + case 'INDEX_REF_OR_NEG_ONE_INT32': markColumnWithNegOneableFields( col, markBuffer, @@ -419,10 +428,12 @@ function markSelfColumnPrefixOffset( } function markColumnWithNegOneableFields( - col: Array, + col: Array | Int32Array, shouldMark: BitSet, markBuf: BitSet ) { + // Polymorphic: indexing works the same on Int32Array as on number[], so the + // INDEX_REF_OR_NEG_ONE and INDEX_REF_OR_NEG_ONE_INT32 cases share this function. for (let i = 0; i < col.length; i++) { if (checkBit(shouldMark, i)) { const val = col[i]; @@ -554,6 +565,14 @@ function _compactTable( newLength ); break; + case 'INDEX_REF_OR_NEG_ONE_INT32': + result[key] = _compactColIndexOrNegOneInt32( + oldCol, + markBuffer, + desc.referencedTable.oldIndexToNewIndexPlusOne, + newLength + ); + break; case 'NO_REF': result[key] = _compactColCopy(oldCol, markBuffer, newLength); break; @@ -646,6 +665,23 @@ function _compactColIndexOrNegOne( return newCol; } +function _compactColIndexOrNegOneInt32( + oldCol: Array | Int32Array, + markBuffer: BitSet, + oldIndexToNewIndexPlusOne: Int32Array, + newLength: number +): Int32Array { + const newCol = new Int32Array(newLength); + let newIndex = 0; + for (let i = 0; i < oldCol.length; i++) { + if (checkBit(markBuffer, i)) { + const val = oldCol[i]; + newCol[newIndex++] = val !== -1 ? oldIndexToNewIndexPlusOne[val] - 1 : -1; + } + } + return newCol; +} + function _compactColSelfPrefixOffset( oldCol: Int32Array, markBuffer: BitSet, diff --git a/src/profile-logic/profile-data.ts b/src/profile-logic/profile-data.ts index be33781b78..bfc600d87f 100644 --- a/src/profile-logic/profile-data.ts +++ b/src/profile-logic/profile-data.ts @@ -4358,6 +4358,7 @@ export function nudgeReturnAddresses(profile: Profile): Profile { newFrameTable.category.push(frameTable.category[frame]); newFrameTable.subcategory.push(frameTable.subcategory[frame]); newFrameTable.func.push(frameTable.func[frame]); + newFrameTable.lib.push(frameTable.lib[frame]); newFrameTable.nativeSymbol.push(frameTable.nativeSymbol[frame]); newFrameTable.innerWindowID.push(frameTable.innerWindowID[frame]); newFrameTable.line.push(frameTable.line[frame]); @@ -4453,7 +4454,7 @@ export function findAddressProofForFile( sourceIndex: IndexIntoSourceTable ): AddressProof | null { const { libs } = profile; - const { frameTable, funcTable, resourceTable } = profile.shared; + const { frameTable, funcTable } = profile.shared; const func = funcTable.source.indexOf(sourceIndex); if (func === -1) { return null; @@ -4463,15 +4464,11 @@ export function findAddressProofForFile( return null; } const address = frameTable.address[frame]; - if (address === null) { + if (address === -1) { return null; } - const resource = funcTable.resource[func]; - if (resourceTable.type[resource] !== ResourceType.Library) { - return null; - } - const libIndex = resourceTable.lib[resource]; - if (libIndex === null) { + const libIndex = frameTable.lib[frame]; + if (libIndex === -1) { return null; } const lib = libs[libIndex]; @@ -4768,6 +4765,7 @@ export function computeFrameTableFromRawFrameTable( category: rawFrameTable.category, subcategory: rawFrameTable.subcategory, func, + lib: toInt32Array(rawFrameTable.lib), nativeSymbol: rawFrameTable.nativeSymbol, innerWindowID: rawFrameTable.innerWindowID, line: rawFrameTable.line, diff --git a/src/profile-logic/sanitize.ts b/src/profile-logic/sanitize.ts index 9bba0aedc4..81a78c1032 100644 --- a/src/profile-logic/sanitize.ts +++ b/src/profile-logic/sanitize.ts @@ -257,7 +257,6 @@ export function sanitizePII( if (resourcesToBeSanitized.size) { const newResourceTable = (newShared.resourceTable = { ...resourceTable, - lib: resourceTable.lib.slice(), name: resourceTable.name.slice(), host: resourceTable.host.slice(), }); @@ -272,7 +271,6 @@ export function sanitizePII( `` ); newResourceTable.name[resourceIndex] = name; - newResourceTable.lib[resourceIndex] = null; newResourceTable.host[resourceIndex] = null; } } diff --git a/src/profile-logic/symbolication.ts b/src/profile-logic/symbolication.ts index b0c8d1d7e6..5ba00ad5e0 100644 --- a/src/profile-logic/symbolication.ts +++ b/src/profile-logic/symbolication.ts @@ -31,7 +31,6 @@ import type { CallNodePath, Lib, } from 'firefox-profiler/types'; -import { ResourceType } from 'firefox-profiler/types'; import type { AbstractSymbolStore, AddressResult, @@ -119,9 +118,13 @@ import { updateRawThreadStacks } from './profile-data'; * It also creates a nativeSymbols table but leaves it completely empty. * - The frame and its func both get their address field set to the * library-relative offset. - * - The func's resource field is set to a resource of type "library" that - * points to the lib object in the thread's "libs" list that contained this - * address. The frame's and func's address fields are relative to that lib. + * - The frame's lib field is set to the index of the containing library in + * profile.libs. The frame's address field is relative to that lib. + * Symbolication groups frames by this field. + * - The func's resource field is set to a resource of type "library" with the + * name of the library. Multiple libraries with the same name can exist and + * they will share the same resource, for example in a merged profile from + * two different builds of the same library. * - All frames start out with their nativeSymbol field set to null. * - All return addresses are adjusted by subtracting one byte, to point into * the call instruction. See nudgeReturnAddresses for details. @@ -208,8 +211,9 @@ export type SymbolicationStepCallback = ( ) => void; type ProfileLibSymbolicationInfo = { - // The resourceIndex for this lib in this thread. - resourceIndex: IndexIntoResourceTable; + // The resource to give to funcs which are newly created for this lib, or -1 + // if this lib's frames have no resource. Multiple libs can share a resource. + resourceIndex: IndexIntoResourceTable | -1; // The libIndex for this lib in this thread. libIndex: IndexIntoLibs; // The set of funcs for this lib in this thread. @@ -283,78 +287,107 @@ function makeConsensusMap( /** * Gather the symbols needed in this thread, and some auxiliary information that * allows the symbol substitation step at the end to work efficiently. - * Returns a map with one entry for each library resource. + * Returns a map with one entry for each library that has frames in the profile. */ function getSymbolicationInfo( shared: RawProfileSharedData, libs: Lib[] ): ProfileSymbolicationInfo { - const { frameTable, funcTable, nativeSymbols, resourceTable } = shared; + const { frameTable, funcTable, nativeSymbols } = shared; - const map = new Map(); - for ( - let resourceIndex = 0; - resourceIndex < resourceTable.length; - resourceIndex++ - ) { - const resourceType = resourceTable.type[resourceIndex]; - if (resourceType !== ResourceType.Library) { + // Pass 1 over the frames: group frames (and their addresses) by library, and + // work out which library each func belongs to. + // + // A func can be referenced by frames from more than one library, for example + // when two builds of the same library share a resource and symbolication has + // resolved frames from both builds to the same name. Such a func must not be + // recycled by either library, so we mark it with FUNC_LIB_SHARED. + const FUNC_LIB_NONE = -1; + const FUNC_LIB_SHARED = -2; + const libForFunc = new Int32Array(funcTable.length).fill(FUNC_LIB_NONE); + const framesByLib = new Map< + IndexIntoLibs, + { frames: IndexIntoFrameTable[]; addresses: Address[] } + >(); + for (let frameIndex = 0; frameIndex < frameTable.length; frameIndex++) { + const libIndex = frameTable.lib[frameIndex]; + if (libIndex === -1) { continue; } - const libIndex = resourceTable.lib[resourceIndex]; - if (libIndex === null) { - // We can get here if we have pre-symbolicated "funcName (in LibraryName)" - // frames. Those get ResourceType.Library but no libIndex. - continue; + let entry = framesByLib.get(libIndex); + if (entry === undefined) { + entry = { frames: [], addresses: [] }; + framesByLib.set(libIndex, entry); } - const lib = libs[libIndex]; - if (lib === undefined) { - throw new Error('Did not find a lib.'); + entry.frames.push(frameIndex); + entry.addresses.push(frameTable.address[frameIndex]); + + const funcIndex = frameTable.func[frameIndex]; + const knownLib = libForFunc[funcIndex]; + if (knownLib === FUNC_LIB_NONE) { + libForFunc[funcIndex] = libIndex; + } else if (knownLib !== libIndex) { + libForFunc[funcIndex] = FUNC_LIB_SHARED; } + } - // Collect the set of funcs for this library in this thread. - const allFuncsForThisLib = new Set(); - for (let funcIndex = 0; funcIndex < funcTable.length; funcIndex++) { - if (funcTable.resource[funcIndex] !== resourceIndex) { - continue; + // Pass over the funcs: assign each func to the library that gets to recycle + // it, and remember one resource per library for the funcs we create later. + const funcsByLib = new Map>(); + const resourceForLib = new Map(); + for (let funcIndex = 0; funcIndex < funcTable.length; funcIndex++) { + const libIndex = libForFunc[funcIndex]; + if (libIndex < 0) { + continue; + } + let funcs = funcsByLib.get(libIndex); + if (funcs === undefined) { + funcs = new Set(); + funcsByLib.set(libIndex, funcs); + } + funcs.add(funcIndex); + if (!resourceForLib.has(libIndex)) { + const resourceIndex = funcTable.resource[funcIndex]; + if (resourceIndex !== -1) { + resourceForLib.set(libIndex, resourceIndex); } - allFuncsForThisLib.add(funcIndex); } + } - // Collect the set of native symbols for this library in this thread. - const allNativeSymbolsForThisLib: Set = - new Set(); - for ( - let nativeSymbolIndex = 0; - nativeSymbolIndex < nativeSymbols.length; - nativeSymbolIndex++ - ) { - if (nativeSymbols.libIndex[nativeSymbolIndex] !== libIndex) { - continue; - } - allNativeSymbolsForThisLib.add(nativeSymbolIndex); + // Pass over the native symbols: group them by library. + const nativeSymbolsByLib = new Map< + IndexIntoLibs, + Set + >(); + for ( + let nativeSymbolIndex = 0; + nativeSymbolIndex < nativeSymbols.length; + nativeSymbolIndex++ + ) { + const libIndex = nativeSymbols.libIndex[nativeSymbolIndex]; + let symbols = nativeSymbolsByLib.get(libIndex); + if (symbols === undefined) { + symbols = new Set(); + nativeSymbolsByLib.set(libIndex, symbols); } + symbols.add(nativeSymbolIndex); + } - // Collect the sets of frames and addresses for this library. - const allFramesForThisLib = []; - const frameAddresses = []; - for (let frameIndex = 0; frameIndex < frameTable.length; frameIndex++) { - const funcIndex = frameTable.func[frameIndex]; - if (funcTable.resource[funcIndex] !== resourceIndex) { - continue; - } - allFramesForThisLib.push(frameIndex); - frameAddresses.push(frameTable.address[frameIndex]); + const map = new Map(); + for (const [libIndex, { frames, addresses }] of framesByLib) { + const lib = libs[libIndex]; + if (lib === undefined) { + throw new Error('Did not find a lib.'); } const libKey = `${lib.debugName}/${lib.breakpadId}`; map.set(libKey, { libIndex, - resourceIndex, - allFuncsForThisLib, - allNativeSymbolsForThisLib, - allFramesForThisLib, - frameAddresses, + resourceIndex: resourceForLib.get(libIndex) ?? -1, + allFuncsForThisLib: funcsByLib.get(libIndex) ?? new Set(), + allNativeSymbolsForThisLib: nativeSymbolsByLib.get(libIndex) ?? new Set(), + allFramesForThisLib: frames, + frameAddresses: addresses, }); } return map; @@ -903,6 +936,7 @@ function _partiallyApplySymbolicationStep( frameTable.subcategory[expansionFrameIndex] = subcategory; frameTable.innerWindowID[expansionFrameIndex] = innerWindowID; frameTable.address[expansionFrameIndex] = address; + frameTable.lib[expansionFrameIndex] = libIndex; frameTable.nativeSymbol[expansionFrameIndex] = nativeSymbolIndex; frameTable.originalLocation[expansionFrameIndex] = null; diff --git a/src/profile-query/formatters/call-tree.ts b/src/profile-query/formatters/call-tree.ts index 5113b81607..fb3a96b26b 100644 --- a/src/profile-query/formatters/call-tree.ts +++ b/src/profile-query/formatters/call-tree.ts @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import type { CallTree } from 'firefox-profiler/profile-logic/call-tree'; -import type { IndexIntoCallNodeTable, Lib } from 'firefox-profiler/types'; +import type { IndexIntoCallNodeTable } from 'firefox-profiler/types'; import { assertExhaustiveCheck } from 'firefox-profiler/utils/types'; import type { CallTreeNode, @@ -172,7 +172,6 @@ export type CallTreeCollectionOptions = { */ export function collectCallTree( tree: CallTree, - libs: Lib[], options: CallTreeCollectionOptions = {} ): CallTreeNode { const maxNodes = options.maxNodes ?? 100; @@ -244,7 +243,7 @@ export function collectCallTree( } } - return buildTreeStructure(tree, includedNodes, libs); + return buildTreeStructure(tree, includedNodes); } /** @@ -252,8 +251,7 @@ export function collectCallTree( */ function buildTreeStructure( tree: CallTree, - includedNodes: Set, - libs: Lib[] + includedNodes: Set ): CallTreeNode { // Get total sample count from the tree for percentage calculations const totalSampleCount = tree.getTotal(); @@ -299,8 +297,7 @@ function buildTreeStructure( // Format function name with library prefix const nameWithLibrary = formatFunctionNameWithLibrary( funcIndex, - tree._thread, - libs + tree._thread ); const inlineStatus = inlineStatusForNode(tree, callNodeIndex); diff --git a/src/profile-query/formatters/marker-info.ts b/src/profile-query/formatters/marker-info.ts index 3efbc9ddef..f671c78a34 100644 --- a/src/profile-query/formatters/marker-info.ts +++ b/src/profile-query/formatters/marker-info.ts @@ -951,8 +951,7 @@ function collectStackTrace( return null; } - const { stackTable, frameTable, funcTable, stringTable, resourceTable } = - thread; + const { stackTable, frameTable, funcTable, stringTable } = thread; const frames: StackTraceData['frames'] = []; let currentStackIndex: IndexIntoStackTable = stackIndex; @@ -960,19 +959,12 @@ function collectStackTrace( const frameIndex = stackTable.frame[currentStackIndex]; const funcIndex = frameTable.func[frameIndex]; const funcName = stringTable.getString(funcTable.name[funcIndex]); - const nameWithLibrary = formatFunctionNameWithLibrary( - funcIndex, - thread, - libs - ); + const nameWithLibrary = formatFunctionNameWithLibrary(funcIndex, thread); let library: string | undefined; - const resourceIndex = funcTable.resource[funcIndex]; - if (resourceIndex !== -1) { - const libIndex = resourceTable.lib[resourceIndex]; - if (libIndex !== null && libs) { - library = libs[libIndex].name; - } + const libIndex = frameTable.lib[frameIndex]; + if (libIndex !== -1 && libs) { + library = libs[libIndex].name; } frames.push({ name: funcName, nameWithLibrary, library }); diff --git a/src/profile-query/formatters/thread-info.ts b/src/profile-query/formatters/thread-info.ts index 96095e4ffc..4495d45eaf 100644 --- a/src/profile-query/formatters/thread-info.ts +++ b/src/profile-query/formatters/thread-info.ts @@ -9,7 +9,6 @@ import { import { getCategories, getDefaultCategory, - getProfile, } from 'firefox-profiler/selectors/profile'; import { collectSliceTree } from '../cpu-activity'; import { computeThreadNetworkSummary } from '../network-summary'; @@ -114,14 +113,13 @@ export function collectThreadSamples( const threadSelectors = getThreadSelectors(threadIndexes); const friendlyThreadName = threadSelectors.getFriendlyThreadName(state); const thread = threadSelectors.getFilteredThread(state); - const libs = getProfile(state).libs; // Get call trees for analysis const functionListTree = threadSelectors.getFunctionListTree(state); const callTree = threadSelectors.getCallTree(state); // Extract function data - const functions = extractFunctionData(functionListTree, thread, libs); + const functions = extractFunctionData(functionListTree, thread); // Sort by total and take top 50 const sortedByTotal = functions @@ -201,11 +199,7 @@ export function collectThreadSamples( let hasInlinedFrames = false; const frames = heaviestPath.map((funcIndex, depth) => { - const funcName = formatFunctionNameWithLibrary( - funcIndex, - thread, - libs - ); + const funcName = formatFunctionNameWithLibrary(funcIndex, thread); const funcData = funcMap.get(funcIndex); const prefixPath = heaviestPath.slice(0, depth + 1); const frameCallNodeIndex = @@ -307,8 +301,7 @@ export function collectThreadSamplesBottomUp( weightType ); - const libs = getProfile(state).libs; - const invertedCallTree = collectCallTree(invertedTree, libs, callTreeOptions); + const invertedCallTree = collectCallTree(invertedTree, callTreeOptions); return { type: 'thread-samples-bottom-up', @@ -337,10 +330,9 @@ export function collectThreadSamplesTopDown( const threadSelectors = getThreadSelectors(threadIndexes); const friendlyThreadName = threadSelectors.getFriendlyThreadName(state); const callTree = threadSelectors.getCallTree(state); - const libs = getProfile(state).libs; // Collect regular call tree - const regularCallTree = collectCallTree(callTree, libs, callTreeOptions); + const regularCallTree = collectCallTree(callTree, callTreeOptions); return { type: 'thread-samples-top-down', @@ -369,13 +361,12 @@ export function collectThreadFunctions( const threadSelectors = getThreadSelectors(threadIndexes); const friendlyThreadName = threadSelectors.getFriendlyThreadName(state); const thread = threadSelectors.getFilteredThread(state); - const libs = getProfile(state).libs; // Get function list tree const functionListTree = threadSelectors.getFunctionListTree(state); // Extract function data - const allFunctions = extractFunctionData(functionListTree, thread, libs); + const allFunctions = extractFunctionData(functionListTree, thread); const totalFunctionCount = allFunctions.length; // Check if we're zoomed (have committed ranges) diff --git a/src/profile-query/function-annotate.ts b/src/profile-query/function-annotate.ts index 20c86a4444..715e18a22b 100644 --- a/src/profile-query/function-annotate.ts +++ b/src/profile-query/function-annotate.ts @@ -6,7 +6,7 @@ import { getProfile } from 'firefox-profiler/selectors/profile'; import { getSelectedThreadIndexes } from 'firefox-profiler/selectors/url-state'; import { getThreadSelectors } from 'firefox-profiler/selectors/per-thread'; import { parseFunctionHandle } from './function-map'; -import { getLibForFunc } from './function-list'; +import { getLibNameForFunc } from './function-list'; import type { ThreadMap } from './thread-map'; import { getStackLineInfo, @@ -343,12 +343,12 @@ export async function functionAnnotate( const funcIndex = parseFunctionHandle(functionHandle, funcTable.length); const funcName = stringArray[funcTable.name[funcIndex]]; - const libraryName = getLibForFunc( + const libraryName = getLibNameForFunc( funcIndex, funcTable, resourceTable, - profile.libs - )?.name; + stringArray + ); const fullName = libraryName ? `${libraryName}!${funcName}` : funcName; const threadIndexes = getSelectedThreadIndexes(state); diff --git a/src/profile-query/function-list.ts b/src/profile-query/function-list.ts index ebb5edca97..b1ef369411 100644 --- a/src/profile-query/function-list.ts +++ b/src/profile-query/function-list.ts @@ -5,31 +5,58 @@ import type { Thread, Lib, + RawFrameTable, FuncTable, ResourceTable, } from 'firefox-profiler/types'; +import { ResourceType } from 'firefox-profiler/types'; import { getFunctionHandle } from './function-map'; /** * Look up the Lib record for a function, or undefined if none is associated. + * + * For funcs which are shared across frames from multiple different libs, this + * will return the first match. */ -export function getLibForFunc( +export function getAnyLibForFunc( funcIndex: number, - funcTable: FuncTable, - resourceTable: ResourceTable, + frameTable: RawFrameTable, libs: Lib[] ): Lib | undefined { - const resourceIndex = funcTable.resource[funcIndex]; - if (resourceIndex === -1) { - return undefined; - } - const libIndex = resourceTable.lib[resourceIndex]; - if (libIndex !== null && libIndex !== undefined && libIndex >= 0) { - return libs[libIndex]; + for (let frameIndex = 0; frameIndex < frameTable.length; frameIndex++) { + if (frameTable.func[frameIndex] !== funcIndex) { + continue; + } + const libIndex = frameTable.lib[frameIndex]; + if (libIndex !== -1) { + return libs[libIndex]; + } } return undefined; } +/** + * Look up the name of the library a function came from, or null if the function + * isn't native code. This is the name of the func's resource, which is cheap to + * get at; note that it names the library but does not identify a specific + * build, since several libs can share one resource. + */ +export function getLibNameForFunc( + funcIndex: number, + funcTable: FuncTable, + resourceTable: ResourceTable, + stringArray: string[] +): string | null { + const resourceIndex = funcTable.resource[funcIndex]; + if ( + resourceIndex === -1 || + resourceTable.type[resourceIndex] !== ResourceType.Library + ) { + return null; + } + return stringArray[resourceTable.name[resourceIndex]]; +} + export type FunctionData = { funcName: string; funcIndex: number; @@ -351,22 +378,13 @@ export function truncateFunctionName( */ export function formatFunctionNameWithLibrary( funcIndex: number, - thread: Thread, - libs: Lib[] + thread: Thread ): string { const funcName = thread.stringTable.getString( thread.funcTable.name[funcIndex] ); - const lib = getLibForFunc( - funcIndex, - thread.funcTable, - thread.resourceTable, - libs - ); - if (lib) { - return `${lib.name}!${funcName}`; - } - // Fall back to resource name if no library + // The func's resource carries the library name for native code, and the + // origin / URL for JS code. const resourceIndex = thread.funcTable.resource[funcIndex]; if (resourceIndex !== -1) { const resourceName = thread.stringTable.getString( @@ -393,18 +411,13 @@ export function extractFunctionData( selfRelative: number; }; }, - thread: Thread, - libs: Lib[] + thread: Thread ): FunctionData[] { const roots = tree.getRoots(); return roots.map((nodeIndex) => { const data = tree.getNodeData(nodeIndex); // The node index IS the function index for function list trees - const formattedName = formatFunctionNameWithLibrary( - nodeIndex, - thread, - libs - ); + const formattedName = formatFunctionNameWithLibrary(nodeIndex, thread); return { ...data, funcName: formattedName, diff --git a/src/profile-query/index.ts b/src/profile-query/index.ts index 11d58a0cf8..c752ee4c74 100644 --- a/src/profile-query/index.ts +++ b/src/profile-query/index.ts @@ -61,7 +61,7 @@ import { type EligibleSource, } from 'firefox-profiler/profile-logic/source-map-matching'; import { assertExhaustiveCheck } from 'firefox-profiler/utils/types'; -import { getLibForFunc } from './function-list'; +import { getAnyLibForFunc, getLibNameForFunc } from './function-list'; import { MarkerMap } from './marker-map'; import { loadProfileFromFileOrUrl, type LoadOptions } from './loader'; import { collectProfileInfo } from './formatters/profile-info'; @@ -1110,12 +1110,9 @@ export class ProfileQuerier { // Look up the function const funcIndex = parseFunctionHandle(functionHandle, funcTable.length); const funcName = stringArray[funcTable.name[funcIndex]]; - const library = getLibForFunc( - funcIndex, - funcTable, - resourceTable, - profile.libs - )?.name; + const library = + getLibNameForFunc(funcIndex, funcTable, resourceTable, stringArray) ?? + undefined; const fullName = library ? `${library}!${funcName}` : funcName; return { @@ -1156,10 +1153,9 @@ export class ProfileQuerier { }; } - const lib = getLibForFunc( + const lib = getAnyLibForFunc( funcIndex, - funcTable, - resourceTable, + profile.shared.frameTable, profile.libs ); if (lib) { diff --git a/src/profile-query/types.ts b/src/profile-query/types.ts index 5f3dc6cc2d..feb0d83dc8 100644 --- a/src/profile-query/types.ts +++ b/src/profile-query/types.ts @@ -184,6 +184,11 @@ export type FunctionInfoResult = { name: string; index: number; }; + // The library of this function, if any. + // Note that, these days, funcs can be associated with multiple + // libraries, so this isn't the best representation anymore. In those + // cases this will be set to one of them, but it's arbitrary which one. + // See https://github.com/firefox-devtools/profiler/issues/6270 library?: { name: string; path: string; diff --git a/src/reducers/url-state.ts b/src/reducers/url-state.ts index 0c8a92be9a..5374e6440f 100644 --- a/src/reducers/url-state.ts +++ b/src/reducers/url-state.ts @@ -628,7 +628,6 @@ const profileName: Reducer = (state = null, action) => { const sourceView: Reducer = ( state = { scrollGeneration: 0, - libIndex: null, sourceIndex: null, highlightedLine: null, }, @@ -641,7 +640,6 @@ const sourceView: Reducer = ( scrollGeneration: shouldScroll ? state.scrollGeneration + 1 : state.scrollGeneration, - libIndex: action.libIndex, sourceIndex: action.sourceIndex, scrollToLineNumber: action.scrollToLineNumber, highlightedLine: action.highlightedLineNumber, diff --git a/src/test/fixtures/profiles/call-nodes.ts b/src/test/fixtures/profiles/call-nodes.ts index 8692daf1ab..9f35027858 100644 --- a/src/test/fixtures/profiles/call-nodes.ts +++ b/src/test/fixtures/profiles/call-nodes.ts @@ -78,6 +78,7 @@ export default function getProfile(): Profile { func: frameFuncs.map((stringIndex) => funcTable.name.indexOf(stringIndex)), address: Array(frameFuncs.length).fill(-1), inlineDepth: Array(frameFuncs.length).fill(0), + lib: Array(frameFuncs.length).fill(-1), nativeSymbol: Array(frameFuncs.length).fill(null), category: Array(frameFuncs.length).fill(null), subcategory: Array(frameFuncs.length).fill(null), diff --git a/src/test/fixtures/profiles/processed-profile.ts b/src/test/fixtures/profiles/processed-profile.ts index dc886ee205..a765bef123 100644 --- a/src/test/fixtures/profiles/processed-profile.ts +++ b/src/test/fixtures/profiles/processed-profile.ts @@ -41,6 +41,7 @@ import type { Thread, ThreadIndex, IndexIntoCategoryList, + IndexIntoLibs, IndexIntoStackTable, CategoryList, JsTracerTable, @@ -583,7 +584,7 @@ export type ProfileWithDicts = { * * The following func and frame attributes are supported: * - [cat:*] - The category name, affects frameTable.category - * - [lib:*] - The library name, affects funcTable.resource + resourceTable + libs + * - [lib:*] - The library name, affects frameTable.lib + funcTable.resource + resourceTable + libs * - [file:*] - The filename, affects funcTable.file * - [line:*] - The line, affects frameTable.line * - [address:*] - The frame address, affects frameTable.address @@ -600,8 +601,8 @@ function getFrame( ) { const funcIndex = frameTable.func[frameIndex]; let s = stringTable.getString(funcTable.name[funcIndex]); - const libIndex = resourceTable.lib[funcTable.resource[funcIndex]]; - if (libIndex !== null) { + const libIndex = frameTable.lib[frameIndex]; + if (libIndex !== -1) { const libName = libs[libIndex].name; s += `[lib:${libName}]`; } @@ -902,7 +903,7 @@ function _buildThreadFromTextOnlyStacks( // Find the library name from the function name and create an entry if needed. const libraryName = _findLibNameFromFuncName(funcNameWithModifier); let resourceIndex = -1; - let libIndex = null; + let libIndex: IndexIntoLibs | -1 = -1; if (libraryName !== null) { libIndex = globalDataCollector.indexForLib({ arch: '', @@ -913,7 +914,7 @@ function _buildThreadFromTextOnlyStacks( breakpadId: 'SOMETHING_FAKE', codeId: null, }); - resourceIndex = globalDataCollector.indexForLibResource(libIndex); + resourceIndex = globalDataCollector.indexForLibResourceByLib(libIndex); } // Find the file name from the function name @@ -947,7 +948,7 @@ function _buildThreadFromTextOnlyStacks( const nativeSymbolInfo = _findNativeSymbolNameFromFuncName(funcNameWithModifier); if (nativeSymbolInfo) { - if (libIndex === null) { + if (libIndex === -1) { throw new Error( `[sym:] has to be used together with [lib:] - missing lib in "${funcNameWithModifier}"` ); @@ -975,7 +976,8 @@ function _buildThreadFromTextOnlyStacks( lineNumber === frameTable.line[i] && address === frameTable.address[i] && inlineDepth === frameTable.inlineDepth[i] && - nativeSymbol === frameTable.nativeSymbol[i] + nativeSymbol === frameTable.nativeSymbol[i] && + libIndex === frameTable.lib[i] ) { frameIndex = i; break; @@ -984,6 +986,7 @@ function _buildThreadFromTextOnlyStacks( if (frameIndex === undefined) { frameTable.func.push(funcIndex); + frameTable.lib.push(libIndex); frameTable.address.push(address); frameTable.inlineDepth.push(inlineDepth); frameTable.category.push(category); @@ -2084,6 +2087,7 @@ export function addInnerWindowIdToStacks( frameTable.subcategory[foundFrameIndex] ); frameTableBuilder.func.push(frameTable.func[foundFrameIndex]); + frameTableBuilder.lib.push(frameTable.lib[foundFrameIndex]); frameTableBuilder.nativeSymbol.push( frameTable.nativeSymbol[foundFrameIndex] ); diff --git a/src/test/integration/profiler-edit/__snapshots__/profiler-edit.test.ts.snap b/src/test/integration/profiler-edit/__snapshots__/profiler-edit.test.ts.snap index 8df0ad49ac..4732177b42 100644 --- a/src/test/integration/profiler-edit/__snapshots__/profiler-edit.test.ts.snap +++ b/src/test/integration/profiler-edit/__snapshots__/profiler-edit.test.ts.snap @@ -87,7 +87,7 @@ Object { "markerSchema": Array [], "oscpu": "macOS 14.6.1", "pausedRanges": Array [], - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "processType": 0, "product": "a.out", "sampleUnits": Object { @@ -370,6 +370,50 @@ Object { null, ], "length": 42, + "lib": Array [ + 0, + 1, + 2, + 3, + 2, + 1, + 1, + 4, + 4, + 3, + 2, + 1, + 1, + 4, + 4, + 3, + 1, + 2, + 1, + 1, + 4, + 4, + 3, + 1, + 2, + 1, + 1, + 4, + 4, + 3, + 1, + 2, + 2, + 2, + 5, + 2, + 1, + 1, + 4, + 4, + 3, + 1, + ], "line": Array [ null, null, @@ -743,14 +787,6 @@ Object { null, ], "length": 6, - "lib": Array [ - 0, - 1, - 2, - 3, - 4, - 5, - ], "name": Array [ 0, 1, @@ -1452,7 +1488,7 @@ Object { "markerSchema": Array [], "oscpu": "macOS 14.6.1", "pausedRanges": Array [], - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "processType": 0, "product": "a.out", "sampleUnits": Object { @@ -1735,6 +1771,50 @@ Object { null, ], "length": 42, + "lib": Array [ + 0, + 1, + 2, + 3, + 2, + 1, + 1, + 4, + 4, + 3, + 2, + 1, + 1, + 4, + 4, + 3, + 1, + 2, + 1, + 1, + 4, + 4, + 3, + 1, + 2, + 1, + 1, + 4, + 4, + 3, + 1, + 2, + 2, + 2, + 5, + 2, + 1, + 1, + 4, + 4, + 3, + 1, + ], "line": Array [ null, null, @@ -2108,14 +2188,6 @@ Object { null, ], "length": 6, - "lib": Array [ - 0, - 1, - 2, - 3, - 4, - 5, - ], "name": Array [ 0, 1, @@ -2817,7 +2889,7 @@ Object { "markerSchema": Array [], "oscpu": "macOS 14.6.1", "pausedRanges": Array [], - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "processType": 0, "product": "a.out", "sampleUnits": Object { @@ -3100,6 +3172,50 @@ Object { null, ], "length": 42, + "lib": Array [ + 0, + 1, + 2, + 3, + 2, + 1, + 1, + 4, + 4, + 3, + 2, + 1, + 1, + 4, + 4, + 3, + 1, + 2, + 1, + 1, + 4, + 4, + 3, + 1, + 2, + 1, + 1, + 4, + 4, + 3, + 1, + 2, + 2, + 2, + 5, + 2, + 1, + 1, + 4, + 4, + 3, + 1, + ], "line": Array [ null, null, @@ -3473,14 +3589,6 @@ Object { null, ], "length": 6, - "lib": Array [ - 0, - 1, - 2, - 3, - 4, - 5, - ], "name": Array [ 0, 1, @@ -4182,7 +4290,7 @@ Object { "markerSchema": Array [], "oscpu": "macOS 14.6.1", "pausedRanges": Array [], - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "processType": 0, "product": "a.out", "sampleUnits": Object { @@ -4465,6 +4573,50 @@ Object { null, ], "length": 42, + "lib": Array [ + 0, + 1, + 2, + 3, + 2, + 1, + 1, + 4, + 4, + 3, + 2, + 1, + 1, + 4, + 4, + 3, + 1, + 2, + 1, + 1, + 4, + 4, + 3, + 1, + 2, + 1, + 1, + 4, + 4, + 3, + 1, + 2, + 2, + 2, + 5, + 2, + 1, + 1, + 4, + 4, + 3, + 1, + ], "line": Array [ null, null, @@ -4838,14 +4990,6 @@ Object { null, ], "length": 6, - "lib": Array [ - 0, - 1, - 2, - 3, - 4, - 5, - ], "name": Array [ 0, 1, diff --git a/src/test/store/__snapshots__/profile-view.test.ts.snap b/src/test/store/__snapshots__/profile-view.test.ts.snap index c16fdb16e5..2b7fc08e1a 100644 --- a/src/test/store/__snapshots__/profile-view.test.ts.snap +++ b/src/test/store/__snapshots__/profile-view.test.ts.snap @@ -428,7 +428,7 @@ Object { "oscpu": "", "physicalCPUs": 0, "platform": "", - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "processType": 0, "product": "Firefox", "sourceURL": "", @@ -515,6 +515,17 @@ Object { 0, ], "length": 9, + "lib": Int32Array [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], "line": Array [ null, null, @@ -661,7 +672,6 @@ Object { "resourceTable": Object { "host": Array [], "length": 0, - "lib": Array [], "name": Array [], "type": Array [], }, @@ -2486,6 +2496,17 @@ CallTree { 0, ], "length": 9, + "lib": Int32Array [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], "line": Array [ null, null, @@ -2655,7 +2676,6 @@ CallTree { "resourceTable": Object { "host": Array [], "length": 0, - "lib": Array [], "name": Array [], "type": Array [], }, @@ -2874,6 +2894,17 @@ Object { 0, ], "length": 9, + "lib": Int32Array [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], "line": Array [ null, null, @@ -3043,7 +3074,6 @@ Object { "resourceTable": Object { "host": Array [], "length": 0, - "lib": Array [], "name": Array [], "type": Array [], }, @@ -3342,6 +3372,17 @@ Object { 0, ], "length": 9, + "lib": Int32Array [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], "line": Array [ null, null, @@ -3511,7 +3552,6 @@ Object { "resourceTable": Object { "host": Array [], "length": 0, - "lib": Array [], "name": Array [], "type": Array [], }, @@ -3716,6 +3756,17 @@ Object { 0, ], "length": 9, + "lib": Int32Array [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], "line": Array [ null, null, @@ -3885,7 +3936,6 @@ Object { "resourceTable": Object { "host": Array [], "length": 0, - "lib": Array [], "name": Array [], "type": Array [], }, @@ -4102,6 +4152,17 @@ Object { 0, ], "length": 9, + "lib": Int32Array [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], "line": Array [ null, null, @@ -4271,7 +4332,6 @@ Object { "resourceTable": Object { "host": Array [], "length": 0, - "lib": Array [], "name": Array [], "type": Array [], }, diff --git a/src/test/store/bottom-box.test.ts b/src/test/store/bottom-box.test.ts index 32a6e1c1a4..64037f2faa 100644 --- a/src/test/store/bottom-box.test.ts +++ b/src/test/store/bottom-box.test.ts @@ -354,7 +354,6 @@ describe('bottom box', function () { dispatch( updateBottomBoxContentsAndMaybeOpen('calltree', { - libIndex: null, sourceIndex: null, nativeSymbols: [], initialNativeSymbol: 0, diff --git a/src/test/store/symbolication.test.ts b/src/test/store/symbolication.test.ts index 81771af3ea..352f32a2d8 100644 --- a/src/test/store/symbolication.test.ts +++ b/src/test/store/symbolication.test.ts @@ -620,7 +620,6 @@ function _createUnsymbolicatedProfile() { profile.shared.resourceTable = { length: 1, - lib: [libIndex], name: [stringTable.indexForString('example lib')], host: [stringTable.indexForString('example host')], type: [ResourceType.Library], @@ -628,6 +627,9 @@ function _createUnsymbolicatedProfile() { for (let i = 0; i < profile.shared.funcTable.length; i++) { profile.shared.funcTable.resource[i] = 0; } + for (let i = 0; i < profile.shared.frameTable.length; i++) { + profile.shared.frameTable.lib[i] = libIndex; + } // Add a marker with a cause stack. We use the stack of the first sample. // This sample has 0x000a in its stack, which has an inlined function call, diff --git a/src/test/unit/__snapshots__/process-profile.test.ts.snap b/src/test/unit/__snapshots__/process-profile.test.ts.snap index 72c8a2406d..5f7b34f429 100644 --- a/src/test/unit/__snapshots__/process-profile.test.ts.snap +++ b/src/test/unit/__snapshots__/process-profile.test.ts.snap @@ -207,7 +207,7 @@ Array [ "host": null, "isJS": false, "lib": undefined, - "libIndex": undefined, + "libIndex": null, "lineNumber": null, "resourceIndex": -1, "resourceName": null, diff --git a/src/test/unit/__snapshots__/profile-conversion.test.ts.snap b/src/test/unit/__snapshots__/profile-conversion.test.ts.snap index a3831da47a..907fa724ce 100644 --- a/src/test/unit/__snapshots__/profile-conversion.test.ts.snap +++ b/src/test/unit/__snapshots__/profile-conversion.test.ts.snap @@ -42,7 +42,7 @@ Object { "RefreshDriverTick", "Network", ], - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "product": "ART Trace (Android)", "symbolicated": true, "version": 36, @@ -1022,7 +1022,7 @@ Object { "RefreshDriverTick", "Network", ], - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "product": "ART Trace (Android)", "symbolicated": true, "version": 36, @@ -2305,7 +2305,7 @@ Object { "markerSchemaNames": Array [ "EventDispatch", ], - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "product": "Chrome Trace", "symbolicated": true, "version": 36, @@ -2697,7 +2697,7 @@ Object { "markerSchemaNames": Array [ "EventDispatch", ], - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "product": "Chrome Trace", "symbolicated": true, "version": 36, @@ -3086,7 +3086,7 @@ Object { "markerSchemaNames": Array [ "EventDispatch", ], - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "product": "Chrome Trace", "symbolicated": true, "version": 36, @@ -3187,7 +3187,7 @@ Object { "markerSchemaNames": Array [ "EventDispatch", ], - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "product": "Chrome Trace", "symbolicated": true, "version": 36, @@ -3540,7 +3540,7 @@ Object { "markerSchemaNames": Array [ "EventDispatch", ], - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "product": "Chrome Trace", "symbolicated": true, "version": 36, @@ -3605,7 +3605,7 @@ Object { "markerSchemaNames": Array [ "EventDispatch", ], - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "product": "Chrome Trace", "symbolicated": true, "version": 36, @@ -3759,7 +3759,7 @@ Object { "markerSchemaNames": Array [ "EventDispatch", ], - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "product": "Chrome Trace", "symbolicated": true, "version": 36, @@ -3817,7 +3817,7 @@ Object { "importedFrom": undefined, "interval": 1, "markerSchemaNames": Array [], - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "product": "Firefox", "symbolicated": true, "version": 36, @@ -4207,7 +4207,7 @@ Object { "importedFrom": undefined, "interval": 1, "markerSchemaNames": Array [], - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "product": "Firefox", "symbolicated": true, "version": 36, @@ -4265,7 +4265,7 @@ Object { "importedFrom": undefined, "interval": 1, "markerSchemaNames": Array [], - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "product": "Firefox", "symbolicated": true, "version": 36, @@ -4323,7 +4323,7 @@ Object { "importedFrom": undefined, "interval": 1, "markerSchemaNames": Array [], - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "product": "Firefox", "symbolicated": true, "version": 36, @@ -4643,7 +4643,7 @@ Object { "importedFrom": "Simpleperf", "interval": 0, "markerSchemaNames": Array [], - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "product": "com.example.sampleapplication", "symbolicated": undefined, "version": 30, @@ -5019,7 +5019,7 @@ Object { "importedFrom": "Simpleperf", "interval": 0, "markerSchemaNames": Array [], - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "product": "com.example.sampleapplication", "symbolicated": undefined, "version": 30, @@ -5319,7 +5319,7 @@ Object { "importedFrom": "dhat", "interval": 1, "markerSchemaNames": Array [], - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "product": "target/debug/examples/work_log (dhat)", "symbolicated": true, "version": 36, @@ -5452,7 +5452,7 @@ Object { "importedFrom": undefined, "interval": 1, "markerSchemaNames": Array [], - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "product": "Flamegraph", "symbolicated": true, "version": 36, @@ -5510,7 +5510,7 @@ Object { "importedFrom": undefined, "interval": 1, "markerSchemaNames": Array [], - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "product": "Flamegraph", "symbolicated": true, "version": 36, diff --git a/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap b/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap index 394eaa6d2e..07d2263740 100644 --- a/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap +++ b/src/test/unit/__snapshots__/profile-upgrading.test.ts.snap @@ -40,7 +40,7 @@ Object { "oscpu": undefined, "physicalCPUs": undefined, "platform": undefined, - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "processType": 0, "product": "Firefox", "sampleUnits": undefined, @@ -1287,6 +1287,210 @@ Object { 0, ], "length": 202, + "lib": Int32Array [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + ], "line": Array [ null, null, @@ -2722,20 +2926,6 @@ Object { null, ], "length": 12, - "lib": Array [ - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - ], "name": Array [ 2, 5, @@ -7644,7 +7834,7 @@ Object { "misc": "rv:48.0", "oscpu": "Intel Mac OS X 10.11", "platform": "Macintosh", - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "processType": 0, "product": "Firefox", "stackwalk": 1, @@ -7793,6 +7983,29 @@ Object { 0, ], "length": 21, + "lib": Array [ + -1, + 0, + 0, + -1, + -1, + 0, + 0, + -1, + 0, + 0, + -1, + -1, + 0, + 0, + -1, + 1, + 1, + -1, + -1, + 1, + 1, + ], "line": Array [ null, null, @@ -7995,11 +8208,6 @@ Object { null, ], "length": 3, - "lib": Array [ - 0, - null, - 1, - ], "name": Array [ 11, 10, @@ -9021,7 +9229,7 @@ Object { "misc": "rv:48.0", "oscpu": "Intel Mac OS X 10.11", "platform": "Macintosh", - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "processType": 0, "product": "Firefox", "stackwalk": 1, @@ -9182,6 +9390,31 @@ Object { 0, ], "length": 23, + "lib": Array [ + -1, + 0, + 0, + -1, + -1, + 0, + 0, + -1, + 0, + 0, + -1, + -1, + 0, + 0, + -1, + 1, + 1, + -1, + -1, + -1, + -1, + 1, + 1, + ], "line": Array [ null, null, @@ -9408,11 +9641,6 @@ Object { null, ], "length": 3, - "lib": Array [ - 0, - null, - 1, - ], "name": Array [ 12, 11, @@ -10566,7 +10794,7 @@ Object { "misc": "rv:48.0", "oscpu": "Intel Mac OS X 10.11", "platform": "Macintosh", - "preprocessedProfileVersion": 69, + "preprocessedProfileVersion": 70, "processType": 0, "product": "Firefox", "stackwalk": 1, @@ -10753,6 +10981,31 @@ Object { 0, ], "length": 23, + "lib": Array [ + -1, + 0, + 0, + -1, + -1, + 0, + 0, + -1, + 0, + 0, + -1, + -1, + 0, + 0, + -1, + 1, + 1, + -1, + -1, + -1, + -1, + 1, + 1, + ], "line": Array [ null, null, @@ -10979,11 +11232,6 @@ Object { null, ], "length": 3, - "lib": Array [ - 0, - null, - 1, - ], "name": Array [ 13, 12, diff --git a/src/test/unit/merge-compare.test.ts b/src/test/unit/merge-compare.test.ts index c064168786..8dd75b0ea1 100644 --- a/src/test/unit/merge-compare.test.ts +++ b/src/test/unit/merge-compare.test.ts @@ -18,6 +18,7 @@ import { ensureExists } from 'firefox-profiler/utils/types'; import { getTimeRangeIncludingAllThreads } from 'firefox-profiler/profile-logic/profile-data'; import { StringTable } from '../../utils/string-table'; import type { RawProfileSharedData, Profile } from 'firefox-profiler/types'; +import { ResourceType } from 'firefox-profiler/types'; import { callTreeFromProfile, formatTree } from '../fixtures/utils'; import { storeWithProfile } from '../fixtures/stores'; import { addTransformToStack } from '../../actions/profile-view'; @@ -50,6 +51,7 @@ describe('mergeProfilesForDiffing function', function () { const mergedShared = mergedProfile.shared; const mergedResources = mergedShared.resourceTable; const mergedFunctions = mergedShared.funcTable; + const mergedFrames = mergedShared.frameTable; const stringArray = mergedShared.stringArray; expect(mergedLibs).toHaveLength(3); @@ -59,9 +61,8 @@ describe('mergeProfilesForDiffing function', function () { // Now check that all functions are linked to the right resources. // We should have 2 A functions, linked to 2 different resources. // And we should have 1 B function, and 1 C function. - const libsForA = []; - const resourcesForA = []; - for (let funcIndex = 0; funcIndex < mergedFunctions.length; funcIndex++) { + for (let frameIndex = 0; frameIndex < mergedFrames.length; frameIndex++) { + const funcIndex = mergedFrames.func[frameIndex]; const funcName = stringArray[mergedFunctions.name[funcIndex]]; const resourceIndex = mergedFunctions.resource[funcIndex]; @@ -72,18 +73,17 @@ describe('mergeProfilesForDiffing function', function () { if (nameIndex >= 0) { resourceName = stringArray[nameIndex]; } - - const libIndex = mergedResources.lib[resourceIndex]; - if (libIndex !== null && libIndex !== undefined && libIndex >= 0) { - libName = mergedLibs[libIndex].name; - } + } + const libIndex = mergedFrames.lib[frameIndex]; + if (libIndex !== null) { + libName = mergedLibs[libIndex].name; } /* eslint-disable jest/no-conditional-expect */ switch (funcName) { case 'A': - libsForA.push(libName); - resourcesForA.push(resourceName); + expect(libName).toBeOneOf(['libA', 'libB']); + expect(resourceName).toBeOneOf(['libA', 'libB']); break; case 'B': expect(libName).toBe('libA'); @@ -97,8 +97,46 @@ describe('mergeProfilesForDiffing function', function () { } /* eslint-enable */ } - expect(libsForA).toEqual(['libA', 'libB']); - expect(resourcesForA).toEqual(['libA', 'libB']); + }); + + it('keeps two builds of the same library apart while sharing one resource', function () { + // This is the case that motivated moving the lib column off the + // resourceTable and onto the frameTable: comparing two builds of libxul. + // Both builds have the same name, so they share a single resource, but they + // have different breakpadIds and so must stay separate libs - symbols have + // to be looked up separately for each build. + const sampleProfileA = getProfileFromTextSamples('A[lib:libxul.so]'); + const sampleProfileB = getProfileFromTextSamples('A[lib:libxul.so]'); + sampleProfileB.profile.libs[0] = { + ...sampleProfileB.profile.libs[0], + breakpadId: 'A_DIFFERENT_BUILD', + }; + const profileState = stateFromLocation({ + pathname: '/public/fakehash1/', + search: '?thread=0&v=3', + hash: '', + }); + + const { profile: mergedProfile } = mergeProfilesForDiffing( + [sampleProfileA.profile, sampleProfileB.profile], + [profileState, profileState] + ); + const { resourceTable, frameTable, stringArray } = mergedProfile.shared; + + // The two builds have separate libs. + expect(mergedProfile.libs.map((lib) => lib.breakpadId)).toEqual([ + 'SOMETHING_FAKE', + 'A_DIFFERENT_BUILD', + ]); + + // But there's only one resource because both libs are called `libxul.so`. + const libraryResources = resourceTable.name + .filter((_name, i) => resourceTable.type[i] === ResourceType.Library) + .map((nameIndex) => stringArray[nameIndex]); + expect(libraryResources).toEqual(['libxul.so']); + + // The frames still have different libs. + expect([...frameTable.lib]).toEqual([0, 1]); }); it('should set interval of merged profile to minimum of all intervals', function () { diff --git a/src/test/unit/process-profile.test.ts b/src/test/unit/process-profile.test.ts index 945bfd78e6..12404111f8 100644 --- a/src/test/unit/process-profile.test.ts +++ b/src/test/unit/process-profile.test.ts @@ -110,14 +110,15 @@ describe('extract functions and resource from location strings', function () { globalDataCollector.addExtensionOrigins(extensions); it('extracts the information for all different types of locations', function () { - const { frameFuncs } = extractFuncsAndResourcesFromFrameLocations( - locationIndexes, - locationIndexes.map(() => false), - geckoThreadStringArray, - libs, - globalDataCollector, - getEmptySourceTable() - ); + const { frameFuncs, frameLibs } = + extractFuncsAndResourcesFromFrameLocations( + locationIndexes, + locationIndexes.map(() => false), + geckoThreadStringArray, + libs, + globalDataCollector, + getEmptySourceTable() + ); const { shared: { sources, funcTable, resourceTable }, @@ -139,15 +140,13 @@ describe('extract functions and resource from location strings', function () { fileNameIndex === null ? null : stringTable.getString(fileNameIndex); const lineNumber = funcTable.lineNumber[funcIndex]; const columnNumber = funcTable.columnNumber[funcIndex]; - - let libIndex, resourceName, host, resourceType; + let resourceName, host, resourceType; if (resourceIndex === -1) { resourceName = null; host = null; resourceType = null; } else { const hostStringIndex = resourceTable.host[resourceIndex]; - libIndex = resourceTable.lib[resourceIndex]; resourceName = stringTable.getString( resourceTable.name[resourceIndex] ); @@ -157,10 +156,8 @@ describe('extract functions and resource from location strings', function () { : stringTable.getString(hostStringIndex); resourceType = resourceTable.type[resourceIndex]; } - const lib = - libIndex === undefined || libIndex === null || libIndex === -1 - ? undefined - : libs[libIndex]; + const libIndex = frameLibs[locationIndex]; + const lib = libIndex === null ? undefined : libs[libIndex]; return [ locationName, diff --git a/src/test/unit/profile-query/call-tree.test.ts b/src/test/unit/profile-query/call-tree.test.ts index 41dbbf43b1..7bf2b1d1c6 100644 --- a/src/test/unit/profile-query/call-tree.test.ts +++ b/src/test/unit/profile-query/call-tree.test.ts @@ -23,10 +23,9 @@ describe('call-tree collection', function () { const state = store.getState(); const threadSelectors = getThreadSelectors(0); const callTree = threadSelectors.getCallTree(state); - const libs = profile.libs; // Collect with budget of 3 nodes - const result = collectCallTree(callTree, libs, { + const result = collectCallTree(callTree, { maxNodes: 3, }); @@ -47,10 +46,9 @@ describe('call-tree collection', function () { const state = store.getState(); const threadSelectors = getThreadSelectors(0); const callTree = threadSelectors.getCallTree(state); - const libs = profile.libs; // With small budget, should still include D (100% at depth 3) - const result = collectCallTree(callTree, libs, { + const result = collectCallTree(callTree, { maxNodes: 4, }); @@ -75,10 +73,9 @@ describe('call-tree collection', function () { const state = store.getState(); const threadSelectors = getThreadSelectors(0); const callTree = threadSelectors.getCallTree(state); - const libs = profile.libs; // With budget of 4: should get A, B (50%), D (50%), C (50%) - const result = collectCallTree(callTree, libs, { + const result = collectCallTree(callTree, { maxNodes: 4, }); @@ -99,10 +96,9 @@ describe('call-tree collection', function () { const state = store.getState(); const threadSelectors = getThreadSelectors(0); const callTree = threadSelectors.getCallTree(state); - const libs = profile.libs; // With budget of 2: A and B, should show C/D/E as elided - const result = collectCallTree(callTree, libs, { + const result = collectCallTree(callTree, { maxNodes: 2, }); @@ -125,9 +121,8 @@ describe('call-tree collection', function () { const state = store.getState(); const threadSelectors = getThreadSelectors(0); const callTree = threadSelectors.getCallTree(state); - const libs = profile.libs; - const result = collectCallTree(callTree, libs, { + const result = collectCallTree(callTree, { maxNodes: 4, scoringStrategy: 'exponential-0.9', }); @@ -149,9 +144,8 @@ describe('call-tree collection', function () { const state = store.getState(); const threadSelectors = getThreadSelectors(0); const callTree = threadSelectors.getCallTree(state); - const libs = profile.libs; - const result = collectCallTree(callTree, libs, { + const result = collectCallTree(callTree, { maxNodes: 4, scoringStrategy: 'percentage-only', }); @@ -175,9 +169,8 @@ describe('call-tree collection', function () { const state = store.getState(); const threadSelectors = getThreadSelectors(0); const callTree = threadSelectors.getCallTree(state); - const libs = profile.libs; - const result = collectCallTree(callTree, libs, { + const result = collectCallTree(callTree, { maxNodes: 10, scoringStrategy: 'exponential-0.9', }); @@ -203,10 +196,9 @@ describe('call-tree collection', function () { const state = store.getState(); const threadSelectors = getThreadSelectors(0); const callTree = threadSelectors.getCallTree(state); - const libs = profile.libs; // Small budget to force truncation - const result = collectCallTree(callTree, libs, { + const result = collectCallTree(callTree, { maxNodes: 3, }); @@ -237,7 +229,6 @@ describe('call-tree collection', function () { const state = store.getState(); const threadSelectors = getThreadSelectors(0); const callTree = threadSelectors.getCallTree(state); - const libs = profile.libs; // First verify that A has many children const roots = callTree.getRoots(); @@ -246,7 +237,7 @@ describe('call-tree collection', function () { const aChildren = callTree.getChildren(aCallNode); expect(aChildren.length).toBe(16); // B through Q - const result = collectCallTree(callTree, libs, { + const result = collectCallTree(callTree, { maxNodes: 5, // Small budget to ensure truncation maxChildrenPerNode: 10, }); @@ -272,9 +263,8 @@ describe('call-tree collection', function () { const state = store.getState(); const threadSelectors = getThreadSelectors(0); const callTree = threadSelectors.getCallTree(state); - const libs = profile.libs; - const result = collectCallTree(callTree, libs, { + const result = collectCallTree(callTree, { maxNodes: 10, }); @@ -302,9 +292,8 @@ describe('call-tree collection', function () { const state = store.getState(); const threadSelectors = getThreadSelectors(0); const callTree = threadSelectors.getCallTree(state); - const libs = profile.libs; - const result = collectCallTree(callTree, libs, { + const result = collectCallTree(callTree, { maxNodes: 8, scoringStrategy: 'exponential-0.9', }); @@ -331,9 +320,8 @@ describe('call-tree collection', function () { const state = store.getState(); const threadSelectors = getThreadSelectors(0); const callTree = threadSelectors.getCallTree(state); - const libs = profile.libs; - const result = collectCallTree(callTree, libs, { + const result = collectCallTree(callTree, { maxNodes: 100, maxDepth: 20, }); @@ -354,10 +342,9 @@ describe('call-tree collection', function () { const state = store.getState(); const threadSelectors = getThreadSelectors(0); const callTree = threadSelectors.getCallTree(state); - const libs = profile.libs; // Budget that includes A and B, but not the other children - const result = collectCallTree(callTree, libs, { + const result = collectCallTree(callTree, { maxNodes: 2, }); @@ -392,9 +379,8 @@ describe('call-tree collection', function () { const state = store.getState(); const threadSelectors = getThreadSelectors(0); const callTree = threadSelectors.getCallTree(state); - const libs = profile.libs; - const result = collectCallTree(callTree, libs, { + const result = collectCallTree(callTree, { maxNodes: 2, }); @@ -422,9 +408,8 @@ describe('call-tree collection', function () { const state = store.getState(); const threadSelectors = getThreadSelectors(0); const callTree = threadSelectors.getCallTree(state); - const libs = profile.libs; - const result = collectCallTree(callTree, libs, { + const result = collectCallTree(callTree, { maxNodes: 1000, // High budget maxDepth: 10, // But limited depth }); diff --git a/src/test/unit/profile-query/function-list.test.ts b/src/test/unit/profile-query/function-list.test.ts index d6a736d7cf..0d6ed65b72 100644 --- a/src/test/unit/profile-query/function-list.test.ts +++ b/src/test/unit/profile-query/function-list.test.ts @@ -12,7 +12,6 @@ import { type FunctionData, } from '../../../profile-query/function-list'; import { getProfileFromTextSamples } from '../../fixtures/profiles/processed-profile'; -import type { Lib } from 'firefox-profiler/types'; function createMockTree(functions: FunctionData[]) { return { @@ -24,12 +23,11 @@ function createMockTree(functions: FunctionData[]) { describe('function-list', function () { describe('extractFunctionData', function () { it('extracts function data from a tree', function () { - const { profile, derivedThreads } = getProfileFromTextSamples(` + const { derivedThreads } = getProfileFromTextSamples(` foo bar `); const [thread] = derivedThreads; - const libs: Lib[] = profile.libs; const functions: FunctionData[] = [ { @@ -51,7 +49,7 @@ describe('function-list', function () { ]; const tree = createMockTree(functions); - const result = extractFunctionData(tree, thread, libs); + const result = extractFunctionData(tree, thread); expect(result).toEqual(functions); }); diff --git a/src/test/unit/profile-tree.test.ts b/src/test/unit/profile-tree.test.ts index 90f8a59a91..64fbc98f44 100644 --- a/src/test/unit/profile-tree.test.ts +++ b/src/test/unit/profile-tree.test.ts @@ -697,7 +697,6 @@ describe('origin annotation', function () { shared.funcTable.source[funcIndex] = location ? addSourceToTable(shared.sources, stringTable.indexForString(location)) : null; - shared.resourceTable.lib.push(-1); shared.resourceTable.name.push(stringTable.indexForString(name)); shared.resourceTable.host.push( host ? stringTable.indexForString(host) : null diff --git a/src/test/unit/sanitize.test.ts b/src/test/unit/sanitize.test.ts index 8ab505fbcc..720a290a39 100644 --- a/src/test/unit/sanitize.test.ts +++ b/src/test/unit/sanitize.test.ts @@ -1660,6 +1660,30 @@ describe('sanitizePII', function () { ); }); + it('should compact the libs and translate the frameTable lib column', function () { + // Thread 0's frames are in libA, thread 1's are in libB, and thread 2's + // frames have no library at all (lib === -1). + const { profile } = getProfileFromTextSamples( + `A[lib:libA.so]`, + `B[lib:libB.so]`, + `Cjs` + ); + + expect(profile.libs.map((lib) => lib.name)).toEqual(['libA.so', 'libB.so']); + expect([...profile.shared.frameTable.lib]).toEqual([0, 1, -1]); + + // Remove thread 0, which is the only user of libA.so. + const { sanitizedProfile } = setup( + { shouldRemoveThreads: new Set([0]) }, + profile + ); + + // libA.so is gone, and the surviving frames point at the reindexed libB.so + // while the lib-less frame keeps its -1 sentinel. + expect(sanitizedProfile.libs.map((lib) => lib.name)).toEqual(['libB.so']); + expect([...sanitizedProfile.shared.frameTable.lib]).toEqual([0, -1]); + }); + it('always removes source contents even when no PII removal is requested', function () { const { profile } = getProfileFromTextSamples(`A[file:file1.js]`); // There should be only one source. diff --git a/src/test/url-handling.test.ts b/src/test/url-handling.test.ts index 8855e4094f..c3f5919768 100644 --- a/src/test/url-handling.test.ts +++ b/src/test/url-handling.test.ts @@ -2030,7 +2030,6 @@ describe('URL persistence of bottom box (source view and assembly view)', functi 'hg:hg.mozilla.org/mozilla-central:xpcom/threads/nsThread.cpp:5bb3e281dc9ec8a619c781d52882adb1cacf20bb'; const sourceIndex = getSourceIndex(sourceFile); const bottomBoxInfo: BottomBoxInfo = { - libIndex: 0, sourceIndex, nativeSymbols: [], initialNativeSymbol: null, @@ -2065,7 +2064,6 @@ describe('URL persistence of bottom box (source view and assembly view)', functi 'hg:hg.mozilla.org/mozilla-central:xpcom/threads/nsThread.cpp:5bb3e281dc9ec8a619c781d52882adb1cacf20bb'; const sourceIndex = getSourceIndex(sourceFile); const bottomBoxInfo: BottomBoxInfo = { - libIndex: 0, sourceIndex, nativeSymbols: [], initialNativeSymbol: null, @@ -2096,7 +2094,6 @@ describe('URL persistence of bottom box (source view and assembly view)', functi functionSizeIsKnown: false, }; const bottomBoxInfo: BottomBoxInfo = { - libIndex: 0, sourceIndex: null, nativeSymbols: [nativeSymbolInfo], initialNativeSymbol: 0, @@ -2141,7 +2138,6 @@ describe('URL persistence of bottom box (source view and assembly view)', functi functionSizeIsKnown: false, }; const bottomBoxInfo: BottomBoxInfo = { - libIndex: 0, sourceIndex, nativeSymbols: [nativeSymbolInfo], initialNativeSymbol: 0, diff --git a/src/types/actions.ts b/src/types/actions.ts index 7bff525522..888a3b05a9 100644 --- a/src/types/actions.ts +++ b/src/types/actions.ts @@ -11,7 +11,6 @@ import type { Pid, TabID, IndexIntoCategoryList, - IndexIntoLibs, PageList, IndexIntoSourceTable, FuncTable, @@ -347,7 +346,6 @@ type ProfileAction = } | { readonly type: 'UPDATE_BOTTOM_BOX'; - readonly libIndex: IndexIntoLibs | null; readonly sourceIndex: IndexIntoSourceTable | null; readonly nativeSymbols: NativeSymbolInfo[]; readonly currentNativeSymbol: number | null; diff --git a/src/types/profile-derived.ts b/src/types/profile-derived.ts index 4da76d2ba1..d21f63645c 100644 --- a/src/types/profile-derived.ts +++ b/src/types/profile-derived.ts @@ -287,10 +287,10 @@ export type StackTable = { * * Differs from `RawFrameTable` in that the following columns are always * stored as typed arrays: `address` (`Int32Array`, with `-1` as the sentinel - * for missing addresses), `inlineDepth` (`Uint8Array`), and `func` - * (`Int32Array`). In `RawFrameTable`, these columns may be either regular - * arrays or typed arrays, since regular arrays are convenient during - * construction. + * for missing addresses), `inlineDepth` (`Uint8Array`), `func` (`Int32Array`), + * and `lib` (`Int32Array`, with `-1` as the sentinel for frames with no + * library). In `RawFrameTable`, these columns may be either regular arrays or + * typed arrays, since regular arrays are convenient during construction. */ export type FrameTable = { // Differs from RawFrameTable: always Int32Array (-1 sentinel preserved). @@ -301,6 +301,8 @@ export type FrameTable = { subcategory: (IndexIntoSubcategoryListForCategory | null)[]; // Differs from RawFrameTable: always Int32Array. func: Int32Array; + // Differs from RawFrameTable: always Int32Array (-1 sentinel preserved). + lib: Int32Array; nativeSymbol: (IndexIntoNativeSymbolTable | null)[]; innerWindowID: (InnerWindowID | null)[]; line: (number | null)[]; @@ -861,7 +863,6 @@ export type NativeSymbolInfo = { * assembly view) is updated. */ export type BottomBoxInfo = { - libIndex: IndexIntoLibs | null; sourceIndex: IndexIntoSourceTable | null; nativeSymbols: NativeSymbolInfo[]; initialNativeSymbol: number | null; // index into `nativeSymbols` diff --git a/src/types/profile.ts b/src/types/profile.ts index a473c58223..debe76dba3 100644 --- a/src/types/profile.ts +++ b/src/types/profile.ts @@ -240,7 +240,7 @@ export type RawMarkerTable = { */ export type RawFrameTable = { // If this is a frame for native code, the address is the address of the frame's - // assembly instruction, relative to the native library that contains it. + // assembly instruction, relative to the native library given by `lib`. // // For frames obtained from stack walking, the address points into the call instruction. // It is not a return address, it is a "nudged" return address (i.e. return address @@ -248,12 +248,14 @@ export type RawFrameTable = { // is performed at the end of profile processing. See the big comment above // nudgeReturnAddresses for more details. // - // The library which this address is relative to is given by the frame's nativeSymbol: - // frame -> nativeSymbol -> lib. - // // Frames with no address use the sentinel value `-1`. address: Array
| Int32Array; + // The native library that this frame's code is in. Frames which aren't native + // code (JS frames, label frames), or whose library is unknown, use the + // sentinel value `-1`. + lib: Array | Int32Array; + // The inline depth for this frame. If there is an inline stack at an address, // we create multiple frames with the same address, one for each depth. // The outermost frame always has depth 0. @@ -408,12 +410,12 @@ export const enum ResourceType { } /** - * The ResourceTable holds additional information about functions. It tends to contain - * sparse arrays. Multiple functions can point to the same resource. + * "Resources" are groupings of functions - for example, these functions came + * from JS code from add-on X, or from JS files hosted at a certain origin, + * or from a native library with some name. */ export type ResourceTable = { length: number; - lib: Array; name: Array; host: Array; type: ResourceType[]; diff --git a/src/types/state.ts b/src/types/state.ts index f5198e2a7f..5583e72181 100644 --- a/src/types/state.ts +++ b/src/types/state.ts @@ -22,7 +22,6 @@ import type { ThreadIndex, Pid, TabID, - IndexIntoLibs, IndexIntoSourceTable, } from './profile'; @@ -260,13 +259,6 @@ export type SourceViewState = { scrollGeneration: number; // Optional line number to scroll to in the source view. scrollToLineNumber?: number; - // Non-null if this source file was opened for a function from native code. - // In theory, multiple different libraries can have source files with the same - // path but different content. - // Null if the source file is not for native code or if the lib is not known, - // for example if the source view was opened via the URL (the source URL param - // currently discards the libIndex). - libIndex: IndexIntoLibs | null; // Index into source table. Contains information (filename and uuid) about the // source. Null if a function without a file path was double clicked. sourceIndex: IndexIntoSourceTable | null;