summaryrefslogtreecommitdiff
path: root/toolkit/devtools/profiler/profiler.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/devtools/profiler/profiler.js')
-rw-r--r--toolkit/devtools/profiler/profiler.js234
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);
+}