2014-10-15 07:46:00 +00:00
|
|
|
/* 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");
|
2014-11-25 14:01:00 +00:00
|
|
|
Cu.import("resource://gre/modules/devtools/Console.jsm");
|
2014-12-06 17:08:10 +00:00
|
|
|
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
2014-10-15 07:46:00 +00:00
|
|
|
|
|
|
|
devtools.lazyRequireGetter(this, "Services");
|
|
|
|
devtools.lazyRequireGetter(this, "promise");
|
|
|
|
devtools.lazyRequireGetter(this, "EventEmitter",
|
|
|
|
"devtools/toolkit/event-emitter");
|
|
|
|
devtools.lazyRequireGetter(this, "DevToolsUtils",
|
|
|
|
"devtools/toolkit/DevToolsUtils");
|
2014-12-23 16:50:50 +00:00
|
|
|
|
2015-01-11 16:45:23 +00:00
|
|
|
devtools.lazyRequireGetter(this, "TIMELINE_BLUEPRINT",
|
|
|
|
"devtools/timeline/global", true);
|
2014-11-18 13:37:00 +00:00
|
|
|
devtools.lazyRequireGetter(this, "L10N",
|
|
|
|
"devtools/profiler/global", true);
|
2014-12-23 16:50:50 +00:00
|
|
|
devtools.lazyRequireGetter(this, "PerformanceIO",
|
|
|
|
"devtools/performance/io", true);
|
2015-01-15 19:54:46 +00:00
|
|
|
devtools.lazyRequireGetter(this, "RecordingModel",
|
|
|
|
"devtools/performance/recording-model", true);
|
|
|
|
devtools.lazyRequireGetter(this, "RECORDING_IN_PROGRESS",
|
|
|
|
"devtools/performance/recording-model", true);
|
|
|
|
devtools.lazyRequireGetter(this, "RECORDING_UNAVAILABLE",
|
|
|
|
"devtools/performance/recording-model", true);
|
|
|
|
|
2014-12-13 03:31:27 +00:00
|
|
|
devtools.lazyRequireGetter(this, "MarkersOverview",
|
|
|
|
"devtools/timeline/markers-overview", true);
|
|
|
|
devtools.lazyRequireGetter(this, "MemoryOverview",
|
|
|
|
"devtools/timeline/memory-overview", true);
|
2014-12-03 15:36:00 +00:00
|
|
|
devtools.lazyRequireGetter(this, "Waterfall",
|
|
|
|
"devtools/timeline/waterfall", true);
|
|
|
|
devtools.lazyRequireGetter(this, "MarkerDetails",
|
|
|
|
"devtools/timeline/marker-details", true);
|
2014-11-20 12:02:00 +00:00
|
|
|
devtools.lazyRequireGetter(this, "CallView",
|
|
|
|
"devtools/profiler/tree-view", true);
|
|
|
|
devtools.lazyRequireGetter(this, "ThreadNode",
|
|
|
|
"devtools/profiler/tree-model", true);
|
2015-01-15 19:54:46 +00:00
|
|
|
devtools.lazyRequireGetter(this, "FrameNode",
|
|
|
|
"devtools/profiler/tree-model", true);
|
2014-10-15 07:46:00 +00:00
|
|
|
|
2014-12-13 03:31:27 +00:00
|
|
|
devtools.lazyImporter(this, "CanvasGraphUtils",
|
|
|
|
"resource:///modules/devtools/Graphs.jsm");
|
2014-12-06 17:08:10 +00:00
|
|
|
devtools.lazyImporter(this, "LineGraphWidget",
|
|
|
|
"resource:///modules/devtools/Graphs.jsm");
|
2015-01-11 16:45:23 +00:00
|
|
|
devtools.lazyImporter(this, "FlameGraphUtils",
|
|
|
|
"resource:///modules/devtools/FlameGraph.jsm");
|
|
|
|
devtools.lazyImporter(this, "FlameGraph",
|
|
|
|
"resource:///modules/devtools/FlameGraph.jsm");
|
2015-01-15 19:54:46 +00:00
|
|
|
devtools.lazyImporter(this, "SideMenuWidget",
|
|
|
|
"resource:///modules/devtools/SideMenuWidget.jsm");
|
2015-01-11 16:45:23 +00:00
|
|
|
|
2014-12-13 03:31:27 +00:00
|
|
|
// Events emitted by various objects in the panel.
|
2014-10-30 11:35:00 +00:00
|
|
|
const EVENTS = {
|
2015-01-13 08:26:00 +00:00
|
|
|
// Emitted by the PerformanceController or RecordingView
|
|
|
|
// when a recording model is selected
|
|
|
|
RECORDING_SELECTED: "Performance:RecordingSelected",
|
|
|
|
|
2014-12-23 16:50:50 +00:00
|
|
|
// Emitted by the PerformanceView on record button click
|
|
|
|
UI_START_RECORDING: "Performance:UI:StartRecording",
|
|
|
|
UI_STOP_RECORDING: "Performance:UI:StopRecording",
|
|
|
|
|
2015-01-13 08:26:00 +00:00
|
|
|
// Emitted by the PerformanceView on import button click
|
2014-12-23 16:50:50 +00:00
|
|
|
UI_IMPORT_RECORDING: "Performance:UI:ImportRecording",
|
2015-01-13 08:26:00 +00:00
|
|
|
// Emitted by the RecordingsView on export button click
|
2014-12-23 16:50:50 +00:00
|
|
|
UI_EXPORT_RECORDING: "Performance:UI:ExportRecording",
|
|
|
|
|
2014-12-13 03:31:27 +00:00
|
|
|
// When a recording is started or stopped via the PerformanceController
|
2014-10-30 11:35:00 +00:00
|
|
|
RECORDING_STARTED: "Performance:RecordingStarted",
|
|
|
|
RECORDING_STOPPED: "Performance:RecordingStopped",
|
2014-12-13 03:31:27 +00:00
|
|
|
|
2014-12-23 16:50:50 +00:00
|
|
|
// When a recording is imported or exported via the PerformanceController
|
|
|
|
RECORDING_IMPORTED: "Performance:RecordingImported",
|
|
|
|
RECORDING_EXPORTED: "Performance:RecordingExported",
|
2014-10-30 11:35:00 +00:00
|
|
|
|
2014-12-23 16:50:50 +00:00
|
|
|
// When the PerformanceController has new recording data
|
|
|
|
TIMELINE_DATA: "Performance:TimelineData",
|
2014-11-18 13:37:00 +00:00
|
|
|
|
|
|
|
// Emitted by the OverviewView when more data has been rendered
|
2014-11-20 12:02:00 +00:00
|
|
|
OVERVIEW_RENDERED: "Performance:UI:OverviewRendered",
|
2014-12-13 03:31:27 +00:00
|
|
|
FRAMERATE_GRAPH_RENDERED: "Performance:UI:OverviewFramerateRendered",
|
|
|
|
MARKERS_GRAPH_RENDERED: "Performance:UI:OverviewMarkersRendered",
|
|
|
|
MEMORY_GRAPH_RENDERED: "Performance:UI:OverviewMemoryRendered",
|
|
|
|
|
2014-11-25 14:01:00 +00:00
|
|
|
// Emitted by the OverviewView when a range has been selected in the graphs
|
|
|
|
OVERVIEW_RANGE_SELECTED: "Performance:UI:OverviewRangeSelected",
|
|
|
|
// Emitted by the OverviewView when a selection range has been removed
|
|
|
|
OVERVIEW_RANGE_CLEARED: "Performance:UI:OverviewRangeCleared",
|
2014-11-20 12:02:00 +00:00
|
|
|
|
2014-12-03 15:36:00 +00:00
|
|
|
// Emitted by the DetailsView when a subview is selected
|
|
|
|
DETAILS_VIEW_SELECTED: "Performance:UI:DetailsViewSelected",
|
|
|
|
|
2014-11-20 12:02:00 +00:00
|
|
|
// Emitted by the CallTreeView when a call tree has been rendered
|
2014-12-03 15:36:00 +00:00
|
|
|
CALL_TREE_RENDERED: "Performance:UI:CallTreeRendered",
|
|
|
|
|
2014-12-10 16:34:00 +00:00
|
|
|
// When a source is shown in the JavaScript Debugger at a specific location.
|
|
|
|
SOURCE_SHOWN_IN_JS_DEBUGGER: "Performance:UI:SourceShownInJsDebugger",
|
|
|
|
SOURCE_NOT_FOUND_IN_JS_DEBUGGER: "Performance:UI:SourceNotFoundInJsDebugger",
|
|
|
|
|
2014-12-03 15:36:00 +00:00
|
|
|
// Emitted by the WaterfallView when it has been rendered
|
2015-01-11 16:45:23 +00:00
|
|
|
WATERFALL_RENDERED: "Performance:UI:WaterfallRendered",
|
|
|
|
|
|
|
|
// Emitted by the FlameGraphView when it has been rendered
|
|
|
|
FLAMEGRAPH_RENDERED: "Performance:UI:FlameGraphRendered"
|
2014-10-30 11:35:00 +00:00
|
|
|
};
|
|
|
|
|
2014-10-15 07:46:00 +00:00
|
|
|
/**
|
|
|
|
* The current target and the profiler connection, set by this tool's host.
|
|
|
|
*/
|
|
|
|
let gToolbox, gTarget, gFront;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initializes the profiler controller and views.
|
|
|
|
*/
|
|
|
|
let startupPerformance = Task.async(function*() {
|
|
|
|
yield promise.all([
|
|
|
|
PrefObserver.register(),
|
2014-10-30 11:35:00 +00:00
|
|
|
PerformanceController.initialize(),
|
2014-11-20 12:02:00 +00:00
|
|
|
PerformanceView.initialize()
|
2014-10-15 07:46:00 +00:00
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Destroys the profiler controller and views.
|
|
|
|
*/
|
|
|
|
let shutdownPerformance = Task.async(function*() {
|
|
|
|
yield promise.all([
|
|
|
|
PrefObserver.unregister(),
|
2014-10-30 11:35:00 +00:00
|
|
|
PerformanceController.destroy(),
|
2014-11-20 12:02:00 +00:00
|
|
|
PerformanceView.destroy()
|
2014-10-15 07:46:00 +00:00
|
|
|
]);
|
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2014-10-30 11:35:00 +00:00
|
|
|
* Functions handling target-related lifetime events and
|
|
|
|
* UI interaction.
|
2014-10-15 07:46:00 +00:00
|
|
|
*/
|
2014-10-30 11:35:00 +00:00
|
|
|
let PerformanceController = {
|
2015-01-13 08:26:00 +00:00
|
|
|
_recordings: [],
|
|
|
|
_currentRecording: null,
|
2014-12-13 03:31:27 +00:00
|
|
|
|
2014-10-15 07:46:00 +00:00
|
|
|
/**
|
2014-10-30 11:35:00 +00:00
|
|
|
* Listen for events emitted by the current tab target and
|
|
|
|
* main UI events.
|
2014-10-15 07:46:00 +00:00
|
|
|
*/
|
|
|
|
initialize: function() {
|
2014-10-30 11:35:00 +00:00
|
|
|
this.startRecording = this.startRecording.bind(this);
|
|
|
|
this.stopRecording = this.stopRecording.bind(this);
|
2014-12-23 16:50:50 +00:00
|
|
|
this.importRecording = this.importRecording.bind(this);
|
|
|
|
this.exportRecording = this.exportRecording.bind(this);
|
2014-11-18 13:37:00 +00:00
|
|
|
this._onTimelineData = this._onTimelineData.bind(this);
|
2015-01-13 08:26:00 +00:00
|
|
|
this._onRecordingSelectFromView = this._onRecordingSelectFromView.bind(this);
|
2014-10-30 11:35:00 +00:00
|
|
|
|
|
|
|
PerformanceView.on(EVENTS.UI_START_RECORDING, this.startRecording);
|
|
|
|
PerformanceView.on(EVENTS.UI_STOP_RECORDING, this.stopRecording);
|
2014-12-23 16:50:50 +00:00
|
|
|
PerformanceView.on(EVENTS.UI_IMPORT_RECORDING, this.importRecording);
|
2015-01-13 08:26:00 +00:00
|
|
|
RecordingsView.on(EVENTS.UI_EXPORT_RECORDING, this.exportRecording);
|
|
|
|
RecordingsView.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelectFromView);
|
2014-12-23 16:50:50 +00:00
|
|
|
|
2014-12-13 03:31:27 +00:00
|
|
|
gFront.on("ticks", this._onTimelineData); // framerate
|
|
|
|
gFront.on("markers", this._onTimelineData); // timeline markers
|
2014-12-29 10:32:00 +00:00
|
|
|
gFront.on("frames", this._onTimelineData); // stack frames
|
2014-12-13 03:31:27 +00:00
|
|
|
gFront.on("memory", this._onTimelineData); // timeline memory
|
2014-10-15 07:46:00 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2014-10-30 11:35:00 +00:00
|
|
|
* Remove events handled by the PerformanceController
|
2014-10-15 07:46:00 +00:00
|
|
|
*/
|
|
|
|
destroy: function() {
|
2014-10-30 11:35:00 +00:00
|
|
|
PerformanceView.off(EVENTS.UI_START_RECORDING, this.startRecording);
|
|
|
|
PerformanceView.off(EVENTS.UI_STOP_RECORDING, this.stopRecording);
|
2014-12-23 16:50:50 +00:00
|
|
|
PerformanceView.off(EVENTS.UI_IMPORT_RECORDING, this.importRecording);
|
2015-01-13 08:26:00 +00:00
|
|
|
RecordingsView.off(EVENTS.UI_EXPORT_RECORDING, this.exportRecording);
|
|
|
|
RecordingsView.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelectFromView);
|
2014-12-23 16:50:50 +00:00
|
|
|
|
2014-12-03 15:36:00 +00:00
|
|
|
gFront.off("ticks", this._onTimelineData);
|
|
|
|
gFront.off("markers", this._onTimelineData);
|
2014-12-29 10:32:00 +00:00
|
|
|
gFront.off("frames", this._onTimelineData);
|
2014-12-13 03:31:27 +00:00
|
|
|
gFront.off("memory", this._onTimelineData);
|
2014-10-30 11:35:00 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Starts recording with the PerformanceFront. Emits `EVENTS.RECORDING_STARTED`
|
2014-12-13 03:31:27 +00:00
|
|
|
* when the front has started to record.
|
2014-10-30 11:35:00 +00:00
|
|
|
*/
|
|
|
|
startRecording: Task.async(function *() {
|
2015-01-16 18:44:24 +00:00
|
|
|
let recording = this.createNewRecording();
|
|
|
|
this.setCurrentRecording(recording);
|
|
|
|
yield recording.startRecording();
|
2014-12-13 03:31:27 +00:00
|
|
|
|
2015-01-16 18:44:24 +00:00
|
|
|
this.emit(EVENTS.RECORDING_STARTED, recording);
|
2014-10-30 11:35:00 +00:00
|
|
|
}),
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stops recording with the PerformanceFront. Emits `EVENTS.RECORDING_STOPPED`
|
2014-12-13 03:31:27 +00:00
|
|
|
* when the front has stopped recording.
|
2014-10-30 11:35:00 +00:00
|
|
|
*/
|
|
|
|
stopRecording: Task.async(function *() {
|
2015-01-16 18:44:24 +00:00
|
|
|
let recording = this._getLatestRecording();
|
2015-01-13 08:26:00 +00:00
|
|
|
yield recording.stopRecording();
|
2014-12-13 03:31:27 +00:00
|
|
|
|
2015-01-13 08:26:00 +00:00
|
|
|
this.emit(EVENTS.RECORDING_STOPPED, recording);
|
2014-11-18 13:37:00 +00:00
|
|
|
}),
|
|
|
|
|
2014-12-23 16:50:50 +00:00
|
|
|
/**
|
|
|
|
* Saves the current recording to a file.
|
|
|
|
*
|
2015-01-13 08:26:00 +00:00
|
|
|
* @param RecordingModel recording
|
|
|
|
* The model that holds the recording data.
|
2014-12-23 16:50:50 +00:00
|
|
|
* @param nsILocalFile file
|
|
|
|
* The file to stream the data into.
|
|
|
|
*/
|
2015-01-13 08:26:00 +00:00
|
|
|
exportRecording: Task.async(function*(_, recording, file) {
|
|
|
|
let recordingData = recording.getAllData();
|
2014-12-23 16:50:50 +00:00
|
|
|
yield PerformanceIO.saveRecordingToFile(recordingData, file);
|
|
|
|
|
|
|
|
this.emit(EVENTS.RECORDING_EXPORTED, recordingData);
|
|
|
|
}),
|
|
|
|
|
|
|
|
/**
|
2015-01-13 08:26:00 +00:00
|
|
|
* Loads a recording from a file, adding it to the recordings list.
|
2014-12-23 16:50:50 +00:00
|
|
|
*
|
|
|
|
* @param nsILocalFile file
|
|
|
|
* The file to import the data from.
|
|
|
|
*/
|
|
|
|
importRecording: Task.async(function*(_, file) {
|
2015-01-16 18:44:24 +00:00
|
|
|
let recording = this.createNewRecording();
|
|
|
|
yield recording.importRecording(file);
|
2014-12-23 16:50:50 +00:00
|
|
|
|
2015-01-16 18:44:24 +00:00
|
|
|
this.emit(EVENTS.RECORDING_IMPORTED, recording.getAllData(), recording);
|
2015-01-13 08:26:00 +00:00
|
|
|
}),
|
2014-12-23 16:50:50 +00:00
|
|
|
|
2015-01-13 08:26:00 +00:00
|
|
|
/**
|
|
|
|
* Creates a new RecordingModel, fires events and stores it
|
|
|
|
* internally in the controller.
|
2015-01-16 18:44:24 +00:00
|
|
|
*
|
|
|
|
* @return RecordingModel
|
|
|
|
* The newly created recording model.
|
2015-01-13 08:26:00 +00:00
|
|
|
*/
|
|
|
|
createNewRecording: function () {
|
2015-01-16 18:44:24 +00:00
|
|
|
let recording = new RecordingModel({
|
2015-01-13 08:26:00 +00:00
|
|
|
front: gFront,
|
|
|
|
performance: performance
|
|
|
|
});
|
2015-01-16 18:44:24 +00:00
|
|
|
this._recordings.push(recording);
|
|
|
|
|
|
|
|
this.emit(EVENTS.RECORDING_CREATED, recording);
|
|
|
|
return recording;
|
2015-01-13 08:26:00 +00:00
|
|
|
},
|
2014-12-23 16:50:50 +00:00
|
|
|
|
2015-01-13 08:26:00 +00:00
|
|
|
/**
|
2015-01-16 18:44:24 +00:00
|
|
|
* Sets the currently active RecordingModel.
|
|
|
|
* @param RecordingModel recording
|
2015-01-13 08:26:00 +00:00
|
|
|
*/
|
|
|
|
setCurrentRecording: function (recording) {
|
|
|
|
if (this._currentRecording !== recording) {
|
|
|
|
this._currentRecording = recording;
|
|
|
|
this.emit(EVENTS.RECORDING_SELECTED, recording);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2015-01-16 18:44:24 +00:00
|
|
|
* Gets the currently active RecordingModel.
|
|
|
|
* @return RecordingModel
|
2015-01-13 08:26:00 +00:00
|
|
|
*/
|
|
|
|
getCurrentRecording: function () {
|
|
|
|
return this._currentRecording;
|
|
|
|
},
|
2014-12-23 16:50:50 +00:00
|
|
|
|
|
|
|
/**
|
2015-01-16 18:44:24 +00:00
|
|
|
* Get most recently added recording that was triggered manually (via UI).
|
|
|
|
* @return RecordingModel
|
2014-11-18 13:37:00 +00:00
|
|
|
*/
|
2015-01-16 18:44:24 +00:00
|
|
|
_getLatestRecording: function () {
|
2015-01-13 08:26:00 +00:00
|
|
|
for (let i = this._recordings.length - 1; i >= 0; i--) {
|
|
|
|
return this._recordings[i];
|
2014-12-13 03:31:27 +00:00
|
|
|
}
|
2015-01-13 08:26:00 +00:00
|
|
|
return null;
|
|
|
|
},
|
2014-12-13 03:31:27 +00:00
|
|
|
|
2015-01-13 08:26:00 +00:00
|
|
|
/**
|
|
|
|
* Fired whenever the PerformanceFront emits markers, memory or ticks.
|
|
|
|
*/
|
|
|
|
_onTimelineData: function (...data) {
|
|
|
|
this._recordings.forEach(profile => profile.addTimelineData.apply(profile, data));
|
|
|
|
this.emit(EVENTS.TIMELINE_DATA, ...data);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2015-01-16 18:44:24 +00:00
|
|
|
* Fired from RecordingsView, we listen on the PerformanceController so we can
|
|
|
|
* set it here and re-emit on the controller, where all views can listen.
|
2015-01-13 08:26:00 +00:00
|
|
|
*/
|
|
|
|
_onRecordingSelectFromView: function (_, recording) {
|
|
|
|
this.setCurrentRecording(recording);
|
2014-11-18 13:37:00 +00:00
|
|
|
}
|
2014-10-15 07:46:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2014-10-30 11:35:00 +00:00
|
|
|
* Convenient way of emitting events from the controller.
|
2014-10-15 07:46:00 +00:00
|
|
|
*/
|
2014-10-30 11:35:00 +00:00
|
|
|
EventEmitter.decorate(PerformanceController);
|
2014-10-15 07:46:00 +00:00
|
|
|
|
|
|
|
/**
|
2014-10-30 11:35:00 +00:00
|
|
|
* Shortcuts for accessing various profiler preferences.
|
2014-10-15 07:46:00 +00:00
|
|
|
*/
|
2014-10-30 11:35:00 +00:00
|
|
|
const Prefs = new ViewHelpers.Prefs("devtools.profiler", {
|
2015-01-15 19:54:46 +00:00
|
|
|
flattenTreeRecursion: ["Bool", "ui.flatten-tree-recursion"],
|
|
|
|
showPlatformData: ["Bool", "ui.show-platform-data"],
|
2015-01-15 19:54:46 +00:00
|
|
|
showIdleBlocks: ["Bool", "ui.show-idle-blocks"],
|
2014-10-30 11:35:00 +00:00
|
|
|
});
|
2014-10-15 07:46:00 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* DOM query helpers.
|
|
|
|
*/
|
|
|
|
function $(selector, target = document) {
|
|
|
|
return target.querySelector(selector);
|
|
|
|
}
|
|
|
|
function $$(selector, target = document) {
|
|
|
|
return target.querySelectorAll(selector);
|
|
|
|
}
|