mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1172180 - Create a PerformanceActor, and migrate existing pseudo PerformanceFront to a legacy front. r=vp
This commit is contained in:
parent
286196be2d
commit
0a12d64761
@ -3,7 +3,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
/* globals gDevTools, DOMHelpers, toolboxStrings, InspectorFront, Selection,
|
||||
CommandUtils, DevToolsUtils, Hosts, osString, showDoorhanger,
|
||||
getHighlighterUtils, getPerformanceFront */
|
||||
getHighlighterUtils, createPerformanceFront */
|
||||
|
||||
"use strict";
|
||||
|
||||
@ -59,8 +59,8 @@ loader.lazyRequireGetter(this, "DevToolsUtils",
|
||||
"devtools/toolkit/DevToolsUtils");
|
||||
loader.lazyRequireGetter(this, "showDoorhanger",
|
||||
"devtools/shared/doorhanger", true);
|
||||
loader.lazyRequireGetter(this, "getPerformanceFront",
|
||||
"devtools/performance/front", true);
|
||||
loader.lazyRequireGetter(this, "createPerformanceFront",
|
||||
"devtools/server/actors/performance", true);
|
||||
loader.lazyRequireGetter(this, "system",
|
||||
"devtools/toolkit/shared/system");
|
||||
loader.lazyGetter(this, "osString", () => {
|
||||
@ -135,6 +135,7 @@ function Toolbox(target, selectedTool, hostType, hostOptions) {
|
||||
this._onBottomHostMinimized = this._onBottomHostMinimized.bind(this);
|
||||
this._onBottomHostMaximized = this._onBottomHostMaximized.bind(this);
|
||||
this._onToolSelectWhileMinimized = this._onToolSelectWhileMinimized.bind(this);
|
||||
this._onPerformanceFrontEvent = this._onPerformanceFrontEvent.bind(this);
|
||||
this._onBottomHostWillChange = this._onBottomHostWillChange.bind(this);
|
||||
this._toggleMinimizeMode = this._toggleMinimizeMode.bind(this);
|
||||
|
||||
@ -1987,12 +1988,15 @@ Toolbox.prototype = {
|
||||
}
|
||||
|
||||
if (this.performance) {
|
||||
yield this.performance.open();
|
||||
yield this.performance.connect();
|
||||
return this.performance;
|
||||
}
|
||||
|
||||
this._performance = getPerformanceFront(this.target);
|
||||
yield this.performance.open();
|
||||
this._performance = createPerformanceFront(this._target);
|
||||
this.performance.on("*", this._onPerformanceFrontEvent);
|
||||
|
||||
yield this.performance.connect();
|
||||
|
||||
// Emit an event when connected, but don't wait on startup for this.
|
||||
this.emit("profiler-connected");
|
||||
|
||||
@ -2008,10 +2012,46 @@ Toolbox.prototype = {
|
||||
if (!this.performance) {
|
||||
return;
|
||||
}
|
||||
this.performance.off("*", this._onPerformanceFrontEvent);
|
||||
yield this.performance.destroy();
|
||||
this._performance = null;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Called when any event comes from the PerformanceFront. If the performance tool is already
|
||||
* loaded when the first event comes in, immediately unbind this handler, as this is
|
||||
* only used to queue up observed recordings before the performance tool can handle them,
|
||||
* which will only occur when `console.profile()` recordings are started before the tool loads.
|
||||
*/
|
||||
_onPerformanceFrontEvent: Task.async(function*(eventName, recording) {
|
||||
if (this.getPanel("performance")) {
|
||||
this.performance.off("*", this._onPerformanceFrontEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
let recordings = this._performanceQueuedRecordings = this._performanceQueuedRecordings || [];
|
||||
|
||||
// Before any console recordings, we'll get a `console-profile-start` event
|
||||
// warning us that a recording will come later (via `recording-started`), so
|
||||
// start to boot up the tool and populate the tool with any other recordings
|
||||
// observed during that time.
|
||||
if (eventName === "console-profile-start" && !this._performanceToolOpenedViaConsole) {
|
||||
this._performanceToolOpenedViaConsole = this.loadTool("performance");
|
||||
let panel = yield this._performanceToolOpenedViaConsole;
|
||||
yield panel.open();
|
||||
|
||||
panel.panelWin.PerformanceController.populateWithRecordings(recordings);
|
||||
this.performance.off("*", this._onPerformanceFrontEvent);
|
||||
}
|
||||
|
||||
// Otherwise, if it's a recording-started event, we've already started loading
|
||||
// the tool, so just store this recording in our array to be later populated
|
||||
// once the tool loads.
|
||||
if (eventName === "recording-started") {
|
||||
recordings.push(recording);
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Returns gViewSourceUtils for viewing source.
|
||||
*/
|
||||
|
@ -1,163 +0,0 @@
|
||||
/* 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 { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
|
||||
loader.lazyRequireGetter(this, "Services");
|
||||
loader.lazyRequireGetter(this, "promise");
|
||||
loader.lazyRequireGetter(this, "RecordingUtils",
|
||||
"devtools/performance/recording-utils");
|
||||
|
||||
loader.lazyImporter(this, "FileUtils",
|
||||
"resource://gre/modules/FileUtils.jsm");
|
||||
loader.lazyImporter(this, "NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
// 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 PERF_TOOL_SERIALIZER_IDENTIFIER = "Recorded Performance Data";
|
||||
const PERF_TOOL_SERIALIZER_LEGACY_VERSION = 1;
|
||||
const PERF_TOOL_SERIALIZER_CURRENT_VERSION = 2;
|
||||
|
||||
/**
|
||||
* Helpers for importing/exporting JSON.
|
||||
*/
|
||||
let PerformanceIO = {
|
||||
/**
|
||||
* Gets a nsIScriptableUnicodeConverter instance with a default UTF-8 charset.
|
||||
* @return object
|
||||
*/
|
||||
getUnicodeConverter: function() {
|
||||
let className = "@mozilla.org/intl/scriptableunicodeconverter";
|
||||
let converter = Cc[className].createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
return converter;
|
||||
},
|
||||
|
||||
/**
|
||||
* Saves a recording as JSON to a file. The provided data is assumed to be
|
||||
* acyclical, so that it can be properly serialized.
|
||||
*
|
||||
* @param object recordingData
|
||||
* The recording data to stream as JSON.
|
||||
* @param nsILocalFile file
|
||||
* The file to stream the data into.
|
||||
* @return object
|
||||
* A promise that is resolved once streaming finishes, or rejected
|
||||
* if there was an error.
|
||||
*/
|
||||
saveRecordingToFile: function(recordingData, file) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
recordingData.fileType = PERF_TOOL_SERIALIZER_IDENTIFIER;
|
||||
recordingData.version = PERF_TOOL_SERIALIZER_CURRENT_VERSION;
|
||||
|
||||
let string = JSON.stringify(recordingData);
|
||||
let inputStream = this.getUnicodeConverter().convertToInputStream(string);
|
||||
let outputStream = FileUtils.openSafeFileOutputStream(file);
|
||||
|
||||
NetUtil.asyncCopy(inputStream, outputStream, deferred.resolve);
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads a recording stored as JSON from a file.
|
||||
*
|
||||
* @param nsILocalFile file
|
||||
* The file to import the data from.
|
||||
* @return object
|
||||
* A promise that is resolved once importing finishes, or rejected
|
||||
* if there was an error.
|
||||
*/
|
||||
loadRecordingFromFile: function(file) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
let channel = NetUtil.newChannel({
|
||||
uri: NetUtil.newURI(file),
|
||||
loadUsingSystemPrincipal: true});
|
||||
|
||||
channel.contentType = "text/plain";
|
||||
|
||||
NetUtil.asyncFetch(channel, (inputStream, status) => {
|
||||
try {
|
||||
let string = NetUtil.readInputStreamToString(inputStream, inputStream.available());
|
||||
var recordingData = JSON.parse(string);
|
||||
} catch (e) {
|
||||
deferred.reject(new Error("Could not read recording data file."));
|
||||
return;
|
||||
}
|
||||
if (recordingData.fileType != PERF_TOOL_SERIALIZER_IDENTIFIER) {
|
||||
deferred.reject(new Error("Unrecognized recording data file."));
|
||||
return;
|
||||
}
|
||||
if (!isValidSerializerVersion(recordingData.version)) {
|
||||
deferred.reject(new Error("Unsupported recording data file version."));
|
||||
return;
|
||||
}
|
||||
if (recordingData.version === PERF_TOOL_SERIALIZER_LEGACY_VERSION) {
|
||||
recordingData = convertLegacyData(recordingData);
|
||||
}
|
||||
if (recordingData.profile.meta.version === 2) {
|
||||
RecordingUtils.deflateProfile(recordingData.profile);
|
||||
}
|
||||
deferred.resolve(recordingData);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating whether or not the passed in `version`
|
||||
* is supported by this serializer.
|
||||
*
|
||||
* @param number version
|
||||
* @return boolean
|
||||
*/
|
||||
function isValidSerializerVersion (version) {
|
||||
return !!~[
|
||||
PERF_TOOL_SERIALIZER_LEGACY_VERSION,
|
||||
PERF_TOOL_SERIALIZER_CURRENT_VERSION
|
||||
].indexOf(version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes recording data (with version `1`, from the original profiler tool), and
|
||||
* massages the data to be line with the current performance tool's property names
|
||||
* and values.
|
||||
*
|
||||
* @param object legacyData
|
||||
* @return object
|
||||
*/
|
||||
function convertLegacyData (legacyData) {
|
||||
let { profilerData, ticksData, recordingDuration } = legacyData;
|
||||
|
||||
// The `profilerData` and `ticksData` stay, but the previously unrecorded
|
||||
// fields just are empty arrays or objects.
|
||||
let data = {
|
||||
label: profilerData.profilerLabel,
|
||||
duration: recordingDuration,
|
||||
markers: [],
|
||||
frames: [],
|
||||
memory: [],
|
||||
ticks: ticksData,
|
||||
allocations: { sites: [], timestamps: [], frames: [] },
|
||||
profile: profilerData.profile,
|
||||
// Fake a configuration object here if there's tick data,
|
||||
// so that it can be rendered
|
||||
configuration: {
|
||||
withTicks: !!ticksData.length,
|
||||
withMarkers: false,
|
||||
withMemory: false,
|
||||
withAllocations: false
|
||||
}
|
||||
};
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
exports.PerformanceIO = PerformanceIO;
|
@ -5,15 +5,9 @@
|
||||
|
||||
EXTRA_JS_MODULES.devtools.performance += [
|
||||
'modules/global.js',
|
||||
'modules/logic/actors.js',
|
||||
'modules/logic/compatibility.js',
|
||||
'modules/logic/frame-utils.js',
|
||||
'modules/logic/front.js',
|
||||
'modules/logic/io.js',
|
||||
'modules/logic/jit.js',
|
||||
'modules/logic/marker-utils.js',
|
||||
'modules/logic/recording-model.js',
|
||||
'modules/logic/recording-utils.js',
|
||||
'modules/logic/tree-model.js',
|
||||
'modules/logic/waterfall-utils.js',
|
||||
'modules/markers.js',
|
||||
|
@ -11,8 +11,6 @@ const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
loader.lazyRequireGetter(this, "promise");
|
||||
loader.lazyRequireGetter(this, "EventEmitter",
|
||||
"devtools/toolkit/event-emitter");
|
||||
loader.lazyRequireGetter(this, "PerformanceFront",
|
||||
"devtools/performance/front", true);
|
||||
|
||||
function PerformancePanel(iframeWindow, toolbox) {
|
||||
this.panelWin = iframeWindow;
|
||||
@ -32,9 +30,15 @@ PerformancePanel.prototype = {
|
||||
* completes opening.
|
||||
*/
|
||||
open: Task.async(function*() {
|
||||
if (this._opening) {
|
||||
return this._opening;
|
||||
}
|
||||
let deferred = promise.defer();
|
||||
this._opening = deferred.promise;
|
||||
|
||||
this.panelWin.gToolbox = this._toolbox;
|
||||
this.panelWin.gTarget = this.target;
|
||||
this._onRecordingStartOrStop = this._onRecordingStartOrStop.bind(this);
|
||||
this._checkRecordingStatus = this._checkRecordingStatus.bind(this);
|
||||
|
||||
// Actor is already created in the toolbox; reuse
|
||||
// the same front, and the toolbox will also initialize the front,
|
||||
@ -50,14 +54,21 @@ PerformancePanel.prototype = {
|
||||
}
|
||||
|
||||
this.panelWin.gFront = front;
|
||||
this.panelWin.gFront.on("recording-started", this._onRecordingStartOrStop);
|
||||
this.panelWin.gFront.on("recording-stopped", this._onRecordingStartOrStop);
|
||||
|
||||
let { PerformanceController, EVENTS } = this.panelWin;
|
||||
PerformanceController.on(EVENTS.NEW_RECORDING, this._checkRecordingStatus);
|
||||
PerformanceController.on(EVENTS.RECORDING_STATE_CHANGE, this._checkRecordingStatus);
|
||||
yield this.panelWin.startupPerformance();
|
||||
|
||||
// Fire this once incase we have an in-progress recording (console profile)
|
||||
// that caused this start up, and no state change yet, so we can highlight the
|
||||
// tab if we need.
|
||||
this._checkRecordingStatus();
|
||||
|
||||
this.isReady = true;
|
||||
this.emit("ready");
|
||||
return this;
|
||||
|
||||
deferred.resolve(this);
|
||||
return this._opening;
|
||||
}),
|
||||
|
||||
// DevToolPanel API
|
||||
@ -72,16 +83,16 @@ PerformancePanel.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
this.panelWin.gFront.off("recording-started", this._onRecordingStartOrStop);
|
||||
this.panelWin.gFront.off("recording-stopped", this._onRecordingStartOrStop);
|
||||
let { PerformanceController, EVENTS } = this.panelWin;
|
||||
PerformanceController.off(EVENTS.NEW_RECORDING, this._checkRecordingStatus);
|
||||
PerformanceController.off(EVENTS.RECORDING_STATE_CHANGE, this._checkRecordingStatus);
|
||||
yield this.panelWin.shutdownPerformance();
|
||||
this.emit("destroyed");
|
||||
this._destroyed = true;
|
||||
}),
|
||||
|
||||
_onRecordingStartOrStop: function () {
|
||||
let front = this.panelWin.gFront;
|
||||
if (front.isRecording()) {
|
||||
_checkRecordingStatus: function () {
|
||||
if (this.panelWin.PerformanceController.isRecording()) {
|
||||
this._toolbox.highlightTool("performance");
|
||||
} else {
|
||||
this._toolbox.unhighlightTool("performance");
|
||||
|
@ -25,9 +25,7 @@ loader.lazyRequireGetter(this, "L10N",
|
||||
loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT",
|
||||
"devtools/performance/markers", true);
|
||||
loader.lazyRequireGetter(this, "RecordingUtils",
|
||||
"devtools/performance/recording-utils");
|
||||
loader.lazyRequireGetter(this, "RecordingModel",
|
||||
"devtools/performance/recording-model", true);
|
||||
"devtools/toolkit/performance/utils");
|
||||
loader.lazyRequireGetter(this, "GraphsController",
|
||||
"devtools/performance/graphs", true);
|
||||
loader.lazyRequireGetter(this, "WaterfallHeader",
|
||||
@ -92,15 +90,15 @@ const EVENTS = {
|
||||
UI_STOP_RECORDING: "Performance:UI:StopRecording",
|
||||
|
||||
// Emitted by the PerformanceView on import button click
|
||||
UI_IMPORT_RECORDING: "Performance:UI:ImportRecording",
|
||||
UI_RECORDING_IMPORTED: "Performance:UI:ImportRecording",
|
||||
// Emitted by the RecordingsView on export button click
|
||||
UI_EXPORT_RECORDING: "Performance:UI:ExportRecording",
|
||||
|
||||
// When a recording is started or stopped via the PerformanceController
|
||||
RECORDING_STARTED: "Performance:RecordingStarted",
|
||||
RECORDING_STOPPED: "Performance:RecordingStopped",
|
||||
RECORDING_WILL_START: "Performance:RecordingWillStart",
|
||||
RECORDING_WILL_STOP: "Performance:RecordingWillStop",
|
||||
// When a new recording is being tracked in the panel.
|
||||
NEW_RECORDING: "Performance:NewRecording",
|
||||
|
||||
// When a recording is started or stopped or stopping via the PerformanceController
|
||||
RECORDING_STATE_CHANGE: "Performance:RecordingStateChange",
|
||||
|
||||
// Emitted by the PerformanceController or RecordingView
|
||||
// when a recording model is selected
|
||||
@ -109,8 +107,7 @@ const EVENTS = {
|
||||
// When recordings have been cleared out
|
||||
RECORDINGS_CLEARED: "Performance:RecordingsCleared",
|
||||
|
||||
// When a recording is imported or exported via the PerformanceController
|
||||
RECORDING_IMPORTED: "Performance:RecordingImported",
|
||||
// When a recording is exported via the PerformanceController
|
||||
RECORDING_EXPORTED: "Performance:RecordingExported",
|
||||
|
||||
// When the front has updated information on the profiler's circular buffer
|
||||
@ -153,7 +150,25 @@ const EVENTS = {
|
||||
|
||||
// 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"
|
||||
SOURCE_NOT_FOUND_IN_JS_DEBUGGER: "Performance:UI:SourceNotFoundInJsDebugger",
|
||||
|
||||
// These are short hands for the RECORDING_STATE_CHANGE event to make refactoring
|
||||
// tests easier. UI components should use RECORDING_STATE_CHANGE, and these are
|
||||
// deprecated for test usage only.
|
||||
RECORDING_STARTED: "Performance:RecordingStarted",
|
||||
RECORDING_WILL_STOP: "Performance:RecordingWillStop",
|
||||
RECORDING_STOPPED: "Performance:RecordingStopped",
|
||||
|
||||
// Fired by the PerformanceController when `populateWithRecordings` is finished.
|
||||
RECORDINGS_SEEDED: "Performance:RecordingsSeeded",
|
||||
|
||||
// Emitted by the PerformanceController when `PerformanceController.stopRecording()`
|
||||
// is completed; used in tests, to know when a manual UI click is finished.
|
||||
CONTROLLER_STOPPED_RECORDING: "Performance:Controller:StoppedRecording",
|
||||
|
||||
// Emitted by the PerformanceController when a recording is imported. Used
|
||||
// only in tests. Should use the normal RECORDING_STATE_CHANGE in the UI.
|
||||
RECORDING_IMPORTED: "Performance:ImportedRecording",
|
||||
};
|
||||
|
||||
/**
|
||||
@ -165,20 +180,16 @@ let gToolbox, gTarget, gFront;
|
||||
* Initializes the profiler controller and views.
|
||||
*/
|
||||
let startupPerformance = Task.async(function*() {
|
||||
yield promise.all([
|
||||
PerformanceController.initialize(),
|
||||
PerformanceView.initialize()
|
||||
]);
|
||||
yield PerformanceController.initialize();
|
||||
yield PerformanceView.initialize();
|
||||
});
|
||||
|
||||
/**
|
||||
* Destroys the profiler controller and views.
|
||||
*/
|
||||
let shutdownPerformance = Task.async(function*() {
|
||||
yield promise.all([
|
||||
PerformanceController.destroy(),
|
||||
PerformanceView.destroy()
|
||||
]);
|
||||
yield PerformanceController.destroy();
|
||||
yield PerformanceView.destroy();
|
||||
});
|
||||
|
||||
/**
|
||||
@ -202,8 +213,7 @@ let PerformanceController = {
|
||||
this._onRecordingSelectFromView = this._onRecordingSelectFromView.bind(this);
|
||||
this._onPrefChanged = this._onPrefChanged.bind(this);
|
||||
this._onThemeChanged = this._onThemeChanged.bind(this);
|
||||
this._onRecordingStateChange = this._onRecordingStateChange.bind(this);
|
||||
this._onProfilerStatusUpdated = this._onProfilerStatusUpdated.bind(this);
|
||||
this._onFrontEvent = this._onFrontEvent.bind(this);
|
||||
|
||||
// Store data regarding if e10s is enabled.
|
||||
this._e10s = Services.appinfo.browserTabsRemoteAutostart;
|
||||
@ -212,15 +222,11 @@ let PerformanceController = {
|
||||
this._prefs = require("devtools/performance/global").PREFS;
|
||||
this._prefs.on("pref-changed", this._onPrefChanged);
|
||||
|
||||
gFront.on("recording-starting", this._onRecordingStateChange);
|
||||
gFront.on("recording-started", this._onRecordingStateChange);
|
||||
gFront.on("recording-stopping", this._onRecordingStateChange);
|
||||
gFront.on("recording-stopped", this._onRecordingStateChange);
|
||||
gFront.on("profiler-status", this._onProfilerStatusUpdated);
|
||||
gFront.on("*", this._onFrontEvent);
|
||||
ToolbarView.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
|
||||
PerformanceView.on(EVENTS.UI_START_RECORDING, this.startRecording);
|
||||
PerformanceView.on(EVENTS.UI_STOP_RECORDING, this.stopRecording);
|
||||
PerformanceView.on(EVENTS.UI_IMPORT_RECORDING, this.importRecording);
|
||||
PerformanceView.on(EVENTS.UI_RECORDING_IMPORTED, this.importRecording);
|
||||
PerformanceView.on(EVENTS.UI_CLEAR_RECORDINGS, this.clearRecordings);
|
||||
RecordingsView.on(EVENTS.UI_EXPORT_RECORDING, this.exportRecording);
|
||||
RecordingsView.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelectFromView);
|
||||
@ -234,15 +240,11 @@ let PerformanceController = {
|
||||
destroy: function() {
|
||||
this._prefs.off("pref-changed", this._onPrefChanged);
|
||||
|
||||
gFront.off("recording-starting", this._onRecordingStateChange);
|
||||
gFront.off("recording-started", this._onRecordingStateChange);
|
||||
gFront.off("recording-stopping", this._onRecordingStateChange);
|
||||
gFront.off("recording-stopped", this._onRecordingStateChange);
|
||||
gFront.off("profiler-status", this._onProfilerStatusUpdated);
|
||||
gFront.off("*", this._onFrontEvent);
|
||||
ToolbarView.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
|
||||
PerformanceView.off(EVENTS.UI_START_RECORDING, this.startRecording);
|
||||
PerformanceView.off(EVENTS.UI_STOP_RECORDING, this.stopRecording);
|
||||
PerformanceView.off(EVENTS.UI_IMPORT_RECORDING, this.importRecording);
|
||||
PerformanceView.off(EVENTS.UI_RECORDING_IMPORTED, this.importRecording);
|
||||
PerformanceView.off(EVENTS.UI_CLEAR_RECORDINGS, this.clearRecordings);
|
||||
RecordingsView.off(EVENTS.UI_EXPORT_RECORDING, this.exportRecording);
|
||||
RecordingsView.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelectFromView);
|
||||
@ -292,8 +294,7 @@ let PerformanceController = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts recording with the PerformanceFront. Emits `EVENTS.RECORDING_STARTED`
|
||||
* when the front has started to record.
|
||||
* Starts recording with the PerformanceFront.
|
||||
*/
|
||||
startRecording: Task.async(function *() {
|
||||
let options = {
|
||||
@ -312,19 +313,25 @@ let PerformanceController = {
|
||||
}),
|
||||
|
||||
/**
|
||||
* Stops recording with the PerformanceFront. Emits `EVENTS.RECORDING_STOPPED`
|
||||
* when the front has stopped recording.
|
||||
* Stops recording with the PerformanceFront.
|
||||
*/
|
||||
stopRecording: Task.async(function *() {
|
||||
let recording = this.getLatestManualRecording();
|
||||
yield gFront.stopRecording(recording);
|
||||
|
||||
// Emit another stop event here, as a lot of tests use
|
||||
// the RECORDING_STOPPED event, but in the case of a UI click on a button,
|
||||
// the RECORDING_STOPPED event happens from the server, where this request may
|
||||
// not have yet finished, so listen to this in tests that fail because the `stopRecording`
|
||||
// request is not yet completed. Should only be used in that scenario.
|
||||
this.emit(EVENTS.CONTROLLER_STOPPED_RECORDING);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Saves the given recording to a file. Emits `EVENTS.RECORDING_EXPORTED`
|
||||
* when the file was saved.
|
||||
*
|
||||
* @param RecordingModel recording
|
||||
* @param PerformanceRecording recording
|
||||
* The model that holds the recording data.
|
||||
* @param nsILocalFile file
|
||||
* The file to stream the data into.
|
||||
@ -346,7 +353,7 @@ let PerformanceController = {
|
||||
// If last recording is not recording, but finalizing itself,
|
||||
// wait for that to finish
|
||||
if (latest && !latest.isCompleted()) {
|
||||
yield this.once(EVENTS.RECORDING_STOPPED);
|
||||
yield this.waitForStateChangeOnRecording(latest, "recording-stopped");
|
||||
}
|
||||
|
||||
this._recordings.length = 0;
|
||||
@ -362,18 +369,22 @@ let PerformanceController = {
|
||||
* The file to import the data from.
|
||||
*/
|
||||
importRecording: Task.async(function*(_, file) {
|
||||
let recording = new RecordingModel();
|
||||
this._recordings.push(recording);
|
||||
yield recording.importRecording(file);
|
||||
let recording = yield gFront.importRecording(file);
|
||||
this._addNewRecording(recording);
|
||||
|
||||
this.emit(EVENTS.RECORDING_IMPORTED, recording);
|
||||
// Only emit in tests for legacy purposes for shorthand --
|
||||
// other things in UI should handle the generic NEW_RECORDING
|
||||
// event to handle lazy recordings.
|
||||
if (DevToolsUtils.testing) {
|
||||
this.emit(EVENTS.RECORDING_IMPORTED, recording);
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Sets the currently active RecordingModel. Should rarely be called directly,
|
||||
* Sets the currently active PerformanceRecording. Should rarely be called directly,
|
||||
* as RecordingsView handles this when manually selected a recording item. Exceptions
|
||||
* are when clearing the view.
|
||||
* @param RecordingModel recording
|
||||
* @param PerformanceRecording recording
|
||||
*/
|
||||
setCurrentRecording: function (recording) {
|
||||
if (this._currentRecording !== recording) {
|
||||
@ -383,8 +394,8 @@ let PerformanceController = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the currently active RecordingModel.
|
||||
* @return RecordingModel
|
||||
* Gets the currently active PerformanceRecording.
|
||||
* @return PerformanceRecording
|
||||
*/
|
||||
getCurrentRecording: function () {
|
||||
return this._currentRecording;
|
||||
@ -392,7 +403,7 @@ let PerformanceController = {
|
||||
|
||||
/**
|
||||
* Get most recently added recording that was triggered manually (via UI).
|
||||
* @return RecordingModel
|
||||
* @return PerformanceRecording
|
||||
*/
|
||||
getLatestManualRecording: function () {
|
||||
for (let i = this._recordings.length - 1; i >= 0; i--) {
|
||||
@ -434,48 +445,85 @@ let PerformanceController = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Emitted when the front updates RecordingModel's buffer status.
|
||||
* Fired from the front on any event. Propagates to other handlers from here.
|
||||
*/
|
||||
_onProfilerStatusUpdated: function (_, data) {
|
||||
_onFrontEvent: function (eventName, ...data) {
|
||||
if (eventName === "profiler-status") {
|
||||
this._onProfilerStatusUpdated(...data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (["recording-started", "recording-stopped", "recording-stopping"].indexOf(eventName) !== -1) {
|
||||
this._onRecordingStateChange(eventName, ...data);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Emitted when the front updates PerformanceRecording's buffer status.
|
||||
*/
|
||||
_onProfilerStatusUpdated: function (data) {
|
||||
this.emit(EVENTS.PROFILER_STATUS_UPDATED, data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Stores a recording internally.
|
||||
*
|
||||
* @param {PerformanceRecordingFront} recording
|
||||
*/
|
||||
_addNewRecording: function (recording) {
|
||||
if (this._recordings.indexOf(recording) === -1) {
|
||||
this._recordings.push(recording);
|
||||
this.emit(EVENTS.NEW_RECORDING, recording);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired when a recording model changes state.
|
||||
*
|
||||
* @param {string} state
|
||||
* @param {RecordingModel} model
|
||||
* Can be "recording-started", "recording-stopped" or "recording-stopping".
|
||||
* @param {PerformanceRecording} model
|
||||
*/
|
||||
_onRecordingStateChange: function (state, model) {
|
||||
// If we get a state change for a recording that isn't being tracked in the front,
|
||||
// just ignore it. This can occur when stopping a profile via console that was cleared.
|
||||
if (state !== "recording-starting" && this.getRecordings().indexOf(model) === -1) {
|
||||
return;
|
||||
}
|
||||
this._addNewRecording(model);
|
||||
|
||||
switch (state) {
|
||||
// Fired when a RecordingModel was just created from the front
|
||||
case "recording-starting":
|
||||
// When a recording is just starting, store it internally
|
||||
this._recordings.push(model);
|
||||
this.emit(EVENTS.RECORDING_WILL_START, model);
|
||||
break;
|
||||
// Fired when a RecordingModel has started recording
|
||||
case "recording-started":
|
||||
this.emit(EVENTS.RECORDING_STARTED, model);
|
||||
break;
|
||||
// Fired when a RecordingModel is no longer recording, and
|
||||
// starting to fetch all the profiler data
|
||||
case "recording-stopping":
|
||||
this.emit(EVENTS.RECORDING_WILL_STOP, model);
|
||||
break;
|
||||
// Fired when a RecordingModel is finished fetching all of its data
|
||||
case "recording-stopped":
|
||||
this.emit(EVENTS.RECORDING_STOPPED, model);
|
||||
break;
|
||||
this.emit(EVENTS.RECORDING_STATE_CHANGE, state, model);
|
||||
|
||||
// Emit the state specific events for tests that I'm too
|
||||
// lazy and frusterated to change right now. These events
|
||||
// should only be used in tests, as the rest of the UI should
|
||||
// react to general RECORDING_STATE_CHANGE events and NEW_RECORDING
|
||||
// events to handle lazy recordings.
|
||||
if (DevToolsUtils.testing) {
|
||||
switch (state) {
|
||||
case "recording-started":
|
||||
this.emit(EVENTS.RECORDING_STARTED, model);
|
||||
break;
|
||||
case "recording-stopping":
|
||||
this.emit(EVENTS.RECORDING_WILL_STOP, model);
|
||||
break;
|
||||
case "recording-stopped":
|
||||
this.emit(EVENTS.RECORDING_STOPPED, model);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes a recording and returns a value between 0 and 1 indicating how much
|
||||
* of the buffer is used.
|
||||
*/
|
||||
getBufferUsageForRecording: function (recording) {
|
||||
return gFront.getBufferUsageForRecording(recording);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating if any recordings are currently in progress or not.
|
||||
*/
|
||||
isRecording: function () {
|
||||
return this._recordings.some(r => r.isRecording());
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the internal store of recording models.
|
||||
*/
|
||||
@ -483,6 +531,13 @@ let PerformanceController = {
|
||||
return this._recordings;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns traits from the front.
|
||||
*/
|
||||
getTraits: function () {
|
||||
return gFront.traits;
|
||||
},
|
||||
|
||||
/**
|
||||
* Utility method taking a string or an array of strings of feature names (like
|
||||
* "withAllocations" or "withMarkers"), and returns whether or not the current
|
||||
@ -508,6 +563,21 @@ let PerformanceController = {
|
||||
return [].concat(features).every(f => config[f]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes an array of PerformanceRecordingFronts and adds them to the internal
|
||||
* store of the UI. Used by the toolbox to lazily seed recordings that
|
||||
* were observed before the panel was loaded in the scenario where `console.profile()`
|
||||
* is used before the tool is loaded.
|
||||
*
|
||||
* @param {Array<PerformanceRecordingFront>} recordings
|
||||
*/
|
||||
populateWithRecordings: function (recordings=[]) {
|
||||
for (let recording of recordings) {
|
||||
PerformanceController._addNewRecording(recording);
|
||||
}
|
||||
this.emit(EVENTS.RECORDINGS_SEEDED);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an object with `supported` and `enabled` properties indicating
|
||||
* whether or not the platform is capable of turning on e10s and whether or not
|
||||
@ -530,6 +600,25 @@ let PerformanceController = {
|
||||
return { supported, enabled };
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes a PerformanceRecording and a state, and waits for
|
||||
* the event to be emitted from the front for that recording.
|
||||
*
|
||||
* @param {PerformanceRecordingFront} recording
|
||||
* @param {string} expectedState
|
||||
* @return {Promise}
|
||||
*/
|
||||
waitForStateChangeOnRecording: Task.async(function *(recording, expectedState) {
|
||||
let deferred = promise.defer();
|
||||
this.on(EVENTS.RECORDING_STATE_CHANGE, function handler (state, model) {
|
||||
if (state === expectedState && model === recording) {
|
||||
this.off(EVENTS.RECORDING_STATE_CHANGE, handler);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
yield deferred.promise;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Called on init, sets an `e10s` attribute on the main view container with
|
||||
* "disabled" if e10s is possible on the platform and just not on, or "unsupported"
|
||||
|
@ -51,10 +51,7 @@ let PerformanceView = {
|
||||
this._onClearButtonClick = this._onClearButtonClick.bind(this);
|
||||
this._onRecordingSelected = this._onRecordingSelected.bind(this);
|
||||
this._onProfilerStatusUpdated = this._onProfilerStatusUpdated.bind(this);
|
||||
this._onRecordingWillStart = this._onRecordingWillStart.bind(this);
|
||||
this._onRecordingStarted = this._onRecordingStarted.bind(this);
|
||||
this._onRecordingWillStop = this._onRecordingWillStop.bind(this);
|
||||
this._onRecordingStopped = this._onRecordingStopped.bind(this);
|
||||
this._onRecordingStateChange = this._onRecordingStateChange.bind(this);
|
||||
|
||||
for (let button of $$(".record-button")) {
|
||||
button.addEventListener("click", this._onRecordButtonClick);
|
||||
@ -65,10 +62,8 @@ let PerformanceView = {
|
||||
// Bind to controller events to unlock the record button
|
||||
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
|
||||
PerformanceController.on(EVENTS.PROFILER_STATUS_UPDATED, this._onProfilerStatusUpdated);
|
||||
PerformanceController.on(EVENTS.RECORDING_WILL_START, this._onRecordingWillStart);
|
||||
PerformanceController.on(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
|
||||
PerformanceController.on(EVENTS.RECORDING_WILL_STOP, this._onRecordingWillStop);
|
||||
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
|
||||
PerformanceController.on(EVENTS.RECORDING_STATE_CHANGE, this._onRecordingStateChange);
|
||||
PerformanceController.on(EVENTS.NEW_RECORDING, this._onRecordingStateChange);
|
||||
|
||||
this.setState("empty");
|
||||
|
||||
@ -92,10 +87,8 @@ let PerformanceView = {
|
||||
|
||||
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
|
||||
PerformanceController.off(EVENTS.PROFILER_STATUS_UPDATED, this._onProfilerStatusUpdated);
|
||||
PerformanceController.off(EVENTS.RECORDING_WILL_START, this._onRecordingWillStart);
|
||||
PerformanceController.off(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
|
||||
PerformanceController.off(EVENTS.RECORDING_WILL_STOP, this._onRecordingWillStop);
|
||||
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
|
||||
PerformanceController.off(EVENTS.RECORDING_STATE_CHANGE, this._onRecordingStateChange);
|
||||
PerformanceController.off(EVENTS.NEW_RECORDING, this._onRecordingStateChange);
|
||||
|
||||
yield ToolbarView.destroy();
|
||||
yield RecordingsView.destroy();
|
||||
@ -157,7 +150,7 @@ let PerformanceView = {
|
||||
return;
|
||||
}
|
||||
|
||||
let bufferUsage = recording.getBufferUsage();
|
||||
let bufferUsage = PerformanceController.getBufferUsageForRecording(recording) || 0;
|
||||
|
||||
// Normalize to a percentage value
|
||||
let percent = Math.floor(bufferUsage * 100);
|
||||
@ -174,7 +167,7 @@ let PerformanceView = {
|
||||
}
|
||||
|
||||
$bufferLabel.value = L10N.getFormatStr("profiler.bufferFull", percent);
|
||||
this.emit(EVENTS.UI_BUFFER_UPDATED, percent);
|
||||
this.emit(EVENTS.UI_BUFFER_STATUS_UPDATED, percent);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -209,61 +202,25 @@ let PerformanceView = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired when a recording is just starting, but actors may not have
|
||||
* yet started actually recording.
|
||||
*/
|
||||
_onRecordingWillStart: function (_, recording) {
|
||||
if (!recording.isConsole()) {
|
||||
this._lockRecordButtons(true);
|
||||
this._activateRecordButtons(true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* When a recording has started.
|
||||
*/
|
||||
_onRecordingStarted: function (_, recording) {
|
||||
// A stopped recording can be from `console.profileEnd` -- only unlock
|
||||
// the button if it's the main recording that was started via UI.
|
||||
if (!recording.isConsole()) {
|
||||
this._lockRecordButtons(false);
|
||||
}
|
||||
if (recording.isRecording()) {
|
||||
this.updateBufferStatus();
|
||||
}
|
||||
},
|
||||
_onRecordingStateChange: function () {
|
||||
let currentRecording = PerformanceController.getCurrentRecording();
|
||||
let recordings = PerformanceController.getRecordings();
|
||||
|
||||
/**
|
||||
* Fired when a recording is stopping, but not yet completed
|
||||
*/
|
||||
_onRecordingWillStop: function (_, recording) {
|
||||
if (!recording.isConsole()) {
|
||||
this._lockRecordButtons(true);
|
||||
this._activateRecordButtons(false);
|
||||
}
|
||||
// Lock the details view while the recording is being loaded in the UI.
|
||||
// Only do this if this is the current recording.
|
||||
if (recording === PerformanceController.getCurrentRecording()) {
|
||||
this._activateRecordButtons(recordings.find(r => !r.isConsole() && r.isRecording()));
|
||||
this._lockRecordButtons(recordings.find(r => !r.isConsole() && r.isFinalizing()));
|
||||
|
||||
if (currentRecording && currentRecording.isFinalizing()) {
|
||||
this.setState("loading");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* When a recording is complete.
|
||||
*/
|
||||
_onRecordingStopped: function (_, recording) {
|
||||
// A stopped recording can be from `console.profileEnd` -- only unlock
|
||||
// the button if it's the main recording that was started via UI.
|
||||
if (!recording.isConsole()) {
|
||||
this._lockRecordButtons(false);
|
||||
}
|
||||
|
||||
// If the currently selected recording is the one that just stopped,
|
||||
// switch state to "recorded".
|
||||
if (recording === PerformanceController.getCurrentRecording()) {
|
||||
if (currentRecording && currentRecording.isCompleted()) {
|
||||
this.setState("recorded");
|
||||
}
|
||||
if (currentRecording && currentRecording.isRecording()) {
|
||||
this.updateBufferStatus();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -280,6 +237,8 @@ let PerformanceView = {
|
||||
if (this._recordButton.hasAttribute("checked")) {
|
||||
this.emit(EVENTS.UI_STOP_RECORDING);
|
||||
} else {
|
||||
this._lockRecordButtons(true);
|
||||
this._activateRecordButtons(true);
|
||||
this.emit(EVENTS.UI_START_RECORDING);
|
||||
}
|
||||
},
|
||||
|
@ -21,14 +21,6 @@ support-files =
|
||||
[browser_markers-timestamp.js]
|
||||
[browser_perf-allocations-to-samples.js]
|
||||
[browser_perf-categories-js-calltree.js]
|
||||
[browser_perf-compatibility-01.js]
|
||||
[browser_perf-compatibility-02.js]
|
||||
[browser_perf-compatibility-03.js]
|
||||
[browser_perf-compatibility-04.js]
|
||||
[browser_perf-compatibility-05.js]
|
||||
[browser_perf-compatibility-06.js]
|
||||
[browser_perf-compatibility-07.js]
|
||||
[browser_perf-compatibility-08.js]
|
||||
[browser_perf-clear-01.js]
|
||||
[browser_perf-clear-02.js]
|
||||
[browser_perf-columns-js-calltree.js]
|
||||
@ -42,8 +34,6 @@ support-files =
|
||||
[browser_perf-console-record-07.js]
|
||||
[browser_perf-console-record-08.js]
|
||||
[browser_perf-console-record-09.js]
|
||||
[browser_perf-data-massaging-01.js]
|
||||
[browser_perf-data-samples.js]
|
||||
[browser_perf-details-calltree-render.js]
|
||||
[browser_perf-details-flamegraph-render.js]
|
||||
[browser_perf-details-memory-calltree-render.js]
|
||||
@ -57,19 +47,15 @@ support-files =
|
||||
[browser_perf-details-06.js]
|
||||
[browser_perf-details-07.js]
|
||||
[browser_perf-events-calltree.js]
|
||||
[browser_perf-front-basic-profiler-01.js]
|
||||
[browser_perf-front-basic-timeline-01.js]
|
||||
#[browser_perf-front-profiler-01.js] bug 1077464
|
||||
[browser_perf-front-profiler-02.js]
|
||||
[browser_perf-front-profiler-03.js]
|
||||
[browser_perf-front-profiler-04.js]
|
||||
#[browser_perf-front-profiler-05.js] bug 1077464
|
||||
#[browser_perf-front-profiler-06.js]
|
||||
[browser_perf-front-01.js]
|
||||
[browser_perf-front-02.js]
|
||||
[browser_perf-highlighted.js]
|
||||
#[browser_perf-jit-view-01.js] bug 1176056
|
||||
#[browser_perf-jit-view-02.js] bug 1176056
|
||||
[browser_perf-legacy-front-01.js]
|
||||
[browser_perf-legacy-front-02.js]
|
||||
[browser_perf-legacy-front-03.js]
|
||||
[browser_perf-legacy-front-04.js]
|
||||
[browser_perf-legacy-front-05.js]
|
||||
[browser_perf-legacy-front-06.js]
|
||||
[browser_perf-loading-01.js]
|
||||
[browser_perf-loading-02.js]
|
||||
[browser_perf-marker-details-01.js]
|
||||
@ -101,13 +87,9 @@ skip-if = os == 'linux' # Bug 1172120
|
||||
[browser_perf-overview-selection-02.js]
|
||||
[browser_perf-overview-selection-03.js]
|
||||
[browser_perf-overview-time-interval.js]
|
||||
[browser_perf-shared-connection-02.js]
|
||||
[browser_perf-shared-connection-03.js]
|
||||
[browser_perf-states.js]
|
||||
[browser_perf-refresh.js]
|
||||
[browser_perf-ui-recording.js]
|
||||
[browser_perf-recording-model-01.js]
|
||||
[browser_perf-recording-model-02.js]
|
||||
[browser_perf-recording-notices-01.js]
|
||||
[browser_perf-recording-notices-02.js]
|
||||
[browser_perf-recording-notices-03.js]
|
||||
|
@ -12,13 +12,13 @@ function waitForMarkerType(front, type) {
|
||||
info("Waiting for marker of type = " + type);
|
||||
const { promise, resolve } = Promise.defer();
|
||||
|
||||
const handler = (_, name, markers) => {
|
||||
const handler = (name, data) => {
|
||||
if (name !== "markers") {
|
||||
return;
|
||||
}
|
||||
|
||||
let markers = data.markers;
|
||||
info("Got markers: " + JSON.stringify(markers, null, 2));
|
||||
|
||||
if (markers.some(m => m.name === type)) {
|
||||
ok(true, "Found marker of type = " + type);
|
||||
front.off("timeline-data", handler);
|
||||
@ -36,7 +36,7 @@ function* spawnTest () {
|
||||
|
||||
let { target, front } = yield initBackend(TEST_URL);
|
||||
|
||||
yield front.startRecording({ withMarkers: true, withTicks: true });
|
||||
let rec = yield front.startRecording({ withMarkers: true, withTicks: true });
|
||||
|
||||
yield Promise.all([
|
||||
waitForMarkerType(front, "nsCycleCollector::Collect"),
|
||||
@ -44,7 +44,7 @@ function* spawnTest () {
|
||||
]);
|
||||
ok(true, "Got expected cycle collection events");
|
||||
|
||||
yield front.stopRecording();
|
||||
yield front.stopRecording(rec);
|
||||
|
||||
// Destroy the front before removing tab to ensure no
|
||||
// lingering requests
|
||||
|
@ -24,13 +24,11 @@ function* spawnTest () {
|
||||
|
||||
info(`Got ${markers.length} markers.`);
|
||||
|
||||
let maxMarkerTime = model._timelineStartTime + model.getDuration() + TIME_CLOSE_TO;
|
||||
|
||||
ok(markers.every(({name}) => name === "GarbageCollection"), "All markers found are GC markers");
|
||||
ok(markers.length > 0, "found atleast one GC marker");
|
||||
ok(markers.every(({start}) => typeof start === "number" && start > 0 && start < maxMarkerTime),
|
||||
ok(markers.every(({start, end}) => typeof start === "number" && start > 0 && start < end),
|
||||
"All markers have a start time between the valid range.");
|
||||
ok(markers.every(({end}) => typeof end === "number" && end > 0 && end < maxMarkerTime),
|
||||
ok(markers.every(({end}) => typeof end === "number"),
|
||||
"All markers have an end time between the valid range.");
|
||||
ok(markers.every(({causeName}) => typeof causeName === "string"),
|
||||
"All markers have a causeName.");
|
||||
@ -41,7 +39,8 @@ function* spawnTest () {
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
|
||||
function handler (_, name, m) {
|
||||
function handler (name, m) {
|
||||
m = m.markers;
|
||||
if (name === "markers" && m[0].name === "GarbageCollection") {
|
||||
markers = m;
|
||||
}
|
||||
|
@ -31,12 +31,12 @@ function* spawnTest () {
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
|
||||
function handler (_, name, _markers) {
|
||||
function handler (name, data) {
|
||||
if (name !== "markers") {
|
||||
return;
|
||||
}
|
||||
|
||||
_markers.forEach(marker => {
|
||||
data.markers.forEach(marker => {
|
||||
info(marker.name);
|
||||
if (marker.name === "Parse HTML") {
|
||||
markers.push(marker);
|
||||
|
@ -33,9 +33,9 @@ function* spawnTest () {
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
|
||||
function handler (_, name, m) {
|
||||
function handler (name, data) {
|
||||
if (name === "markers") {
|
||||
markers = markers.concat(m.filter(marker => marker.name === "Styles"));
|
||||
markers = markers.concat(data.markers.filter(marker => marker.name === "Styles"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,10 +29,8 @@ function* spawnTest () {
|
||||
ok(markers.every(({stack}) => typeof stack === "number"), "All markers have stack references.");
|
||||
ok(markers.every(({name}) => name === "TimeStamp"), "All markers found are TimeStamp markers");
|
||||
ok(markers.length === 2, "found 2 TimeStamp markers");
|
||||
ok(markers.every(({start}) => typeof start === "number" && start > 0 && start < maxMarkerTime),
|
||||
"All markers have a start time between the valid range.");
|
||||
ok(markers.every(({end}) => typeof end === "number" && end > 0 && end < maxMarkerTime),
|
||||
"All markers have an end time between the valid range.");
|
||||
ok(markers.every(({start, end}) => typeof start === "number" && start === end),
|
||||
"All markers have equal start and end times");
|
||||
is(markers[0].causeName, void 0, "Unlabeled timestamps have an empty causeName");
|
||||
is(markers[1].causeName, "myLabel", "Labeled timestamps have correct causeName");
|
||||
|
||||
@ -42,9 +40,9 @@ function* spawnTest () {
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
|
||||
function handler (_, name, m) {
|
||||
function handler (name, data) {
|
||||
if (name === "markers") {
|
||||
markers = markers.concat(m.filter(marker => marker.name === "TimeStamp"));
|
||||
markers = markers.concat(data.markers.filter(marker => marker.name === "TimeStamp"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,6 @@
|
||||
*/
|
||||
|
||||
function test() {
|
||||
let RecordingUtils = require("devtools/performance/recording-utils");
|
||||
|
||||
let output = RecordingUtils.getProfileThreadFromAllocations(TEST_DATA);
|
||||
is(output.toSource(), EXPECTED_OUTPUT.toSource(), "The output is correct.");
|
||||
|
||||
|
@ -1,53 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test basic functionality of PerformanceFront with mock memory and timeline actors.
|
||||
*/
|
||||
|
||||
let WAIT_TIME = 100;
|
||||
|
||||
function* spawnTest() {
|
||||
let { target, front } = yield initBackend(SIMPLE_URL, {
|
||||
TEST_MOCK_MEMORY_ACTOR: true,
|
||||
TEST_MOCK_TIMELINE_ACTOR: true
|
||||
});
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
Services.prefs.setBoolPref(ALLOCATIONS_PREF, true);
|
||||
|
||||
let { memory, timeline } = front.getActorSupport();
|
||||
ok(!memory, "memory should be mocked.");
|
||||
ok(!timeline, "timeline should be mocked.");
|
||||
|
||||
let recording = yield front.startRecording({
|
||||
withTicks: true,
|
||||
withMarkers: true,
|
||||
withMemory: true,
|
||||
withAllocations: true,
|
||||
allocationsSampleProbability: +Services.prefs.getCharPref(MEMORY_SAMPLE_PROB_PREF),
|
||||
allocationsMaxLogLength: Services.prefs.getIntPref(MEMORY_MAX_LOG_LEN_PREF)
|
||||
});
|
||||
|
||||
ok(typeof recording._profilerStartTime === "number",
|
||||
"The front.startRecording() returns a recording with a profiler start time");
|
||||
ok(typeof recording._timelineStartTime === "number",
|
||||
"The front.startRecording() returns a recording with a timeline start time");
|
||||
ok(typeof recording._memoryStartTime === "number",
|
||||
"The front.startRecording() returns a recording with a memory start time");
|
||||
|
||||
yield busyWait(WAIT_TIME);
|
||||
|
||||
yield front.stopRecording(recording);
|
||||
|
||||
ok(typeof recording.getDuration() === "number",
|
||||
"The front.stopRecording() allows recording to get a duration.");
|
||||
|
||||
ok(recording.getDuration() >= 0,
|
||||
"The profilerEndTime is after profilerStartTime.");
|
||||
|
||||
// Destroy the front before removing tab to ensure no
|
||||
// lingering requests
|
||||
yield front.destroy();
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test basic functionality of PerformanceFront with only mock memory.
|
||||
*/
|
||||
|
||||
let WAIT_TIME = 100;
|
||||
|
||||
function* spawnTest() {
|
||||
let { target, front } = yield initBackend(SIMPLE_URL, {
|
||||
TEST_MOCK_MEMORY_ACTOR: true
|
||||
});
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
Services.prefs.setBoolPref(ALLOCATIONS_PREF, true);
|
||||
|
||||
let { memory, timeline } = front.getActorSupport();
|
||||
ok(!memory, "memory should be mocked.");
|
||||
ok(timeline, "timeline should not be mocked.");
|
||||
|
||||
let recording = yield front.startRecording({
|
||||
withTicks: true,
|
||||
withMarkers: true,
|
||||
withMemory: true,
|
||||
withAllocations: true,
|
||||
allocationsSampleProbability: +Services.prefs.getCharPref(MEMORY_SAMPLE_PROB_PREF),
|
||||
allocationsMaxLogLength: Services.prefs.getIntPref(MEMORY_MAX_LOG_LEN_PREF)
|
||||
});
|
||||
|
||||
ok(typeof recording._profilerStartTime === "number",
|
||||
"The front.startRecording() returns a recording with a profiler start time");
|
||||
ok(typeof recording._timelineStartTime === "number",
|
||||
"The front.startRecording() returns a recording with a timeline start time");
|
||||
ok(typeof recording._memoryStartTime === "number",
|
||||
"The front.startRecording() returns a recording with a memory start time");
|
||||
|
||||
yield busyWait(WAIT_TIME);
|
||||
|
||||
yield front.stopRecording(recording);
|
||||
|
||||
ok(typeof recording.getDuration() === "number",
|
||||
"The front.stopRecording() allows recording to get a duration.");
|
||||
|
||||
ok(recording.getDuration() >= 0,
|
||||
"The profilerEndTime is after profilerStartTime.");
|
||||
|
||||
// Destroy the front before removing tab to ensure no
|
||||
// lingering requests
|
||||
yield front.destroy();
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that when using an older server (< Fx40) where the profiler actor does not
|
||||
* have the `getBufferInfo` method that nothing breaks and RecordingModels have null
|
||||
* `getBufferUsage()` values.
|
||||
*/
|
||||
|
||||
function* spawnTest() {
|
||||
let { target, front } = yield initBackend(SIMPLE_URL, {
|
||||
TEST_MOCK_PROFILER_CHECK_TIMER: 10,
|
||||
TEST_PROFILER_FILTER_STATUS: ["position", "totalSize", "generation"]
|
||||
});
|
||||
|
||||
let model = yield front.startRecording();
|
||||
let count = 0;
|
||||
while (count < 5) {
|
||||
let [_, stats] = yield onceSpread(front._profiler, "profiler-status");
|
||||
is(stats.generation, void 0, "profiler-status has void `generation`");
|
||||
is(stats.totalSize, void 0, "profiler-status has void `totalSize`");
|
||||
is(stats.position, void 0, "profiler-status has void `position`");
|
||||
count++;
|
||||
}
|
||||
|
||||
is(model.getBufferUsage(), null, "model should have `null` for its buffer usage");
|
||||
yield front.stopRecording(model);
|
||||
is(model.getBufferUsage(), null, "after recording, model should still have `null` for its buffer usage");
|
||||
|
||||
// Destroy the front before removing tab to ensure no
|
||||
// lingering requests
|
||||
yield front.destroy();
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
@ -6,14 +6,11 @@
|
||||
* before it was opened.
|
||||
*/
|
||||
|
||||
let { getPerformanceFront } = require("devtools/performance/front");
|
||||
let WAIT_TIME = 10;
|
||||
|
||||
function* spawnTest() {
|
||||
let profilerConnected = waitForProfilerConnection();
|
||||
let { target, toolbox, console } = yield initConsole(SIMPLE_URL);
|
||||
yield profilerConnected;
|
||||
let front = getPerformanceFront(target);
|
||||
let front = toolbox.performance;
|
||||
|
||||
let profileStart = once(front, "recording-started");
|
||||
console.profile("rust");
|
||||
@ -29,6 +26,7 @@ function* spawnTest() {
|
||||
let { panelWin: { PerformanceController, RecordingsView }} = panel;
|
||||
|
||||
let recordings = PerformanceController.getRecordings();
|
||||
yield waitUntil(() => PerformanceController.getRecordings().length === 1);
|
||||
is(recordings.length, 1, "one recording found in the performance panel.");
|
||||
is(recordings[0].isConsole(), true, "recording came from console.profile.");
|
||||
is(recordings[0].getLabel(), "rust", "correct label in the recording model.");
|
||||
|
@ -6,14 +6,11 @@
|
||||
* when it is opened.
|
||||
*/
|
||||
|
||||
let { getPerformanceFront } = require("devtools/performance/front");
|
||||
let WAIT_TIME = 10;
|
||||
|
||||
function* spawnTest() {
|
||||
let profilerConnected = waitForProfilerConnection();
|
||||
let { target, toolbox, console } = yield initConsole(SIMPLE_URL);
|
||||
yield profilerConnected;
|
||||
let front = getPerformanceFront(target);
|
||||
let front = toolbox.performance;
|
||||
|
||||
let profileStart = once(front, "recording-started");
|
||||
console.profile("rust");
|
||||
@ -26,6 +23,7 @@ function* spawnTest() {
|
||||
let panel = toolbox.getCurrentPanel();
|
||||
let { panelWin: { PerformanceController, RecordingsView }} = panel;
|
||||
|
||||
yield waitUntil(() => PerformanceController.getRecordings().length === 2);
|
||||
let recordings = PerformanceController.getRecordings();
|
||||
is(recordings.length, 2, "two recordings found in the performance panel.");
|
||||
is(recordings[0].isConsole(), true, "recording came from console.profile (1).");
|
||||
|
@ -6,14 +6,11 @@
|
||||
* also console recordings that have finished before it was opened.
|
||||
*/
|
||||
|
||||
let { getPerformanceFront } = require("devtools/performance/front");
|
||||
let WAIT_TIME = 10;
|
||||
|
||||
function* spawnTest() {
|
||||
let profilerConnected = waitForProfilerConnection();
|
||||
let { target, toolbox, console } = yield initConsole(SIMPLE_URL);
|
||||
yield profilerConnected;
|
||||
let front = getPerformanceFront(target);
|
||||
let front = toolbox.performance;
|
||||
|
||||
let profileStart = once(front, "recording-started");
|
||||
console.profile("rust");
|
||||
@ -31,6 +28,7 @@ function* spawnTest() {
|
||||
let panel = toolbox.getCurrentPanel();
|
||||
let { panelWin: { PerformanceController, RecordingsView }} = panel;
|
||||
|
||||
yield waitUntil(() => PerformanceController.getRecordings().length === 2);
|
||||
let recordings = PerformanceController.getRecordings();
|
||||
is(recordings.length, 2, "two recordings found in the performance panel.");
|
||||
is(recordings[0].isConsole(), true, "recording came from console.profile (1).");
|
||||
|
@ -1,41 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the retrieved profiler data samples always have a (root) node.
|
||||
* If this ever changes, the |ThreadNode.prototype.insert| function in
|
||||
* browser/devtools/performance/modules/logic/tree-model.js will have to be changed.
|
||||
*/
|
||||
|
||||
const WAIT_TIME = 1000; // ms
|
||||
|
||||
function* spawnTest() {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let front = panel.panelWin.gFront;
|
||||
|
||||
let rec = yield front.startRecording();
|
||||
busyWait(WAIT_TIME); // allow the profiler module to sample some cpu activity
|
||||
|
||||
yield front.stopRecording(rec);
|
||||
let profile = rec.getProfile();
|
||||
let sampleCount = 0;
|
||||
|
||||
for (let thread of profile.threads) {
|
||||
info("Checking thread: " + thread.name);
|
||||
|
||||
for (let sample of thread.samples.data) {
|
||||
sampleCount++;
|
||||
|
||||
let stack = getInflatedStackLocations(thread, sample);
|
||||
if (stack[0] != "(root)") {
|
||||
ok(false, "The sample " + stack.toSource() + " doesn't have a root node.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ok(sampleCount > 0,
|
||||
"At least some samples have been iterated over, checking for root nodes.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that the waterfall view renders content after recording.
|
||||
*/
|
||||
function* spawnTest() {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, PerformanceController, DetailsView, WaterfallView } = panel.panelWin;
|
||||
|
||||
Services.prefs.setBoolPref(ALLOCATIONS_PREF, false);
|
||||
yield startRecording(panel);
|
||||
yield waitUntil(hasGCMarkers(PerformanceController));
|
||||
|
||||
let rendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
ok(DetailsView.isViewSelected(WaterfallView),
|
||||
"The waterfall view is selected by default in the details view.");
|
||||
yield rendered;
|
||||
|
||||
|
||||
Services.prefs.setBoolPref(ALLOCATIONS_PREF, false);
|
||||
yield startRecording(panel);
|
||||
yield waitUntil(() => PerformanceController.getCurrentRecording().getMarkers().length);
|
||||
|
||||
rendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
|
||||
yield stopRecording(panel);
|
||||
yield rendered;
|
||||
|
||||
ok(true, "WaterfallView rendered again after recording completed a second time.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
||||
|
||||
function hasGCMarkers (controller) {
|
||||
return function () {
|
||||
controller.getCurrentRecording().getMarkers().filter(m => m.name === "GarbageCollection").length >== 2;
|
||||
};
|
||||
}
|
@ -5,7 +5,6 @@
|
||||
* Tests that the call tree up/down events work for js calltree and memory calltree.
|
||||
*/
|
||||
const { ThreadNode } = require("devtools/performance/tree-model");
|
||||
const RecordingUtils = require("devtools/performance/recording-utils")
|
||||
|
||||
function* spawnTest() {
|
||||
let focus = 0;
|
||||
|
@ -1,55 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test basic functionality of PerformanceFront, emitting start and endtime values
|
||||
*/
|
||||
|
||||
let WAIT_TIME = 1000;
|
||||
|
||||
function* spawnTest() {
|
||||
let { target, front } = yield initBackend(SIMPLE_URL);
|
||||
|
||||
let recording = yield front.startRecording({
|
||||
withAllocations: true,
|
||||
allocationsSampleProbability: +Services.prefs.getCharPref(MEMORY_SAMPLE_PROB_PREF),
|
||||
allocationsMaxLogLength: Services.prefs.getIntPref(MEMORY_MAX_LOG_LEN_PREF)
|
||||
});
|
||||
|
||||
let allocationsCount = 0;
|
||||
let allocationsCounter = (_, type) => type === "allocations" && allocationsCount++;
|
||||
|
||||
// Record allocation events to ensure it's called more than once
|
||||
// so we know it's polling
|
||||
front.on("timeline-data", allocationsCounter);
|
||||
|
||||
ok(typeof recording._profilerStartTime === "number",
|
||||
"The front.startRecording() returns a recording model with a profiler start time.");
|
||||
ok(typeof recording._timelineStartTime === "number",
|
||||
"The front.startRecording() returns a recording model with a timeline start time.");
|
||||
ok(typeof recording._memoryStartTime === "number",
|
||||
"The front.startRecording() returns a recording model with a memory start time.");
|
||||
|
||||
yield Promise.all([
|
||||
busyWait(WAIT_TIME),
|
||||
waitUntil(() => allocationsCount > 1)
|
||||
]);
|
||||
|
||||
yield front.stopRecording(recording);
|
||||
|
||||
front.off("timeline-data", allocationsCounter);
|
||||
|
||||
ok(typeof recording.getDuration() === "number",
|
||||
"The front.stopRecording() gives the recording model a stop time and duration.");
|
||||
ok(recording.getDuration() > 0,
|
||||
"The front.stopRecording() gives a positive duration amount.");
|
||||
|
||||
is((yield front._request("memory", "getState")), "detached",
|
||||
"Memory actor is detached when stopping recording with allocations.");
|
||||
|
||||
// Destroy the front before removing tab to ensure no
|
||||
// lingering requests
|
||||
yield front.destroy();
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test that timeline and memory actors can be started multiple times to get
|
||||
* different start times for different recording sessions.
|
||||
*/
|
||||
|
||||
let WAIT_TIME = 1000;
|
||||
|
||||
function* spawnTest() {
|
||||
let { target, front } = yield initBackend(SIMPLE_URL);
|
||||
let config = { withMarkers: true, withAllocations: true, withTicks: true };
|
||||
|
||||
yield front._request("memory", "attach");
|
||||
|
||||
let timelineStart1 = yield front._request("timeline", "start", config);
|
||||
let memoryStart1 = yield front._request("memory", "startRecordingAllocations");
|
||||
let timelineStart2 = yield front._request("timeline", "start", config);
|
||||
let memoryStart2 = yield front._request("memory", "startRecordingAllocations");
|
||||
let timelineStop = yield front._request("timeline", "stop");
|
||||
let memoryStop = yield front._request("memory", "stopRecordingAllocations");
|
||||
|
||||
info(timelineStart1);
|
||||
info(timelineStart2);
|
||||
ok(typeof timelineStart1 === "number", "first timeline start returned a number");
|
||||
ok(typeof timelineStart2 === "number", "second timeline start returned a number");
|
||||
ok(typeof memoryStart1 === "number", "first memory start returned a number");
|
||||
ok(typeof memoryStart2 === "number", "second memory start returned a number");
|
||||
ok(timelineStart1 < timelineStart2, "can start timeline actor twice and get different start times");
|
||||
ok(memoryStart1 < memoryStart2, "can start memory actor twice and get different start times");
|
||||
|
||||
// Destroy the front before removing tab to ensure no
|
||||
// lingering requests
|
||||
yield front.destroy();
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test basic functionality of PerformanceFront
|
||||
*/
|
||||
|
||||
let WAIT_TIME = 1000;
|
||||
|
||||
function* spawnTest() {
|
||||
let { target, front } = yield initBackend(SIMPLE_URL);
|
||||
|
||||
let startModel = yield front.startRecording();
|
||||
let { profilerStartTime, timelineStartTime, memoryStartTime } = startModel;
|
||||
|
||||
ok(startModel._profilerStartTime !== undefined,
|
||||
"A `_profilerStartTime` property exists in the recording model.");
|
||||
ok(startModel._timelineStartTime !== undefined,
|
||||
"A `_timelineStartTime` property exists in the recording model.");
|
||||
is(startModel._memoryStartTime, 0,
|
||||
"A `_memoryStartTime` property exists in the recording model, but it's 0.");
|
||||
|
||||
yield busyWait(WAIT_TIME);
|
||||
|
||||
let stopModel = yield front.stopRecording(startModel);
|
||||
|
||||
ok(stopModel.getProfile(), "recording model has a profile after stopping.");
|
||||
ok(stopModel.getDuration(), "recording model has a duration after stopping.");
|
||||
|
||||
// Destroy the front before removing tab to ensure no
|
||||
// lingering requests
|
||||
yield front.destroy();
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the built-in profiler module doesn't deactivate when the toolbox
|
||||
* is destroyed if there are other consumers using it.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let { panel: firstPanel } = yield initPerformance(SIMPLE_URL);
|
||||
let firstFront = firstPanel.panelWin.gFront;
|
||||
|
||||
let activated = firstFront.once("profiler-activated");
|
||||
yield firstFront.startRecording();
|
||||
yield activated;
|
||||
|
||||
let { panel: secondPanel } = yield initPerformance(SIMPLE_URL);
|
||||
let secondFront = secondPanel.panelWin.gFront;
|
||||
loadFrameScripts();
|
||||
|
||||
let alreadyActive = secondFront.once("profiler-already-active");
|
||||
yield secondFront.startRecording();
|
||||
yield alreadyActive;
|
||||
|
||||
// Manually teardown the tabs so we can check profiler status
|
||||
let tab1 = firstPanel.target.tab;
|
||||
let tab2 = secondPanel.target.tab;
|
||||
yield firstPanel._toolbox.destroy();
|
||||
yield removeTab(tab1);
|
||||
ok((yield PMM_isProfilerActive()),
|
||||
"The built-in profiler module should still be active.");
|
||||
|
||||
yield secondPanel._toolbox.destroy();
|
||||
ok(!(yield PMM_isProfilerActive()),
|
||||
"The built-in profiler module should no longer be active.");
|
||||
yield removeTab(tab2);
|
||||
tab1 = tab2 = null;
|
||||
|
||||
finish();
|
||||
});
|
@ -1,44 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the built-in profiler module is not reactivated if no other
|
||||
* consumer was using it over the remote debugger protocol, and ensures
|
||||
* that the actor will work properly even in such cases (e.g. the Gecko Profiler
|
||||
* addon was installed and automatically activated the profiler module).
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
// Ensure the profiler is already running when the test starts.
|
||||
loadFrameScripts();
|
||||
let ENTRIES = 1000000;
|
||||
let INTERVAL = 1;
|
||||
let FEATURES = ["js"];
|
||||
yield sendProfilerCommand("StartProfiler", [ENTRIES, INTERVAL, FEATURES, FEATURES.length]);
|
||||
|
||||
let { panel: firstPanel } = yield initPerformance(SIMPLE_URL);
|
||||
let firstFront = firstPanel.panelWin.gFront;
|
||||
|
||||
let firstAlreadyActive = firstFront.once("profiler-already-active");
|
||||
let recording = yield firstFront.startRecording();
|
||||
yield firstAlreadyActive;
|
||||
ok(recording._profilerStartTime > 0, "The profiler was not restarted.");
|
||||
|
||||
let { panel: secondPanel } = yield initPerformance(SIMPLE_URL);
|
||||
let secondFront = secondPanel.panelWin.gFront;
|
||||
|
||||
let secondAlreadyActive = secondFront.once("profiler-already-active");
|
||||
let secondRecording = yield secondFront.startRecording();
|
||||
yield secondAlreadyActive;
|
||||
ok(secondRecording._profilerStartTime > 0, "The profiler was not restarted.");
|
||||
|
||||
yield teardown(firstPanel);
|
||||
ok((yield PMM_isProfilerActive()),
|
||||
"The built-in profiler module should still be active.");
|
||||
|
||||
yield teardown(secondPanel);
|
||||
ok(!(yield PMM_isProfilerActive()),
|
||||
"The built-in profiler module should have been automatically stoped.");
|
||||
|
||||
finish();
|
||||
});
|
@ -6,19 +6,17 @@
|
||||
* whether already loaded, or via console.profile with an unloaded performance tools.
|
||||
*/
|
||||
|
||||
let { getPerformanceFront } = require("devtools/performance/front");
|
||||
|
||||
function* spawnTest() {
|
||||
let profilerConnected = waitForProfilerConnection();
|
||||
let { target, toolbox, console } = yield initConsole(SIMPLE_URL);
|
||||
yield profilerConnected;
|
||||
let front = getPerformanceFront(target);
|
||||
let front = toolbox.performance;
|
||||
let tab = toolbox.doc.getElementById("toolbox-tab-performance");
|
||||
|
||||
let profileStart = once(front, "recording-started");
|
||||
console.profile("rust");
|
||||
yield profileStart;
|
||||
|
||||
yield waitUntil(() => tab.hasAttribute("highlighted"));
|
||||
|
||||
ok(tab.hasAttribute("highlighted"),
|
||||
"performance tab is highlighted during recording from console.profile when unloaded");
|
||||
|
||||
|
@ -6,8 +6,6 @@
|
||||
* if on, and displays selected frames on focus.
|
||||
*/
|
||||
|
||||
const RecordingUtils = require("devtools/performance/recording-utils");
|
||||
|
||||
Services.prefs.setBoolPref(INVERT_PREF, false);
|
||||
|
||||
function* spawnTest() {
|
||||
|
@ -6,9 +6,6 @@
|
||||
* for meta nodes when viewing "content only".
|
||||
*/
|
||||
|
||||
const { CATEGORY_MASK } = require("devtools/performance/global");
|
||||
const RecordingUtils = require("devtools/performance/recording-utils");
|
||||
|
||||
Services.prefs.setBoolPref(INVERT_PREF, false);
|
||||
Services.prefs.setBoolPref(PLATFORM_DATA_PREF, false);
|
||||
|
||||
|
@ -18,19 +18,20 @@ let test = Task.async(function*() {
|
||||
// Test mock memory
|
||||
function *testMockMemory () {
|
||||
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL, "performance", {
|
||||
TEST_MOCK_MEMORY_ACTOR: true,
|
||||
TEST_PERFORMANCE_LEGACY_FRONT: true,
|
||||
});
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
Services.prefs.setBoolPref(FRAMERATE_PREF, true);
|
||||
Services.prefs.setBoolPref(ALLOCATIONS_PREF, true);
|
||||
let { EVENTS, $, gFront, PerformanceController, PerformanceView, DetailsView, WaterfallView } = panel.panelWin;
|
||||
|
||||
let { memory: memorySupport, timeline: timelineSupport } = gFront.getActorSupport();
|
||||
yield startRecording(panel, { waitForOverview: false });
|
||||
yield waitUntil(() => PerformanceController.getCurrentRecording().getTicks().length);
|
||||
yield waitUntil(() => PerformanceController.getCurrentRecording().getMarkers().length);
|
||||
yield stopRecording(panel, { waitForOverview: false });
|
||||
|
||||
ok(gFront.LEGACY_FRONT, "using legacy front");
|
||||
|
||||
let config = PerformanceController.getCurrentRecording().getConfiguration();
|
||||
let {
|
||||
markers, allocations, memory, ticks
|
||||
@ -75,7 +76,7 @@ function *testMockMemory () {
|
||||
// Test mock memory and timeline actor
|
||||
function *testMockMemoryAndTimeline() {
|
||||
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL, "performance", {
|
||||
TEST_MOCK_MEMORY_ACTOR: true,
|
||||
TEST_PERFORMANCE_LEGACY_FRONT: true,
|
||||
TEST_MOCK_TIMELINE_ACTOR: true,
|
||||
});
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
@ -83,7 +84,6 @@ function *testMockMemoryAndTimeline() {
|
||||
Services.prefs.setBoolPref(ALLOCATIONS_PREF, true);
|
||||
let { EVENTS, $, gFront, PerformanceController, PerformanceView, DetailsView, WaterfallView } = panel.panelWin;
|
||||
|
||||
let { memory: memorySupport, timeline: timelineSupport } = gFront.getActorSupport();
|
||||
yield startRecording(panel, { waitForOverview: false });
|
||||
yield busyWait(WAIT_TIME);
|
||||
yield stopRecording(panel, { waitForOverview: false });
|
@ -10,15 +10,17 @@ const WAIT_TIME = 1000;
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL, "performance", {
|
||||
TEST_MOCK_MEMORY_ACTOR: true,
|
||||
TEST_MOCK_TIMELINE_ACTOR: true
|
||||
TEST_MOCK_TIMELINE_ACTOR: true,
|
||||
TEST_PERFORMANCE_LEGACY_FRONT: true,
|
||||
});
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
let { EVENTS, $, gFront, PerformanceController, PerformanceView, DetailsView, JsCallTreeView } = panel.panelWin;
|
||||
let { EVENTS, $, gFront: front, PerformanceController, PerformanceView, DetailsView, JsCallTreeView } = panel.panelWin;
|
||||
|
||||
let { memory: memorySupport, timeline: timelineSupport } = gFront.getActorSupport();
|
||||
ok(!memorySupport, "memory should be mocked.");
|
||||
ok(!timelineSupport, "timeline should be mocked.");
|
||||
ok(front.LEGACY_FRONT, true, "Using legacy front");
|
||||
is(front.traits.features.withMarkers, false, "traits.features.withMarkers is false.");
|
||||
is(front.traits.features.withTicks, false, "traits.features.withTicks is false.");
|
||||
is(front.traits.features.withMemory, false, "traits.features.withMemory is false.");
|
||||
is(front.traits.features.withAllocations, false, "traits.features.withAllocations is false.");
|
||||
|
||||
yield startRecording(panel, { waitForOverview: false });
|
||||
busyWait(WAIT_TIME); // allow the profiler module to sample some cpu activity
|
@ -7,18 +7,21 @@
|
||||
*/
|
||||
function* spawnTest() {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL, void 0, {
|
||||
TEST_MOCK_PROFILER_CHECK_TIMER: 10,
|
||||
TEST_PERFORMANCE_LEGACY_FRONT: true,
|
||||
TEST_PROFILER_FILTER_STATUS: ["position", "totalSize", "generation"]
|
||||
});
|
||||
let { gFront: front, EVENTS, $, PerformanceController, PerformanceView, RecordingsView } = panel.panelWin;
|
||||
|
||||
let { gFront: front, EVENTS, $, PerformanceController, PerformanceView } = panel.panelWin;
|
||||
front.setProfilerStatusInterval(10);
|
||||
yield startRecording(panel);
|
||||
|
||||
yield once(front._profiler, "profiler-status");
|
||||
front.on("profiler-status", () => ok(false, "profiler-status should not be emitted when not supported"));
|
||||
|
||||
yield busyWait(100);
|
||||
ok(!$("#details-pane-container").getAttribute("buffer-status"),
|
||||
"container does not have [buffer-status] attribute when not supported");
|
||||
|
||||
yield once(front._profiler, "profiler-status");
|
||||
yield busyWait(100);
|
||||
ok(!$("#details-pane-container").getAttribute("buffer-status"),
|
||||
"container does not have [buffer-status] attribute when not supported");
|
||||
|
@ -10,15 +10,16 @@ const WAIT_TIME = 1000;
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL, "performance", {
|
||||
TEST_MOCK_MEMORY_ACTOR: true
|
||||
TEST_PERFORMANCE_LEGACY_FRONT: true
|
||||
});
|
||||
Services.prefs.setBoolPref(MEMORY_PREF, true);
|
||||
let { EVENTS, $, gFront, PerformanceController, PerformanceView, DetailsView, WaterfallView } = panel.panelWin;
|
||||
let { EVENTS, $, gFront: front, PerformanceController, PerformanceView, DetailsView, WaterfallView } = panel.panelWin;
|
||||
|
||||
|
||||
let { memory: memorySupport, timeline: timelineSupport } = gFront.getActorSupport();
|
||||
ok(!memorySupport, "memory should be mocked.");
|
||||
ok(timelineSupport, "timeline should not be mocked.");
|
||||
ok(front.LEGACY_FRONT, true, "Using legacy front");
|
||||
is(front.traits.features.withMarkers, true, "traits.features.withMarkers is true.");
|
||||
is(front.traits.features.withTicks, true, "traits.features.withTicks is true.");
|
||||
is(front.traits.features.withMemory, false, "traits.features.withMemory is false.");
|
||||
is(front.traits.features.withAllocations, false, "traits.features.withAllocations is false.");
|
||||
|
||||
yield startRecording(panel);
|
||||
yield busyWait(100);
|
@ -10,7 +10,9 @@
|
||||
const WAIT_TIME = 1000; // ms
|
||||
|
||||
function* spawnTest() {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { panel } = yield initPerformance(SIMPLE_URL, void 0, {
|
||||
TEST_PERFORMANCE_LEGACY_FRONT: true,
|
||||
});
|
||||
let { gFront: front, gTarget: target } = panel.panelWin;
|
||||
|
||||
// Explicitly override the profiler's trait `filterable`
|
@ -0,0 +1,48 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the profiler is populated by in-progress console recordings
|
||||
* when it is opened with the legacy front.
|
||||
*/
|
||||
|
||||
let WAIT_TIME = 10;
|
||||
|
||||
function* spawnTest() {
|
||||
let { target, toolbox, console } = yield initConsole(SIMPLE_URL, { TEST_PERFORMANCE_LEGACY_FRONT: true });
|
||||
let front = toolbox.performance;
|
||||
|
||||
let profileStart = once(front, "recording-started");
|
||||
console.profile("rust");
|
||||
yield profileStart;
|
||||
profileStart = once(front, "recording-started");
|
||||
console.profile("rust2");
|
||||
yield profileStart;
|
||||
|
||||
yield gDevTools.showToolbox(target, "performance");
|
||||
let panel = toolbox.getCurrentPanel();
|
||||
let { panelWin: { PerformanceController, RecordingsView }} = panel;
|
||||
|
||||
yield waitUntil(() => PerformanceController.getRecordings().length === 2);
|
||||
let recordings = PerformanceController.getRecordings();
|
||||
is(recordings.length, 2, "two recordings found in the performance panel.");
|
||||
is(recordings[0].isConsole(), true, "recording came from console.profile (1).");
|
||||
is(recordings[0].getLabel(), "rust", "correct label in the recording model (1).");
|
||||
is(recordings[0].isRecording(), true, "recording is still recording (1).");
|
||||
is(recordings[1].isConsole(), true, "recording came from console.profile (2).");
|
||||
is(recordings[1].getLabel(), "rust2", "correct label in the recording model (2).");
|
||||
is(recordings[1].isRecording(), true, "recording is still recording (2).");
|
||||
|
||||
is(RecordingsView.selectedItem.attachment, recordings[0],
|
||||
"The first console recording should be selected.");
|
||||
|
||||
let profileEnd = once(front, "recording-stopped");
|
||||
console.profileEnd("rust");
|
||||
yield profileEnd;
|
||||
profileEnd = once(front, "recording-stopped");
|
||||
console.profileEnd("rust2");
|
||||
yield profileEnd;
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
@ -21,21 +21,21 @@ let test = Task.async(function*() {
|
||||
"The duration node should show the 'recording' message while recording");
|
||||
|
||||
info("Stop the recording and wait for the WILL_STOP and STOPPED events");
|
||||
let clicked = PerformanceView.once(EVENTS.UI_STOP_RECORDING);
|
||||
|
||||
let willStop = PerformanceController.once(EVENTS.RECORDING_WILL_STOP);
|
||||
let hasStopped = PerformanceController.once(EVENTS.RECORDING_STOPPED);
|
||||
let stoppingRecording = PerformanceController.stopRecording();
|
||||
|
||||
click(panel.panelWin, $("#main-record-button"));
|
||||
yield clicked;
|
||||
yield willStop;
|
||||
|
||||
is(durationNode.getAttribute("value"),
|
||||
L10N.getStr("recordingsList.loadingLabel"),
|
||||
"The duration node should show the 'loading' message while stopping");
|
||||
|
||||
let stateChanged = once(PerformanceView, EVENTS.UI_STATE_CHANGED);
|
||||
yield hasStopped;
|
||||
yield stateChanged;
|
||||
yield stoppingRecording;
|
||||
|
||||
ok(PerformanceController.getCurrentRecording().isCompleted(), "recording should be completed");
|
||||
|
||||
let duration = RecordingsView.selectedItem.attachment.getDuration().toFixed(0);
|
||||
is(durationNode.getAttribute("value"),
|
||||
@ -43,6 +43,7 @@ let test = Task.async(function*() {
|
||||
"The duration node should show the duration after the record has stopped");
|
||||
|
||||
yield PerformanceController.clearRecordings();
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
||||
|
@ -25,20 +25,17 @@ let test = Task.async(function*() {
|
||||
"The recording-notice is shown while recording");
|
||||
|
||||
info("Stop the recording and wait for the WILL_STOP and STOPPED events");
|
||||
let clicked = PerformanceView.once(EVENTS.UI_STOP_RECORDING);
|
||||
let willStop = PerformanceController.once(EVENTS.RECORDING_WILL_STOP);
|
||||
let hasStopped = PerformanceController.once(EVENTS.RECORDING_STOPPED);
|
||||
let stoppingRecording = PerformanceController.stopRecording();
|
||||
|
||||
click(panel.panelWin, $("#main-record-button"));
|
||||
yield clicked;
|
||||
yield willStop;
|
||||
|
||||
is(detailsContainer.selectedPanel, loadingNotice,
|
||||
"The loading-notice is shown while the record is stopping");
|
||||
|
||||
let stateChanged = once(PerformanceView, EVENTS.UI_STATE_CHANGED);
|
||||
yield hasStopped;
|
||||
yield stateChanged;
|
||||
yield stoppingRecording;
|
||||
|
||||
is(detailsContainer.selectedPanel, detailsPane,
|
||||
"The details panel is shown after the record has stopped");
|
||||
@ -52,21 +49,18 @@ let test = Task.async(function*() {
|
||||
yield select;
|
||||
|
||||
info("Stop the 2nd recording and wait for the WILL_STOP and STOPPED events");
|
||||
clicked = PerformanceView.once(EVENTS.UI_STOP_RECORDING);
|
||||
willStop = PerformanceController.once(EVENTS.RECORDING_WILL_STOP);
|
||||
hasStopped = PerformanceController.once(EVENTS.RECORDING_STOPPED);
|
||||
stoppingRecording = PerformanceController.stopRecording();
|
||||
|
||||
click(panel.panelWin, $("#main-record-button"));
|
||||
yield clicked;
|
||||
yield willStop;
|
||||
|
||||
is(detailsContainer.selectedPanel, detailsPane,
|
||||
"The details panel is still shown while the 2nd record is being stopped");
|
||||
is(RecordingsView.selectedIndex, 0, "The first record is still selected");
|
||||
|
||||
stateChanged = once(PerformanceView, EVENTS.UI_STATE_CHANGED);
|
||||
yield hasStopped;
|
||||
yield stateChanged;
|
||||
yield stoppingRecording;
|
||||
|
||||
is(detailsContainer.selectedPanel, detailsPane,
|
||||
"The details panel is still shown after the 2nd record has stopped");
|
||||
|
@ -18,7 +18,7 @@ function* spawnTest() {
|
||||
|
||||
yield startRecording(panel);
|
||||
|
||||
let { probability, maxLogLength } = yield gFront._request("memory", "getAllocationsSettings");
|
||||
let { probability, maxLogLength } = yield gFront.getConfiguration();
|
||||
|
||||
yield stopRecording(panel);
|
||||
|
||||
|
@ -14,7 +14,7 @@ function* spawnTest() {
|
||||
|
||||
yield startRecording(panel);
|
||||
|
||||
let { entries, interval } = yield gFront._request("profiler", "getStartOptions");
|
||||
let { entries, interval } = yield gFront.getConfiguration();
|
||||
|
||||
yield stopRecording(panel);
|
||||
|
||||
|
@ -10,17 +10,20 @@ function* spawnTest() {
|
||||
loadFrameScripts();
|
||||
// Keep it large, but still get to 1% relatively quick
|
||||
Services.prefs.setIntPref(PROFILER_BUFFER_SIZE_PREF, 1000000);
|
||||
let { panel } = yield initPerformance(SIMPLE_URL, void 0, { TEST_MOCK_PROFILER_CHECK_TIMER: 10 });
|
||||
let { EVENTS, $, PerformanceController, PerformanceView, RecordingsView } = panel.panelWin;
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { gFront, EVENTS, $, PerformanceController, PerformanceView, RecordingsView } = panel.panelWin;
|
||||
|
||||
// Set a fast profiler-status update interval
|
||||
yield gFront.setProfilerStatusInterval(10);
|
||||
|
||||
yield startRecording(panel);
|
||||
|
||||
let percent = 0;
|
||||
while (percent === 0) {
|
||||
[,percent] = yield onceSpread(PerformanceView, EVENTS.UI_BUFFER_UPDATED);
|
||||
[,percent] = yield onceSpread(PerformanceView, EVENTS.UI_BUFFER_STATUS_UPDATED);
|
||||
}
|
||||
|
||||
let bufferUsage = PerformanceController.getCurrentRecording().getBufferUsage();
|
||||
let bufferUsage = PerformanceController.getBufferUsageForRecording(PerformanceController.getCurrentRecording());
|
||||
is($("#details-pane-container").getAttribute("buffer-status"), "in-progress",
|
||||
"container has [buffer-status=in-progress]");
|
||||
ok($("#recording-notice .buffer-status-message").value.indexOf(percent + "%") !== -1,
|
||||
@ -31,11 +34,11 @@ function* spawnTest() {
|
||||
|
||||
percent = 0;
|
||||
while (percent <= (Math.floor(bufferUsage * 100))) {
|
||||
[,percent] = yield onceSpread(PerformanceView, EVENTS.UI_BUFFER_UPDATED);
|
||||
[,percent] = yield onceSpread(PerformanceView, EVENTS.UI_BUFFER_STATUS_UPDATED);
|
||||
}
|
||||
|
||||
ok(percent > Math.floor(bufferUsage * 100), "buffer percentage increased in display");
|
||||
bufferUsage = PerformanceController.getCurrentRecording().getBufferUsage();
|
||||
bufferUsage = PerformanceController.getBufferUsageForRecording(PerformanceController.getCurrentRecording());
|
||||
|
||||
is($("#details-pane-container").getAttribute("buffer-status"), "in-progress",
|
||||
"container has [buffer-status=in-progress]");
|
||||
@ -45,7 +48,7 @@ function* spawnTest() {
|
||||
RecordingsView.selectedIndex = 1;
|
||||
percent = 0;
|
||||
while (percent === 0) {
|
||||
[,percent] = yield onceSpread(PerformanceView, EVENTS.UI_BUFFER_UPDATED);
|
||||
[,percent] = yield onceSpread(PerformanceView, EVENTS.UI_BUFFER_STATUS_UPDATED);
|
||||
}
|
||||
|
||||
ok(percent < Math.floor(bufferUsage * 100), "percentage updated for newly selected recording");
|
||||
@ -59,7 +62,7 @@ function* spawnTest() {
|
||||
|
||||
percent = 0;
|
||||
while (percent <= (Math.floor(bufferUsage * 100))) {
|
||||
[,percent] = yield onceSpread(PerformanceView, EVENTS.UI_BUFFER_UPDATED);
|
||||
[,percent] = yield onceSpread(PerformanceView, EVENTS.UI_BUFFER_STATUS_UPDATED);
|
||||
}
|
||||
ok(percent > Math.floor(bufferUsage * 100), "percentage increased for original recording");
|
||||
is($("#details-pane-container").getAttribute("buffer-status"), "in-progress",
|
||||
|
@ -6,22 +6,24 @@
|
||||
* a class is assigned to the recording notices.
|
||||
*/
|
||||
function* spawnTest() {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL, void 0, { TEST_MOCK_PROFILER_CHECK_TIMER: 10 });
|
||||
let { EVENTS, $, PerformanceController, PerformanceView } = panel.panelWin;
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { gFront, EVENTS, $, PerformanceController, PerformanceView } = panel.panelWin;
|
||||
|
||||
// Make sure the profiler module is stopped so we can set a new buffer limit
|
||||
loadFrameScripts();
|
||||
PMM_loadProfilerScripts(gBrowser);
|
||||
yield PMM_stopProfiler();
|
||||
Services.prefs.setIntPref(PROFILER_BUFFER_SIZE_PREF, 1000);
|
||||
// Set a fast profiler-status update interval
|
||||
yield gFront.setProfilerStatusInterval(10);
|
||||
|
||||
yield startRecording(panel);
|
||||
|
||||
let percent = 0;
|
||||
while (percent < 100) {
|
||||
[,percent] = yield onceSpread(PerformanceView, EVENTS.UI_BUFFER_UPDATED);
|
||||
[,percent] = yield onceSpread(PerformanceView, EVENTS.UI_BUFFER_STATUS_UPDATED);
|
||||
}
|
||||
|
||||
let bufferUsage = PerformanceController.getCurrentRecording().getBufferUsage();
|
||||
let bufferUsage = PerformanceController.getBufferUsageForRecording(PerformanceController.getCurrentRecording());
|
||||
ok(bufferUsage, 1, "Buffer is full for this recording");
|
||||
ok($("#details-pane-container").getAttribute("buffer-status"), "full",
|
||||
"container has [buffer-status=full]");
|
||||
|
@ -6,8 +6,11 @@
|
||||
* a class is assigned to the recording notices.
|
||||
*/
|
||||
function* spawnTest() {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL, void 0, { TEST_MOCK_PROFILER_CHECK_TIMER: 10 });
|
||||
let { EVENTS, $, PerformanceController, PerformanceView } = panel.panelWin;
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { gFront, EVENTS, $, PerformanceController, PerformanceView } = panel.panelWin;
|
||||
|
||||
// Set a fast profiler-status update interval
|
||||
yield gFront.setProfilerStatusInterval(10);
|
||||
|
||||
let supported = false;
|
||||
let enabled = false;
|
||||
|
@ -1,38 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the shared PerformanceFront is only opened once.
|
||||
*/
|
||||
|
||||
let gProfilerConnectionsOpened = 0;
|
||||
Services.obs.addObserver(profilerConnectionObserver, "performance-tools-connection-opened", false);
|
||||
|
||||
function* spawnTest() {
|
||||
let { target, panel } = yield initPerformance(SIMPLE_URL);
|
||||
|
||||
is(gProfilerConnectionsOpened, 1,
|
||||
"Only one profiler connection was opened.");
|
||||
|
||||
let front = getPerformanceFront(target);
|
||||
|
||||
ok(front, "A front for the current toolbox was retrieved.");
|
||||
is(panel.panelWin.gFront, front, "The same front is used by the panel's front");
|
||||
|
||||
yield front.open();
|
||||
|
||||
is(gProfilerConnectionsOpened, 1,
|
||||
"No additional profiler connections were opened.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
||||
|
||||
function profilerConnectionObserver(subject, topic, data) {
|
||||
is(topic, "performance-tools-connection-opened", "The correct topic was observed.");
|
||||
gProfilerConnectionsOpened++;
|
||||
}
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
Services.obs.removeObserver(profilerConnectionObserver, "performance-tools-connection-opened");
|
||||
});
|
@ -1,31 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the shared PerformanceFront can properly send requests.
|
||||
*/
|
||||
|
||||
function* spawnTest() {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let front = panel.panelWin.gFront;
|
||||
|
||||
loadFrameScripts();
|
||||
|
||||
ok(!(yield PMM_isProfilerActive()),
|
||||
"The built-in profiler module should not have been automatically started.");
|
||||
|
||||
let result = yield front._request("profiler", "startProfiler");
|
||||
is(result.started, true,
|
||||
"The request finished successfully and the profiler should've been started.");
|
||||
ok((yield PMM_isProfilerActive()),
|
||||
"The built-in profiler module should now be active.");
|
||||
|
||||
result = yield front._request("profiler", "stopProfiler");
|
||||
is(result.started, false,
|
||||
"The request finished successfully and the profiler should've been stopped.");
|
||||
ok(!(yield PMM_isProfilerActive()),
|
||||
"The built-in profiler module should now be inactive.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
@ -11,7 +11,7 @@ function* spawnTest() {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, PerformanceController } = panel.panelWin;
|
||||
let front = panel.panelWin.gFront;
|
||||
loadFrameScripts();
|
||||
PMM_loadProfilerScripts(gBrowser);
|
||||
|
||||
ok(!(yield PMM_isProfilerActive()),
|
||||
"The built-in profiler module should not have been automatically started.");
|
||||
|
@ -5,8 +5,6 @@
|
||||
* Tests if the performance tool can import profiler data from the
|
||||
* original profiler tool (Performance Recording v1, and Profiler data v2) and the correct views and graphs are loaded.
|
||||
*/
|
||||
let RecordingUtils = require("devtools/performance/recording-utils");
|
||||
|
||||
let TICKS_DATA = (function () {
|
||||
let ticks = [];
|
||||
for (let i = 0; i < 100; i++) {
|
||||
|
@ -6,8 +6,6 @@
|
||||
* and requires deflating, and has an extra thread that's a string. Not sure
|
||||
* what causes this.
|
||||
*/
|
||||
let RecordingUtils = require("devtools/performance/recording-utils");
|
||||
|
||||
let STRINGED_THREAD = (function () {
|
||||
let thread = {};
|
||||
|
||||
|
@ -6,8 +6,7 @@
|
||||
* icon is next to the frame with optimizations
|
||||
*/
|
||||
|
||||
const RecordingUtils = require("devtools/performance/recording-utils");
|
||||
const { CATEGORY_MASK } = require("devtools/performance/global");
|
||||
let { CATEGORY_MASK } = require("devtools/performance/global");
|
||||
|
||||
function* spawnTest() {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
|
@ -15,8 +15,11 @@ const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
|
||||
let { DebuggerServer } = require("devtools/server/main");
|
||||
let { console } = require("resource://gre/modules/devtools/Console.jsm");
|
||||
let { merge } = require("sdk/util/object");
|
||||
let { generateUUID } = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
||||
let { getPerformanceFront, PerformanceFront } = require("devtools/performance/front");
|
||||
let { createPerformanceFront } = require("devtools/server/actors/performance");
|
||||
let RecordingUtils = require("devtools/toolkit/performance/utils");
|
||||
let {
|
||||
PMM_loadProfilerScripts, PMM_isProfilerActive, PMM_stopProfiler, sendProfilerCommand
|
||||
} = require("devtools/toolkit/shared/profiler");
|
||||
|
||||
let mm = null;
|
||||
|
||||
@ -188,17 +191,16 @@ function initBackend(aUrl, targetOps={}) {
|
||||
|
||||
yield target.makeRemote();
|
||||
|
||||
// Attach addition options to `target`. This is used to force mock fronts
|
||||
// Attach addition options to `client`. This is used to force mock fronts
|
||||
// to smokescreen test different servers where memory or timeline actors
|
||||
// may not exist. Possible options that will actually work:
|
||||
// TEST_MOCK_MEMORY_ACTOR = true
|
||||
// TEST_PERFORMANCE_LEGACY_FRONT = true
|
||||
// TEST_MOCK_TIMELINE_ACTOR = true
|
||||
// TEST_MOCK_PROFILER_CHECK_TIMER = number
|
||||
// TEST_PROFILER_FILTER_STATUS = array
|
||||
merge(target, targetOps);
|
||||
|
||||
let front = getPerformanceFront(target);
|
||||
yield front.open();
|
||||
let front = createPerformanceFront(target);
|
||||
yield front.connect();
|
||||
return { target, front };
|
||||
});
|
||||
}
|
||||
@ -212,12 +214,11 @@ function initPerformance(aUrl, tool="performance", targetOps={}) {
|
||||
|
||||
yield target.makeRemote();
|
||||
|
||||
// Attach addition options to `target`. This is used to force mock fronts
|
||||
// Attach addition options to `client`. This is used to force mock fronts
|
||||
// to smokescreen test different servers where memory or timeline actors
|
||||
// may not exist. Possible options that will actually work:
|
||||
// TEST_MOCK_MEMORY_ACTOR = true
|
||||
// TEST_PERFORMANCE_LEGACY_FRONT = true
|
||||
// TEST_MOCK_TIMELINE_ACTOR = true
|
||||
// TEST_MOCK_PROFILER_CHECK_TIMER = number
|
||||
// TEST_PROFILER_FILTER_STATUS = array
|
||||
merge(target, targetOps);
|
||||
|
||||
@ -231,9 +232,9 @@ function initPerformance(aUrl, tool="performance", targetOps={}) {
|
||||
* Initializes a webconsole panel. Returns a target, panel and toolbox reference.
|
||||
* Also returns a console property that allows calls to `profile` and `profileEnd`.
|
||||
*/
|
||||
function initConsole(aUrl) {
|
||||
function initConsole(aUrl, options) {
|
||||
return Task.spawn(function*() {
|
||||
let { target, toolbox, panel } = yield initPerformance(aUrl, "webconsole");
|
||||
let { target, toolbox, panel } = yield initPerformance(aUrl, "webconsole", options);
|
||||
let { hud } = panel;
|
||||
return {
|
||||
target, toolbox, panel, console: {
|
||||
@ -265,13 +266,6 @@ function consoleExecute (console, method, val) {
|
||||
return promise;
|
||||
}
|
||||
|
||||
function waitForProfilerConnection() {
|
||||
let { promise, resolve } = Promise.defer();
|
||||
Services.obs.addObserver(resolve, "performance-tools-connection-opened", false);
|
||||
return promise.then(() =>
|
||||
Services.obs.removeObserver(resolve, "performance-tools-connection-opened"));
|
||||
}
|
||||
|
||||
function* teardown(panel) {
|
||||
info("Destroying the performance tool.");
|
||||
|
||||
@ -337,9 +331,12 @@ function* startRecording(panel, options = {
|
||||
}) {
|
||||
let win = panel.panelWin;
|
||||
let clicked = panel.panelWin.PerformanceView.once(win.EVENTS.UI_START_RECORDING);
|
||||
let willStart = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_WILL_START);
|
||||
let hasStarted = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_STARTED);
|
||||
let button = win.$("#main-record-button");
|
||||
let stateChanged = options.waitForStateChanged
|
||||
? once(win.PerformanceView, win.EVENTS.UI_STATE_CHANGED)
|
||||
: Promise.resolve();
|
||||
|
||||
|
||||
ok(!button.hasAttribute("checked"),
|
||||
"The record button should not be checked yet.");
|
||||
@ -349,25 +346,17 @@ function* startRecording(panel, options = {
|
||||
click(win, button);
|
||||
yield clicked;
|
||||
|
||||
yield willStart;
|
||||
|
||||
ok(button.hasAttribute("checked"),
|
||||
"The record button should now be checked.");
|
||||
ok(button.hasAttribute("locked"),
|
||||
"The record button should be locked.");
|
||||
|
||||
let stateChanged = options.waitForStateChanged
|
||||
? once(win.PerformanceView, win.EVENTS.UI_STATE_CHANGED)
|
||||
: Promise.resolve();
|
||||
|
||||
yield hasStarted;
|
||||
|
||||
let overviewRendered = options.waitForOverview
|
||||
yield options.waitForOverview
|
||||
? once(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED)
|
||||
: Promise.resolve();
|
||||
|
||||
yield stateChanged;
|
||||
yield overviewRendered;
|
||||
|
||||
is(win.PerformanceView.getState(), "recording",
|
||||
"The current state is 'recording'.");
|
||||
@ -386,6 +375,7 @@ function* stopRecording(panel, options = {
|
||||
let clicked = panel.panelWin.PerformanceView.once(win.EVENTS.UI_STOP_RECORDING);
|
||||
let willStop = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_WILL_STOP);
|
||||
let hasStopped = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_STOPPED);
|
||||
let controllerStopped = panel.panelWin.PerformanceController.once(win.EVENTS.CONTROLLER_STOPPED_RECORDING);
|
||||
let button = win.$("#main-record-button");
|
||||
let overviewRendered = null;
|
||||
|
||||
@ -427,6 +417,8 @@ function* stopRecording(panel, options = {
|
||||
"The record button should not be checked.");
|
||||
ok(!button.hasAttribute("locked"),
|
||||
"The record button should not be locked.");
|
||||
|
||||
yield controllerStopped;
|
||||
}
|
||||
|
||||
function waitForWidgetsRendered(panel) {
|
||||
@ -546,8 +538,6 @@ function getInflatedStackLocations(thread, sample) {
|
||||
* Synthesize a profile for testing.
|
||||
*/
|
||||
function synthesizeProfileForTest(samples) {
|
||||
const RecordingUtils = require("devtools/performance/recording-utils");
|
||||
|
||||
samples.unshift({
|
||||
time: 0,
|
||||
frames: [
|
||||
@ -561,39 +551,3 @@ function synthesizeProfileForTest(samples) {
|
||||
markers: []
|
||||
}, uniqueStacks);
|
||||
}
|
||||
|
||||
function PMM_isProfilerActive () {
|
||||
return sendProfilerCommand("IsActive");
|
||||
}
|
||||
|
||||
function PMM_stopProfiler () {
|
||||
return Task.spawn(function*() {
|
||||
let isActive = (yield sendProfilerCommand("IsActive")).isActive;
|
||||
if (isActive) {
|
||||
return sendProfilerCommand("StopProfiler");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sendProfilerCommand (method, args=[]) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
if (!mm) {
|
||||
throw new Error("`loadFrameScripts()` must be called when using MessageManager.");
|
||||
}
|
||||
|
||||
let id = generateUUID().toString();
|
||||
mm.addMessageListener("devtools:test:profiler:response", handler);
|
||||
mm.sendAsyncMessage("devtools:test:profiler", { method, args, id });
|
||||
|
||||
function handler ({ data }) {
|
||||
if (id !== data.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
mm.removeMessageListener("devtools:test:profiler:response", handler);
|
||||
deferred.resolve(data.data);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
let { require } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
let { console } = require("resource://gre/modules/devtools/Console.jsm");
|
||||
const RecordingUtils = require("devtools/performance/recording-utils");
|
||||
const RecordingUtils = require("devtools/toolkit/performance/utils");
|
||||
|
||||
const PLATFORM_DATA_PREF = "devtools.performance.ui.show-platform-data";
|
||||
|
||||
|
@ -16,7 +16,7 @@ let DetailsSubview = {
|
||||
this._onDetailsViewSelected = this._onDetailsViewSelected.bind(this);
|
||||
this._onPrefChanged = this._onPrefChanged.bind(this);
|
||||
|
||||
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
|
||||
PerformanceController.on(EVENTS.RECORDING_STATE_CHANGE, this._onRecordingStoppedOrSelected);
|
||||
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
|
||||
PerformanceController.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
|
||||
OverviewView.on(EVENTS.OVERVIEW_RANGE_SELECTED, this._onOverviewRangeChange);
|
||||
@ -29,7 +29,7 @@ let DetailsSubview = {
|
||||
destroy: function () {
|
||||
clearNamedTimeout("range-change-debounce");
|
||||
|
||||
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
|
||||
PerformanceController.off(EVENTS.RECORDING_STATE_CHANGE, this._onRecordingStoppedOrSelected);
|
||||
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
|
||||
PerformanceController.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
|
||||
OverviewView.off(EVENTS.OVERVIEW_RANGE_SELECTED, this._onOverviewRangeChange);
|
||||
@ -85,7 +85,14 @@ let DetailsSubview = {
|
||||
/**
|
||||
* Called when recording stops or is selected.
|
||||
*/
|
||||
_onRecordingStoppedOrSelected: function(_, recording) {
|
||||
_onRecordingStoppedOrSelected: function(_, state, recording) {
|
||||
if (typeof state !== "string") {
|
||||
recording = state;
|
||||
}
|
||||
if (arguments.length === 3 && state !== "recording-stopped") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!recording || !recording.isCompleted()) {
|
||||
return;
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ let DetailsView = {
|
||||
|
||||
yield this.setAvailableViews();
|
||||
|
||||
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
|
||||
PerformanceController.on(EVENTS.RECORDING_STATE_CHANGE, this._onRecordingStoppedOrSelected);
|
||||
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
|
||||
PerformanceController.on(EVENTS.PREF_CHANGED, this.setAvailableViews);
|
||||
}),
|
||||
@ -77,7 +77,7 @@ let DetailsView = {
|
||||
component.initialized && (yield component.view.destroy());
|
||||
}
|
||||
|
||||
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
|
||||
PerformanceController.off(EVENTS.RECORDING_STATE_CHANGE, this._onRecordingStoppedOrSelected);
|
||||
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
|
||||
PerformanceController.off(EVENTS.PREF_CHANGED, this.setAvailableViews);
|
||||
}),
|
||||
@ -248,7 +248,10 @@ let DetailsView = {
|
||||
/**
|
||||
* Called when recording stops or is selected.
|
||||
*/
|
||||
_onRecordingStoppedOrSelected: function(_, recording) {
|
||||
_onRecordingStoppedOrSelected: function(_, state, recording) {
|
||||
if (typeof state === "string" && state !== "recording-stopped") {
|
||||
return;
|
||||
}
|
||||
this.setAvailableViews();
|
||||
},
|
||||
|
||||
|
@ -44,7 +44,7 @@ let OverviewView = {
|
||||
});
|
||||
|
||||
// If no timeline support, shut it all down.
|
||||
if (!gFront.getActorSupport().timeline) {
|
||||
if (!PerformanceController.getTraits().features.withMarkers) {
|
||||
this.disable();
|
||||
return;
|
||||
}
|
||||
@ -52,10 +52,7 @@ let OverviewView = {
|
||||
// Store info on multiprocess support.
|
||||
this._multiprocessData = PerformanceController.getMultiprocessStatus();
|
||||
|
||||
this._onRecordingWillStart = this._onRecordingWillStart.bind(this);
|
||||
this._onRecordingStarted = this._onRecordingStarted.bind(this);
|
||||
this._onRecordingWillStop = this._onRecordingWillStop.bind(this);
|
||||
this._onRecordingStopped = this._onRecordingStopped.bind(this);
|
||||
this._onRecordingStateChange = this._onRecordingStateChange.bind(this);
|
||||
this._onRecordingSelected = this._onRecordingSelected.bind(this);
|
||||
this._onRecordingTick = this._onRecordingTick.bind(this);
|
||||
this._onGraphSelecting = this._onGraphSelecting.bind(this);
|
||||
@ -67,10 +64,7 @@ let OverviewView = {
|
||||
// based off of prefs.
|
||||
PerformanceController.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
|
||||
PerformanceController.on(EVENTS.THEME_CHANGED, this._onThemeChanged);
|
||||
PerformanceController.on(EVENTS.RECORDING_WILL_START, this._onRecordingWillStart);
|
||||
PerformanceController.on(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
|
||||
PerformanceController.on(EVENTS.RECORDING_WILL_STOP, this._onRecordingWillStop);
|
||||
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
|
||||
PerformanceController.on(EVENTS.RECORDING_STATE_CHANGE, this._onRecordingStateChange);
|
||||
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
|
||||
this.graphs.on("selecting", this._onGraphSelecting);
|
||||
this.graphs.on("rendered", this._onGraphRendered);
|
||||
@ -82,10 +76,7 @@ let OverviewView = {
|
||||
destroy: Task.async(function*() {
|
||||
PerformanceController.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
|
||||
PerformanceController.off(EVENTS.THEME_CHANGED, this._onThemeChanged);
|
||||
PerformanceController.off(EVENTS.RECORDING_WILL_START, this._onRecordingWillStart);
|
||||
PerformanceController.off(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
|
||||
PerformanceController.off(EVENTS.RECORDING_WILL_STOP, this._onRecordingWillStop);
|
||||
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
|
||||
PerformanceController.off(EVENTS.RECORDING_STATE_CHANGE, this._onRecordingStateChange);
|
||||
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
|
||||
this.graphs.off("selecting", this._onGraphSelecting);
|
||||
this.graphs.off("rendered", this._onGraphRendered);
|
||||
@ -211,29 +202,12 @@ let OverviewView = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when recording will start. No recording because it does not
|
||||
* exist yet, but can just disable from here. This will only trigger for
|
||||
* manual recordings.
|
||||
* Called when recording state changes.
|
||||
*/
|
||||
_onRecordingWillStart: OverviewViewOnStateChange(Task.async(function* () {
|
||||
yield this._checkSelection();
|
||||
this.graphs.dropSelection();
|
||||
})),
|
||||
|
||||
/**
|
||||
* Called when recording actually starts.
|
||||
*/
|
||||
_onRecordingStarted: OverviewViewOnStateChange(),
|
||||
|
||||
/**
|
||||
* Called when recording will stop.
|
||||
*/
|
||||
_onRecordingWillStop: OverviewViewOnStateChange(),
|
||||
|
||||
/**
|
||||
* Called when recording actually stops.
|
||||
*/
|
||||
_onRecordingStopped: OverviewViewOnStateChange(Task.async(function* (_, recording) {
|
||||
_onRecordingStateChange: OverviewViewOnStateChange(Task.async(function* (_, state, recording) {
|
||||
if (state !== "recording-stopped") {
|
||||
return;
|
||||
}
|
||||
// Check to see if the recording that just stopped is the current recording.
|
||||
// If it is, render the high-res graphs. For manual recordings, it will also
|
||||
// be the current recording, but profiles generated by `console.profile` can stop
|
||||
@ -394,6 +368,12 @@ let OverviewView = {
|
||||
*/
|
||||
function OverviewViewOnStateChange (fn) {
|
||||
return function _onRecordingStateChange (eventName, recording) {
|
||||
// Normalize arguments for the RECORDING_STATE_CHANGE event,
|
||||
// as it also has a `state` argument.
|
||||
if (typeof recording === "string") {
|
||||
recording = arguments[2];
|
||||
}
|
||||
|
||||
let currentRecording = PerformanceController.getCurrentRecording();
|
||||
|
||||
// All these methods require a recording to exist selected and
|
||||
@ -426,6 +406,7 @@ function OverviewViewOnStateChange (fn) {
|
||||
} else if (currentRecording.isRecording() && !this.isRendering()) {
|
||||
this._startPolling();
|
||||
}
|
||||
|
||||
if (fn) {
|
||||
fn.apply(this, arguments);
|
||||
}
|
||||
|
@ -14,19 +14,15 @@ let RecordingsView = Heritage.extend(WidgetMethods, {
|
||||
this.widget = new SideMenuWidget($("#recordings-list"));
|
||||
|
||||
this._onSelect = this._onSelect.bind(this);
|
||||
this._onRecordingStarted = this._onRecordingStarted.bind(this);
|
||||
this._onRecordingStopped = this._onRecordingStopped.bind(this);
|
||||
this._onRecordingWillStop = this._onRecordingWillStop.bind(this);
|
||||
this._onRecordingImported = this._onRecordingImported.bind(this);
|
||||
this._onRecordingStateChange = this._onRecordingStateChange.bind(this);
|
||||
this._onNewRecording = this._onNewRecording.bind(this);
|
||||
this._onSaveButtonClick = this._onSaveButtonClick.bind(this);
|
||||
this._onRecordingsCleared = this._onRecordingsCleared.bind(this);
|
||||
|
||||
this.emptyText = L10N.getStr("noRecordingsText");
|
||||
|
||||
PerformanceController.on(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
|
||||
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
|
||||
PerformanceController.on(EVENTS.RECORDING_WILL_STOP, this._onRecordingWillStop);
|
||||
PerformanceController.on(EVENTS.RECORDING_IMPORTED, this._onRecordingImported);
|
||||
PerformanceController.on(EVENTS.RECORDING_STATE_CHANGE, this._onRecordingStateChange);
|
||||
PerformanceController.on(EVENTS.NEW_RECORDING, this._onNewRecording);
|
||||
PerformanceController.on(EVENTS.RECORDINGS_CLEARED, this._onRecordingsCleared);
|
||||
this.widget.addEventListener("select", this._onSelect, false);
|
||||
},
|
||||
@ -35,10 +31,8 @@ let RecordingsView = Heritage.extend(WidgetMethods, {
|
||||
* Destruction function, called when the tool is closed.
|
||||
*/
|
||||
destroy: function() {
|
||||
PerformanceController.off(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
|
||||
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
|
||||
PerformanceController.off(EVENTS.RECORDING_WILL_STOP, this._onRecordingWillStop);
|
||||
PerformanceController.off(EVENTS.RECORDING_IMPORTED, this._onRecordingImported);
|
||||
PerformanceController.off(EVENTS.RECORDING_STATE_CHANGE, this._onRecordingStateChange);
|
||||
PerformanceController.off(EVENTS.NEW_RECORDING, this._onNewRecording);
|
||||
PerformanceController.off(EVENTS.RECORDINGS_CLEARED, this._onRecordingsCleared);
|
||||
this.widget.removeEventListener("select", this._onSelect, false);
|
||||
},
|
||||
@ -92,81 +86,59 @@ let RecordingsView = Heritage.extend(WidgetMethods, {
|
||||
},
|
||||
|
||||
/**
|
||||
* Signals that a recording session has started.
|
||||
* Called when a new recording is stored in the UI. This handles
|
||||
* when recordings are lazily loaded (like a console.profile occurring
|
||||
* before the tool is loaded) or imported. In normal manual recording cases,
|
||||
* this will also be fired.
|
||||
*/
|
||||
_onNewRecording: function (_, recording) {
|
||||
this._onRecordingStateChange(_, null, recording);
|
||||
},
|
||||
|
||||
/**
|
||||
* Signals that a recording has changed state.
|
||||
*
|
||||
* @param string state
|
||||
* Can be "recording-started", "recording-stopped", "recording-stopping"
|
||||
* @param RecordingModel recording
|
||||
* Model of the recording that was started.
|
||||
*/
|
||||
_onRecordingStarted: function (_, recording) {
|
||||
// TODO bug 1144388
|
||||
// If a label is identical to an existing recording item,
|
||||
// logically group them here.
|
||||
// For now, insert a "dummy" recording item, to hint that recording has now started.
|
||||
let recordingItem = this.addEmptyRecording(recording);
|
||||
_onRecordingStateChange: function (_, state, recording) {
|
||||
let recordingItem = this.getItemForPredicate(e => e.attachment === recording);
|
||||
if (!recordingItem) {
|
||||
recordingItem = this.addEmptyRecording(recording);
|
||||
|
||||
// Mark the corresponding item as being a "record in progress".
|
||||
recordingItem.isRecording = true;
|
||||
// If this is a manual recording, immediately select it, or
|
||||
// select a console profile if its the only one
|
||||
if (!recording.isConsole() || this.selectedIndex === -1) {
|
||||
this.selectedItem = recordingItem;
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a manual recording, immediately select it, or
|
||||
// select a console profile if its the only one
|
||||
if (!recording.isConsole() || this.selectedIndex === -1) {
|
||||
recordingItem.isRecording = recording.isRecording();
|
||||
|
||||
// This recording is in the process of stopping.
|
||||
if (!recording.isRecording() && !recording.isCompleted()) {
|
||||
// Mark the corresponding item as loading.
|
||||
let durationNode = $(".recording-item-duration", recordingItem.target);
|
||||
durationNode.setAttribute("value", L10N.getStr("recordingsList.loadingLabel"));
|
||||
}
|
||||
|
||||
// Render the recording item with finalized information (timing, etc)
|
||||
if (recording.isCompleted() && !recordingItem.finalized) {
|
||||
this.finalizeRecording(recordingItem);
|
||||
// Select the recording if it was a manual recording only
|
||||
if (!recording.isConsole()) {
|
||||
this.forceSelect(recordingItem);
|
||||
}
|
||||
}
|
||||
|
||||
// Auto select imported items.
|
||||
if (recording.isImported()) {
|
||||
this.selectedItem = recordingItem;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Signals that a recording session has ended.
|
||||
*
|
||||
* @param RecordingModel recording
|
||||
* The model of the recording that just stopped.
|
||||
*/
|
||||
_onRecordingStopped: function (_, recording) {
|
||||
let recordingItem = this.getItemForPredicate(e => e.attachment === recording);
|
||||
|
||||
// Mark the corresponding item as being a "finished recording".
|
||||
recordingItem.isRecording = false;
|
||||
|
||||
// Render the recording item with finalized information (timing, etc)
|
||||
this.finalizeRecording(recordingItem);
|
||||
|
||||
// Select the recording if it was a manual recording only
|
||||
if (!recording.isConsole()) {
|
||||
this.forceSelect(recordingItem);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Signals that a recording session is ending, and hasn't finished being
|
||||
* processed yet.
|
||||
*
|
||||
* @param RecordingModel recording
|
||||
* The model of the recording that is being stopped.
|
||||
*/
|
||||
_onRecordingWillStop: function(_, recording) {
|
||||
let recordingItem = this.getItemForPredicate(e => e.attachment === recording);
|
||||
|
||||
// Mark the corresponding item as loading.
|
||||
let durationNode = $(".recording-item-duration", recordingItem.target);
|
||||
durationNode.setAttribute("value", L10N.getStr("recordingsList.loadingLabel"));
|
||||
},
|
||||
|
||||
/**
|
||||
* Signals that a recording has been imported.
|
||||
*
|
||||
* @param RecordingModel model
|
||||
* The recording model containing data on the recording session.
|
||||
*/
|
||||
_onRecordingImported: function (_, model) {
|
||||
let recordingItem = this.addEmptyRecording(model);
|
||||
recordingItem.isRecording = false;
|
||||
|
||||
// Immediately select the imported recording
|
||||
this.selectedItem = recordingItem;
|
||||
|
||||
// Render the recording item with finalized information (timing, etc)
|
||||
this.finalizeRecording(recordingItem);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears out all recordings.
|
||||
*/
|
||||
@ -182,6 +154,7 @@ let RecordingsView = Heritage.extend(WidgetMethods, {
|
||||
*/
|
||||
finalizeRecording: function (recordingItem) {
|
||||
let model = recordingItem.attachment;
|
||||
recordingItem.finalized = true;
|
||||
|
||||
let saveNode = $(".recording-item-save", recordingItem.target);
|
||||
saveNode.setAttribute("value",
|
||||
|
@ -251,7 +251,7 @@ function* openAndCloseToolbox(nbOfTimes, usageTime, toolId) {
|
||||
* Synthesize a profile for testing.
|
||||
*/
|
||||
function synthesizeProfileForTest(samples) {
|
||||
const RecordingUtils = require("devtools/performance/recording-utils");
|
||||
const RecordingUtils = require("devtools/toolkit/performance/utils");
|
||||
|
||||
samples.unshift({
|
||||
time: 0,
|
||||
|
@ -11,6 +11,7 @@ DIRS += [
|
||||
'discovery',
|
||||
'gcli',
|
||||
'jsbeautify',
|
||||
'performance',
|
||||
'pretty-fast',
|
||||
'qrcode',
|
||||
'security',
|
||||
|
164
toolkit/devtools/performance/io.js
Normal file
164
toolkit/devtools/performance/io.js
Normal file
@ -0,0 +1,164 @@
|
||||
/* 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 { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
|
||||
loader.lazyRequireGetter(this, "Services");
|
||||
loader.lazyRequireGetter(this, "promise");
|
||||
loader.lazyRequireGetter(this, "RecordingUtils",
|
||||
"devtools/toolkit/performance/utils");
|
||||
|
||||
loader.lazyImporter(this, "FileUtils",
|
||||
"resource://gre/modules/FileUtils.jsm");
|
||||
loader.lazyImporter(this, "NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
// 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 PERF_TOOL_SERIALIZER_IDENTIFIER = "Recorded Performance Data";
|
||||
const PERF_TOOL_SERIALIZER_LEGACY_VERSION = 1;
|
||||
const PERF_TOOL_SERIALIZER_CURRENT_VERSION = 2;
|
||||
|
||||
/**
|
||||
* Helpers for importing/exporting JSON.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets a nsIScriptableUnicodeConverter instance with a default UTF-8 charset.
|
||||
* @return object
|
||||
*/
|
||||
function getUnicodeConverter () {
|
||||
let className = "@mozilla.org/intl/scriptableunicodeconverter";
|
||||
let converter = Cc[className].createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
return converter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a recording as JSON to a file. The provided data is assumed to be
|
||||
* acyclical, so that it can be properly serialized.
|
||||
*
|
||||
* @param object recordingData
|
||||
* The recording data to stream as JSON.
|
||||
* @param nsILocalFile file
|
||||
* The file to stream the data into.
|
||||
* @return object
|
||||
* A promise that is resolved once streaming finishes, or rejected
|
||||
* if there was an error.
|
||||
*/
|
||||
function saveRecordingToFile (recordingData, file) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
recordingData.fileType = PERF_TOOL_SERIALIZER_IDENTIFIER;
|
||||
recordingData.version = PERF_TOOL_SERIALIZER_CURRENT_VERSION;
|
||||
|
||||
let string = JSON.stringify(recordingData);
|
||||
let inputStream = this.getUnicodeConverter().convertToInputStream(string);
|
||||
let outputStream = FileUtils.openSafeFileOutputStream(file);
|
||||
|
||||
NetUtil.asyncCopy(inputStream, outputStream, deferred.resolve);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a recording stored as JSON from a file.
|
||||
*
|
||||
* @param nsILocalFile file
|
||||
* The file to import the data from.
|
||||
* @return object
|
||||
* A promise that is resolved once importing finishes, or rejected
|
||||
* if there was an error.
|
||||
*/
|
||||
function loadRecordingFromFile (file) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
let channel = NetUtil.newChannel({
|
||||
uri: NetUtil.newURI(file),
|
||||
loadUsingSystemPrincipal: true});
|
||||
|
||||
channel.contentType = "text/plain";
|
||||
|
||||
NetUtil.asyncFetch(channel, (inputStream, status) => {
|
||||
try {
|
||||
let string = NetUtil.readInputStreamToString(inputStream, inputStream.available());
|
||||
var recordingData = JSON.parse(string);
|
||||
} catch (e) {
|
||||
deferred.reject(new Error("Could not read recording data file."));
|
||||
return;
|
||||
}
|
||||
if (recordingData.fileType != PERF_TOOL_SERIALIZER_IDENTIFIER) {
|
||||
deferred.reject(new Error("Unrecognized recording data file."));
|
||||
return;
|
||||
}
|
||||
if (!isValidSerializerVersion(recordingData.version)) {
|
||||
deferred.reject(new Error("Unsupported recording data file version."));
|
||||
return;
|
||||
}
|
||||
if (recordingData.version === PERF_TOOL_SERIALIZER_LEGACY_VERSION) {
|
||||
recordingData = convertLegacyData(recordingData);
|
||||
}
|
||||
if (recordingData.profile.meta.version === 2) {
|
||||
RecordingUtils.deflateProfile(recordingData.profile);
|
||||
}
|
||||
deferred.resolve(recordingData);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a boolean indicating whether or not the passed in `version`
|
||||
* is supported by this serializer.
|
||||
*
|
||||
* @param number version
|
||||
* @return boolean
|
||||
*/
|
||||
function isValidSerializerVersion (version) {
|
||||
return !!~[
|
||||
PERF_TOOL_SERIALIZER_LEGACY_VERSION,
|
||||
PERF_TOOL_SERIALIZER_CURRENT_VERSION
|
||||
].indexOf(version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes recording data (with version `1`, from the original profiler tool), and
|
||||
* massages the data to be line with the current performance tool's property names
|
||||
* and values.
|
||||
*
|
||||
* @param object legacyData
|
||||
* @return object
|
||||
*/
|
||||
function convertLegacyData (legacyData) {
|
||||
let { profilerData, ticksData, recordingDuration } = legacyData;
|
||||
|
||||
// The `profilerData` and `ticksData` stay, but the previously unrecorded
|
||||
// fields just are empty arrays or objects.
|
||||
let data = {
|
||||
label: profilerData.profilerLabel,
|
||||
duration: recordingDuration,
|
||||
markers: [],
|
||||
frames: [],
|
||||
memory: [],
|
||||
ticks: ticksData,
|
||||
allocations: { sites: [], timestamps: [], frames: [] },
|
||||
profile: profilerData.profile,
|
||||
// Fake a configuration object here if there's tick data,
|
||||
// so that it can be rendered
|
||||
configuration: {
|
||||
withTicks: !!ticksData.length,
|
||||
withMarkers: false,
|
||||
withMemory: false,
|
||||
withAllocations: false
|
||||
}
|
||||
};
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
exports.getUnicodeConverter = getUnicodeConverter;
|
||||
exports.saveRecordingToFile = saveRecordingToFile;
|
||||
exports.loadRecordingFromFile = loadRecordingFromFile;
|
@ -12,27 +12,17 @@ loader.lazyRequireGetter(this, "Poller",
|
||||
"devtools/shared/poller", true);
|
||||
|
||||
loader.lazyRequireGetter(this, "CompatUtils",
|
||||
"devtools/performance/compatibility");
|
||||
"devtools/toolkit/performance/legacy/compatibility");
|
||||
loader.lazyRequireGetter(this, "RecordingUtils",
|
||||
"devtools/performance/recording-utils");
|
||||
"devtools/toolkit/performance/utils");
|
||||
loader.lazyRequireGetter(this, "TimelineFront",
|
||||
"devtools/server/actors/timeline", true);
|
||||
loader.lazyRequireGetter(this, "MemoryFront",
|
||||
"devtools/server/actors/memory", true);
|
||||
loader.lazyRequireGetter(this, "ProfilerFront",
|
||||
"devtools/server/actors/profiler", true);
|
||||
|
||||
// how often do we pull allocation sites from the memory actor
|
||||
const ALLOCATION_SITE_POLL_TIMER = 200; // ms
|
||||
|
||||
// how often do we check the status of the profiler's circular buffer
|
||||
const PROFILER_CHECK_TIMER = 5000; // ms
|
||||
|
||||
const MEMORY_ACTOR_METHODS = [
|
||||
"attach", "detach", "getState", "getAllocationsSettings",
|
||||
"getAllocations", "startRecordingAllocations", "stopRecordingAllocations"
|
||||
];
|
||||
|
||||
const TIMELINE_ACTOR_METHODS = [
|
||||
"start", "stop",
|
||||
];
|
||||
@ -45,7 +35,7 @@ const PROFILER_ACTOR_METHODS = [
|
||||
/**
|
||||
* Constructor for a facade around an underlying ProfilerFront.
|
||||
*/
|
||||
function ProfilerFrontFacade (target) {
|
||||
function LegacyProfilerFront (target) {
|
||||
this._target = target;
|
||||
this._onProfilerEvent = this._onProfilerEvent.bind(this);
|
||||
this._checkProfilerStatus = this._checkProfilerStatus.bind(this);
|
||||
@ -54,7 +44,7 @@ function ProfilerFrontFacade (target) {
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
ProfilerFrontFacade.prototype = {
|
||||
LegacyProfilerFront.prototype = {
|
||||
EVENTS: ["console-api-profiler", "profiler-stopped"],
|
||||
|
||||
// Connects to the targets underlying real ProfilerFront.
|
||||
@ -70,7 +60,7 @@ ProfilerFrontFacade.prototype = {
|
||||
// Directly register to event notifications when connected
|
||||
// to hook into `console.profile|profileEnd` calls.
|
||||
yield this.registerEventNotifications({ events: this.EVENTS });
|
||||
this.EVENTS.forEach(e => this._front.on(e, this._onProfilerEvent));
|
||||
target.client.addListener("eventNotification", this._onProfilerEvent);
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -80,10 +70,8 @@ ProfilerFrontFacade.prototype = {
|
||||
if (this._poller) {
|
||||
yield this._poller.destroy();
|
||||
}
|
||||
|
||||
this.EVENTS.forEach(e => this._front.off(e, this._onProfilerEvent));
|
||||
yield this.unregisterEventNotifications({ events: this.EVENTS });
|
||||
yield this._front.destroy();
|
||||
this._target.client.removeListener("eventNotification", this._onProfilerEvent);
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -107,17 +95,9 @@ ProfilerFrontFacade.prototype = {
|
||||
// nsIPerformance module will be kept recording, because it's the same instance
|
||||
// for all targets and interacts with the whole platform, so we don't want
|
||||
// to affect other clients by stopping (or restarting) it.
|
||||
let status = yield this.getStatus();
|
||||
|
||||
// This should only occur during teardown
|
||||
if (!status) {
|
||||
return;
|
||||
}
|
||||
|
||||
let { isActive, currentTime, position, generation, totalSize } = status;
|
||||
let { isActive, currentTime, position, generation, totalSize } = yield this.getStatus();
|
||||
|
||||
if (isActive) {
|
||||
this.emit("profiler-already-active");
|
||||
return { startTime: currentTime, position, generation, totalSize };
|
||||
}
|
||||
|
||||
@ -130,11 +110,10 @@ ProfilerFrontFacade.prototype = {
|
||||
|
||||
let startInfo = yield this.startProfiler(profilerOptions);
|
||||
let startTime = 0;
|
||||
if ("currentTime" in startInfo) {
|
||||
if ('currentTime' in startInfo) {
|
||||
startTime = startInfo.currentTime;
|
||||
}
|
||||
|
||||
this.emit("profiler-activated");
|
||||
return { startTime, position, generation, totalSize };
|
||||
}),
|
||||
|
||||
@ -151,7 +130,7 @@ ProfilerFrontFacade.prototype = {
|
||||
* Wrapper around `profiler.isActive()` to take profiler status data and emit.
|
||||
*/
|
||||
getStatus: Task.async(function *() {
|
||||
let data = yield CompatUtils.callFrontMethod("isActive").call(this);
|
||||
let data = yield (CompatUtils.callFrontMethod("isActive").call(this));
|
||||
// If no data, the last poll for `isActive()` was wrapping up, and the target.client
|
||||
// is now null, so we no longer have data, so just abort here.
|
||||
if (!data) {
|
||||
@ -178,7 +157,7 @@ ProfilerFrontFacade.prototype = {
|
||||
* Returns profile data from now since `startTime`.
|
||||
*/
|
||||
getProfile: Task.async(function *(options) {
|
||||
let profilerData = yield CompatUtils.callFrontMethod("getProfile").call(this, options);
|
||||
let profilerData = yield (CompatUtils.callFrontMethod("getProfile").call(this, options));
|
||||
// If the backend is not deduped, dedupe it ourselves, as rest of the code
|
||||
// expects a deduped profile.
|
||||
if (profilerData.profile.meta.version === 2) {
|
||||
@ -200,14 +179,12 @@ ProfilerFrontFacade.prototype = {
|
||||
* @param object response
|
||||
* The data received from the backend.
|
||||
*/
|
||||
_onProfilerEvent: function (data) {
|
||||
let { subject, topic, details } = data;
|
||||
|
||||
_onProfilerEvent: function (_, { topic, subject, details }) {
|
||||
if (topic === "console-api-profiler") {
|
||||
if (subject.action === "profile") {
|
||||
this.emit("console-profile-start", details);
|
||||
} else if (subject.action === "profileEnd") {
|
||||
this.emit("console-profile-end", details);
|
||||
this.emit("console-profile-stop", details);
|
||||
}
|
||||
} else if (topic === "profiler-stopped") {
|
||||
this.emit("profiler-stopped");
|
||||
@ -219,19 +196,19 @@ ProfilerFrontFacade.prototype = {
|
||||
yield this.getStatus();
|
||||
}),
|
||||
|
||||
toString: () => "[object ProfilerFrontFacade]"
|
||||
toString: () => "[object LegacyProfilerFront]"
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor for a facade around an underlying TimelineFront.
|
||||
*/
|
||||
function TimelineFrontFacade (target) {
|
||||
function LegacyTimelineFront (target) {
|
||||
this._target = target;
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
TimelineFrontFacade.prototype = {
|
||||
EVENTS: ["markers", "frames", "memory", "ticks"],
|
||||
LegacyTimelineFront.prototype = {
|
||||
EVENTS: ["markers", "frames", "ticks"],
|
||||
|
||||
connect: Task.async(function*() {
|
||||
let supported = yield CompatUtils.timelineActorSupported(this._target);
|
||||
@ -259,131 +236,19 @@ TimelineFrontFacade.prototype = {
|
||||
}),
|
||||
|
||||
/**
|
||||
* An aggregate of all events (markers, frames, memory, ticks) and exposes
|
||||
* to PerformanceFront as a single event.
|
||||
* An aggregate of all events (markers, frames, ticks) and exposes
|
||||
* to PerformanceActorsConnection as a single event.
|
||||
*/
|
||||
_onTimelineData: function (type, ...data) {
|
||||
this.emit("timeline-data", type, ...data);
|
||||
},
|
||||
|
||||
toString: () => "[object TimelineFrontFacade]"
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor for a facade around an underlying ProfilerFront.
|
||||
*/
|
||||
function MemoryFrontFacade (target) {
|
||||
this._target = target;
|
||||
this._pullAllocationSites = this._pullAllocationSites.bind(this);
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
MemoryFrontFacade.prototype = {
|
||||
connect: Task.async(function*() {
|
||||
let supported = yield CompatUtils.memoryActorSupported(this._target);
|
||||
this._front = supported ?
|
||||
new MemoryFront(this._target.client, this._target.form) :
|
||||
new CompatUtils.MockMemoryFront();
|
||||
|
||||
this.IS_MOCK = !supported;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Disables polling and destroys actor.
|
||||
*/
|
||||
destroy: Task.async(function *() {
|
||||
if (this._poller) {
|
||||
yield this._poller.destroy();
|
||||
}
|
||||
yield this._front.destroy();
|
||||
}),
|
||||
|
||||
/**
|
||||
* Starts polling for allocation information.
|
||||
*/
|
||||
start: Task.async(function *(options) {
|
||||
if (!options.withAllocations) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
yield this.attach();
|
||||
|
||||
// Reconstruct our options because the server actor fails when being passed
|
||||
// undefined values in objects.
|
||||
let allocationOptions = {};
|
||||
if (options.allocationsSampleProbability !== void 0) {
|
||||
allocationOptions.probability = options.allocationsSampleProbability;
|
||||
}
|
||||
if (options.allocationsMaxLogLength !== void 0) {
|
||||
allocationOptions.maxLogLength = options.allocationsMaxLogLength;
|
||||
}
|
||||
|
||||
let startTime = yield this.startRecordingAllocations(allocationOptions);
|
||||
|
||||
if (!this._poller) {
|
||||
this._poller = new Poller(this._pullAllocationSites, ALLOCATION_SITE_POLL_TIMER, false);
|
||||
}
|
||||
if (!this._poller.isPolling()) {
|
||||
this._poller.on();
|
||||
}
|
||||
|
||||
return startTime;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Stops polling for allocation information.
|
||||
*/
|
||||
stop: Task.async(function *(options) {
|
||||
if (!options.withAllocations) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Since `_pullAllocationSites` is usually running inside a timeout, and
|
||||
// it's performing asynchronous requests to the server, a recording may
|
||||
// be stopped before that method finishes executing. Therefore, we need to
|
||||
// wait for the last request to `getAllocations` to finish before actually
|
||||
// stopping recording allocations.
|
||||
yield this._poller.off();
|
||||
yield this._lastPullAllocationSitesFinished;
|
||||
|
||||
let endTime = yield this.stopRecordingAllocations();
|
||||
yield this.detach();
|
||||
|
||||
return endTime;
|
||||
}),
|
||||
|
||||
/**
|
||||
* At regular intervals, pull allocations from the memory actor, and
|
||||
* forward them on this Front facade as "timeline-data" events. This
|
||||
* gives the illusion that the MemoryActor supports an EventEmitter-style
|
||||
* event stream.
|
||||
*/
|
||||
_pullAllocationSites: Task.async(function *() {
|
||||
let deferred = promise.defer();
|
||||
this._lastPullAllocationSitesFinished = deferred.promise;
|
||||
|
||||
if ((yield this.getState()) !== "attached") {
|
||||
deferred.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
let memoryData = yield this.getAllocations();
|
||||
// Match the signature of the TimelineFront events, with "timeline-data"
|
||||
// being the event name, and the second argument describing the type.
|
||||
this.emit("timeline-data", "allocations", memoryData);
|
||||
|
||||
deferred.resolve();
|
||||
}),
|
||||
|
||||
toString: () => "[object MemoryFrontFacade]"
|
||||
toString: () => "[object LegacyTimelineFront]"
|
||||
};
|
||||
|
||||
// Bind all the methods that directly proxy to the actor
|
||||
PROFILER_ACTOR_METHODS.forEach(m => ProfilerFrontFacade.prototype[m] = CompatUtils.callFrontMethod(m));
|
||||
TIMELINE_ACTOR_METHODS.forEach(m => TimelineFrontFacade.prototype[m] = CompatUtils.callFrontMethod(m));
|
||||
MEMORY_ACTOR_METHODS.forEach(m => MemoryFrontFacade.prototype[m] = CompatUtils.callFrontMethod(m));
|
||||
PROFILER_ACTOR_METHODS.forEach(m => LegacyProfilerFront.prototype[m] = CompatUtils.callFrontMethod(m));
|
||||
TIMELINE_ACTOR_METHODS.forEach(m => LegacyTimelineFront.prototype[m] = CompatUtils.callFrontMethod(m));
|
||||
|
||||
exports.ProfilerFront = ProfilerFrontFacade;
|
||||
exports.TimelineFront = TimelineFrontFacade;
|
||||
exports.MemoryFront = MemoryFrontFacade;
|
||||
exports.LegacyProfilerFront = LegacyProfilerFront;
|
||||
exports.LegacyTimelineFront = LegacyTimelineFront;
|
@ -21,20 +21,6 @@ function MockFront (blueprint) {
|
||||
}
|
||||
}
|
||||
|
||||
function MockMemoryFront () {
|
||||
MockFront.call(this, [
|
||||
["start", 0], // for facade
|
||||
["stop", 0], // for facade
|
||||
["destroy"],
|
||||
["attach"],
|
||||
["detach"],
|
||||
["getState", "detached"],
|
||||
["startRecordingAllocations", 0],
|
||||
["stopRecordingAllocations", 0],
|
||||
["getAllocations", createMockAllocations],
|
||||
]);
|
||||
}
|
||||
|
||||
function MockTimelineFront () {
|
||||
MockFront.call(this, [
|
||||
["destroy"],
|
||||
@ -43,46 +29,6 @@ function MockTimelineFront () {
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a fake allocations object, to be used with the MockMemoryFront
|
||||
* so we create a fresh object each time.
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
function createMockAllocations () {
|
||||
return {
|
||||
allocations: [],
|
||||
allocationsTimestamps: [],
|
||||
frames: [],
|
||||
counts: []
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a TabTarget, and checks through all methods that are needed
|
||||
* on the server's memory actor to determine if a mock or real MemoryActor
|
||||
* should be used. The last of the methods added to MemoryActor
|
||||
* landed in Gecko 35, so previous versions will fail this. Setting the `target`'s
|
||||
* TEST_MOCK_MEMORY_ACTOR property to true will cause this function to indicate that
|
||||
* the memory actor is not supported.
|
||||
*
|
||||
* @param {TabTarget} target
|
||||
* @return {Boolean}
|
||||
*/
|
||||
function memoryActorSupported (target) {
|
||||
// This `target` property is used only in tests to test
|
||||
// instances where the memory actor is not available.
|
||||
if (target.TEST_MOCK_MEMORY_ACTOR) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We need to test that both the root actor has `memoryActorAllocations`,
|
||||
// which is in Gecko 38+, and also that the target has a memory actor. There
|
||||
// are scenarios, like addon debugging, where memoryActorAllocations is available,
|
||||
// but no memory actor (like addon debugging in Gecko 38+)
|
||||
return !!target.getTrait("memoryActorAllocations") && target.hasActor("memory");
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a TabTarget, and checks existence of a TimelineActor on
|
||||
* the server, or if TEST_MOCK_TIMELINE_ACTOR is to be used.
|
||||
@ -117,8 +63,6 @@ function callFrontMethod (method) {
|
||||
};
|
||||
}
|
||||
|
||||
exports.MockMemoryFront = MockMemoryFront;
|
||||
exports.MockTimelineFront = MockTimelineFront;
|
||||
exports.memoryActorSupported = memoryActorSupported;
|
||||
exports.timelineActorSupported = timelineActorSupported;
|
||||
exports.callFrontMethod = callFrontMethod;
|
@ -3,87 +3,66 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
const { Cu } = require("chrome");
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
|
||||
loader.lazyRequireGetter(this, "Services");
|
||||
loader.lazyRequireGetter(this, "promise");
|
||||
loader.lazyRequireGetter(this, "EventEmitter",
|
||||
"devtools/toolkit/event-emitter");
|
||||
loader.lazyRequireGetter(this, "extend",
|
||||
"sdk/util/object", true);
|
||||
|
||||
loader.lazyRequireGetter(this, "Actors",
|
||||
"devtools/performance/actors");
|
||||
loader.lazyRequireGetter(this, "RecordingModel",
|
||||
"devtools/performance/recording-model", true);
|
||||
"devtools/toolkit/performance/legacy/actors");
|
||||
loader.lazyRequireGetter(this, "LegacyPerformanceRecording",
|
||||
"devtools/toolkit/performance/legacy/recording", true);
|
||||
loader.lazyRequireGetter(this, "importRecording",
|
||||
"devtools/toolkit/performance/legacy/recording", true);
|
||||
loader.lazyRequireGetter(this, "normalizePerformanceFeatures",
|
||||
"devtools/performance/recording-utils", true);
|
||||
"devtools/toolkit/performance/utils", true);
|
||||
loader.lazyRequireGetter(this, "DevToolsUtils",
|
||||
"devtools/toolkit/DevToolsUtils");
|
||||
|
||||
loader.lazyImporter(this, "gDevTools",
|
||||
"resource:///modules/devtools/gDevTools.jsm");
|
||||
loader.lazyRequireGetter(this, "events",
|
||||
"sdk/event/core");
|
||||
loader.lazyRequireGetter(this, "EventTarget",
|
||||
"sdk/event/target", true);
|
||||
loader.lazyRequireGetter(this, "Class",
|
||||
"sdk/core/heritage", true);
|
||||
|
||||
/**
|
||||
* A cache of all PerformanceFront instances.
|
||||
* The keys are Target objects.
|
||||
*/
|
||||
let PerformanceFronts = new WeakMap();
|
||||
|
||||
/**
|
||||
* Instantiates a shared PerformanceFront for the specified target.
|
||||
* Consumers must yield on `open` to make sure the connection is established.
|
||||
*
|
||||
* @param Target target
|
||||
* The target owning this connection.
|
||||
* @return PerformanceFront
|
||||
* The pseudofront for all the underlying actors.
|
||||
*/
|
||||
PerformanceFronts.forTarget = function(target) {
|
||||
if (this.has(target)) {
|
||||
return this.get(target);
|
||||
}
|
||||
|
||||
let instance = new PerformanceFront(target);
|
||||
this.set(target, instance);
|
||||
return instance;
|
||||
};
|
||||
|
||||
/**
|
||||
* A connection to underlying actors (profiler, memory, framerate, etc.)
|
||||
* A connection to underlying actors (profiler, framerate, etc.)
|
||||
* shared by all tools in a target.
|
||||
*
|
||||
* Use `PerformanceFronts.forTarget` to make sure you get the same
|
||||
* instance every time, and the `PerformanceFront` to start/stop recordings.
|
||||
*
|
||||
* @param Target target
|
||||
* The target owning this connection.
|
||||
*/
|
||||
function PerformanceFront (target) {
|
||||
EventEmitter.decorate(this);
|
||||
const LegacyPerformanceFront = Class({
|
||||
extends: EventTarget,
|
||||
|
||||
this._target = target;
|
||||
this._client = this._target.client;
|
||||
this._pendingConsoleRecordings = [];
|
||||
this._sitesPullTimeout = 0;
|
||||
this._recordings = [];
|
||||
LEGACY_FRONT: true,
|
||||
|
||||
this._pipeToFront = this._pipeToFront.bind(this);
|
||||
this._onTimelineData = this._onTimelineData.bind(this);
|
||||
this._onConsoleProfileStart = this._onConsoleProfileStart.bind(this);
|
||||
this._onConsoleProfileEnd = this._onConsoleProfileEnd.bind(this);
|
||||
this._onProfilerStatus = this._onProfilerStatus.bind(this);
|
||||
this._onProfilerUnexpectedlyStopped = this._onProfilerUnexpectedlyStopped.bind(this);
|
||||
traits: {
|
||||
features: {
|
||||
withMarkers: true,
|
||||
withTicks: true,
|
||||
withMemory: false,
|
||||
withAllocations: false,
|
||||
withJITOptimizations: false,
|
||||
},
|
||||
},
|
||||
|
||||
Services.obs.notifyObservers(null, "performance-tools-connection-created", null);
|
||||
}
|
||||
initialize: function (target) {
|
||||
let { form, client } = target;
|
||||
this._target = target;
|
||||
this._form = form;
|
||||
this._client = client;
|
||||
this._pendingConsoleRecordings = [];
|
||||
this._sitesPullTimeout = 0;
|
||||
this._recordings = [];
|
||||
|
||||
PerformanceFront.prototype = {
|
||||
|
||||
// Properties set based off of server actor support
|
||||
_memorySupported: true,
|
||||
_timelineSupported: true,
|
||||
this._pipeToFront = this._pipeToFront.bind(this);
|
||||
this._onTimelineData = this._onTimelineData.bind(this);
|
||||
this._onConsoleProfileStart = this._onConsoleProfileStart.bind(this);
|
||||
this._onConsoleProfileStop = this._onConsoleProfileStop.bind(this);
|
||||
this._onProfilerStatus = this._onProfilerStatus.bind(this);
|
||||
this._onProfilerUnexpectedlyStopped = this._onProfilerUnexpectedlyStopped.bind(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes a connection to the profiler and other miscellaneous actors.
|
||||
@ -92,7 +71,7 @@ PerformanceFront.prototype = {
|
||||
* @return object
|
||||
* A promise that is resolved once the connection is established.
|
||||
*/
|
||||
open: Task.async(function*() {
|
||||
connect: Task.async(function*() {
|
||||
if (this._connecting) {
|
||||
return this._connecting.promise;
|
||||
}
|
||||
@ -101,18 +80,14 @@ PerformanceFront.prototype = {
|
||||
// other attempts to open the connection use the same resolution promise
|
||||
this._connecting = promise.defer();
|
||||
|
||||
// Local debugging needs to make the target remote.
|
||||
yield this._target.makeRemote();
|
||||
|
||||
// Sets `this._profiler`, `this._timeline` and `this._memory`.
|
||||
// Only initialize the timeline and memory fronts if the respective actors
|
||||
// Sets `this._profiler`, `this._timeline`.
|
||||
// Only initialize the timeline fronts if the respective actors
|
||||
// are available. Older Gecko versions don't have existing implementations,
|
||||
// in which case all the methods we need can be easily mocked.
|
||||
yield this._connectActors();
|
||||
yield this._registerListeners();
|
||||
|
||||
this._connecting.resolve();
|
||||
Services.obs.notifyObservers(null, "performance-tools-connection-opened", null);
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -131,9 +106,9 @@ PerformanceFront.prototype = {
|
||||
this._connecting = null;
|
||||
this._profiler = null;
|
||||
this._timeline = null;
|
||||
this._memory = null;
|
||||
this._target = null;
|
||||
this._client = null;
|
||||
this._form = null;
|
||||
this._target = this._target;
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -141,20 +116,17 @@ PerformanceFront.prototype = {
|
||||
* found in ./actors.js.
|
||||
*/
|
||||
_connectActors: Task.async(function*() {
|
||||
this._profiler = new Actors.ProfilerFront(this._target);
|
||||
this._memory = new Actors.MemoryFront(this._target);
|
||||
this._timeline = new Actors.TimelineFront(this._target);
|
||||
this._profiler = new Actors.LegacyProfilerFront(this._target);
|
||||
this._timeline = new Actors.LegacyTimelineFront(this._target);
|
||||
|
||||
yield promise.all([
|
||||
this._profiler.connect(),
|
||||
this._memory.connect(),
|
||||
this._timeline.connect()
|
||||
]);
|
||||
|
||||
// Expose server support status of underlying actors
|
||||
// after connecting.
|
||||
this._memorySupported = !this._memory.IS_MOCK;
|
||||
this._timelineSupported = !this._timeline.IS_MOCK;
|
||||
// If mocked timeline, update the traits
|
||||
this.traits.features.withMarkers = !this._timeline.IS_MOCK;
|
||||
this.traits.features.withTicks = !this._timeline.IS_MOCK;
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -163,12 +135,9 @@ PerformanceFront.prototype = {
|
||||
*/
|
||||
_registerListeners: function () {
|
||||
this._timeline.on("timeline-data", this._onTimelineData);
|
||||
this._memory.on("timeline-data", this._onTimelineData);
|
||||
this._profiler.on("console-profile-start", this._onConsoleProfileStart);
|
||||
this._profiler.on("console-profile-end", this._onConsoleProfileEnd);
|
||||
this._profiler.on("console-profile-stop", this._onConsoleProfileStop);
|
||||
this._profiler.on("profiler-stopped", this._onProfilerUnexpectedlyStopped);
|
||||
this._profiler.on("profiler-already-active", this._pipeToFront);
|
||||
this._profiler.on("profiler-activated", this._pipeToFront);
|
||||
this._profiler.on("profiler-status", this._onProfilerStatus);
|
||||
},
|
||||
|
||||
@ -177,12 +146,9 @@ PerformanceFront.prototype = {
|
||||
*/
|
||||
_unregisterListeners: function () {
|
||||
this._timeline.off("timeline-data", this._onTimelineData);
|
||||
this._memory.off("timeline-data", this._onTimelineData);
|
||||
this._profiler.off("console-profile-start", this._onConsoleProfileStart);
|
||||
this._profiler.off("console-profile-end", this._onConsoleProfileEnd);
|
||||
this._profiler.off("console-profile-stop", this._onConsoleProfileStop);
|
||||
this._profiler.off("profiler-stopped", this._onProfilerUnexpectedlyStopped);
|
||||
this._profiler.off("profiler-already-active", this._pipeToFront);
|
||||
this._profiler.off("profiler-activated", this._pipeToFront);
|
||||
this._profiler.off("profiler-status", this._onProfilerStatus);
|
||||
},
|
||||
|
||||
@ -193,7 +159,6 @@ PerformanceFront.prototype = {
|
||||
yield promise.all([
|
||||
this._profiler.destroy(),
|
||||
this._timeline.destroy(),
|
||||
this._memory.destroy()
|
||||
]);
|
||||
}),
|
||||
|
||||
@ -214,13 +179,9 @@ PerformanceFront.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure the performance front is set up and ready.
|
||||
// Slight performance overhead for this, should research some more.
|
||||
// This is to ensure that there is a front to receive the events for
|
||||
// the console profiles.
|
||||
yield gDevTools.getToolbox(this._target).loadTool("performance");
|
||||
events.emit(this, "console-profile-start");
|
||||
|
||||
let model = yield this.startRecording(extend(getRecordingModelPrefs(), {
|
||||
yield this.startRecording(extend({}, getLegacyPerformanceRecordingPrefs(), {
|
||||
console: true,
|
||||
label: profileLabel
|
||||
}));
|
||||
@ -235,7 +196,7 @@ PerformanceFront.prototype = {
|
||||
* The time (in milliseconds) when the call was made, relative to when
|
||||
* the nsIProfiler module was started.
|
||||
*/
|
||||
_onConsoleProfileEnd: Task.async(function *(_, data) {
|
||||
_onConsoleProfileStop: Task.async(function *(_, data) {
|
||||
// If no data, abort; can occur if profiler isn't running and we get a surprise
|
||||
// call to console.profileEnd()
|
||||
if (!data) {
|
||||
@ -280,15 +241,13 @@ PerformanceFront.prototype = {
|
||||
* Called whenever there is timeline data of any of the following types:
|
||||
* - markers
|
||||
* - frames
|
||||
* - memory
|
||||
* - ticks
|
||||
* - allocations
|
||||
*
|
||||
* Populate our internal store of recordings for all currently recording sessions.
|
||||
*/
|
||||
_onTimelineData: function (_, ...data) {
|
||||
this._recordings.forEach(e => e._addTimelineData.apply(e, data));
|
||||
this.emit("timeline-data", ...data);
|
||||
events.emit(this, "timeline-data", ...data);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -297,15 +256,12 @@ PerformanceFront.prototype = {
|
||||
_onProfilerStatus: function (_, data) {
|
||||
// If no data emitted (whether from an older actor being destroyed
|
||||
// from a previous test, or the server does not support it), just ignore.
|
||||
if (!data) {
|
||||
if (!data || data.position === void 0) {
|
||||
return;
|
||||
}
|
||||
// Check for a value of buffer status (`position`) to see if the server
|
||||
// supports buffer status -- apply to the recording models if so.
|
||||
if (data.position !== void 0) {
|
||||
this._recordings.forEach(e => e._addBufferStatusData.call(e, data));
|
||||
}
|
||||
this.emit("profiler-status", data);
|
||||
|
||||
this._currentBufferStatus = data;
|
||||
events.emit(this, "profiler-status", data);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -318,24 +274,20 @@ PerformanceFront.prototype = {
|
||||
* A promise that is resolved once recording has started.
|
||||
*/
|
||||
startRecording: Task.async(function*(options = {}) {
|
||||
let model = new RecordingModel(normalizePerformanceFeatures(options, this.getActorSupport()));
|
||||
|
||||
this.emit("recording-starting", model);
|
||||
let model = new LegacyPerformanceRecording(normalizePerformanceFeatures(options, this.traits.features));
|
||||
|
||||
// All actors are started asynchronously over the remote debugging protocol.
|
||||
// Get the corresponding start times from each one of them.
|
||||
// The timeline and memory actors are target-dependent, so start those as well,
|
||||
// The timeline actors are target-dependent, so start those as well,
|
||||
// even though these are mocked in older Geckos (FF < 35)
|
||||
let profilerStart = this._profiler.start(options);
|
||||
let timelineStart = this._timeline.start(options);
|
||||
let memoryStart = this._memory.start(options);
|
||||
|
||||
let { startTime, position, generation, totalSize } = yield profilerStart;
|
||||
let timelineStartTime = yield timelineStart;
|
||||
let memoryStartTime = yield memoryStart;
|
||||
|
||||
let data = {
|
||||
profilerStartTime: startTime, timelineStartTime, memoryStartTime,
|
||||
profilerStartTime: startTime, timelineStartTime,
|
||||
generation, position, totalSize
|
||||
};
|
||||
|
||||
@ -344,20 +296,20 @@ PerformanceFront.prototype = {
|
||||
model._populate(data);
|
||||
this._recordings.push(model);
|
||||
|
||||
this.emit("recording-started", model);
|
||||
events.emit(this, "recording-started", model);
|
||||
return model;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Manually ends the recording session for the corresponding RecordingModel.
|
||||
* Manually ends the recording session for the corresponding LegacyPerformanceRecording.
|
||||
*
|
||||
* @param RecordingModel model
|
||||
* The corresponding RecordingModel that belongs to the recording session wished to stop.
|
||||
* @return RecordingModel
|
||||
* @param LegacyPerformanceRecording model
|
||||
* The corresponding LegacyPerformanceRecording that belongs to the recording session wished to stop.
|
||||
* @return LegacyPerformanceRecording
|
||||
* Returns the same model, populated with the profiling data.
|
||||
*/
|
||||
stopRecording: Task.async(function*(model) {
|
||||
// If model isn't in the PerformanceFront internal store,
|
||||
// If model isn't in the LegacyPerformanceFront internal store,
|
||||
// then do nothing.
|
||||
if (this._recordings.indexOf(model) === -1) {
|
||||
return;
|
||||
@ -368,12 +320,12 @@ PerformanceFront.prototype = {
|
||||
// the recording can be considered "completed".
|
||||
let endTime = Date.now();
|
||||
model._onStoppingRecording(endTime);
|
||||
this.emit("recording-stopping", model);
|
||||
events.emit(this, "recording-stopping", model);
|
||||
|
||||
// Currently there are two ways profiles stop recording. Either manually in the
|
||||
// performance tool, or via console.profileEnd. Once a recording is done,
|
||||
// we want to deliver the model to the performance tool (either as a return
|
||||
// from the PerformanceFront or via `console-profile-end` event) and then
|
||||
// from the LegacyPerformanceFront or via `console-profile-stop` event) and then
|
||||
// remove it from the internal store.
|
||||
//
|
||||
// In the case where a console.profile is generated via the console (so the tools are
|
||||
@ -383,46 +335,42 @@ PerformanceFront.prototype = {
|
||||
let config = model.getConfiguration();
|
||||
let startTime = model.getProfilerStartTime();
|
||||
let profilerData = yield this._profiler.getProfile({ startTime });
|
||||
let memoryEndTime = Date.now();
|
||||
let timelineEndTime = Date.now();
|
||||
|
||||
// Only if there are no more sessions recording do we stop
|
||||
// the underlying memory and timeline actors. If we're still recording,
|
||||
// juse use Date.now() for the memory and timeline end times, as those
|
||||
// the underlying timeline actors. If we're still recording,
|
||||
// juse use Date.now() for the timeline end times, as those
|
||||
// are only used in tests.
|
||||
if (!this.isRecording()) {
|
||||
// This doesn't stop the profiler, just turns off polling for
|
||||
// events, and also turns off events on memory/timeline actors.
|
||||
// events, and also turns off events on timeline actors.
|
||||
yield this._profiler.stop();
|
||||
memoryEndTime = yield this._memory.stop(config);
|
||||
timelineEndTime = yield this._timeline.stop(config);
|
||||
}
|
||||
|
||||
// Set the results on the RecordingModel itself.
|
||||
// Set the results on the LegacyPerformanceRecording itself.
|
||||
model._onStopRecording({
|
||||
// Data available only at the end of a recording.
|
||||
profile: profilerData.profile,
|
||||
|
||||
// End times for all the actors.
|
||||
profilerEndTime: profilerData.currentTime,
|
||||
timelineEndTime: timelineEndTime,
|
||||
memoryEndTime: memoryEndTime
|
||||
timelineEndTime: timelineEndTime
|
||||
});
|
||||
|
||||
this.emit("recording-stopped", model);
|
||||
events.emit(this, "recording-stopped", model);
|
||||
return model;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Returns an object indicating what server actors are available and
|
||||
* initialized. A falsy value indicates that the server does not support
|
||||
* that feature, or that mock actors were explicitly requested (tests).
|
||||
* Creates a recording object when given a nsILocalFile.
|
||||
*
|
||||
* @param {nsILocalFile} file
|
||||
* The file to import the data from.
|
||||
* @return {Promise<LegacyPerformanceRecording>}
|
||||
*/
|
||||
getActorSupport: function () {
|
||||
return {
|
||||
memory: this._memorySupported,
|
||||
timeline: this._timelineSupported
|
||||
};
|
||||
importRecording: function (file) {
|
||||
return importRecording(file);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -435,12 +383,43 @@ PerformanceFront.prototype = {
|
||||
return this._recordings.some(recording => recording.isRecording());
|
||||
},
|
||||
|
||||
/**
|
||||
* Pass in a PerformanceRecording and get a normalized value from 0 to 1 of how much
|
||||
* of this recording's lifetime remains without being overwritten.
|
||||
*
|
||||
* @param {PerformanceRecording} recording
|
||||
* @return {number?}
|
||||
*/
|
||||
getBufferUsageForRecording: function (recording) {
|
||||
if (!recording.isRecording() || !this._currentBufferStatus) {
|
||||
return null;
|
||||
}
|
||||
let { position: currentPosition, totalSize, generation: currentGeneration } = this._currentBufferStatus;
|
||||
let { position: origPosition, generation: origGeneration } = recording.getStartingBufferStatus();
|
||||
|
||||
let normalizedCurrent = (totalSize * (currentGeneration - origGeneration)) + currentPosition;
|
||||
let percent = (normalizedCurrent - origPosition) / totalSize;
|
||||
return percent > 1 ? 1 : percent;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the configurations set on underlying components, used in tests.
|
||||
* Returns an object with `probability`, `maxLogLength` for allocations, and
|
||||
* `entries` and `interval` for profiler.
|
||||
*
|
||||
* @return {object}
|
||||
*/
|
||||
getConfiguration: Task.async(function *() {
|
||||
let profilerConfig = yield this._request("profiler", "getStartOptions");
|
||||
return profilerConfig;
|
||||
}),
|
||||
|
||||
/**
|
||||
* An event from an underlying actor that we just want
|
||||
* to pipe to the front itself.
|
||||
*/
|
||||
_pipeToFront: function (eventName, ...args) {
|
||||
this.emit(eventName, ...args);
|
||||
events.emit(this, eventName, ...args);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -449,19 +428,30 @@ PerformanceFront.prototype = {
|
||||
*/
|
||||
_request: function (actorName, method, ...args) {
|
||||
if (!DevToolsUtils.testing) {
|
||||
throw new Error("PerformanceFront._request may only be used in tests.");
|
||||
throw new Error("LegacyPerformanceFront._request may only be used in tests.");
|
||||
}
|
||||
let actor = this[`_${actorName}`];
|
||||
return actor[method].apply(actor, args);
|
||||
},
|
||||
|
||||
toString: () => "[object PerformanceFront]"
|
||||
};
|
||||
/**
|
||||
* Sets how often the "profiler-status" event should be emitted.
|
||||
* Used in tests.
|
||||
*/
|
||||
setProfilerStatusInterval: function (n) {
|
||||
if (this._profiler._poller) {
|
||||
this._profiler._poller._wait = n;
|
||||
}
|
||||
this._profiler._PROFILER_CHECK_TIMER = n;
|
||||
},
|
||||
|
||||
toString: () => "[object LegacyPerformanceFront]"
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates an object of configurations based off of preferences for a RecordingModel.
|
||||
* Creates an object of configurations based off of preferences for a LegacyPerformanceRecording.
|
||||
*/
|
||||
function getRecordingModelPrefs () {
|
||||
function getLegacyPerformanceRecordingPrefs () {
|
||||
return {
|
||||
withMarkers: true,
|
||||
withMemory: Services.prefs.getBoolPref("devtools.performance.ui.enable-memory"),
|
||||
@ -473,5 +463,4 @@ function getRecordingModelPrefs () {
|
||||
};
|
||||
}
|
||||
|
||||
exports.getPerformanceFront = t => PerformanceFronts.forTarget(t);
|
||||
exports.PerformanceFront = PerformanceFront;
|
||||
exports.LegacyPerformanceFront = LegacyPerformanceFront;
|
@ -7,16 +7,16 @@ const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
|
||||
loader.lazyRequireGetter(this, "PerformanceIO",
|
||||
"devtools/performance/io", true);
|
||||
"devtools/toolkit/performance/io");
|
||||
loader.lazyRequireGetter(this, "RecordingUtils",
|
||||
"devtools/performance/recording-utils");
|
||||
"devtools/toolkit/performance/utils");
|
||||
|
||||
/**
|
||||
* Model for a wholistic profile, containing the duration, profiling data,
|
||||
* frames data, timeline (marker, tick, memory) data, and methods to mark
|
||||
* a recording as 'in progress' or 'finished'.
|
||||
*/
|
||||
const RecordingModel = function (options={}) {
|
||||
const LegacyPerformanceRecording = function (options={}) {
|
||||
this._label = options.label || "";
|
||||
this._console = options.console || false;
|
||||
|
||||
@ -33,7 +33,7 @@ const RecordingModel = function (options={}) {
|
||||
};
|
||||
};
|
||||
|
||||
RecordingModel.prototype = {
|
||||
LegacyPerformanceRecording.prototype = {
|
||||
// Private fields, only needed when a recording is started or stopped.
|
||||
_console: false,
|
||||
_imported: false,
|
||||
@ -43,7 +43,7 @@ RecordingModel.prototype = {
|
||||
_timelineStartTime: 0,
|
||||
_memoryStartTime: 0,
|
||||
_configuration: {},
|
||||
_originalBufferStatus: null,
|
||||
_startingBufferStatus: null,
|
||||
_bufferPercent: null,
|
||||
|
||||
// Serializable fields, necessary and sufficient for import and export.
|
||||
@ -102,7 +102,7 @@ RecordingModel.prototype = {
|
||||
this._profilerStartTime = info.profilerStartTime;
|
||||
this._timelineStartTime = info.timelineStartTime;
|
||||
this._memoryStartTime = info.memoryStartTime;
|
||||
this._originalBufferStatus = {
|
||||
this._startingBufferStatus = {
|
||||
position: info.position,
|
||||
totalSize: info.totalSize,
|
||||
generation: info.generation
|
||||
@ -293,29 +293,19 @@ RecordingModel.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the percent (value between 0 and 1) of buffer used in this
|
||||
* recording. Returns `null` for recordings that are no longer recording.
|
||||
* Returns a boolean indicating if this recording is no longer recording, but
|
||||
* not yet completed.
|
||||
*/
|
||||
getBufferUsage: function () {
|
||||
return this.isRecording() ? this._bufferPercent : null;
|
||||
isFinalizing: function () {
|
||||
return !this.isRecording() && !this.isCompleted();
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired whenever the PerformanceFront has new buffer data.
|
||||
* Returns the position, generation and totalSize of the profiler
|
||||
* when this recording was started.
|
||||
*/
|
||||
_addBufferStatusData: function (bufferStatus) {
|
||||
// If this model isn't currently recording, or if the server does not
|
||||
// support buffer status (or if this fires after actors are being destroyed),
|
||||
// ignore this information.
|
||||
if (!bufferStatus || !this.isRecording()) {
|
||||
return;
|
||||
}
|
||||
let { position: currentPosition, totalSize, generation: currentGeneration } = bufferStatus;
|
||||
let { position: origPosition, generation: origGeneration } = this._originalBufferStatus;
|
||||
|
||||
let normalizedCurrent = (totalSize * (currentGeneration - origGeneration)) + currentPosition;
|
||||
let percent = (normalizedCurrent - origPosition) / totalSize;
|
||||
this._bufferPercent = percent > 1 ? 1 : percent;
|
||||
getStartingBufferStatus: function () {
|
||||
return this._startingBufferStatus;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -337,25 +327,14 @@ RecordingModel.prototype = {
|
||||
if (!config.withMarkers) { break; }
|
||||
let [markers] = data;
|
||||
RecordingUtils.offsetMarkerTimes(markers, this._timelineStartTime);
|
||||
pushAll(this._markers, markers);
|
||||
RecordingUtils.pushAll(this._markers, markers);
|
||||
break;
|
||||
}
|
||||
// Accumulate stack frames into an array.
|
||||
case "frames": {
|
||||
if (!config.withMarkers) { break; }
|
||||
let [, frames] = data;
|
||||
pushAll(this._frames, frames);
|
||||
break;
|
||||
}
|
||||
// Accumulate memory measurements into an array. Furthermore, the timestamp
|
||||
// does not have a zero epoch, so offset it by the actor's start time.
|
||||
case "memory": {
|
||||
if (!config.withMemory) { break; }
|
||||
let [currentTime, measurement] = data;
|
||||
this._memory.push({
|
||||
delta: currentTime - this._timelineStartTime,
|
||||
value: measurement.total / 1024 / 1024
|
||||
});
|
||||
RecordingUtils.pushAll(this._frames, frames);
|
||||
break;
|
||||
}
|
||||
// Save the accumulated refresh driver ticks.
|
||||
@ -365,47 +344,10 @@ RecordingModel.prototype = {
|
||||
this._ticks = timestamps;
|
||||
break;
|
||||
}
|
||||
// Accumulate allocation sites into an array. Furthermore, the timestamps
|
||||
// do not have a zero epoch, and are microseconds instead of milliseconds,
|
||||
// so offset all of them by the start time, also converting from µs to ms.
|
||||
case "allocations": {
|
||||
if (!config.withAllocations) { break; }
|
||||
let [{
|
||||
allocations: sites,
|
||||
allocationsTimestamps: timestamps,
|
||||
frames,
|
||||
}] = data;
|
||||
|
||||
let timeOffset = this._memoryStartTime;
|
||||
RecordingUtils.offsetAndScaleTimestamps(timestamps, timeOffset);
|
||||
pushAll(this._allocations.sites, sites);
|
||||
pushAll(this._allocations.timestamps, timestamps);
|
||||
pushAll(this._allocations.frames, frames);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
toString: () => "[object RecordingModel]"
|
||||
toString: () => "[object LegacyPerformanceRecording]"
|
||||
};
|
||||
|
||||
/**
|
||||
* Push all elements of src array into dest array. Marker data will come in small chunks
|
||||
* and add up over time, whereas allocation arrays can be > 500000 elements (and
|
||||
* Function.prototype.apply throws if applying more than 500000 elements, which
|
||||
* is what spawned this separate function), so iterate one element at a time.
|
||||
* @see bug 1166823
|
||||
* @see http://jsperf.com/concat-large-arrays
|
||||
* @see http://jsperf.com/concat-large-arrays/2
|
||||
*
|
||||
* @param {Array} dest
|
||||
* @param {Array} src
|
||||
*/
|
||||
function pushAll (dest, src) {
|
||||
let length = src.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
dest.push(src[i]);
|
||||
}
|
||||
}
|
||||
|
||||
exports.RecordingModel = RecordingModel;
|
||||
exports.LegacyPerformanceRecording = LegacyPerformanceRecording;
|
18
toolkit/devtools/performance/moz.build
Normal file
18
toolkit/devtools/performance/moz.build
Normal file
@ -0,0 +1,18 @@
|
||||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
EXTRA_JS_MODULES.devtools.performance += [
|
||||
'io.js',
|
||||
'recorder.js',
|
||||
'utils.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.devtools.performance.legacy += [
|
||||
'legacy/actors.js',
|
||||
'legacy/compatibility.js',
|
||||
'legacy/front.js',
|
||||
'legacy/recording.js',
|
||||
]
|
462
toolkit/devtools/performance/recorder.js
Normal file
462
toolkit/devtools/performance/recorder.js
Normal file
@ -0,0 +1,462 @@
|
||||
/* 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 { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
|
||||
loader.lazyRequireGetter(this, "Services");
|
||||
loader.lazyRequireGetter(this, "promise");
|
||||
loader.lazyRequireGetter(this, "extend",
|
||||
"sdk/util/object", true);
|
||||
loader.lazyRequireGetter(this, "Class",
|
||||
"sdk/core/heritage", true);
|
||||
loader.lazyRequireGetter(this, "EventTarget",
|
||||
"sdk/event/target", true);
|
||||
loader.lazyRequireGetter(this, "events",
|
||||
"sdk/event/core");
|
||||
|
||||
loader.lazyRequireGetter(this, "Memory",
|
||||
"devtools/toolkit/shared/memory", true);
|
||||
loader.lazyRequireGetter(this, "Timeline",
|
||||
"devtools/toolkit/shared/timeline", true);
|
||||
loader.lazyRequireGetter(this, "Profiler",
|
||||
"devtools/toolkit/shared/profiler", true);
|
||||
loader.lazyRequireGetter(this, "PerformanceRecordingActor",
|
||||
"devtools/server/actors/performance-recording", true);
|
||||
|
||||
loader.lazyRequireGetter(this, "PerformanceRecordingFront",
|
||||
"devtools/server/actors/performance-recording", true);
|
||||
loader.lazyRequireGetter(this, "mapRecordingOptions",
|
||||
"devtools/toolkit/performance/utils", true);
|
||||
loader.lazyRequireGetter(this, "DevToolsUtils",
|
||||
"devtools/toolkit/DevToolsUtils");
|
||||
|
||||
const PROFILER_EVENTS = [
|
||||
"console-api-profiler",
|
||||
"profiler-started",
|
||||
"profiler-stopped",
|
||||
"profiler-status"
|
||||
];
|
||||
|
||||
// Max time in milliseconds for the allocations event to occur, which will
|
||||
// occur on every GC, or at least as often as DRAIN_ALLOCATIONS_TIMEOUT.
|
||||
const DRAIN_ALLOCATIONS_TIMEOUT = 2000;
|
||||
|
||||
/**
|
||||
* A connection to underlying actors (profiler, memory, framerate, etc.)
|
||||
* shared by all tools in a target.
|
||||
*
|
||||
* @param Target target
|
||||
* The target owning this connection.
|
||||
*/
|
||||
const PerformanceRecorder = exports.PerformanceRecorder = Class({
|
||||
extends: EventTarget,
|
||||
|
||||
initialize: function (conn, tabActor) {
|
||||
this.conn = conn;
|
||||
this.tabActor = tabActor;
|
||||
|
||||
this._pendingConsoleRecordings = [];
|
||||
this._recordings = [];
|
||||
|
||||
this._onTimelineData = this._onTimelineData.bind(this);
|
||||
this._onProfilerEvent = this._onProfilerEvent.bind(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes a connection to the profiler and other miscellaneous actors.
|
||||
* If in the process of opening, or already open, nothing happens.
|
||||
*
|
||||
* @return object
|
||||
* A promise that is resolved once the connection is established.
|
||||
*/
|
||||
connect: function () {
|
||||
if (this._connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sets `this._profiler`, `this._timeline` and `this._memory`.
|
||||
// Only initialize the timeline and memory fronts if the respective actors
|
||||
// are available. Older Gecko versions don't have existing implementations,
|
||||
// in which case all the methods we need can be easily mocked.
|
||||
this._connectComponents();
|
||||
this._registerListeners();
|
||||
|
||||
this._connected = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroys this connection.
|
||||
*/
|
||||
destroy: function () {
|
||||
this._unregisterListeners();
|
||||
this._disconnectComponents();
|
||||
|
||||
this._connected = null;
|
||||
this._profiler = null;
|
||||
this._timeline = null;
|
||||
this._memory = null;
|
||||
this._target = null;
|
||||
this._client = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes fronts and connects to the underlying actors using the facades
|
||||
* found in ./actors.js.
|
||||
*/
|
||||
_connectComponents: function () {
|
||||
this._profiler = new Profiler(this.tabActor);
|
||||
this._memory = new Memory(this.tabActor);
|
||||
this._timeline = new Timeline(this.tabActor);
|
||||
this._profiler.registerEventNotifications({ events: PROFILER_EVENTS });
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers listeners on events from the underlying
|
||||
* actors, so the connection can handle them.
|
||||
*/
|
||||
_registerListeners: function () {
|
||||
this._timeline.on("*", this._onTimelineData);
|
||||
this._memory.on("*", this._onTimelineData);
|
||||
this._profiler.on("*", this._onProfilerEvent);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unregisters listeners on events on the underlying actors.
|
||||
*/
|
||||
_unregisterListeners: function () {
|
||||
this._timeline.off("*", this._onTimelineData);
|
||||
this._memory.off("*", this._onTimelineData);
|
||||
this._profiler.off("*", this._onProfilerEvent);
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes the connections to non-profiler actors.
|
||||
*/
|
||||
_disconnectComponents: function () {
|
||||
this._profiler.unregisterEventNotifications({ events: PROFILER_EVENTS });
|
||||
this._profiler.destroy();
|
||||
this._timeline.destroy();
|
||||
this._memory.destroy();
|
||||
},
|
||||
|
||||
_onProfilerEvent: function (topic, data) {
|
||||
if (topic === "console-api-profiler") {
|
||||
if (data.subject.action === "profile") {
|
||||
this._onConsoleProfileStart(data.details);
|
||||
} else if (data.subject.action === "profileEnd") {
|
||||
this._onConsoleProfileEnd(data.details);
|
||||
}
|
||||
} else if (topic === "profiler-stopped") {
|
||||
this._onProfilerUnexpectedlyStopped();
|
||||
} else if (topic === "profiler-status") {
|
||||
events.emit(this, "profiler-status", data);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoked whenever `console.profile` is called.
|
||||
*
|
||||
* @param string profileLabel
|
||||
* The provided string argument if available; undefined otherwise.
|
||||
* @param number currentTime
|
||||
* The time (in milliseconds) when the call was made, relative to when
|
||||
* the nsIProfiler module was started.
|
||||
*/
|
||||
_onConsoleProfileStart: Task.async(function *({ profileLabel, currentTime: startTime }) {
|
||||
let recordings = this._recordings;
|
||||
|
||||
// Abort if a profile with this label already exists.
|
||||
if (recordings.find(e => e.getLabel() === profileLabel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Immediately emit this so the client can start setting things up,
|
||||
// expecting a recording very soon.
|
||||
events.emit(this, "console-profile-start");
|
||||
|
||||
let model = yield this.startRecording(extend({}, getPerformanceRecordingPrefs(), {
|
||||
console: true,
|
||||
label: profileLabel
|
||||
}));
|
||||
}),
|
||||
|
||||
/**
|
||||
* Invoked whenever `console.profileEnd` is called.
|
||||
*
|
||||
* @param string profileLabel
|
||||
* The provided string argument if available; undefined otherwise.
|
||||
* @param number currentTime
|
||||
* The time (in milliseconds) when the call was made, relative to when
|
||||
* the nsIProfiler module was started.
|
||||
*/
|
||||
_onConsoleProfileEnd: Task.async(function *(data) {
|
||||
// If no data, abort; can occur if profiler isn't running and we get a surprise
|
||||
// call to console.profileEnd()
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
let { profileLabel, currentTime: endTime } = data;
|
||||
|
||||
let pending = this._recordings.filter(r => r.isConsole() && r.isRecording());
|
||||
if (pending.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let model;
|
||||
// Try to find the corresponding `console.profile` call if
|
||||
// a label was used in profileEnd(). If no matches, abort.
|
||||
if (profileLabel) {
|
||||
model = pending.find(e => e.getLabel() === profileLabel);
|
||||
}
|
||||
// If no label supplied, pop off the most recent pending console recording
|
||||
else {
|
||||
model = pending[pending.length - 1];
|
||||
}
|
||||
|
||||
// If `profileEnd()` was called with a label, and there are no matching
|
||||
// sessions, abort.
|
||||
if (!model) {
|
||||
Cu.reportError("console.profileEnd() called with label that does not match a recording.");
|
||||
return;
|
||||
}
|
||||
|
||||
yield this.stopRecording(model);
|
||||
}),
|
||||
|
||||
/**
|
||||
* TODO handle bug 1144438
|
||||
*/
|
||||
_onProfilerUnexpectedlyStopped: function () {
|
||||
Cu.reportError("Profiler unexpectedly stopped.", arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called whenever there is timeline data of any of the following types:
|
||||
* - markers
|
||||
* - frames
|
||||
* - memory
|
||||
* - ticks
|
||||
* - allocations
|
||||
*/
|
||||
_onTimelineData: function (eventName, ...data) {
|
||||
let eventData = Object.create(null);
|
||||
|
||||
switch (eventName) {
|
||||
case "markers": {
|
||||
eventData = { markers: data[0], endTime: data[1] };
|
||||
break;
|
||||
}
|
||||
case "ticks": {
|
||||
eventData = { delta: data[0], timestamps: data[1] };
|
||||
break;
|
||||
}
|
||||
case "memory": {
|
||||
eventData = { delta: data[0], measurement: data[1] };
|
||||
break;
|
||||
}
|
||||
case "frames": {
|
||||
eventData = { delta: data[0], frames: data[1] };
|
||||
break;
|
||||
}
|
||||
case "allocations": {
|
||||
eventData = data[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by only recordings that are currently recording;
|
||||
// TODO should filter by recordings that have realtimeMarkers enabled.
|
||||
let activeRecordings = this._recordings.filter(r => r.isRecording());
|
||||
|
||||
if (activeRecordings.length) {
|
||||
events.emit(this, "timeline-data", eventName, eventData, activeRecordings);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Begins a recording session
|
||||
*
|
||||
* @param boolean options.withMarkers
|
||||
* @param boolean options.withJITOptimizations
|
||||
* @param boolean options.withTicks
|
||||
* @param boolean options.withMemory
|
||||
* @param boolean options.withAllocations
|
||||
* @param boolean options.allocationsSampleProbability
|
||||
* @param boolean options.allocationsMaxLogLength
|
||||
* @param boolean options.bufferSize
|
||||
* @param boolean options.sampleFrequency
|
||||
* @param boolean options.console
|
||||
* @param string options.label
|
||||
* @param boolean options.realtimeMarkers
|
||||
* @return object
|
||||
* A promise that is resolved once recording has started.
|
||||
*/
|
||||
startRecording: Task.async(function*(options) {
|
||||
let profilerStart, timelineStart, memoryStart;
|
||||
|
||||
profilerStart = Task.spawn(function *() {
|
||||
let data = yield this._profiler.isActive();
|
||||
if (data.isActive) {
|
||||
return data;
|
||||
}
|
||||
let startData = yield this._profiler.start(mapRecordingOptions("profiler", options));
|
||||
|
||||
// If no current time is exposed from starting, set it to 0 -- this is an
|
||||
// older Gecko that does not return its starting time, and uses an epoch based
|
||||
// on the profiler's start time.
|
||||
if (startData.currentTime == null) {
|
||||
startData.currentTime = 0;
|
||||
}
|
||||
return startData;
|
||||
}.bind(this));
|
||||
|
||||
// Timeline will almost always be on if using the DevTools, but using component
|
||||
// independently could result in no timeline.
|
||||
if (options.withMarkers || options.withTicks || options.withMemory) {
|
||||
timelineStart = this._timeline.start(mapRecordingOptions("timeline", options));
|
||||
}
|
||||
|
||||
if (options.withAllocations) {
|
||||
if (this._memory.getState() === "detached") {
|
||||
this._memory.attach();
|
||||
}
|
||||
memoryStart = this._memory.startRecordingAllocations(extend(mapRecordingOptions("memory", options), {
|
||||
drainAllocationsTimeout: DRAIN_ALLOCATIONS_TIMEOUT
|
||||
}));
|
||||
}
|
||||
|
||||
let [profilerStartData, timelineStartData, memoryStartData] = yield promise.all([
|
||||
profilerStart, timelineStart, memoryStart
|
||||
]);
|
||||
|
||||
let data = Object.create(null);
|
||||
// Filter out start times that are not actually used (0 or undefined), and
|
||||
// find the earliest time since all sources use same epoch.
|
||||
let startTimes = [profilerStartData.currentTime, memoryStartData, timelineStartData].filter(Boolean);
|
||||
data.startTime = Math.min(...startTimes);
|
||||
data.position = profilerStartData.position;
|
||||
data.generation = profilerStartData.generation;
|
||||
data.totalSize = profilerStartData.totalSize;
|
||||
|
||||
let model = new PerformanceRecordingActor(this.conn, options, data);
|
||||
this._recordings.push(model);
|
||||
|
||||
events.emit(this, "recording-started", model);
|
||||
return model;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Manually ends the recording session for the corresponding PerformanceRecording.
|
||||
*
|
||||
* @param PerformanceRecording model
|
||||
* The corresponding PerformanceRecording that belongs to the recording session wished to stop.
|
||||
* @return PerformanceRecording
|
||||
* Returns the same model, populated with the profiling data.
|
||||
*/
|
||||
stopRecording: Task.async(function *(model) {
|
||||
// If model isn't in the Recorder's internal store,
|
||||
// then do nothing, like if this was a console.profileEnd
|
||||
// from a different target.
|
||||
if (this._recordings.indexOf(model) === -1) {
|
||||
return model;
|
||||
}
|
||||
|
||||
// Flag the recording as no longer recording, so that `model.isRecording()`
|
||||
// is false. Do this before we fetch all the data, and then subsequently
|
||||
// the recording can be considered "completed".
|
||||
let endTime = Date.now();
|
||||
events.emit(this, "recording-stopping", model);
|
||||
|
||||
// Currently there are two ways profiles stop recording. Either manually in the
|
||||
// performance tool, or via console.profileEnd. Once a recording is done,
|
||||
// we want to deliver the model to the performance tool (either as a return
|
||||
// from the PerformanceFront or via `console-profile-stop` event) and then
|
||||
// remove it from the internal store.
|
||||
//
|
||||
// In the case where a console.profile is generated via the console (so the tools are
|
||||
// open), we initialize the Performance tool so it can listen to those events.
|
||||
this._recordings.splice(this._recordings.indexOf(model), 1);
|
||||
|
||||
let startTime = model._startTime;
|
||||
let profilerData = this._profiler.getProfile({ startTime });
|
||||
|
||||
// Only if there are no more sessions recording do we stop
|
||||
// the underlying memory and timeline actors. If we're still recording,
|
||||
// juse use Date.now() for the memory and timeline end times, as those
|
||||
// are only used in tests.
|
||||
if (!this.isRecording()) {
|
||||
// Check to see if memory is recording, so we only stop recording
|
||||
// if necessary (otherwise if the memory component is not attached, this will fail)
|
||||
if (this._memory.isRecordingAllocations()) {
|
||||
this._memory.stopRecordingAllocations();
|
||||
}
|
||||
this._timeline.stop();
|
||||
}
|
||||
|
||||
let recordingData = {
|
||||
// Data available only at the end of a recording.
|
||||
profile: profilerData.profile,
|
||||
// End times for all the actors.
|
||||
duration: profilerData.currentTime - startTime,
|
||||
};
|
||||
|
||||
events.emit(this, "recording-stopped", model, recordingData);
|
||||
return model;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Checks all currently stored recording handles and returns a boolean
|
||||
* if there is a session currently being recorded.
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
isRecording: function () {
|
||||
return this._recordings.some(h => h.isRecording());
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns all current recordings.
|
||||
*/
|
||||
getRecordings: function () {
|
||||
return this._recordings;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets how often the "profiler-status" event should be emitted.
|
||||
* Used in tests.
|
||||
*/
|
||||
setProfilerStatusInterval: function (n) {
|
||||
this._profiler.setProfilerStatusInterval(n);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the configurations set on underlying components, used in tests.
|
||||
* Returns an object with `probability`, `maxLogLength` for allocations, and
|
||||
* `features`, `threadFilters`, `entries` and `interval` for profiler.
|
||||
*
|
||||
* @return {object}
|
||||
*/
|
||||
getConfiguration: function () {
|
||||
return extend({}, this._memory.getAllocationsSettings(), this._profiler.getStartOptions());
|
||||
},
|
||||
|
||||
toString: () => "[object PerformanceRecorder]"
|
||||
});
|
||||
|
||||
/**
|
||||
* Creates an object of configurations based off of preferences for a PerformanceRecording.
|
||||
*/
|
||||
function getPerformanceRecordingPrefs () {
|
||||
return {
|
||||
withMarkers: true,
|
||||
withMemory: Services.prefs.getBoolPref("devtools.performance.ui.enable-memory"),
|
||||
withTicks: Services.prefs.getBoolPref("devtools.performance.ui.enable-framerate"),
|
||||
withAllocations: Services.prefs.getBoolPref("devtools.performance.ui.enable-allocations"),
|
||||
withJITOptimizations: Services.prefs.getBoolPref("devtools.performance.ui.enable-jit-optimizations"),
|
||||
allocationsSampleProbability: +Services.prefs.getCharPref("devtools.performance.memory.sample-probability"),
|
||||
allocationsMaxLogLength: Services.prefs.getIntPref("devtools.performance.memory.max-log-length")
|
||||
};
|
||||
}
|
@ -12,6 +12,31 @@ loader.lazyRequireGetter(this, "extend",
|
||||
* such as filtering profile samples or offsetting timestamps.
|
||||
*/
|
||||
|
||||
function mapRecordingOptions (type, options) {
|
||||
if (type === "profiler") {
|
||||
return {
|
||||
entries: options.bufferSize,
|
||||
interval: options.sampleFrequency ? (1000 / (options.sampleFrequency * 1000)) : void 0
|
||||
};
|
||||
}
|
||||
|
||||
if (type === "memory") {
|
||||
return {
|
||||
probability: options.allocationsSampleProbability,
|
||||
maxLogLength: options.allocationsMaxLogLength
|
||||
};
|
||||
}
|
||||
|
||||
if (type === "timeline") {
|
||||
return {
|
||||
withMemory: options.withMemory,
|
||||
withTicks: options.withTicks,
|
||||
};
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an options object for `startRecording`, and normalizes
|
||||
* it based off of server support. For example, if the user
|
||||
@ -21,26 +46,15 @@ loader.lazyRequireGetter(this, "extend",
|
||||
* what the user initially requested.
|
||||
*
|
||||
* @param {object} options
|
||||
* @param {boolean} support.timeline
|
||||
* @param {boolean} support.memory
|
||||
* @param {boolean}
|
||||
*/
|
||||
function normalizePerformanceFeatures (options, support) {
|
||||
let supportOptions = Object.create(null);
|
||||
|
||||
// TODO bug 1172180 disable `withAllocations` and `withJITOptimizations` when using the
|
||||
// pseudo front, as we only want to support it directly from the real actor
|
||||
// in Fx42+
|
||||
if (!support.memory) {
|
||||
supportOptions.withMemory = false;
|
||||
supportOptions.withAllocations = false;
|
||||
}
|
||||
if (!support.timeline) {
|
||||
supportOptions.withMarkers = false;
|
||||
supportOptions.withTicks = false;
|
||||
}
|
||||
|
||||
return extend(options, supportOptions);
|
||||
function normalizePerformanceFeatures (options, supportedFeatures) {
|
||||
return Object.keys(options).reduce((modifiedOptions, feature) => {
|
||||
if (supportedFeatures[feature] !== false) {
|
||||
modifiedOptions[feature] = options[feature];
|
||||
}
|
||||
return modifiedOptions;
|
||||
}, Object.create(null));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,6 +126,25 @@ function offsetAndScaleTimestamps(timestamps, timeOffset, timeScale) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Push all elements of src array into dest array. Marker data will come in small chunks
|
||||
* and add up over time, whereas allocation arrays can be > 500000 elements (and
|
||||
* Function.prototype.apply throws if applying more than 500000 elements, which
|
||||
* is what spawned this separate function), so iterate one element at a time.
|
||||
* @see bug 1166823
|
||||
* @see http://jsperf.com/concat-large-arrays
|
||||
* @see http://jsperf.com/concat-large-arrays/2
|
||||
*
|
||||
* @param {Array} dest
|
||||
* @param {Array} src
|
||||
*/
|
||||
function pushAll (dest, src) {
|
||||
let length = src.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
dest.push(src[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache used in `RecordingUtils.getProfileThreadFromAllocations`.
|
||||
*/
|
||||
@ -576,6 +609,8 @@ UniqueStacks.prototype.getOrAddStringIndex = function(s) {
|
||||
return this._uniqueStrings.getOrAddStringIndex(s);
|
||||
};
|
||||
|
||||
exports.pushAll = pushAll;
|
||||
exports.mapRecordingOptions = mapRecordingOptions;
|
||||
exports.normalizePerformanceFeatures = normalizePerformanceFeatures;
|
||||
exports.filterSamples = filterSamples;
|
||||
exports.offsetSampleTimes = offsetSampleTimes;
|
352
toolkit/devtools/server/actors/performance-recording.js
Normal file
352
toolkit/devtools/server/actors/performance-recording.js
Normal file
@ -0,0 +1,352 @@
|
||||
/* 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 { Cu } = require("chrome");
|
||||
const protocol = require("devtools/server/protocol");
|
||||
const { custom, method, RetVal, Arg, Option, types, preEvent } = protocol;
|
||||
const { actorBridge } = require("devtools/server/actors/common");
|
||||
|
||||
loader.lazyRequireGetter(this, "events", "sdk/event/core");
|
||||
loader.lazyRequireGetter(this, "merge", "sdk/util/object", true);
|
||||
loader.lazyRequireGetter(this, "PerformanceIO",
|
||||
"devtools/toolkit/performance/io");
|
||||
loader.lazyRequireGetter(this, "RecordingUtils",
|
||||
"devtools/toolkit/performance/utils");
|
||||
|
||||
/**
|
||||
* A set of functions used by both the front and actor to access
|
||||
* internal properties.
|
||||
*/
|
||||
const PerformanceRecordingCommon = {
|
||||
// Private fields, only needed when a recording is started or stopped.
|
||||
_console: false,
|
||||
_imported: false,
|
||||
_recording: false,
|
||||
_completed: false,
|
||||
_configuration: {},
|
||||
_startingBufferStatus: null,
|
||||
_localStartTime: null,
|
||||
|
||||
// Serializable fields, necessary and sufficient for import and export.
|
||||
_label: "",
|
||||
_duration: 0,
|
||||
_markers: null,
|
||||
_frames: null,
|
||||
_memory: null,
|
||||
_ticks: null,
|
||||
_allocations: null,
|
||||
_profile: null,
|
||||
|
||||
/**
|
||||
* Helper methods for returning the status of the recording.
|
||||
* These methods should be consistent on both the front and actor.
|
||||
*/
|
||||
isRecording: function () { return this._recording; },
|
||||
isCompleted: function () { return this._completed || this.isImported(); },
|
||||
isFinalizing: function () { return !this.isRecording() && !this.isCompleted(); },
|
||||
isConsole: function () { return this._console; },
|
||||
isImported: function () { return this._imported; },
|
||||
|
||||
/**
|
||||
* Helper methods for returning configuration for the recording.
|
||||
* These methods should be consistent on both the front and actor.
|
||||
*/
|
||||
getConfiguration: function () { return this._configuration; },
|
||||
getLabel: function () { return this._label; },
|
||||
|
||||
/**
|
||||
* Helper methods for returning recording data.
|
||||
* These methods should be consistent on both the front and actor.
|
||||
*/
|
||||
getMarkers: function() { return this._markers; },
|
||||
getFrames: function() { return this._frames; },
|
||||
getMemory: function() { return this._memory; },
|
||||
getTicks: function() { return this._ticks; },
|
||||
getAllocations: function() { return this._allocations; },
|
||||
getProfile: function() { return this._profile; },
|
||||
|
||||
getAllData: function () {
|
||||
let label = this.getLabel();
|
||||
let duration = this.getDuration();
|
||||
let markers = this.getMarkers();
|
||||
let frames = this.getFrames();
|
||||
let memory = this.getMemory();
|
||||
let ticks = this.getTicks();
|
||||
let allocations = this.getAllocations();
|
||||
let profile = this.getProfile();
|
||||
let configuration = this.getConfiguration();
|
||||
return { label, duration, markers, frames, memory, ticks, allocations, profile, configuration };
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* This actor wraps the Performance module at toolkit/devtools/shared/performance.js
|
||||
* and provides RDP definitions.
|
||||
*
|
||||
* @see toolkit/devtools/shared/performance.js for documentation.
|
||||
*/
|
||||
let PerformanceRecordingActor = exports.PerformanceRecordingActor = protocol.ActorClass(merge({
|
||||
typeName: "performance-recording",
|
||||
|
||||
form: function(detail) {
|
||||
if (detail === "actorid") {
|
||||
return this.actorID;
|
||||
}
|
||||
|
||||
let form = {
|
||||
actor: this.actorID, // actorID is set when this is added to a pool
|
||||
configuration: this._configuration,
|
||||
startingBufferStatus: this._startingBufferStatus,
|
||||
console: this._console,
|
||||
label: this._label,
|
||||
startTime: this._startTime,
|
||||
localStartTime: this._localStartTime,
|
||||
recording: this._recording,
|
||||
completed: this._completed,
|
||||
duration: this._duration,
|
||||
};
|
||||
|
||||
// Only send profiler data once it exists and it has
|
||||
// not yet been sent
|
||||
if (this._profile && !this._sentProfilerData) {
|
||||
form.profile = this._profile;
|
||||
this._sentProfilerData = true;
|
||||
}
|
||||
|
||||
return form;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {object} conn
|
||||
* @param {object} options
|
||||
* A hash of features that this recording is utilizing.
|
||||
* @param {object} meta
|
||||
* A hash of temporary metadata for a recording that is recording
|
||||
* (as opposed to an imported recording).
|
||||
*/
|
||||
initialize: function (conn, options, meta) {
|
||||
protocol.Actor.prototype.initialize.call(this, conn);
|
||||
this._configuration = {
|
||||
withMarkers: options.withMarkers || false,
|
||||
withTicks: options.withTicks || false,
|
||||
withMemory: options.withMemory || false,
|
||||
withAllocations: options.withAllocations || false,
|
||||
withJITOptimizations: options.withJITOptimizations || false,
|
||||
allocationsSampleProbability: options.allocationsSampleProbability || 0,
|
||||
allocationsMaxLogLength: options.allocationsMaxLogLength || 0,
|
||||
bufferSize: options.bufferSize || 0,
|
||||
sampleFrequency: options.sampleFrequency || 1
|
||||
};
|
||||
|
||||
this._console = !!options.console;
|
||||
this._label = options.label || "";
|
||||
|
||||
if (meta) {
|
||||
// Store the start time roughly with Date.now() so when we
|
||||
// are checking the duration during a recording, we can get close
|
||||
// to the approximate duration to render elements without
|
||||
// making a real request
|
||||
this._localStartTime = Date.now();
|
||||
|
||||
this._startTime = meta.startTime;
|
||||
this._startingBufferStatus = {
|
||||
position: meta.position,
|
||||
totalSize: meta.totalSize,
|
||||
generation: meta.generation
|
||||
};
|
||||
|
||||
this._recording = true;
|
||||
this._markers = [];
|
||||
this._frames = [];
|
||||
this._memory = [];
|
||||
this._ticks = [];
|
||||
this._allocations = { sites: [], timestamps: [], frames: [], counts: [] };
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
protocol.Actor.prototype.destroy.call(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Internal utility called by the PerformanceActor and PerformanceFront on state changes
|
||||
* to update the internal state of the PerformanceRecording.
|
||||
*
|
||||
* @param {string} state
|
||||
* @param {object} extraData
|
||||
*/
|
||||
_setState: function (state, extraData) {
|
||||
switch (state) {
|
||||
case "recording-started": {
|
||||
this._recording = true;
|
||||
break;
|
||||
}
|
||||
case "recording-stopping": {
|
||||
this._recording = false;
|
||||
break;
|
||||
}
|
||||
case "recording-stopped": {
|
||||
this._profile = extraData.profile;
|
||||
this._duration = extraData.duration;
|
||||
|
||||
// We filter out all samples that fall out of current profile's range
|
||||
// since the profiler is continuously running. Because of this, sample
|
||||
// times are not guaranteed to have a zero epoch, so offset the
|
||||
// timestamps.
|
||||
RecordingUtils.offsetSampleTimes(this._profile, this._startTime);
|
||||
|
||||
// Markers need to be sorted ascending by time, to be properly displayed
|
||||
// in a waterfall view.
|
||||
this._markers = this._markers.sort((a, b) => (a.start > b.start));
|
||||
|
||||
this._completed = true;
|
||||
break;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
}, PerformanceRecordingCommon));
|
||||
|
||||
/**
|
||||
* This can be used on older Profiler implementations, but the methods cannot
|
||||
* be changed -- you must introduce a new method, and detect the server.
|
||||
*/
|
||||
let PerformanceRecordingFront = exports.PerformanceRecordingFront = protocol.FrontClass(PerformanceRecordingActor, merge({
|
||||
|
||||
form: function(form, detail) {
|
||||
if (detail === "actorid") {
|
||||
this.actorID = form;
|
||||
return;
|
||||
}
|
||||
this.actorID = form.actor;
|
||||
this._form = form;
|
||||
this._configuration = form.configuration;
|
||||
this._startingBufferStatus = form.startingBufferStatus;
|
||||
this._console = form.console;
|
||||
this._label = form.label;
|
||||
this._startTime = form.startTime;
|
||||
this._localStartTime = form.localStartTime;
|
||||
this._recording = form.recording;
|
||||
this._completed = form.completed;
|
||||
this._duration = form.duration;
|
||||
|
||||
if (form.profile) {
|
||||
this._profile = form.profile;
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function (client, form, config) {
|
||||
protocol.Front.prototype.initialize.call(this, client, form);
|
||||
this._markers = [];
|
||||
this._frames = [];
|
||||
this._memory = [];
|
||||
this._ticks = [];
|
||||
this._allocations = { sites: [], timestamps: [], frames: [], counts: [] };
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
protocol.Front.prototype.destroy.call(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Saves the current recording to a file.
|
||||
*
|
||||
* @param nsILocalFile file
|
||||
* The file to stream the data into.
|
||||
*/
|
||||
exportRecording: function (file) {
|
||||
let recordingData = this.getAllData();
|
||||
return PerformanceIO.saveRecordingToFile(recordingData, file);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the position, generation, and totalSize of the profiler
|
||||
* when this recording was started.
|
||||
*
|
||||
* @return {object}
|
||||
*/
|
||||
getStartingBufferStatus: function () {
|
||||
return this._form.startingBufferStatus;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets duration of this recording, in milliseconds.
|
||||
* @return number
|
||||
*/
|
||||
getDuration: function () {
|
||||
// Compute an approximate ending time for the current recording if it is
|
||||
// still in progress. This is needed to ensure that the view updates even
|
||||
// when new data is not being generated. If recording is completed, use
|
||||
// the duration from the profiler; if between recording and being finalized,
|
||||
// use the last estimated duration.
|
||||
if (this.isRecording()) {
|
||||
return this._estimatedDuration = Date.now() - this._localStartTime;
|
||||
} else {
|
||||
return this._duration || this._estimatedDuration || 0;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired whenever the PerformanceFront emits markers, memory or ticks.
|
||||
*/
|
||||
_addTimelineData: function (eventName, data) {
|
||||
let config = this.getConfiguration();
|
||||
|
||||
switch (eventName) {
|
||||
// Accumulate timeline markers into an array. Furthermore, the timestamps
|
||||
// do not have a zero epoch, so offset all of them by the start time.
|
||||
case "markers": {
|
||||
if (!config.withMarkers) { break; }
|
||||
let { markers } = data;
|
||||
RecordingUtils.offsetMarkerTimes(markers, this._startTime);
|
||||
RecordingUtils.pushAll(this._markers, markers);
|
||||
break;
|
||||
}
|
||||
// Accumulate stack frames into an array.
|
||||
case "frames": {
|
||||
if (!config.withMarkers) { break; }
|
||||
let { frames } = data;
|
||||
RecordingUtils.pushAll(this._frames, frames);
|
||||
break;
|
||||
}
|
||||
// Accumulate memory measurements into an array. Furthermore, the timestamp
|
||||
// does not have a zero epoch, so offset it by the actor's start time.
|
||||
case "memory": {
|
||||
if (!config.withMemory) { break; }
|
||||
let { delta, measurement } = data;
|
||||
this._memory.push({
|
||||
delta: delta - this._startTime,
|
||||
value: measurement.total / 1024 / 1024
|
||||
});
|
||||
break;
|
||||
}
|
||||
// Save the accumulated refresh driver ticks.
|
||||
case "ticks": {
|
||||
if (!config.withTicks) { break; }
|
||||
let { timestamps } = data;
|
||||
this._ticks = timestamps;
|
||||
break;
|
||||
}
|
||||
// Accumulate allocation sites into an array.
|
||||
case "allocations": {
|
||||
if (!config.withAllocations) { break; }
|
||||
let {
|
||||
allocations: sites,
|
||||
allocationsTimestamps: timestamps,
|
||||
frames,
|
||||
} = data;
|
||||
|
||||
RecordingUtils.offsetAndScaleTimestamps(timestamps, this._startTime);
|
||||
RecordingUtils.pushAll(this._allocations.sites, sites);
|
||||
RecordingUtils.pushAll(this._allocations.timestamps, timestamps);
|
||||
RecordingUtils.pushAll(this._allocations.frames, frames);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
toString: () => "[object PerformanceRecordingFront]"
|
||||
}, PerformanceRecordingCommon));
|
259
toolkit/devtools/server/actors/performance.js
Normal file
259
toolkit/devtools/server/actors/performance.js
Normal file
@ -0,0 +1,259 @@
|
||||
/* 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 { Cu } = require("chrome");
|
||||
const protocol = require("devtools/server/protocol");
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
const { Actor, custom, method, RetVal, Arg, Option, types, preEvent } = protocol;
|
||||
const { actorBridge } = require("devtools/server/actors/common");
|
||||
const { PerformanceRecordingActor, PerformanceRecordingFront } = require("devtools/server/actors/performance-recording");
|
||||
|
||||
loader.lazyRequireGetter(this, "events", "sdk/event/core");
|
||||
loader.lazyRequireGetter(this, "extend", "sdk/util/object", true);
|
||||
|
||||
loader.lazyRequireGetter(this, "PerformanceRecorder",
|
||||
"devtools/toolkit/performance/recorder", true);
|
||||
loader.lazyRequireGetter(this, "PerformanceIO",
|
||||
"devtools/toolkit/performance/io");
|
||||
loader.lazyRequireGetter(this, "normalizePerformanceFeatures",
|
||||
"devtools/toolkit/performance/utils", true);
|
||||
loader.lazyRequireGetter(this, "LegacyPerformanceFront",
|
||||
"devtools/toolkit/performance/legacy/front", true);
|
||||
|
||||
const PIPE_TO_FRONT_EVENTS = new Set([
|
||||
"recording-started", "recording-stopping", "recording-stopped",
|
||||
"profiler-status", "timeline-data", "console-profile-start"
|
||||
]);
|
||||
|
||||
const RECORDING_STATE_CHANGE_EVENTS = new Set([
|
||||
"recording-started", "recording-stopping", "recording-stopped"
|
||||
]);
|
||||
|
||||
/**
|
||||
* This actor wraps the Performance module at toolkit/devtools/shared/performance.js
|
||||
* and provides RDP definitions.
|
||||
*
|
||||
* @see toolkit/devtools/shared/performance.js for documentation.
|
||||
*/
|
||||
let PerformanceActor = exports.PerformanceActor = protocol.ActorClass({
|
||||
typeName: "performance",
|
||||
|
||||
traits: {
|
||||
features: {
|
||||
withMarkers: true,
|
||||
withMemory: true,
|
||||
withTicks: true,
|
||||
withAllocations: true,
|
||||
withJITOptimizations: true,
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* The set of events the PerformanceActor emits over RDP.
|
||||
*/
|
||||
events: {
|
||||
"recording-started": {
|
||||
recording: Arg(0, "performance-recording"),
|
||||
},
|
||||
"recording-stopping": {
|
||||
recording: Arg(0, "performance-recording"),
|
||||
},
|
||||
"recording-stopped": {
|
||||
recording: Arg(0, "performance-recording"),
|
||||
data: Arg(1, "json"),
|
||||
},
|
||||
"profiler-status": {
|
||||
data: Arg(0, "json"),
|
||||
},
|
||||
"console-profile-start": {},
|
||||
"timeline-data": {
|
||||
name: Arg(0, "string"),
|
||||
data: Arg(1, "json"),
|
||||
recordings: Arg(2, "array:performance-recording"),
|
||||
},
|
||||
},
|
||||
|
||||
initialize: function (conn, tabActor) {
|
||||
Actor.prototype.initialize.call(this, conn);
|
||||
this._onRecorderEvent = this._onRecorderEvent.bind(this);
|
||||
this.bridge = new PerformanceRecorder(conn, tabActor);
|
||||
events.on(this.bridge, "*", this._onRecorderEvent);
|
||||
},
|
||||
|
||||
/**
|
||||
* `disconnect` method required to call destroy, since this
|
||||
* actor is not managed by a parent actor.
|
||||
*/
|
||||
disconnect: function() {
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
events.off(this.bridge, "*", this._onRecorderEvent);
|
||||
this.bridge.destroy();
|
||||
protocol.Actor.prototype.destroy.call(this);
|
||||
},
|
||||
|
||||
connect: method(function () {
|
||||
this.bridge.connect();
|
||||
return this.traits;
|
||||
}, { response: RetVal("json") }),
|
||||
|
||||
startRecording: method(Task.async(function *(options={}) {
|
||||
let normalizedOptions = normalizePerformanceFeatures(options, this.traits.features);
|
||||
let recording = yield this.bridge.startRecording(normalizedOptions);
|
||||
|
||||
this.manage(recording);
|
||||
|
||||
return recording;
|
||||
}), {
|
||||
request: {
|
||||
options: Arg(0, "nullable:json"),
|
||||
},
|
||||
response: RetVal("performance-recording"),
|
||||
}),
|
||||
|
||||
stopRecording: actorBridge("stopRecording", {
|
||||
request: {
|
||||
options: Arg(0, "performance-recording"),
|
||||
},
|
||||
response: RetVal("performance-recording"),
|
||||
}),
|
||||
|
||||
isRecording: actorBridge("isRecording", {
|
||||
response: { isRecording: RetVal("boolean") }
|
||||
}),
|
||||
|
||||
getRecordings: actorBridge("getRecordings", {
|
||||
response: { recordings: RetVal("array:performance-recording") }
|
||||
}),
|
||||
|
||||
getConfiguration: actorBridge("getConfiguration", {
|
||||
response: { config: RetVal("json") }
|
||||
}),
|
||||
|
||||
setProfilerStatusInterval: actorBridge("setProfilerStatusInterval", {
|
||||
request: { interval: Arg(0, "number") },
|
||||
response: { oneway: true }
|
||||
}),
|
||||
|
||||
/**
|
||||
* Filter which events get piped to the front.
|
||||
*/
|
||||
_onRecorderEvent: function (eventName, ...data) {
|
||||
// If this is a recording state change, call
|
||||
// a method on the related PerformanceRecordingActor so it can
|
||||
// update its internal state.
|
||||
if (RECORDING_STATE_CHANGE_EVENTS.has(eventName)) {
|
||||
let recording = data[0];
|
||||
let extraData = data[1];
|
||||
recording._setState(eventName, extraData);
|
||||
}
|
||||
|
||||
if (PIPE_TO_FRONT_EVENTS.has(eventName)) {
|
||||
events.emit(this, eventName, ...data);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
exports.createPerformanceFront = function createPerformanceFront (target) {
|
||||
// If we force legacy mode, or the server does not have a performance actor (< Fx42),
|
||||
// use our LegacyPerformanceFront which will handle
|
||||
// the communication over RDP to other underlying actors.
|
||||
if (target.TEST_PERFORMANCE_LEGACY_FRONT || !target.form.performanceActor) {
|
||||
return new LegacyPerformanceFront(target);
|
||||
}
|
||||
// If our server has a PerformanceActor implementation, set this
|
||||
// up like a normal front.
|
||||
return new PerformanceFront(target.client, target.form);
|
||||
};
|
||||
|
||||
const PerformanceFront = exports.PerformanceFront = protocol.FrontClass(PerformanceActor, {
|
||||
initialize: function (client, form) {
|
||||
protocol.Front.prototype.initialize.call(this, client, form);
|
||||
this.actorID = form.performanceActor;
|
||||
this.manage(this);
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
protocol.Front.prototype.destroy.call(this);
|
||||
},
|
||||
|
||||
connect: custom(function () {
|
||||
return this._connect().then(traits => this._traits = traits);
|
||||
}, {
|
||||
impl: "_connect"
|
||||
}),
|
||||
|
||||
get traits() {
|
||||
if (!this._traits) {
|
||||
Cu.reportError("Cannot access traits of PerformanceFront before calling `connect()`.");
|
||||
}
|
||||
return this._traits;
|
||||
},
|
||||
|
||||
/**
|
||||
* Pass in a PerformanceRecording and get a normalized value from 0 to 1 of how much
|
||||
* of this recording's lifetime remains without being overwritten.
|
||||
*
|
||||
* @param {PerformanceRecording} recording
|
||||
* @return {number?}
|
||||
*/
|
||||
getBufferUsageForRecording: function (recording) {
|
||||
if (!recording.isRecording()) {
|
||||
return void 0;
|
||||
}
|
||||
let { position: currentPosition, totalSize, generation: currentGeneration } = this._currentBufferStatus;
|
||||
let { position: origPosition, generation: origGeneration } = recording.getStartingBufferStatus();
|
||||
|
||||
let normalizedCurrent = (totalSize * (currentGeneration - origGeneration)) + currentPosition;
|
||||
let percent = (normalizedCurrent - origPosition) / totalSize;
|
||||
return percent > 1 ? 1 : percent;
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads a recording from a file.
|
||||
*
|
||||
* @param {nsILocalFile} file
|
||||
* The file to import the data from.
|
||||
* @return {Promise<PerformanceRecordingFront>}
|
||||
*/
|
||||
importRecording: function (file) {
|
||||
return PerformanceIO.loadRecordingFromFile(file).then(recordingData => {
|
||||
let model = new PerformanceRecordingFront();
|
||||
model._imported = true;
|
||||
model._label = recordingData.label || "";
|
||||
model._duration = recordingData.duration;
|
||||
model._markers = recordingData.markers;
|
||||
model._frames = recordingData.frames;
|
||||
model._memory = recordingData.memory;
|
||||
model._ticks = recordingData.ticks;
|
||||
model._allocations = recordingData.allocations;
|
||||
model._profile = recordingData.profile;
|
||||
model._configuration = recordingData.configuration || {};
|
||||
return model;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Store profiler status when the position has been update so we can
|
||||
* calculate recording's buffer percentage usage after emitting the event.
|
||||
*/
|
||||
_onProfilerStatus: preEvent("profiler-status", function (data) {
|
||||
this._currentBufferStatus = data;
|
||||
}),
|
||||
|
||||
/**
|
||||
* For all PerformanceRecordings that are recording, and needing realtime markers,
|
||||
* apply the timeline data to the front PerformanceRecording (so we only have one event
|
||||
* for each timeline data chunk as they could be shared amongst several recordings).
|
||||
*/
|
||||
_onTimelineEvent: preEvent("timeline-data", function (type, data, recordings) {
|
||||
for (let recording of recordings) {
|
||||
recording._addTimelineData(type, data);
|
||||
}
|
||||
}),
|
||||
});
|
@ -534,6 +534,11 @@ var DebuggerServer = {
|
||||
constructor: "ProfilerActor",
|
||||
type: { tab: true }
|
||||
});
|
||||
this.registerModule("devtools/server/actors/performance", {
|
||||
prefix: "performance",
|
||||
constructor: "PerformanceActor",
|
||||
type: { tab: true }
|
||||
});
|
||||
}
|
||||
this.registerModule("devtools/server/actors/animation", {
|
||||
prefix: "animations",
|
||||
|
@ -76,6 +76,8 @@ EXTRA_JS_MODULES.devtools.server.actors += [
|
||||
'actors/monitor.js',
|
||||
'actors/object.js',
|
||||
'actors/performance-entries.js',
|
||||
'actors/performance-recording.js',
|
||||
'actors/performance.js',
|
||||
'actors/preference.js',
|
||||
'actors/pretty-print-worker.js',
|
||||
'actors/profiler.js',
|
||||
|
@ -5,6 +5,7 @@ subsuite = devtools
|
||||
support-files =
|
||||
head.js
|
||||
animation.html
|
||||
doc_perf.html
|
||||
navigate-first.html
|
||||
navigate-second.html
|
||||
storage-dynamic-windows.html
|
||||
@ -40,6 +41,20 @@ support-files =
|
||||
[browser_canvasframe_helper_05.js]
|
||||
[browser_canvasframe_helper_06.js]
|
||||
[browser_navigateEvents.js]
|
||||
[browser_perf-legacy-front-01.js]
|
||||
[browser_perf-legacy-front-02.js]
|
||||
[browser_perf-legacy-front-03.js]
|
||||
[browser_perf-profiler-01.js]
|
||||
[browser_perf-profiler-02.js]
|
||||
[browser_perf-profiler-03.js]
|
||||
[browser_perf-realtime-markers.js]
|
||||
[browser_perf-recording-actor-01.js]
|
||||
[browser_perf-recording-actor-02.js]
|
||||
[browser_perf-samples-01.js]
|
||||
[browser_perf-samples-02.js]
|
||||
#[browser_perf-front-profiler-01.js] bug 1077464
|
||||
#[browser_perf-front-profiler-05.js] bug 1077464
|
||||
#[browser_perf-front-profiler-06.js]
|
||||
[browser_storage_dynamic_windows.js]
|
||||
[browser_storage_listings.js]
|
||||
[browser_storage_updates.js]
|
||||
|
@ -0,0 +1,79 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test basic functionality of PerformanceFront with mock memory and timeline actors.
|
||||
*/
|
||||
|
||||
let WAIT_TIME = 100;
|
||||
|
||||
const { TargetFactory } = require("devtools/framework/target");
|
||||
const { LegacyPerformanceFront } = require("devtools/toolkit/performance/legacy/front");
|
||||
const { defer } = require("sdk/core/promise");
|
||||
const { merge } = require("sdk/util/object");
|
||||
|
||||
add_task(function*() {
|
||||
let tab = yield getTab(MAIN_DOMAIN + "doc_perf.html");
|
||||
let target = TargetFactory.forTab(tab);
|
||||
yield target.makeRemote();
|
||||
|
||||
merge(target, {
|
||||
TEST_MOCK_TIMELINE_ACTOR: true,
|
||||
TEST_PERFORMANCE_LEGACY_FRONT: true,
|
||||
});
|
||||
|
||||
let front = new LegacyPerformanceFront(target);
|
||||
yield front.connect();
|
||||
|
||||
ok(front.LEGACY_FRONT, true, "Using legacy front");
|
||||
front.on("timeline-data", () => ok(false, "There should not be any timeline-data events when mocked"));
|
||||
|
||||
let recording = yield front.startRecording({
|
||||
withTicks: true,
|
||||
withMarkers: true,
|
||||
withMemory: true,
|
||||
withAllocations: true,
|
||||
});
|
||||
|
||||
is(recording.getConfiguration().withMarkers, false, "overrides withMarkers based off of actor support");
|
||||
is(recording.getConfiguration().withTicks, false, "overrides withTicks based off of actor support");
|
||||
is(recording.getConfiguration().withMemory, false, "overrides withMemory based off of actor support");
|
||||
is(recording.getConfiguration().withAllocations, false, "overrides withAllocations based off of actor support");
|
||||
|
||||
yield busyWait(WAIT_TIME);
|
||||
|
||||
yield front.stopRecording(recording);
|
||||
|
||||
ok(typeof recording.getDuration() === "number",
|
||||
"The front.stopRecording() allows recording to get a duration.");
|
||||
ok(recording.getDuration() >= 0, "duration is a positive number");
|
||||
isEmptyArray(recording.getMarkers(), "markers");
|
||||
isEmptyArray(recording.getTicks(), "ticks");
|
||||
isEmptyArray(recording.getMemory(), "memory");
|
||||
isEmptyArray(recording.getAllocations().sites, "allocations.sites");
|
||||
isEmptyArray(recording.getAllocations().timestamps, "allocations.timestamps");
|
||||
isEmptyArray(recording.getAllocations().frames, "allocations.frames");
|
||||
ok(recording.getProfile().threads[0].samples.data.length, "profile data has some samples");
|
||||
|
||||
yield front.destroy();
|
||||
yield closeDebuggerClient(target.client);
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
function isEmptyArray (array, name) {
|
||||
ok(Array.isArray(array), `${name} is an array`);
|
||||
ok(array.length === 0, `${name} is empty`);
|
||||
}
|
||||
|
||||
function getTab (url) {
|
||||
let tab = gBrowser.selectedTab = gBrowser.addTab();
|
||||
let loaded = once(gBrowser.selectedBrowser, "load", true);
|
||||
|
||||
content.location = url;
|
||||
return loaded.then(() => {
|
||||
return new Promise(resolve => {
|
||||
let isBlank = url == "about:blank";
|
||||
waitForFocus(() => resolve(tab), content, isBlank);
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test basic functionality of PerformanceFront without a mock Timeline actor.
|
||||
*/
|
||||
|
||||
let WAIT_TIME = 100;
|
||||
|
||||
const { TargetFactory } = require("devtools/framework/target");
|
||||
const { LegacyPerformanceFront } = require("devtools/toolkit/performance/legacy/front");
|
||||
const { defer } = require("sdk/core/promise");
|
||||
const { merge } = require("sdk/util/object");
|
||||
|
||||
add_task(function*() {
|
||||
let tab = yield getTab(MAIN_DOMAIN + "doc_perf.html");
|
||||
let target = TargetFactory.forTab(tab);
|
||||
yield target.makeRemote();
|
||||
|
||||
merge(target, {
|
||||
TEST_PERFORMANCE_LEGACY_FRONT: true,
|
||||
});
|
||||
|
||||
let front = new LegacyPerformanceFront(target);
|
||||
yield front.connect();
|
||||
|
||||
ok(front.LEGACY_FRONT, true, "Using legacy front");
|
||||
|
||||
let recording = yield front.startRecording({
|
||||
withTicks: true,
|
||||
withMarkers: true,
|
||||
withMemory: true,
|
||||
withAllocations: true,
|
||||
});
|
||||
|
||||
is(recording.getConfiguration().withMarkers, true, "allows withMarkers based off of actor support");
|
||||
is(recording.getConfiguration().withTicks, true, "allows withTicks based off of actor support");
|
||||
is(recording.getConfiguration().withMemory, false, "overrides withMemory based off of actor support");
|
||||
is(recording.getConfiguration().withAllocations, false, "overrides withAllocations based off of actor support");
|
||||
|
||||
yield waitUntil(() => recording.getMarkers().length);
|
||||
yield waitUntil(() => recording.getTicks().length);
|
||||
|
||||
yield front.stopRecording(recording);
|
||||
|
||||
ok(recording.getMarkers().length, "we have several markers");
|
||||
ok(recording.getTicks().length, "we have several ticks");
|
||||
|
||||
ok(typeof recording.getDuration() === "number",
|
||||
"The front.stopRecording() allows recording to get a duration.");
|
||||
ok(recording.getDuration() >= 0, "duration is a positive number");
|
||||
isEmptyArray(recording.getMemory(), "memory");
|
||||
isEmptyArray(recording.getAllocations().sites, "allocations.sites");
|
||||
isEmptyArray(recording.getAllocations().timestamps, "allocations.timestamps");
|
||||
isEmptyArray(recording.getAllocations().frames, "allocations.frames");
|
||||
ok(recording.getProfile().threads[0].samples.data.length, "profile data has some samples");
|
||||
|
||||
yield front.destroy();
|
||||
yield closeDebuggerClient(target.client);
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
function isEmptyArray (array, name) {
|
||||
ok(Array.isArray(array), `${name} is an array`);
|
||||
ok(array.length === 0, `${name} is empty`);
|
||||
}
|
||||
|
||||
function getTab (url) {
|
||||
let tab = gBrowser.selectedTab = gBrowser.addTab();
|
||||
let loaded = once(gBrowser.selectedBrowser, "load", true);
|
||||
|
||||
content.location = url;
|
||||
return loaded.then(() => {
|
||||
return new Promise(resolve => {
|
||||
let isBlank = url == "about:blank";
|
||||
waitForFocus(() => resolve(tab), content, isBlank);
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that when using an older server (< Fx40) where the profiler actor does not
|
||||
* have the `getBufferInfo` method that nothing breaks and RecordingModels have null
|
||||
* `getBufferUsage()` values.
|
||||
*/
|
||||
|
||||
const { TargetFactory } = require("devtools/framework/target");
|
||||
const { LegacyPerformanceFront } = require("devtools/toolkit/performance/legacy/front");
|
||||
const { merge } = require("sdk/util/object");
|
||||
|
||||
add_task(function*() {
|
||||
let tab = yield getTab(MAIN_DOMAIN + "doc_perf.html");
|
||||
let target = TargetFactory.forTab(tab);
|
||||
yield target.makeRemote();
|
||||
|
||||
merge(target, {
|
||||
TEST_PROFILER_FILTER_STATUS: ["position", "totalSize", "generation"],
|
||||
TEST_PERFORMANCE_LEGACY_FRONT: true,
|
||||
});
|
||||
|
||||
let front = new LegacyPerformanceFront(target);
|
||||
yield front.connect();
|
||||
front.setProfilerStatusInterval(10);
|
||||
|
||||
front.on("profiler-status", () => ok(false, "profiler-status should not be called when not supported"));
|
||||
let model = yield front.startRecording();
|
||||
|
||||
yield busyWait(100);
|
||||
is(front.getBufferUsageForRecording(model), null, "buffer usage for recording should be null");
|
||||
|
||||
yield front.stopRecording(model);
|
||||
yield front.destroy();
|
||||
yield closeDebuggerClient(target.client);
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
function getTab (url) {
|
||||
let tab = gBrowser.selectedTab = gBrowser.addTab();
|
||||
let loaded = once(gBrowser.selectedBrowser, "load", true);
|
||||
|
||||
content.location = url;
|
||||
return loaded.then(() => {
|
||||
return new Promise(resolve => {
|
||||
let isBlank = url == "about:blank";
|
||||
waitForFocus(() => resolve(tab), content, isBlank);
|
||||
});
|
||||
});
|
||||
}
|
@ -7,35 +7,37 @@
|
||||
* a recording is stopped.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
|
||||
let front = panel.panelWin.gFront;
|
||||
loadFrameScripts();
|
||||
const { PerformanceFront } = require("devtools/server/actors/performance");
|
||||
const { PMM_isProfilerActive, PMM_stopProfiler, PMM_loadProfilerScripts } = require("devtools/toolkit/shared/profiler");
|
||||
|
||||
add_task(function*() {
|
||||
let doc = yield addTab(MAIN_DOMAIN + "doc_perf.html");
|
||||
|
||||
initDebuggerServer();
|
||||
let client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
let form = yield connectDebuggerClient(client);
|
||||
let front = PerformanceFront(client, form);
|
||||
yield front.connect();
|
||||
|
||||
PMM_loadProfilerScripts(gBrowser);
|
||||
|
||||
ok(!(yield PMM_isProfilerActive()),
|
||||
"The built-in profiler module should not have been automatically started.");
|
||||
|
||||
let activated = front.once("profiler-activated");
|
||||
let rec = yield front.startRecording();
|
||||
yield activated;
|
||||
yield front.stopRecording(rec);
|
||||
ok((yield PMM_isProfilerActive()),
|
||||
"The built-in profiler module should still be active (1).");
|
||||
|
||||
let alreadyActive = front.once("profiler-already-active");
|
||||
rec = yield front.startRecording();
|
||||
yield alreadyActive;
|
||||
yield front.stopRecording(rec);
|
||||
ok((yield PMM_isProfilerActive()),
|
||||
"The built-in profiler module should still be active (2).");
|
||||
|
||||
// Manually tear down so we can check profiler status
|
||||
let tab = panel.target.tab;
|
||||
yield panel._toolbox.destroy();
|
||||
yield closeDebuggerClient(client);
|
||||
|
||||
ok(!(yield PMM_isProfilerActive()),
|
||||
"The built-in profiler module should no longer be active.");
|
||||
yield removeTab(tab);
|
||||
tab = null;
|
||||
|
||||
finish();
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
@ -0,0 +1,44 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the built-in profiler module doesn't deactivate when the toolbox
|
||||
* is destroyed if there are other consumers using it.
|
||||
*/
|
||||
|
||||
const { PerformanceFront } = require("devtools/server/actors/performance");
|
||||
const { PMM_isProfilerActive, PMM_stopProfiler, PMM_loadProfilerScripts } = require("devtools/toolkit/shared/profiler");
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab(MAIN_DOMAIN + "doc_perf.html");
|
||||
initDebuggerServer();
|
||||
let client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
let form = yield connectDebuggerClient(client);
|
||||
let firstFront = PerformanceFront(client, form);
|
||||
yield firstFront.connect();
|
||||
|
||||
PMM_loadProfilerScripts(gBrowser);
|
||||
|
||||
yield firstFront.startRecording();
|
||||
|
||||
yield addTab(MAIN_DOMAIN + "doc_perf.html");
|
||||
let client2 = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
let form2 = yield connectDebuggerClient(client2);
|
||||
let secondFront = PerformanceFront(client2, form2);
|
||||
yield secondFront.connect();
|
||||
PMM_loadProfilerScripts(gBrowser);
|
||||
|
||||
yield secondFront.startRecording();
|
||||
|
||||
// Manually teardown the tabs so we can check profiler status
|
||||
yield closeDebuggerClient(client2);
|
||||
ok((yield PMM_isProfilerActive()),
|
||||
"The built-in profiler module should still be active.");
|
||||
|
||||
yield closeDebuggerClient(client);
|
||||
ok(!(yield PMM_isProfilerActive()),
|
||||
"The built-in profiler module should no longer be active.");
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
@ -0,0 +1,50 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the built-in profiler module is not reactivated if no other
|
||||
* consumer was using it over the remote debugger protocol, and ensures
|
||||
* that the actor will work properly even in such cases (e.g. the Gecko Profiler
|
||||
* addon was installed and automatically activated the profiler module).
|
||||
*/
|
||||
|
||||
const { PerformanceFront } = require("devtools/server/actors/performance");
|
||||
const { sendProfilerCommand, PMM_isProfilerActive, PMM_stopProfiler, PMM_loadProfilerScripts } = require("devtools/toolkit/shared/profiler");
|
||||
|
||||
add_task(function*() {
|
||||
// Ensure the profiler is already running when the test starts.
|
||||
PMM_loadProfilerScripts(gBrowser);
|
||||
let ENTRIES = 1000000;
|
||||
let INTERVAL = 1;
|
||||
let FEATURES = ["js"];
|
||||
yield sendProfilerCommand("StartProfiler", [ENTRIES, INTERVAL, FEATURES, FEATURES.length]);
|
||||
|
||||
ok((yield PMM_isProfilerActive()),
|
||||
"The built-in profiler module should still be active.");
|
||||
|
||||
yield addTab(MAIN_DOMAIN + "doc_perf.html");
|
||||
initDebuggerServer();
|
||||
let client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
let form = yield connectDebuggerClient(client);
|
||||
let firstFront = PerformanceFront(client, form);
|
||||
yield firstFront.connect();
|
||||
|
||||
let recording = yield firstFront.startRecording();
|
||||
|
||||
yield addTab(MAIN_DOMAIN + "doc_perf.html");
|
||||
let client2 = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
let form2 = yield connectDebuggerClient(client2);
|
||||
let secondFront = PerformanceFront(client2, form2);
|
||||
yield secondFront.connect();
|
||||
|
||||
yield closeDebuggerClient(client2);
|
||||
ok((yield PMM_isProfilerActive()),
|
||||
"The built-in profiler module should still be active.");
|
||||
|
||||
yield closeDebuggerClient(client);
|
||||
ok(!(yield PMM_isProfilerActive()),
|
||||
"The built-in profiler module should have been automatically stopped.");
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
@ -2,11 +2,20 @@
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test basic functionality of PerformanceFront, retrieving timeline data.
|
||||
* Test functionality of real time markers.
|
||||
*/
|
||||
|
||||
function* spawnTest() {
|
||||
let { target, front } = yield initBackend(SIMPLE_URL);
|
||||
const { PerformanceFront } = require("devtools/server/actors/performance");
|
||||
const { defer } = require("sdk/core/promise");
|
||||
|
||||
add_task(function*() {
|
||||
let doc = yield addTab(MAIN_DOMAIN + "doc_perf.html");
|
||||
|
||||
initDebuggerServer();
|
||||
let client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
let form = yield connectDebuggerClient(client);
|
||||
let front = PerformanceFront(client, form);
|
||||
yield front.connect();
|
||||
|
||||
let lastMemoryDelta = 0;
|
||||
let lastTickDelta = 0;
|
||||
@ -18,41 +27,37 @@ function* spawnTest() {
|
||||
};
|
||||
|
||||
let deferreds = {
|
||||
markers: Promise.defer(),
|
||||
memory: Promise.defer(),
|
||||
ticks: Promise.defer()
|
||||
markers: defer(),
|
||||
memory: defer(),
|
||||
ticks: defer()
|
||||
};
|
||||
|
||||
front.on("timeline-data", handler);
|
||||
|
||||
yield front.startRecording({ withMarkers: true, withMemory: true, withTicks: true });
|
||||
let rec = yield front.startRecording({ withMarkers: true, withMemory: true, withTicks: true });
|
||||
yield Promise.all(Object.keys(deferreds).map(type => deferreds[type].promise));
|
||||
yield front.stopRecording();
|
||||
yield front.stopRecording(rec);
|
||||
front.off("timeline-data", handler);
|
||||
|
||||
is(counters.markers.length, 1, "one marker event fired.");
|
||||
is(counters.memory.length, 3, "three memory events fired.");
|
||||
is(counters.ticks.length, 3, "three ticks events fired.");
|
||||
|
||||
// Destroy the front before removing tab to ensure no
|
||||
// lingering requests
|
||||
yield front.destroy();
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
yield closeDebuggerClient(client);
|
||||
gBrowser.removeCurrentTab();
|
||||
|
||||
function handler (_, name, ...args) {
|
||||
function handler (name, data) {
|
||||
if (name === "markers") {
|
||||
if (counters.markers.length >= 1) { return; }
|
||||
let [markers] = args;
|
||||
ok(markers[0].start, "received atleast one marker with `start`");
|
||||
ok(markers[0].end, "received atleast one marker with `end`");
|
||||
ok(markers[0].name, "received atleast one marker with `name`");
|
||||
ok(data.markers[0].start, "received atleast one marker with `start`");
|
||||
ok(data.markers[0].end, "received atleast one marker with `end`");
|
||||
ok(data.markers[0].name, "received atleast one marker with `name`");
|
||||
|
||||
counters.markers.push(markers);
|
||||
counters.markers.push(data.markers);
|
||||
}
|
||||
else if (name === "memory") {
|
||||
if (counters.memory.length >= 3) { return; }
|
||||
let [delta, measurement] = args;
|
||||
let { delta, measurement } = data;
|
||||
is(typeof delta, "number", "received `delta` in memory event");
|
||||
ok(delta > lastMemoryDelta, "received `delta` in memory event");
|
||||
ok(measurement.total, "received `total` in memory event");
|
||||
@ -62,7 +67,7 @@ function* spawnTest() {
|
||||
}
|
||||
else if (name === "ticks") {
|
||||
if (counters.ticks.length >= 3) { return; }
|
||||
let [delta, timestamps] = args;
|
||||
let { delta, timestamps } = data;
|
||||
ok(delta > lastTickDelta, "received `delta` in ticks event");
|
||||
|
||||
// Timestamps aren't guaranteed to always contain tick events, since
|
||||
@ -84,4 +89,4 @@ function* spawnTest() {
|
||||
deferreds[name].resolve();
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
@ -6,9 +6,16 @@
|
||||
* completed, and rec data.
|
||||
*/
|
||||
|
||||
function* spawnTest() {
|
||||
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, gFront: front, PerformanceController } = panel.panelWin;
|
||||
const { PerformanceFront } = require("devtools/server/actors/performance");
|
||||
|
||||
add_task(function*() {
|
||||
let doc = yield addTab(MAIN_DOMAIN + "doc_perf.html");
|
||||
|
||||
initDebuggerServer();
|
||||
let client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
let form = yield connectDebuggerClient(client);
|
||||
let front = PerformanceFront(client, form);
|
||||
yield front.connect();
|
||||
|
||||
let rec = yield front.startRecording({ withMarkers: true, withTicks: true, withMemory: true });
|
||||
ok(rec.isRecording(), "RecordingModel is recording when created");
|
||||
@ -35,6 +42,7 @@ function* spawnTest() {
|
||||
ok(rec.isCompleted(), "recording is completed once it has profile data");
|
||||
} else {
|
||||
ok(!rec.isCompleted(), "recording is not yet completed on 'recording-stopping'");
|
||||
ok(rec.isFinalizing(), "recording is considered finalizing between 'recording-stopping' and 'recording-stopped'");
|
||||
}
|
||||
|
||||
yield stopped;
|
||||
@ -44,19 +52,14 @@ function* spawnTest() {
|
||||
// Export and import a rec, and ensure it has the correct state.
|
||||
let file = FileUtils.getFile("TmpD", ["tmpprofile.json"]);
|
||||
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
|
||||
let exported = once(PerformanceController, EVENTS.RECORDING_EXPORTED);
|
||||
yield PerformanceController.exportRecording("", rec, file);
|
||||
yield exported;
|
||||
yield rec.exportRecording(file);
|
||||
|
||||
let imported = once(PerformanceController, EVENTS.RECORDING_IMPORTED);
|
||||
yield PerformanceController.importRecording("", file);
|
||||
|
||||
yield imported;
|
||||
let importedModel = PerformanceController.getCurrentRecording();
|
||||
let importedModel = yield front.importRecording(file);
|
||||
|
||||
ok(importedModel.isCompleted(), "All imported recordings should be completed");
|
||||
ok(!importedModel.isRecording(), "All imported recordings should not be recording");
|
||||
ok(importedModel.isImported(), "All imported recordings should be considerd imported");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
||||
yield closeDebuggerClient(client);
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
@ -6,13 +6,22 @@
|
||||
*/
|
||||
|
||||
let BUFFER_SIZE = 20000;
|
||||
let config = { bufferSize: BUFFER_SIZE };
|
||||
|
||||
function* spawnTest() {
|
||||
let { target, front } = yield initBackend(SIMPLE_URL, { TEST_MOCK_PROFILER_CHECK_TIMER: 10 });
|
||||
let config = { bufferSize: BUFFER_SIZE };
|
||||
const { PerformanceFront } = require("devtools/server/actors/performance");
|
||||
|
||||
add_task(function*() {
|
||||
let doc = yield addTab(MAIN_DOMAIN + "doc_perf.html");
|
||||
|
||||
initDebuggerServer();
|
||||
let client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
let form = yield connectDebuggerClient(client);
|
||||
let front = PerformanceFront(client, form);
|
||||
yield front.connect();
|
||||
|
||||
yield front.setProfilerStatusInterval(10);
|
||||
let model = yield front.startRecording(config);
|
||||
let [_, stats] = yield onceSpread(front, "profiler-status");
|
||||
let stats = yield once(front, "profiler-status");
|
||||
is(stats.totalSize, BUFFER_SIZE, `profiler-status event has totalSize: ${stats.totalSize}`);
|
||||
ok(stats.position < BUFFER_SIZE, `profiler-status event has position: ${stats.position}`);
|
||||
ok(stats.generation >= 0, `profiler-status event has generation: ${stats.generation}`);
|
||||
@ -25,7 +34,7 @@ function* spawnTest() {
|
||||
let lastBufferStatus = 0;
|
||||
let checkCount = 0;
|
||||
while (lastBufferStatus < 1) {
|
||||
let currentBufferStatus = model.getBufferUsage();
|
||||
let currentBufferStatus = front.getBufferUsageForRecording(model);
|
||||
ok(currentBufferStatus > lastBufferStatus, `buffer is more filled than before: ${currentBufferStatus} > ${lastBufferStatus}`);
|
||||
lastBufferStatus = currentBufferStatus;
|
||||
checkCount++;
|
||||
@ -36,11 +45,8 @@ function* spawnTest() {
|
||||
is(lastBufferStatus, 1, "buffer usage cannot surpass 100%");
|
||||
yield front.stopRecording(model);
|
||||
|
||||
is(model.getBufferUsage(), null, "getBufferUsage() should be null when no longer recording.");
|
||||
is(front.getBufferUsageForRecording(model), null, "buffer usage should be null when no longer recording.");
|
||||
|
||||
// Destroy the front before removing tab to ensure no
|
||||
// lingering requests
|
||||
yield front.destroy();
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
||||
yield closeDebuggerClient(client);
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
@ -8,14 +8,21 @@
|
||||
|
||||
const WAIT_TIME = 1000; // ms
|
||||
|
||||
function* spawnTest() {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let front = panel.panelWin.gFront;
|
||||
const { PerformanceFront } = require("devtools/server/actors/performance");
|
||||
|
||||
add_task(function*() {
|
||||
let doc = yield addTab(MAIN_DOMAIN + "doc_perf.html");
|
||||
|
||||
initDebuggerServer();
|
||||
let client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
let form = yield connectDebuggerClient(client);
|
||||
let front = PerformanceFront(client, form);
|
||||
yield front.connect();
|
||||
|
||||
// Perform the first recording...
|
||||
|
||||
let firstRecording = yield front.startRecording();
|
||||
let firstRecordingStartTime = firstRecording._profilerStartTime;
|
||||
let firstRecordingStartTime = firstRecording._startTime;
|
||||
info("Started profiling at: " + firstRecordingStartTime);
|
||||
|
||||
busyWait(WAIT_TIME); // allow the profiler module to sample some cpu activity
|
||||
@ -28,7 +35,7 @@ function* spawnTest() {
|
||||
// Perform the second recording...
|
||||
|
||||
let secondRecording = yield front.startRecording();
|
||||
let secondRecordingStartTime = secondRecording._profilerStartTime;
|
||||
let secondRecordingStartTime = secondRecording._startTime;
|
||||
info("Started profiling at: " + secondRecordingStartTime);
|
||||
|
||||
busyWait(WAIT_TIME); // allow the profiler module to sample more cpu activity
|
||||
@ -49,6 +56,6 @@ function* spawnTest() {
|
||||
"There should be no samples from the first recording in the second one, " +
|
||||
"even though the total number of frames did not overflow.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
||||
yield closeDebuggerClient(client);
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
@ -0,0 +1,75 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the retrieved profiler data samples always have a (root) node.
|
||||
* If this ever changes, the |ThreadNode.prototype.insert| function in
|
||||
* browser/devtools/performance/modules/logic/tree-model.js will have to be changed.
|
||||
*/
|
||||
|
||||
const WAIT_TIME = 1000; // ms
|
||||
|
||||
const { PerformanceFront } = require("devtools/server/actors/performance");
|
||||
|
||||
add_task(function*() {
|
||||
let doc = yield addTab(MAIN_DOMAIN + "doc_perf.html");
|
||||
|
||||
initDebuggerServer();
|
||||
let client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
let form = yield connectDebuggerClient(client);
|
||||
let front = PerformanceFront(client, form);
|
||||
yield front.connect();
|
||||
|
||||
let rec = yield front.startRecording();
|
||||
busyWait(WAIT_TIME); // allow the profiler module to sample some cpu activity
|
||||
|
||||
yield front.stopRecording(rec);
|
||||
let profile = rec.getProfile();
|
||||
let sampleCount = 0;
|
||||
|
||||
for (let thread of profile.threads) {
|
||||
info("Checking thread: " + thread.name);
|
||||
|
||||
for (let sample of thread.samples.data) {
|
||||
sampleCount++;
|
||||
|
||||
let stack = getInflatedStackLocations(thread, sample);
|
||||
if (stack[0] != "(root)") {
|
||||
ok(false, "The sample " + stack.toSource() + " doesn't have a root node.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ok(sampleCount > 0,
|
||||
"At least some samples have been iterated over, checking for root nodes.");
|
||||
|
||||
yield closeDebuggerClient(client);
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
||||
|
||||
/**
|
||||
* Inflate a particular sample's stack and return an array of strings.
|
||||
*/
|
||||
function getInflatedStackLocations(thread, sample) {
|
||||
let stackTable = thread.stackTable;
|
||||
let frameTable = thread.frameTable;
|
||||
let stringTable = thread.stringTable;
|
||||
let SAMPLE_STACK_SLOT = thread.samples.schema.stack;
|
||||
let STACK_PREFIX_SLOT = stackTable.schema.prefix;
|
||||
let STACK_FRAME_SLOT = stackTable.schema.frame;
|
||||
let FRAME_LOCATION_SLOT = frameTable.schema.location;
|
||||
|
||||
// Build the stack from the raw data and accumulate the locations in
|
||||
// an array.
|
||||
let stackIndex = sample[SAMPLE_STACK_SLOT];
|
||||
let locations = [];
|
||||
while (stackIndex !== null) {
|
||||
let stackEntry = stackTable.data[stackIndex];
|
||||
let frame = frameTable.data[stackEntry[STACK_FRAME_SLOT]];
|
||||
locations.push(stringTable[frame[FRAME_LOCATION_SLOT]]);
|
||||
stackIndex = stackEntry[STACK_PREFIX_SLOT];
|
||||
}
|
||||
|
||||
// The profiler tree is inverted, so reverse the array.
|
||||
return locations.reverse();
|
||||
}
|
25
toolkit/devtools/server/tests/browser/doc_perf.html
Normal file
25
toolkit/devtools/server/tests/browser/doc_perf.html
Normal file
@ -0,0 +1,25 @@
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Performance test page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
var x = 1;
|
||||
function test() {
|
||||
document.body.style.borderTop = x + "px solid red";
|
||||
x = 1^x;
|
||||
document.body.innerHeight; // flush pending reflows
|
||||
}
|
||||
|
||||
// Prevent this script from being garbage collected.
|
||||
window.setInterval(test, 1);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -11,6 +11,7 @@ const {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
|
||||
const {require} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
const {DebuggerClient} = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
|
||||
const {DebuggerServer} = require("devtools/server/main");
|
||||
const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
|
||||
|
||||
const PATH = "browser/toolkit/devtools/server/tests/browser/";
|
||||
const MAIN_DOMAIN = "http://test1.example.org/" + PATH;
|
||||
@ -137,3 +138,34 @@ registerCleanupFunction(function tearDown() {
|
||||
gBrowser.removeCurrentTab();
|
||||
}
|
||||
});
|
||||
|
||||
function idleWait(time) {
|
||||
return DevToolsUtils.waitForTime(time);
|
||||
}
|
||||
|
||||
function busyWait(time) {
|
||||
let start = Date.now();
|
||||
let stack;
|
||||
while (Date.now() - start < time) { stack = Components.stack; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until a predicate returns true.
|
||||
*
|
||||
* @param function predicate
|
||||
* Invoked once in a while until it returns true.
|
||||
* @param number interval [optional]
|
||||
* How often the predicate is invoked, in milliseconds.
|
||||
*/
|
||||
function waitUntil(predicate, interval = 10) {
|
||||
if (predicate()) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
setTimeout(function() {
|
||||
waitUntil(predicate).then(() => resolve(true));
|
||||
}, interval);
|
||||
});
|
||||
}
|
||||
|
||||
// EventUtils just doesn't work!
|
||||
|
@ -6,10 +6,13 @@
|
||||
const { Cc, Ci, Cu } = require("chrome");
|
||||
const Services = require("Services");
|
||||
const { Class } = require("sdk/core/heritage");
|
||||
const FRAME_SCRIPT_UTILS_URL = "chrome://browser/content/devtools/frame-script-utils.js";
|
||||
loader.lazyRequireGetter(this, "events", "sdk/event/core");
|
||||
loader.lazyRequireGetter(this, "EventTarget", "sdk/event/target", true);
|
||||
loader.lazyRequireGetter(this, "DevToolsUtils", "devtools/toolkit/DevToolsUtils.js");
|
||||
loader.lazyRequireGetter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm", true);
|
||||
loader.lazyRequireGetter(this, "Task", "resource://gre/modules/Task.jsm", true);
|
||||
loader.lazyRequireGetter(this, "uuid", "sdk/util/uuid", true);
|
||||
|
||||
// Events piped from system observers to Profiler instances.
|
||||
const PROFILER_SYSTEM_EVENTS = [
|
||||
@ -287,6 +290,7 @@ const ProfilerManager = (function () {
|
||||
* - "console-api-profiler"
|
||||
* - "profiler-started"
|
||||
* - "profiler-stopped"
|
||||
* - "profiler-status"
|
||||
*
|
||||
* The ProfilerManager listens to all events, and individual
|
||||
* consumers filter which events they are interested in.
|
||||
@ -513,3 +517,49 @@ function sanitizeHandler (handler, identifier) {
|
||||
return handler.call(this, subject, topic, data);
|
||||
}, identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* The following functions are used in testing to control and inspect
|
||||
* the nsIProfiler in child process content. These should be called from
|
||||
* the parent process.
|
||||
*/
|
||||
let mm = null;
|
||||
exports.PMM_isProfilerActive = function () {
|
||||
return sendProfilerCommand("IsActive");
|
||||
}
|
||||
|
||||
exports.PMM_stopProfiler = function () {
|
||||
return Task.spawn(function*() {
|
||||
let isActive = (yield sendProfilerCommand("IsActive")).isActive;
|
||||
if (isActive) {
|
||||
return sendProfilerCommand("StopProfiler");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
exports.PMM_loadProfilerScripts = function (gBrowser) {
|
||||
mm = gBrowser.selectedBrowser.messageManager;
|
||||
mm.loadFrameScript(FRAME_SCRIPT_UTILS_URL, false);
|
||||
};
|
||||
|
||||
function sendProfilerCommand (method, args=[]) {
|
||||
if (!mm) {
|
||||
throw new Error("`loadFrameScripts()` must be called when using MessageManager.");
|
||||
}
|
||||
|
||||
let id = uuid().toString();
|
||||
return new Promise(resolve => {
|
||||
mm.addMessageListener("devtools:test:profiler:response", handler);
|
||||
mm.sendAsyncMessage("devtools:test:profiler", { method, args, id });
|
||||
|
||||
function handler ({ data }) {
|
||||
if (id !== data.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
mm.removeMessageListener("devtools:test:profiler:response", handler);
|
||||
resolve(data.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
exports.sendProfilerCommand = sendProfilerCommand;
|
||||
|
Loading…
Reference in New Issue
Block a user