diff options
Diffstat (limited to 'toolkit/devtools/profiler/profiler.js')
-rw-r--r-- | toolkit/devtools/profiler/profiler.js | 234 |
1 files changed, 234 insertions, 0 deletions
diff --git a/toolkit/devtools/profiler/profiler.js b/toolkit/devtools/profiler/profiler.js new file mode 100644 index 000000000..2e0ffad14 --- /dev/null +++ b/toolkit/devtools/profiler/profiler.js @@ -0,0 +1,234 @@ +/* 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 { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; + +Cu.import("resource://gre/modules/Task.jsm"); +Cu.import("resource://gre/modules/devtools/Loader.jsm"); +Cu.import("resource:///modules/devtools/ViewHelpers.jsm"); + +devtools.lazyRequireGetter(this, "Services"); +devtools.lazyRequireGetter(this, "promise"); +devtools.lazyRequireGetter(this, "EventEmitter", + "devtools/toolkit/event-emitter"); +devtools.lazyRequireGetter(this, "DevToolsUtils", + "devtools/toolkit/DevToolsUtils"); +devtools.lazyRequireGetter(this, "FramerateFront", + "devtools/server/actors/framerate", true); + +devtools.lazyRequireGetter(this, "L10N", + "devtools/shared/profiler/global", true); +devtools.lazyRequireGetter(this, "CATEGORIES", + "devtools/shared/profiler/global", true); +devtools.lazyRequireGetter(this, "CATEGORY_MAPPINGS", + "devtools/shared/profiler/global", true); +devtools.lazyRequireGetter(this, "CATEGORY_OTHER", + "devtools/shared/profiler/global", true); +devtools.lazyRequireGetter(this, "ThreadNode", + "devtools/shared/profiler/tree-model", true); +devtools.lazyRequireGetter(this, "CallView", + "devtools/shared/profiler/tree-view", true); + +devtools.lazyImporter(this, "FileUtils", + "resource://gre/modules/FileUtils.jsm"); +devtools.lazyImporter(this, "NetUtil", + "resource://gre/modules/NetUtil.jsm"); +devtools.lazyImporter(this, "LineGraphWidget", + "resource:///modules/devtools/Graphs.jsm"); +devtools.lazyImporter(this, "BarGraphWidget", + "resource:///modules/devtools/Graphs.jsm"); +devtools.lazyImporter(this, "CanvasGraphUtils", + "resource:///modules/devtools/Graphs.jsm"); +devtools.lazyImporter(this, "SideMenuWidget", + "resource:///modules/devtools/SideMenuWidget.jsm"); + +const RECORDING_DATA_DISPLAY_DELAY = 10; // ms +const FRAMERATE_CALC_INTERVAL = 16; // ms +const FRAMERATE_GRAPH_HEIGHT = 60; // px +const CATEGORIES_GRAPH_HEIGHT = 60; // px +const CATEGORIES_GRAPH_MIN_BARS_WIDTH = 3; // px +const CALL_VIEW_FOCUS_EVENTS_DRAIN = 10; // ms +const GRAPH_SCROLL_EVENTS_DRAIN = 50; // ms +const GRAPH_ZOOM_MIN_TIMESPAN = 20; // ms + +// This identifier string is used to tentatively ascertain whether or not +// a JSON loaded from disk is actually something generated by this tool. +// It isn't, of course, a definitive verification, but a Good Enoughâ„¢ +// approximation before continuing the import. Don't localize this. +const PROFILE_SERIALIZER_IDENTIFIER = "Recorded Performance Data"; +const PROFILE_SERIALIZER_VERSION = 1; + +// The panel's window global is an EventEmitter firing the following events: +const EVENTS = { + // When a recording is started or stopped, via the `stopwatch` button, or + // when `console.profile` and `console.profileEnd` is invoked. + RECORDING_STARTED: "Profiler:RecordingStarted", + RECORDING_ENDED: "Profiler:RecordingEnded", + + // When a recording is abruptly ended, either because the built-in profiler + // module is stopped by a third party, or because the recordings list is + // cleared while there's one in progress. + RECORDING_LOST: "Profiler:RecordingCancelled", + + // When a recording is displayed in the ProfileView. + RECORDING_DISPLAYED: "Profiler:RecordingDisplayed", + + // When a new tab is spawned in the ProfileView from a graphs selection. + TAB_SPAWNED_FROM_SELECTION: "Profiler:TabSpawnedFromSelection", + + // When a new tab is spawned in the ProfileView from a node in the tree. + TAB_SPAWNED_FROM_FRAME_NODE: "Profiler:TabSpawnedFromFrameNode", + + // When different panels in the ProfileView are shown. + EMPTY_NOTICE_SHOWN: "Profiler:EmptyNoticeShown", + RECORDING_NOTICE_SHOWN: "Profiler:RecordingNoticeShown", + LOADING_NOTICE_SHOWN: "Profiler:LoadingNoticeShown", + TABBED_BROWSER_SHOWN: "Profiler:TabbedBrowserShown", + + // When a source is shown in the JavaScript Debugger at a specific location. + SOURCE_SHOWN_IN_JS_DEBUGGER: "Profiler:SourceShownInJsDebugger", + SOURCE_NOT_FOUND_IN_JS_DEBUGGER: "Profiler:SourceNotFoundInJsDebugger" +}; + +/** + * The current target and the profiler connection, set by this tool's host. + */ +let gToolbox, gTarget, gFront; + +/** + * Initializes the profiler controller and views. + */ +let startupProfiler = Task.async(function*() { + yield promise.all([ + PrefObserver.register(), + EventsHandler.initialize(), + RecordingsListView.initialize(), + ProfileView.initialize() + ]); + + // Profiles may have been created before this tool was opened, e.g. via + // `console.profile` and `console.profileEnd(). Populate the UI with them. + for (let recordingData of gFront.finishedConsoleRecordings) { + let profileLabel = recordingData.profilerData.profileLabel; + let recordingItem = RecordingsListView.addEmptyRecording(profileLabel); + RecordingsListView.customizeRecording(recordingItem, recordingData); + } + for (let { profileLabel } of gFront.pendingConsoleRecordings) { + RecordingsListView.handleRecordingStarted(profileLabel); + } + + // Select the first recording, if available. + RecordingsListView.selectedIndex = 0; +}); + +/** + * Destroys the profiler controller and views. + */ +let shutdownProfiler = Task.async(function*() { + yield promise.all([ + PrefObserver.unregister(), + EventsHandler.destroy(), + RecordingsListView.destroy(), + ProfileView.destroy() + ]); +}); + +/** + * Observes pref changes on the devtools.profiler branch and triggers the + * required frontend modifications. + */ +let PrefObserver = { + register: function() { + this.branch = Services.prefs.getBranch("devtools.profiler."); + this.branch.addObserver("", this, false); + }, + unregister: function() { + this.branch.removeObserver("", this); + }, + observe: function(subject, topic, pref) { + Prefs.refresh(); + + if (pref == "ui.show-platform-data") { + RecordingsListView.forceSelect(RecordingsListView.selectedItem); + } + } +}; + +/** + * Functions handling target-related lifetime events. + */ +let EventsHandler = { + /** + * Listen for events emitted by the current tab target. + */ + initialize: function() { + this._onConsoleProfileStart = this._onConsoleProfileStart.bind(this); + this._onConsoleProfileEnd = this._onConsoleProfileEnd.bind(this); + + gFront.on("profile", this._onConsoleProfileStart); + gFront.on("profileEnd", this._onConsoleProfileEnd); + gFront.on("profiler-unexpectedly-stopped", this._onProfilerDeactivated); + }, + + /** + * Remove events emitted by the current tab target. + */ + destroy: function() { + gFront.off("profile", this._onConsoleProfileStart); + gFront.off("profileEnd", this._onConsoleProfileEnd); + gFront.off("profiler-unexpectedly-stopped", this._onProfilerDeactivated); + }, + + /** + * Invoked whenever `console.profile` is called. + * + * @param string profileLabel + * The provided string argument if available, undefined otherwise. + */ + _onConsoleProfileStart: function(event, profileLabel) { + RecordingsListView.handleRecordingStarted(profileLabel); + }, + + /** + * Invoked whenever `console.profileEnd` is called. + * + * @param object recordingData + * The profiler and refresh driver ticks data received from the front. + */ + _onConsoleProfileEnd: function(event, recordingData) { + RecordingsListView.handleRecordingEnded(recordingData); + }, + + /** + * Invoked whenever the built-in profiler module is deactivated. + * @see ProfilerConnection.prototype._onProfilerUnexpectedlyStopped + */ + _onProfilerDeactivated: function() { + RecordingsListView.removeForPredicate(e => e.isRecording); + RecordingsListView.handleRecordingCancelled(); + } +}; + +/** + * Shortcuts for accessing various profiler preferences. + */ +const Prefs = new ViewHelpers.Prefs("devtools.profiler", { + showPlatformData: ["Bool", "ui.show-platform-data"] +}); + +/** + * Convenient way of emitting events from the panel window. + */ +EventEmitter.decorate(this); + +/** + * DOM query helpers. + */ +function $(selector, target = document) { + return target.querySelector(selector); +} +function $$(selector, target = document) { + return target.querySelectorAll(selector); +} |