diff --git a/src/actions/receive-profile.ts b/src/actions/receive-profile.ts index bfca2b3ba0..2444aa731d 100644 --- a/src/actions/receive-profile.ts +++ b/src/actions/receive-profile.ts @@ -10,7 +10,10 @@ import { processGeckoProfile, unserializeProfileOfArbitraryFormat, } from 'firefox-profiler/profile-logic/process-profile'; -import { SymbolStore } from 'firefox-profiler/profile-logic/symbol-store'; +import { + readSymbolsFromSymbolTable, + SymbolStore, +} from 'firefox-profiler/profile-logic/symbol-store'; import { symbolicateProfile, applySymbolicationSteps, @@ -94,7 +97,14 @@ import type { BrowserConnection, BrowserConnectionStatus, } from '../app-logic/browser-connection'; -import type { LibSymbolicationRequest } from '../profile-logic/symbol-store'; +import type { + AddressResult, + LibSymbolicationRequest, + LibSymbolicationResponse, + SymbolProvider, +} from '../profile-logic/symbol-store'; +import type { SymbolTableAsTuple } from 'firefox-profiler/profile-logic/symbol-store-db'; +import SymbolStoreDB from 'firefox-profiler/profile-logic/symbol-store-db'; /** * This file collects all the actions that are used for receiving the profile in the @@ -569,13 +579,50 @@ function getSymbolStore( return null; } - async function requestSymbolsWithCallback( + // Note, the database name still references the old project name, "perf.html". It was + // left the same as to not invalidate user's information. + const symbolProvider = new RegularSymbolProvider( + 'perf-html-async-storage', + dispatch, + symbolServerUrl, + browserConnection + ); + return new SymbolStore(symbolProvider); +} + +type DemangleFunction = (name: string) => string; + +/** + * The regular implementation of the SymbolProvider interface: Symbols are requested + * from a server via fetch, and from the browser via a BrowserConnection (if present). + * State changes are notified via redux actions to the provided `dispatch` function. + */ +class RegularSymbolProvider implements SymbolProvider { + _dispatch: Dispatch; + _symbolServerUrl: string; + _browserConnection: BrowserConnection | null; + _symbolDb: SymbolStoreDB; + _demangleCallback: DemangleFunction | null = null; + + constructor( + dbNamePrefix: string, + dispatch: Dispatch, + symbolServerUrl: string, + browserConnection: BrowserConnection | null + ) { + this._dispatch = dispatch; + this._symbolServerUrl = symbolServerUrl; + this._browserConnection = browserConnection; + this._symbolDb = new SymbolStoreDB(`${dbNamePrefix}-symbol-tables`); + } + + async _makeSymbolicationAPIRequestWithCallback( symbolSupplierName: string, requests: LibSymbolicationRequest[], callback: (path: string, requestJson: string) => Promise ) { for (const { lib } of requests) { - dispatch(requestingSymbolTable(lib)); + this._dispatch(requestingSymbolTable(lib)); } try { return await MozillaSymbolicationAPI.requestSymbols( @@ -589,73 +636,160 @@ function getSymbolStore( ); } finally { for (const { lib } of requests) { - dispatch(receivedSymbolTableReply(lib)); + this._dispatch(receivedSymbolTableReply(lib)); } } } - // Note, the database name still references the old project name, "perf.html". It was - // left the same as to not invalidate user's information. - const symbolStore = new SymbolStore('perf-html-async-storage', { - requestSymbolsFromServer: (requests) => - requestSymbolsWithCallback( - 'symbol server', - requests, - async (path, json) => { - const response = await fetch(symbolServerUrl + path, { - body: json, - method: 'POST', - mode: 'cors', - // Use a profiler-specific user agent, so that the symbolication server knows - // what's making this request. - headers: new Headers({ - 'User-Agent': `FirefoxProfiler/1.0 (+${location.origin})`, - }), - }); - return response.json(); - } - ), - - requestSymbolsFromBrowser: async (requests) => { - if (browserConnection === null) { - throw new Error( - 'No connection to the browser, cannot run querySymbolicationApi' - ); + requestSymbolsFromServer( + requests: LibSymbolicationRequest[] + ): Promise { + return this._makeSymbolicationAPIRequestWithCallback( + 'symbol server', + requests, + async (path, json) => { + const response = await fetch(this._symbolServerUrl + path, { + body: json, + method: 'POST', + mode: 'cors', + // Use a profiler-specific user agent, so that the symbolication server knows + // what's making this request. + headers: new Headers({ + 'User-Agent': `FirefoxProfiler/1.0 (+${location.origin})`, + }), + }); + return response.json(); } + ); + } - const bc = browserConnection; - return requestSymbolsWithCallback( - 'browser', - requests, - async (path, json) => - JSON.parse(await bc.querySymbolicationApi(path, json)) + async requestSymbolsFromBrowser( + requests: LibSymbolicationRequest[] + ): Promise { + if (this._browserConnection === null) { + throw new Error( + 'No connection to the browser, cannot run querySymbolicationApi' ); - }, + } - requestSymbolTableFromBrowser: async (lib) => { - if (browserConnection === null) { - throw new Error( - 'No connection to the browser, cannot obtain symbol tables' - ); - } + const bc = this._browserConnection; + return this._makeSymbolicationAPIRequestWithCallback( + 'browser', + requests, + async (path, json) => + JSON.parse(await bc.querySymbolicationApi(path, json)) + ); + } - const { debugName, breakpadId } = lib; - dispatch(requestingSymbolTable(lib)); - try { - const symbolTable = await browserConnection.getSymbolTable( - debugName, - breakpadId - ); - dispatch(receivedSymbolTableReply(lib)); - return symbolTable; - } catch (error) { - dispatch(receivedSymbolTableReply(lib)); - throw error; - } - }, - }); + /** + * This function returns a function that can demangle function name using a + * WebAssembly module, but falls back on the identity function if the + * WebAssembly module isn't available for some reason. + */ + async _createDemangleCallback(): Promise { + try { + // When this module imports some WebAssembly module, Webpack's mechanism + // invokes the WebAssembly object which might be absent in some browsers, + // therefore `import` can throw. Also some browsers might refuse to load a + // wasm module because of our CSP. + // See webpack bug https://github.com/webpack/webpack/issues/8517 + const { demangle_any } = await import('gecko-profiler-demangle'); + return demangle_any; + } catch (error) { + // Module loading can fail (for example in browsers without WebAssembly + // support, or due to bad server configuration), so we will fall back + // to a pass-through function if that happens. + console.error('Demangling module could not be imported.', error); + return (mangledString) => mangledString; + } + } + + async _getDemangleCallback(): Promise { + return (this._demangleCallback ??= await this._createDemangleCallback()); + } + + // Try to get individual symbol tables from the browser, for any libraries + // which couldn't be symbolicated with the symbolication API. + // This is needed for two cases: + // 1. Firefox 95 and earlier, which didn't have a querySymbolicationApi + // WebChannel access point, and only supports symbol tables. + // 2. Android system libraries, even in modern versions of Firefox. We don't + // support querySymbolicationApi for them yet, see + // https://bugzilla.mozilla.org/show_bug.cgi?id=1735897 + async requestSymbolsViaSymbolTableFromBrowser( + request: LibSymbolicationRequest, + ignoreCache: boolean + ): Promise> { + // Check this._symbolDb first, and then call this._getSymbolTablesFromBrowser + // if we couldn't find the table in the cache. + + // We also need a demangling function for this, which is in an async module. + const demangleCallback = await this._getDemangleCallback(); + + const { lib, addresses } = request; + const { debugName, breakpadId } = lib; + let symbolTable = null; + + if (!ignoreCache) { + // Try to get the symbol table from the database. + // This call will return null if the symbol table is not present. + symbolTable = await this._symbolDb.getSymbolTable(debugName, breakpadId); + } + + if (symbolTable === null) { + symbolTable = await this._requestSymbolTableFromBrowser(lib); + this._storeSymbolTableInDB(lib, symbolTable); + } + + return readSymbolsFromSymbolTable(addresses, symbolTable, demangleCallback); + } - return symbolStore; + async _requestSymbolTableFromBrowser( + lib: RequestedLib + ): Promise { + if (this._browserConnection === null) { + throw new Error( + 'No connection to the browser, cannot obtain symbol tables' + ); + } + + const { debugName, breakpadId } = lib; + this._dispatch(requestingSymbolTable(lib)); + try { + const symbolTable = await this._browserConnection.getSymbolTable( + debugName, + breakpadId + ); + this._dispatch(receivedSymbolTableReply(lib)); + return symbolTable; + } catch (error) { + this._dispatch(receivedSymbolTableReply(lib)); + throw error; + } + } + + // Store a symbol table in the database. This is only used for symbol tables + // and not for partial symbol results. Symbol tables are obtained from the + // browser via the geckoProfiler object which is defined in a frame script: + // https://searchfox.org/mozilla-central/rev/a9db89754fb507254cb8422e5a00af7c10d98264/devtools/client/performance-new/frame-script.js#67-81 + // + // We do not store results from the Mozilla symbolication API, because those + // only contain the symbols we requested and not all the symbols of a given + // library. + async _storeSymbolTableInDB( + lib: RequestedLib, + symbolTable: SymbolTableAsTuple + ): Promise { + const { debugName, breakpadId } = lib; + try { + await this._symbolDb.storeSymbolTable(debugName, breakpadId, symbolTable); + } catch (error) { + console.log( + `Failed to store the symbol table for ${debugName} in the database:`, + error + ); + } + } } export async function doSymbolicateProfile( diff --git a/src/profile-logic/symbol-store-db.ts b/src/profile-logic/symbol-store-db.ts index 835db72568..dd12b8ee03 100644 --- a/src/profile-logic/symbol-store-db.ts +++ b/src/profile-logic/symbol-store-db.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 { SymbolsNotFoundError } from './errors'; - // Contains a symbol table, which can be used to map addresses to strings. // Symbol tables of this format are created within Firefox's implementation of // the geckoProfiler WebExtension API, at @@ -182,16 +180,16 @@ export default class SymbolStoreDB { /** * Retrieve the symbol table for the given library. - * @param {string} The debugName of the library. - * @param {string} The breakpadId of the library. - * @return A promise that resolves with the symbol table (in - * SymbolTableAsTuple format), or fails if we couldn't - * find a symbol table for the requested library. + * @param {string} debugName The debugName of the library. + * @param {string} breakpadId The breakpadId of the library. + * @return A promise that resolves with the symbol table (in + * SymbolTableAsTuple format), with null if we couldn't + * find a symbol table for the requested library. */ getSymbolTable( debugName: string, breakpadId: string - ): Promise { + ): Promise { return this._getDB().then((db) => { return new Promise((resolve, reject) => { const transaction = db.transaction('symbol-tables', 'readwrite'); @@ -207,12 +205,7 @@ export default class SymbolStoreDB { const { addrs, index, buffer } = value; updateDateReq.onsuccess = () => resolve([addrs, index, buffer]); } else { - reject( - new SymbolsNotFoundError( - 'The requested library does not exist in the database.', - { debugName, breakpadId } - ) - ); + resolve(null); } }; }); diff --git a/src/profile-logic/symbol-store.ts b/src/profile-logic/symbol-store.ts index 55def604d7..df39c76a30 100644 --- a/src/profile-logic/symbol-store.ts +++ b/src/profile-logic/symbol-store.ts @@ -2,10 +2,9 @@ * 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 SymbolStoreDB from './symbol-store-db'; import { SymbolsNotFoundError } from './errors'; -import type { RequestedLib, ISymbolStoreDB } from 'firefox-profiler/types'; +import type { RequestedLib } from 'firefox-profiler/types'; import type { SymbolTableAsTuple } from './symbol-store-db'; import { ensureExists } from '../utils/types'; @@ -72,7 +71,10 @@ export interface SymbolProvider { // Expensive, should be called if the other two options were unsuccessful. // Does not carry file + line + inlines information. - requestSymbolTableFromBrowser(lib: RequestedLib): Promise; + requestSymbolsViaSymbolTableFromBrowser( + request: LibSymbolicationRequest, + ignoreCache: boolean + ): Promise>; } export interface AbstractSymbolStore { @@ -193,31 +195,6 @@ function _partitionIntoChunksOfMaxValue( return chunks.map(({ elements }) => elements); } -type DemangleFunction = (name: string) => string; - -/** - * This function returns a function that can demangle function name using a - * WebAssembly module, but falls back on the identity function if the - * WebAssembly module isn't available for some reason. - */ -async function _getDemangleCallback(): Promise { - try { - // When this module imports some WebAssembly module, Webpack's mechanism - // invokes the WebAssembly object which might be absent in some browsers, - // therefore `import` can throw. Also some browsers might refuse to load a - // wasm module because of our CSP. - // See webpack bug https://github.com/webpack/webpack/issues/8517 - const { demangle_any } = await import('gecko-profiler-demangle'); - return demangle_any; - } catch (error) { - // Module loading can fail (for example in browsers without WebAssembly - // support, or due to bad server configuration), so we will fall back - // to a pass-through function if that happens. - console.error('Demangling module could not be imported.', error); - return (mangledString) => mangledString; - } -} - /** * The SymbolStore implements efficient lookup of symbols for a set of addresses. * It consults multiple sources of symbol information and caches some results. @@ -226,44 +203,9 @@ async function _getDemangleCallback(): Promise { */ export class SymbolStore { _symbolProvider: SymbolProvider; - _db: ISymbolStoreDB; - constructor( - dbNamePrefixOrDB: string | ISymbolStoreDB, - symbolProvider: SymbolProvider - ) { + constructor(symbolProvider: SymbolProvider) { this._symbolProvider = symbolProvider; - if (typeof dbNamePrefixOrDB === 'string') { - this._db = new SymbolStoreDB(`${dbNamePrefixOrDB}-symbol-tables`); - } else { - this._db = dbNamePrefixOrDB; - } - } - - async closeDb() { - await this._db.close(); - } - - // Store a symbol table in the database. This is only used for symbol tables - // and not for partial symbol results. Symbol tables are obtained from the - // browser via the geckoProfiler object which is defined in a frame script: - // https://searchfox.org/mozilla-central/rev/a9db89754fb507254cb8422e5a00af7c10d98264/devtools/client/performance-new/frame-script.js#67-81 - // - // We do not store results from the Mozilla symbolication API, because those - // only contain the symbols we requested and not all the symbols of a given - // library. - _storeSymbolTableInDB( - lib: RequestedLib, - symbolTable: SymbolTableAsTuple - ): Promise { - return this._db - .storeSymbolTable(lib.debugName, lib.breakpadId, symbolTable) - .catch((error) => { - console.log( - `Failed to store the symbol table for ${lib.debugName} in the database:`, - error - ); - }); } /** @@ -281,10 +223,13 @@ export class SymbolStore { ): Promise { // For each library, we have three options to obtain symbol information for // it. We try all options in order, advancing to the next option on failure. - // Option 1: Symbol tables cached in the database, this._db. - // Option 2: Obtain symbols from the symbol server. - // Option 3: Obtain symbols from the browser, using the symbolication API. - // Option 4: Obtain symbols from the browser, via individual symbol tables. + // Option 1: Obtain symbols from the symbol server, using the symbolication API. + // Option 2: Obtain symbols from the browser, using the symbolication API. + // Option 3: Obtain symbols from the browser, via individual symbol tables. + // + // Symbol tables provide less information than the symbolication API (no inlines + // and no file / line information) so we try the API before we check for cached + // symbol tables. // Check requests for validity first. requests = requests.filter((request) => { @@ -303,48 +248,8 @@ export class SymbolStore { return true; }); - // First, try option 1 for all libraries and partition them by whether it - // was successful. - const requestsForNonCachedLibs: LibSymbolicationRequest[] = []; - const resultsForCachedLibs: Array<{ - lib: RequestedLib; - addresses: Set; - symbolTable: SymbolTableAsTuple; - }> = []; - if (ignoreCache) { - requestsForNonCachedLibs.push(...requests); - } else { - await Promise.all( - requests.map(async (request) => { - const { lib, addresses } = request; - const { debugName, breakpadId } = lib; - try { - // Try to get the symbol table from the database. - // This call will throw if the symbol table is not present. - const symbolTable = await this._db.getSymbolTable( - debugName, - breakpadId - ); - - // Did not throw, option 1 was successful! - resultsForCachedLibs.push({ - lib, - addresses, - symbolTable, - }); - } catch (e) { - if (!(e instanceof SymbolsNotFoundError)) { - // rethrow JavaScript programming error - throw e; - } - requestsForNonCachedLibs.push(request); - } - }) - ); - } - - // First phase of option 2: - // Try to service requestsForNonCachedLibs using the symbolication API, + // Option 1: + // Try to service requests using the symbolication API, // requesting chunks of max 10000 addresses each. In reality, this usually // means that all addresses for libxul get processed in one chunk, and the // addresses from all other libraries get processed in a second chunk. @@ -354,7 +259,7 @@ export class SymbolStore { // latency also suffers because of per-request overhead and pipelining limits. // We also limit each chunk to at most MAX_JOB_COUNT_PER_CHUNK libraries. const chunks = _partitionIntoChunksOfMaxValue( - requestsForNonCachedLibs, + requests, 10000, MAX_JOB_COUNT_PER_CHUNK, ({ addresses }) => addresses.size @@ -369,73 +274,96 @@ export class SymbolStore { this._symbolProvider.requestSymbolsFromServer(requests), ]); - // Finalize requests for which option 1 was successful: - // Now that the requests to the server have been kicked off, process - // symbolication for the libraries for which we found symbol tables in the - // database. This is delayed until after the request has been kicked off - // because it can take some time. - - // We also need a demangling function for this, which is in an async module. - const demangleCallback = await _getDemangleCallback(); - - for (const { lib, addresses, symbolTable } of resultsForCachedLibs) { - successCb( - lib, - readSymbolsFromSymbolTable(addresses, symbolTable, demangleCallback) - ); - } - // Process the results from the symbolication API request, as they arrive. // For unsuccessful requests, fall back to _getSymbolsFromBrowser. await Promise.all( symbolicationApiRequestsAndResponsesPerChunk.map( - async ([option2Requests, option2ResponsePromise]) => { + async ([chunkRequests, chunkResponsePromise]) => { // Store any errors encountered along the way in this map. // We will report them if all avenues fail. const errorMap: Map = new Map( requests.map((r): [LibSymbolicationRequest, Error[]] => [r, []]) ); - // Process the results of option 2: The response from the Mozilla symbolication APi. - const option3Requests = + // Process the results of option 1: The response from the Mozilla symbolication APi. + const requestsRemainingAfterServerAPI = await this._processSuccessfulResponsesAndReturnRemaining( - option2Requests, - option2ResponsePromise, + chunkRequests, + chunkResponsePromise, errorMap, successCb ); - if (option3Requests.length === 0) { + if (requestsRemainingAfterServerAPI.length === 0) { + // Done! + return; + } + + // Some libraries are still remaining, try option 2 for them. + // Option 2 is symbolProvider.requestSymbolsFromBrowser. + const browserAPIResponsePromise = + this._symbolProvider.requestSymbolsFromBrowser( + requestsRemainingAfterServerAPI + ); + const requestsRemainingAfterBrowserAPI = + await this._processSuccessfulResponsesAndReturnRemaining( + requestsRemainingAfterServerAPI, + browserAPIResponsePromise, + errorMap, + successCb + ); + + if (requestsRemainingAfterBrowserAPI.length === 0) { // Done! return; } // Some libraries are still remaining, try option 3 for them. - // Option 3 is symbolProvider.requestSymbolsFromBrowser. - const option3ResponsePromise = - this._symbolProvider.requestSymbolsFromBrowser(option3Requests); - const option4Requests = + // Option 3: Request a symbol table from the browser. + // Now we've reached the stage where we check for symbol tables. + const responsesPromise: Promise = + Promise.all( + requestsRemainingAfterBrowserAPI.map(async (request) => { + try { + const { lib } = request; + const results = + await this._symbolProvider.requestSymbolsViaSymbolTableFromBrowser( + request, + ignoreCache + ); + return { type: 'SUCCESS', lib, results }; + } catch (error) { + return { type: 'ERROR', request, error }; + } + }) + ); + + const failedRequests = await this._processSuccessfulResponsesAndReturnRemaining( - option3Requests, - option3ResponsePromise, + requestsRemainingAfterBrowserAPI, + responsesPromise, errorMap, successCb ); - if (option4Requests.length === 0) { + if (failedRequests.length === 0) { // Done! return; } - // Some libraries are still remaining, try option 4 for them. - // Option 4 is symbolProvider.requestSymbolTableFromBrowser. - await this._getSymbolTablesFromBrowser( - option4Requests, - errorMap, - demangleCallback, - successCb, - errorCb - ); + for (const request of failedRequests) { + const { lib } = request; + // None of the symbolication methods were successful. + // Call the error callback with all accumulated errors. + errorCb( + request, + new SymbolsNotFoundError( + `Could not obtain symbols for ${lib.debugName}/${lib.breakpadId}.`, + lib, + ...(errorMap.get(request) ?? []) + ) + ); + } } ) ); @@ -480,51 +408,4 @@ export class SymbolStore { return requests; } } - - // Try to get individual symbol tables from the browser, for any libraries - // which couldn't be symbolicated with the symbolication API. - // This is needed for two cases: - // 1. Firefox 95 and earlier, which didn't have a querySymbolicationApi - // WebChannel access point, and only supports symbol tables. - // 2. Android system libraries, even in modern versions of Firefox. We don't - // support querySymbolicationApi for them yet, see - // https://bugzilla.mozilla.org/show_bug.cgi?id=1735897 - async _getSymbolTablesFromBrowser( - requests: LibSymbolicationRequest[], - errorMap: Map, - demangleCallback: DemangleFunction, - successCb: (lib: RequestedLib, results: Map) => void, - errorCb: (request: LibSymbolicationRequest, error: Error) => void - ): Promise { - for (const request of requests) { - const { lib, addresses } = request; - try { - // Option 4: Request a symbol table from the browser. - // This call will throw if the browser cannot obtain the symbol table. - const symbolTable = - await this._symbolProvider.requestSymbolTableFromBrowser(lib); - - // Did not throw, option 4 was successful! - successCb( - lib, - readSymbolsFromSymbolTable(addresses, symbolTable, demangleCallback) - ); - - // Store the symbol table in the database. - await this._storeSymbolTableInDB(lib, symbolTable); - } catch (lastError) { - // None of the symbolication methods were successful. - // Call the error callback with all accumulated errors. - errorCb( - request, - new SymbolsNotFoundError( - `Could not obtain symbols for ${lib.debugName}/${lib.breakpadId}.`, - lib, - ...(errorMap.get(request) ?? []), - lastError - ) - ); - } - } - } } diff --git a/src/symbolicator-cli/index.ts b/src/symbolicator-cli/index.ts index 2e917c7a52..797c4674b8 100644 --- a/src/symbolicator-cli/index.ts +++ b/src/symbolicator-cli/index.ts @@ -23,54 +23,9 @@ import { applySymbolicationSteps, } from '../profile-logic/symbolication'; import type { SymbolicationStepInfo } from '../profile-logic/symbolication'; -import type { SymbolTableAsTuple } from '../profile-logic/symbol-store-db'; import * as MozillaSymbolicationAPI from '../profile-logic/mozilla-symbolication-api'; -import { SymbolsNotFoundError } from '../profile-logic/errors'; import type { ThreadIndex } from '../types'; -/** - * Simple 'in-memory' symbol DB that conforms to the same interface as SymbolStoreDB but - * just stores everything in a simple dictionary instead of IndexedDB. The composite key - * [debugName, breakpadId] is flattened to a string "debugName:breakpadId" to use as the - * map key. - */ -export class InMemorySymbolDB { - _store: Map; - - constructor() { - this._store = new Map(); - } - - _makeKey(debugName: string, breakpadId: string): string { - return `${debugName}:${breakpadId}`; - } - - async storeSymbolTable( - debugName: string, - breakpadId: string, - symbolTable: SymbolTableAsTuple - ): Promise { - this._store.set(this._makeKey(debugName, breakpadId), symbolTable); - } - - async getSymbolTable( - debugName: string, - breakpadId: string - ): Promise { - const key = this._makeKey(debugName, breakpadId); - const value = this._store.get(key); - if (typeof value !== 'undefined') { - return value; - } - throw new SymbolsNotFoundError( - 'The requested library does not exist in the database.', - { debugName, breakpadId } - ); - } - - async close(): Promise {} -} - export interface CliOptions { input: string; output: string; @@ -91,14 +46,12 @@ export async function run(options: CliOptions) { throw new Error('Unable to parse the profile.'); } - const symbolStoreDB = new InMemorySymbolDB(); - /** * SymbolStore implementation which just forwards everything to the symbol server in * MozillaSymbolicationAPI format. No support for getting symbols from 'the browser' as * there is no browser in this context. */ - const symbolStore = new SymbolStore(symbolStoreDB, { + const symbolStore = new SymbolStore({ requestSymbolsFromServer: async (requests) => { for (const { lib } of requests) { console.log(` Loading symbols for ${lib.debugName}`); @@ -126,7 +79,7 @@ export async function run(options: CliOptions) { return []; }, - requestSymbolTableFromBrowser: async () => { + requestSymbolsViaSymbolTableFromBrowser: async () => { throw new Error('Not supported in this context'); }, }); diff --git a/src/symbolicator-cli/webpack.config.js b/src/symbolicator-cli/webpack.config.js index 6e43c9b4f8..944b25a84b 100644 --- a/src/symbolicator-cli/webpack.config.js +++ b/src/symbolicator-cli/webpack.config.js @@ -12,6 +12,7 @@ module.exports = { output: { path: path.resolve(projectRoot, 'dist'), filename: 'symbolicator-cli.js', + asyncChunks: false, }, entry: './src/symbolicator-cli/index.ts', module: { diff --git a/src/test/store/receive-profile.test.ts b/src/test/store/receive-profile.test.ts index 13a5618f8b..392fb2e833 100644 --- a/src/test/store/receive-profile.test.ts +++ b/src/test/store/receive-profile.test.ts @@ -84,14 +84,7 @@ function simulateSymbolStoreHasNoCache() { (SymbolStoreDB as any).mockImplementation(() => ({ getSymbolTable: jest .fn() - .mockImplementation((debugName, breakpadId) => - Promise.reject( - new SymbolsNotFoundError( - 'The requested library does not exist in the database.', - { debugName, breakpadId } - ) - ) - ), + .mockImplementation((_debugName, _breakpadId) => Promise.resolve(null)), })); } @@ -781,7 +774,6 @@ describe('actions/receive-profile', function () { const browserConnectionStatus = await createBrowserConnection('Firefox/123.0'); await dispatch(retrieveProfileFromBrowser(browserConnectionStatus)); - expect(console.warn).toHaveBeenCalledTimes(2); const state = getState(); expect(getView(state)).toEqual({ phase: 'DATA_LOADED' }); diff --git a/src/test/store/symbolication.test.ts b/src/test/store/symbolication.test.ts index 8f7065ec8b..1eba0dfed1 100644 --- a/src/test/store/symbolication.test.ts +++ b/src/test/store/symbolication.test.ts @@ -9,14 +9,17 @@ import { partialSymbolTable, } from '../fixtures/example-symbol-table'; import type { ExampleSymbolTable } from '../fixtures/example-symbol-table'; -import type { MarkerPayload, RequestedLib } from 'firefox-profiler/types'; +import type { MarkerPayload } from 'firefox-profiler/types'; import type { AddressResult, LibSymbolicationRequest, LibSymbolicationResponse, SymbolProvider, } from '../../profile-logic/symbol-store'; -import { SymbolStore } from '../../profile-logic/symbol-store'; +import { + readSymbolsFromSymbolTable, + SymbolStore, +} from '../../profile-logic/symbol-store'; import * as ProfileViewSelectors from '../../selectors/profile'; import { selectedThreadSelectors } from '../../selectors/per-thread'; import { INTERVAL } from 'firefox-profiler/app-logic/constants'; @@ -34,10 +37,6 @@ import { assertSetContainsOnly } from '../fixtures/custom-assertions'; import { StringTable } from '../../utils/string-table'; import { ensureExists } from 'firefox-profiler/utils/types'; -// fake-indexeddb no longer includes a structuredClone polyfill, so we need to -// import it explicitly. -import 'core-js/stable/structured-clone'; -import { indexedDB, IDBKeyRange } from 'fake-indexeddb'; import { stripIndent } from 'common-tags'; import { SymbolsNotFoundError } from '../../profile-logic/errors'; @@ -46,21 +45,6 @@ import { SymbolsNotFoundError } from '../../profile-logic/errors'; * its own file. */ describe('doSymbolicateProfile', function () { - const symbolStoreName = 'test-db'; - beforeAll(function () { - // The SymbolStore requires IndexedDB, otherwise symbolication will be skipped. - window.indexedDB = indexedDB; - window.IDBKeyRange = IDBKeyRange; - }); - - afterAll(async function () { - // @ts-expect-error property must be optional - delete window.indexedDB; - // @ts-expect-error property must be optional - delete window.IDBKeyRange; - await _deleteDatabase(`${symbolStoreName}-symbol-tables`); - }); - // Initialize a store, an unsymbolicated profile, and helper functions. function init() { // The rejection in `requestSymbolsFromServer` outputs an error log, let's @@ -101,7 +85,7 @@ describe('doSymbolicateProfile', function () { type: 'ERROR' as const, request, error: new SymbolsNotFoundError( - 'Not in from-server mode, try requestSymbolTableFromBrowser.', + 'Not in from-server mode, try requestSymbolsViaSymbolTableFromBrowser.', lib ), }; @@ -123,7 +107,11 @@ describe('doSymbolicateProfile', function () { throw new Error('requestSymbolsFromBrowser unsupported in this test'); }, - requestSymbolTableFromBrowser: async (lib: RequestedLib) => { + requestSymbolsViaSymbolTableFromBrowser: async ( + request: LibSymbolicationRequest, + _ignoreCache: boolean + ) => { + const { lib, addresses } = request; if (lib.debugName !== 'firefox.pdb') { throw new SymbolsNotFoundError( 'Should only have libs called firefox.pdb', @@ -132,11 +120,14 @@ describe('doSymbolicateProfile', function () { } if (symbolicationProviderMode !== 'from-browser') { throw new Error( - 'should not call requestSymbolTableFromBrowser if requestSymbolsFromServer is successful' + 'should not call requestSymbolsViaSymbolTableFromBrowser if requestSymbolsFromServer is successful' ); } - - return symbolTable.asTuple; + return readSymbolsFromSymbolTable( + addresses, + symbolTable.asTuple, + (s: string) => s + ); }, }; @@ -155,7 +146,7 @@ describe('doSymbolicateProfile', function () { }), switchSymbolTable, switchSymbolProviderMode, - symbolStore: new SymbolStore(symbolStoreName, symbolProvider), + symbolStore: new SymbolStore(symbolProvider), }; } @@ -193,8 +184,7 @@ describe('doSymbolicateProfile', function () { ]); }); - it('uses the cache when available', async () => { - // This reuses the db from the previous test + it('does different merging depending on available symbols', async () => { const { store: { dispatch, getState }, profile, @@ -202,10 +192,6 @@ describe('doSymbolicateProfile', function () { switchSymbolTable, } = init(); - // This partial symbol table should not be used, because the db cache is - // used instead. - switchSymbolTable(partialSymbolTable); - await doSymbolicateProfile(dispatch, profile, symbolStore); expect(formatTree(getCallTree(getState()))).toEqual([ // 0x0000 and 0x000a get merged together. @@ -215,7 +201,8 @@ describe('doSymbolicateProfile', function () { '- second symbol (total: 1, self: 1)', ]); - // But the partial symbol table should be used when ignoring the cache. + // Now re-symbolicate with the partial symbol table. + switchSymbolTable(partialSymbolTable); await doSymbolicateProfile( dispatch, profile, @@ -226,25 +213,9 @@ describe('doSymbolicateProfile', function () { '- overencompassing first symbol (total: 4, self: 2)', ' - last symbol (total: 2, self: 2)', ]); - - // And then the cache should have been overwritten, let's check this by - // switching the symbol table again. - switchSymbolTable(completeSymbolTable); - // This time do not ignore the cache. - await doSymbolicateProfile(dispatch, profile, symbolStore); - // The result should be the same despite that the complete symbol table - // has been configured, this means the incomplete symbol table is in the - // DB cache. - expect(formatTree(getCallTree(getState()))).toEqual([ - '- overencompassing first symbol (total: 4, self: 2)', - ' - last symbol (total: 2, self: 2)', - ]); }); it('can symbolicate a profile when symbols come from-server', async () => { - // Get rid of any cached symbol tables from the previous test. - await _deleteDatabase(`${symbolStoreName}-symbol-tables`); - const { store: { dispatch, getState }, profile, @@ -402,7 +373,6 @@ describe('doSymbolicateProfile', function () { describe('merging of functions with different memory addresses, but in the same function', () => { it('starts with expanded call nodes of multiple memory addresses', async function () { - // Don't use the mocks on this test, as no SymbolStore database is needed. const { store: { dispatch, getState }, funcNamesToFuncIndexes, @@ -465,9 +435,6 @@ describe('doSymbolicateProfile', function () { }); it('can symbolicate a profile with a partial symbol table and re-symbolicate it with a complete symbol table', async () => { - // Get rid of any cached symbol tables from the previous tests. - await _deleteDatabase(`${symbolStoreName}-symbol-tables`); - const { store: { dispatch, getState }, symbolStore, @@ -514,9 +481,6 @@ describe('doSymbolicateProfile', function () { }); it('can re-symbolicate a partially-symbolicated profile even if it needs to add funcs to the funcTable', async () => { - // Get rid of any cached symbol tables from the previous tests. - await _deleteDatabase(`${symbolStoreName}-symbol-tables`); - const { store: { dispatch, getState }, symbolStore, @@ -661,11 +625,3 @@ function _createUnsymbolicatedProfile() { return profile; } - -function _deleteDatabase(dbName: string) { - return new Promise((resolve, reject) => { - const req = indexedDB.deleteDatabase(dbName); - req.onsuccess = () => resolve(undefined); - req.onerror = () => reject(req.error); - }); -} diff --git a/src/test/unit/symbol-store-db.test.ts b/src/test/unit/symbol-store-db.test.ts index 65c84247ff..22c954028f 100644 --- a/src/test/unit/symbol-store-db.test.ts +++ b/src/test/unit/symbol-store-db.test.ts @@ -41,7 +41,7 @@ describe('SymbolStoreDB', function () { for (let i = 0; i < 5; i++) { await expect( symbolStoreDB.getSymbolTable(libs[i].debugName, libs[i].breakpadId) - ).rejects.toBeInstanceOf(Error); + ).resolves.toBeNull(); // .rejects.toMatch('does not exist in the database'); // TODO Some future verison of jest should make this work } @@ -61,8 +61,7 @@ describe('SymbolStoreDB', function () { for (let i = 0; i < 5; i++) { await expect( symbolStoreDB.getSymbolTable(libs[i].debugName, libs[i].breakpadId) - ).rejects.toBeInstanceOf(Error); - // .rejects.toMatch('does not exist in the database'); // TODO Some future verison of jest should make this work + ).resolves.toBeNull(); } for (let i = 5; i < 10; i++) { @@ -85,8 +84,7 @@ describe('SymbolStoreDB', function () { for (let i = 0; i < 10; i++) { await expect( symbolStoreDB.getSymbolTable(libs[i].debugName, libs[i].breakpadId) - ).rejects.toBeInstanceOf(Error); - // .rejects.toMatch('does not exist in the database'); // TODO Some future verison of jest should make this work + ).resolves.toBeNull(); } await symbolStoreDB.close(); diff --git a/src/test/unit/symbol-store.test.ts b/src/test/unit/symbol-store.test.ts index 52aeedc00c..4b88f7aa7d 100644 --- a/src/test/unit/symbol-store.test.ts +++ b/src/test/unit/symbol-store.test.ts @@ -3,16 +3,16 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import type { AddressResult, + LibSymbolicationRequest, LibSymbolicationResponse, SymbolProvider, } from '../../profile-logic/symbol-store'; -import { SymbolStore } from '../../profile-logic/symbol-store'; +import { + readSymbolsFromSymbolTable, + SymbolStore, +} from '../../profile-logic/symbol-store'; import { SymbolsNotFoundError } from '../../profile-logic/errors'; import { completeSymbolTableAsTuple } from '../fixtures/example-symbol-table'; -// fake-indexeddb no longer includes a structuredClone polyfill, so we need to -// import it explicitly. -import 'core-js/stable/structured-clone'; -import { indexedDB, IDBKeyRange } from 'fake-indexeddb'; import { FakeSymbolStore } from '../fixtures/fake-symbol-store'; import { ensureExists } from '../../utils/types'; import type { RequestedLib } from 'firefox-profiler/types'; @@ -21,38 +21,6 @@ describe('SymbolStore', function () { let symbolProvider: SymbolProvider | null = null; let symbolStore: SymbolStore | null = null; - function deleteDatabase() { - return new Promise((resolve, reject) => { - const req = indexedDB.deleteDatabase( - 'profiler-async-storage-symbol-tables' - ); - req.onsuccess = () => resolve(undefined); - req.onerror = () => reject(req.error); - }); - } - - beforeAll(function () { - // The SymbolStore requires IndexedDB, otherwise symbolication will be skipped. - window.indexedDB = indexedDB; - window.IDBKeyRange = IDBKeyRange; - }); - - afterAll(function () { - // @ts-expect-error - Check tsconfig DOM stuff in tests directory - delete window.indexedDB; - // @ts-expect-error - Check tsconfig DOM stuff in tests directory - delete window.IDBKeyRange; - - symbolStore = null; - }); - - afterEach(async function () { - if (symbolStore) { - await symbolStore.closeDb().catch(() => {}); - } - await deleteDatabase(); - }); - it('should only request symbols from the symbol provider once per library', async function () { symbolProvider = { requestSymbolsFromServer: jest.fn((requests) => @@ -70,13 +38,22 @@ describe('SymbolStore', function () { requestSymbolsFromBrowser: async (_requests) => { throw new Error('requestSymbolsFromBrowser unsupported in this test'); }, - requestSymbolTableFromBrowser: jest.fn(() => - Promise.resolve(completeSymbolTableAsTuple) + requestSymbolsViaSymbolTableFromBrowser: jest.fn( + (request: LibSymbolicationRequest, _ignoreCache: boolean) => { + const results = readSymbolsFromSymbolTable( + request.addresses, + completeSymbolTableAsTuple, + (s: string) => s + ); + return Promise.resolve(results); + } ), }; - symbolStore = new SymbolStore('profiler-async-storage', symbolProvider); + symbolStore = new SymbolStore(symbolProvider); - expect(symbolProvider.requestSymbolTableFromBrowser).not.toHaveBeenCalled(); + expect( + symbolProvider.requestSymbolsViaSymbolTableFromBrowser + ).not.toHaveBeenCalled(); const lib1 = { debugName: 'firefox', breakpadId: 'dont-care' }; let secondAndThirdSymbol = new Map(); @@ -89,9 +66,9 @@ describe('SymbolStore', function () { errorCallback ); expect(symbolProvider.requestSymbolsFromServer).toHaveBeenCalledTimes(1); - expect(symbolProvider.requestSymbolTableFromBrowser).toHaveBeenCalledTimes( - 1 - ); + expect( + symbolProvider.requestSymbolsViaSymbolTableFromBrowser + ).toHaveBeenCalledTimes(1); expect(errorCallback).not.toHaveBeenCalled(); expect(secondAndThirdSymbol.get(0xf01)).toEqual({ name: 'second symbol', @@ -114,9 +91,9 @@ describe('SymbolStore', function () { errorCallback ); expect(symbolProvider.requestSymbolsFromServer).toHaveBeenCalledTimes(2); - expect(symbolProvider.requestSymbolTableFromBrowser).toHaveBeenCalledTimes( - 2 - ); + expect( + symbolProvider.requestSymbolsViaSymbolTableFromBrowser + ).toHaveBeenCalledTimes(2); expect(errorCallback).not.toHaveBeenCalled(); expect(firstAndLastSymbol.get(0x33)).toEqual({ name: 'first symbol', @@ -138,9 +115,9 @@ describe('SymbolStore', function () { errorCallback ); expect(symbolProvider.requestSymbolsFromServer).toHaveBeenCalledTimes(2); - expect(symbolProvider.requestSymbolTableFromBrowser).toHaveBeenCalledTimes( - 2 - ); + expect( + symbolProvider.requestSymbolsViaSymbolTableFromBrowser + ).toHaveBeenCalledTimes(2); // Empty breakpadIds should result in an error. expect(errorCallback).toHaveBeenCalledWith( @@ -153,55 +130,6 @@ describe('SymbolStore', function () { ); }); - it('should persist in DB', async function () { - symbolProvider = { - requestSymbolsFromServer: jest.fn((requests) => - Promise.resolve( - requests.map((request) => ({ - type: 'ERROR', - request, - error: new Error('this example only supports symbol tables'), - })) - ) - ), - requestSymbolsFromBrowser: jest.fn((_requests) => - Promise.reject( - new Error('requestSymbolsFromBrowser unsupported in this test') - ) - ), - requestSymbolTableFromBrowser: jest.fn(() => - Promise.resolve(completeSymbolTableAsTuple) - ), - }; - symbolStore = new SymbolStore('profiler-async-storage', symbolProvider); - - const lib = { debugName: 'firefox', breakpadId: 'dont-care' }; - const errorCallback = jest.fn((_request, _error) => {}); - await symbolStore.getSymbols( - [{ lib: lib, addresses: new Set([0]) }], - (_request, _results) => {}, - errorCallback - ); - expect(errorCallback).not.toHaveBeenCalled(); - - // Using another symbol store simulates a page reload - // Due to https://github.com/dumbmatter/fakeIndexedDB/issues/22 we need to - // take care to sequence the DB open requests. - await symbolStore.closeDb().catch(() => {}); - symbolStore = new SymbolStore('profiler-async-storage', symbolProvider); - - await symbolStore.getSymbols( - [{ lib: lib, addresses: new Set([0x1]) }], - (_request, _results) => {}, - errorCallback - ); - - expect(symbolProvider.requestSymbolTableFromBrowser).toHaveBeenCalledTimes( - 1 - ); - expect(errorCallback).not.toHaveBeenCalled(); - }); - it('should call requestSymbolsFromServer first', async function () { const symbolTable = new Map([ [0, 'first symbol'], @@ -245,11 +173,18 @@ describe('SymbolStore', function () { new Error('requestSymbolsFromBrowser unsupported in this test') ) ), - requestSymbolTableFromBrowser: jest - .fn() - .mockResolvedValue(completeSymbolTableAsTuple), + requestSymbolsViaSymbolTableFromBrowser: jest.fn( + (request: LibSymbolicationRequest, _ignoreCache: boolean) => { + const results = readSymbolsFromSymbolTable( + request.addresses, + completeSymbolTableAsTuple, + (s: string) => s + ); + return Promise.resolve(results); + } + ), }; - symbolStore = new SymbolStore('profiler-async-storage', symbolProvider); + symbolStore = new SymbolStore(symbolProvider); const lib1 = { debugName: 'available-for-addresses', @@ -283,10 +218,10 @@ describe('SymbolStore', function () { expect(errorCallback).not.toHaveBeenCalled(); // requestSymbolsFromServer should have failed for lib2, so - // requestSymbolTableFromBrowser should have been called for it, once. - expect(symbolProvider.requestSymbolTableFromBrowser).toHaveBeenCalledTimes( - 1 - ); + // requestSymbolsViaSymbolTableFromBrowser should have been called for it, once. + expect( + symbolProvider.requestSymbolsViaSymbolTableFromBrowser + ).toHaveBeenCalledTimes(1); const lib1Symbols = ensureExists(symbolsPerLibrary.get(lib1)); const lib2Symbols = ensureExists(symbolsPerLibrary.get(lib2)); @@ -307,35 +242,6 @@ describe('SymbolStore', function () { name: 'last symbol', symbolAddress: 0x2000, }); - - // Using another symbol store simulates a page reload - // Due to https://github.com/dumbmatter/fakeIndexedDB/issues/22 we need to - // take care to sequence the DB open requests. - const symbolStore2 = new SymbolStore( - 'profiler-async-storage', - symbolProvider - ); - - await symbolStore2.getSymbols( - [ - { lib: lib1, addresses: new Set([0xf01, 0x1a50]) }, - { lib: lib2, addresses: new Set([0x33, 0x2000]) }, - ], - (_request, _results) => {}, - errorCallback - ); - - // The symbolStore should already have a cached symbol table for lib2 now, - // so requestSymbolsFromServer should only have been called for one request. - expect(symbolProvider.requestSymbolsFromServer).toHaveBeenCalledTimes(2); - expect(symbolsForAddressesRequestCount).toEqual(3); - expect(errorCallback).not.toHaveBeenCalled(); - - // requestSymbolsFromServer should have succeeded for that one request, - // so requestSymbolTableFromBrowser should not have been called again. - expect(symbolProvider.requestSymbolTableFromBrowser).toHaveBeenCalledTimes( - 1 - ); }); it('should should report the right errors', async function () { @@ -410,19 +316,28 @@ describe('SymbolStore', function () { new Error('requestSymbolsFromBrowser unsupported in this test') ) ), - requestSymbolTableFromBrowser: async ({ debugName, breakpadId }) => { + requestSymbolsViaSymbolTableFromBrowser: async ( + request, + _ignoreCache + ) => { + const { lib, addresses } = request; + const { debugName, breakpadId } = lib; expect(debugName).not.toEqual(''); expect(breakpadId).not.toEqual(''); if ( debugName === 'available-from-browser' || debugName === 'available-from-both-server-and-browser' ) { - return completeSymbolTableAsTuple; + return readSymbolsFromSymbolTable( + addresses, + completeSymbolTableAsTuple, + (s: string) => s + ); } throw new Error('The browser does not have symbols for this library.'); }, }; - symbolStore = new SymbolStore('profiler-async-storage', symbolProvider); + symbolStore = new SymbolStore(symbolProvider!); const addresses = new Set([0xf01, 0x1a50]); const succeededLibs = new Set(); @@ -452,7 +367,7 @@ describe('SymbolStore', function () { // Empty debugNames or breakpadIds should cause errors. And if symbols are // not available from any source, all errors along the way should be included // in the reported error. - expect(failedLibs.size).toBe(3); + expect([...failedLibs]).toBeArrayOfSize(3); expect(failedLibs.get('empty-breakpadid')).toEqual( expect.objectContaining({ message: expect.stringContaining('Invalid debugName or breakpadId'), diff --git a/src/test/unit/symbolicator-cli.test.ts b/src/test/unit/symbolicator-cli.test.ts index 84b5bb3174..59138c332b 100644 --- a/src/test/unit/symbolicator-cli.test.ts +++ b/src/test/unit/symbolicator-cli.test.ts @@ -1,9 +1,7 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { InMemorySymbolDB, makeOptionsFromArgv } from '../../symbolicator-cli'; -import { completeSymbolTableAsTuple } from '../fixtures/example-symbol-table'; -import { SymbolsNotFoundError } from '../../profile-logic/errors'; +import { makeOptionsFromArgv } from '../../symbolicator-cli'; describe('makeOptionsFromArgv', function () { const commonArgs = ['/path/to/node', '/path/to/symbolicator-cli.js']; @@ -99,29 +97,3 @@ describe('makeOptionsFromArgv', function () { ).toThrow(); }); }); - -describe('InMemorySymbolDB', function () { - const debugName = 'debugName'; - const breakpadId = 'breakpadId'; - - it('should get a SymbolTable that was set', async function () { - const db = new InMemorySymbolDB(); - - await db.storeSymbolTable( - debugName, - breakpadId, - completeSymbolTableAsTuple - ); - - const table = await db.getSymbolTable(debugName, breakpadId); - expect(table).toEqual(completeSymbolTableAsTuple); - }); - - it('should throw when getting a SymbolTable that was not set', async function () { - const db = new InMemorySymbolDB(); - - await expect(async () => { - await db.getSymbolTable(debugName, breakpadId); - }).rejects.toThrow(SymbolsNotFoundError); - }); -}); diff --git a/src/types/symbolication.ts b/src/types/symbolication.ts index 5935020119..5c0bd5b8d1 100644 --- a/src/types/symbolication.ts +++ b/src/types/symbolication.ts @@ -2,37 +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/. */ -export interface ISymbolStoreDB { - /** - * Store the symbol table for a given library. - * @param {string} The debugName of the library. - * @param {string} The breakpadId of the library. - * @param {symbolTable} The symbol table, in SymbolTableAsTuple format. - * @return A promise that resolves (with nothing) once storage - * has succeeded. - */ - storeSymbolTable( - debugName: string, - breakpadId: string, - symbolTable: SymbolTableAsTuple - ): Promise; - - /** - * Retrieve the symbol table for the given library. - * @param {string} The debugName of the library. - * @param {string} The breakpadId of the library. - * @return A promise that resolves with the symbol table (in - * SymbolTableAsTuple format), or fails if we couldn't - * find a symbol table for the requested library. - */ - getSymbolTable( - debugName: string, - breakpadId: string - ): Promise; - - close(): Promise; -} - export type SymbolTableAsTuple = [ Uint32Array, // addrs Uint32Array, // index