/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* vim: set sts=2 sw=2 et tw=80: */ /* 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/. */ "use strict"; const PREF_ASYNC_STACK = "javascript.options.asyncstack"; const ASYNC_STACKS_ENABLED = Services.prefs.getBoolPref( PREF_ASYNC_STACK, false ); var { ExtensionError } = ExtensionUtils; ChromeUtils.defineLazyGetter(this, "symbolicationService", () => { let { createLocalSymbolicationService } = ChromeUtils.importESModule( "resource://devtools/client/performance-new/shared/symbolication.sys.mjs" ); return createLocalSymbolicationService(Services.profiler.sharedLibraries, []); }); const isRunningObserver = { _observers: new Set(), observe(subject, topic) { switch (topic) { case "profiler-started": case "profiler-stopped": // Call observer(false) or observer(true), but do it through a promise // so that it's asynchronous. // We don't want it to be synchronous because of the observer call in // addObserver, which is asynchronous, and we want to get the ordering // right. const isRunningPromise = Promise.resolve(topic === "profiler-started"); for (let observer of this._observers) { isRunningPromise.then(observer); } break; } }, _startListening() { Services.obs.addObserver(this, "profiler-started"); Services.obs.addObserver(this, "profiler-stopped"); }, _stopListening() { Services.obs.removeObserver(this, "profiler-started"); Services.obs.removeObserver(this, "profiler-stopped"); }, addObserver(observer) { if (this._observers.size === 0) { this._startListening(); } this._observers.add(observer); observer(Services.profiler.IsActive()); }, removeObserver(observer) { if (this._observers.delete(observer) && this._observers.size === 0) { this._stopListening(); } }, }; this.geckoProfiler = class extends ExtensionAPI { getAPI(context) { return { geckoProfiler: { async start(options) { const { bufferSize, windowLength, interval, features, threads } = options; Services.prefs.setBoolPref(PREF_ASYNC_STACK, false); if (threads) { Services.profiler.StartProfiler( bufferSize, interval, features, threads, 0, windowLength ); } else { Services.profiler.StartProfiler( bufferSize, interval, features, [], 0, windowLength ); } }, async stop() { if (ASYNC_STACKS_ENABLED !== null) { Services.prefs.setBoolPref(PREF_ASYNC_STACK, ASYNC_STACKS_ENABLED); } Services.profiler.StopProfiler(); }, async pause() { Services.profiler.Pause(); }, async resume() { Services.profiler.Resume(); }, async dumpProfileToFile(fileName) { if (!Services.profiler.IsActive()) { throw new ExtensionError( "The profiler is stopped. " + "You need to start the profiler before you can capture a profile." ); } if (fileName.includes("\\") || fileName.includes("/")) { throw new ExtensionError("Path cannot contain a subdirectory."); } let dirPath = PathUtils.join(PathUtils.profileDir, "profiler"); let filePath = PathUtils.join(dirPath, fileName); try { await IOUtils.makeDirectory(dirPath); await Services.profiler.dumpProfileToFileAsync(filePath); } catch (e) { Cu.reportError(e); throw new ExtensionError(`Dumping profile to ${filePath} failed.`); } }, async getProfile() { if (!Services.profiler.IsActive()) { throw new ExtensionError( "The profiler is stopped. " + "You need to start the profiler before you can capture a profile." ); } return Services.profiler.getProfileDataAsync(); }, async getProfileAsArrayBuffer() { if (!Services.profiler.IsActive()) { throw new ExtensionError( "The profiler is stopped. " + "You need to start the profiler before you can capture a profile." ); } return Services.profiler.getProfileDataAsArrayBuffer(); }, async getProfileAsGzippedArrayBuffer() { if (!Services.profiler.IsActive()) { throw new ExtensionError( "The profiler is stopped. " + "You need to start the profiler before you can capture a profile." ); } return Services.profiler.getProfileDataAsGzippedArrayBuffer(); }, async getSymbols(debugName, breakpadId) { return symbolicationService.getSymbolTable(debugName, breakpadId); }, onRunning: new EventManager({ context, name: "geckoProfiler.onRunning", register: fire => { isRunningObserver.addObserver(fire.async); return () => { isRunningObserver.removeObserver(fire.async); }; }, }).api(), }, }; } };