diff options
Diffstat (limited to 'devtools/server/actors/perf.js')
-rw-r--r-- | devtools/server/actors/perf.js | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/devtools/server/actors/perf.js b/devtools/server/actors/perf.js new file mode 100644 index 0000000000..3f561256c9 --- /dev/null +++ b/devtools/server/actors/perf.js @@ -0,0 +1,187 @@ +/* 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 { Actor } = require("resource://devtools/shared/protocol.js"); +const { perfSpec } = require("resource://devtools/shared/specs/perf.js"); + +loader.lazyRequireGetter( + this, + "RecordingUtils", + "resource://devtools/shared/performance-new/recording-utils.js" +); + +// Some platforms are built without the Gecko Profiler. +const IS_SUPPORTED_PLATFORM = "nsIProfiler" in Ci; + +/** + * The PerfActor wraps the Gecko Profiler interface (aka Services.profiler). + */ +exports.PerfActor = class PerfActor extends Actor { + constructor(conn) { + super(conn, perfSpec); + + // Only setup the observers on a supported platform. + if (IS_SUPPORTED_PLATFORM) { + this._observer = { + observe: this._observe.bind(this), + }; + Services.obs.addObserver(this._observer, "profiler-started"); + Services.obs.addObserver(this._observer, "profiler-stopped"); + } + } + + destroy() { + super.destroy(); + + if (!IS_SUPPORTED_PLATFORM) { + return; + } + Services.obs.removeObserver(this._observer, "profiler-started"); + Services.obs.removeObserver(this._observer, "profiler-stopped"); + } + + startProfiler(options) { + if (!IS_SUPPORTED_PLATFORM) { + return false; + } + + // For a quick implementation, decide on some default values. These may need + // to be tweaked or made configurable as needed. + const settings = { + entries: options.entries || 1000000, + duration: options.duration || 0, + interval: options.interval || 1, + features: options.features || [ + "js", + "stackwalk", + "cpu", + "responsiveness", + ], + threads: options.threads || ["GeckoMain", "Compositor"], + activeTabID: RecordingUtils.getActiveBrowserID(), + }; + + try { + // This can throw an error if the profiler is in the wrong state. + Services.profiler.StartProfiler( + settings.entries, + settings.interval, + settings.features, + settings.threads, + settings.activeTabID, + settings.duration + ); + } catch (e) { + // In case any errors get triggered, bailout with a false. + return false; + } + + return true; + } + + stopProfilerAndDiscardProfile() { + if (!IS_SUPPORTED_PLATFORM) { + return; + } + Services.profiler.StopProfiler(); + } + + /** + * @type {string} debugPath + * @type {string} breakpadId + * @returns {Promise<[number[], number[], number[]]>} + */ + async getSymbolTable(debugPath, breakpadId) { + const [addr, index, buffer] = await Services.profiler.getSymbolTable( + debugPath, + breakpadId + ); + // The protocol does not support the transfer of typed arrays, so we convert + // these typed arrays to plain JS arrays of numbers now. + // Our return value type is declared as "array:array:number". + return [Array.from(addr), Array.from(index), Array.from(buffer)]; + } + + async getProfileAndStopProfiler() { + if (!IS_SUPPORTED_PLATFORM) { + return null; + } + + // Pause profiler before we collect the profile, so that we don't capture + // more samples while the parent process or android threads wait for subprocess profiles. + Services.profiler.Pause(); + + let profile; + try { + // Attempt to pull out the data. + profile = await Services.profiler.getProfileDataAsync(); + + if (Object.keys(profile).length === 0) { + console.error( + "An empty object was received from getProfileDataAsync.getProfileDataAsync(), " + + "meaning that a profile could not successfully be serialized and captured." + ); + profile = null; + } + } catch (e) { + // Explicitly set the profile to null if there as an error. + profile = null; + console.error(`There was an error fetching a profile`, e); + } + + // Stop and discard the buffers. + Services.profiler.StopProfiler(); + + // Returns a profile when successful, and null when there is an error. + return profile; + } + + isActive() { + if (!IS_SUPPORTED_PLATFORM) { + return false; + } + return Services.profiler.IsActive(); + } + + isSupportedPlatform() { + return IS_SUPPORTED_PLATFORM; + } + + /** + * Watch for events that happen within the browser. These can affect the + * current availability and state of the Gecko Profiler. + */ + _observe(subject, topic, _data) { + // Note! If emitting new events make sure and update the list of bridged + // events in the perf actor. + switch (topic) { + case "profiler-started": + const param = subject.QueryInterface(Ci.nsIProfilerStartParams); + this.emit( + topic, + param.entries, + param.interval, + param.features, + param.duration, + param.activeTabID + ); + break; + case "profiler-stopped": + this.emit(topic); + break; + } + } + + /** + * Lists the supported features of the profiler for the current browser. + * @returns {string[]} + */ + getSupportedFeatures() { + if (!IS_SUPPORTED_PLATFORM) { + return []; + } + return Services.profiler.GetFeatures(); + } +}; |