mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1077454 - Handle import/export in new performance tool, r=jsantell
This commit is contained in:
parent
36f390802c
commit
ca34cd6763
@ -4,8 +4,8 @@
|
||||
"use strict";
|
||||
|
||||
const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
const { extend } = require("sdk/util/object");
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
const { extend } = require("sdk/util/object");
|
||||
|
||||
loader.lazyRequireGetter(this, "Services");
|
||||
loader.lazyRequireGetter(this, "promise");
|
||||
@ -20,7 +20,8 @@ loader.lazyImporter(this, "gDevTools",
|
||||
"resource:///modules/devtools/gDevTools.jsm");
|
||||
|
||||
/**
|
||||
* A cache of all PerformanceActorsConnection instances. The keys are Target objects.
|
||||
* A cache of all PerformanceActorsConnection instances.
|
||||
* The keys are Target objects.
|
||||
*/
|
||||
let SharedPerformanceActors = new WeakMap();
|
||||
|
||||
@ -42,7 +43,7 @@ SharedPerformanceActors.forTarget = function(target) {
|
||||
};
|
||||
|
||||
/**
|
||||
* A connection to underlying actors (profiler, memory, framerate, etc)
|
||||
* A connection to underlying actors (profiler, memory, framerate, etc.)
|
||||
* shared by all tools in a target.
|
||||
*
|
||||
* Use `SharedPerformanceActors.forTarget` to make sure you get the same
|
||||
@ -62,7 +63,6 @@ function PerformanceActorsConnection(target) {
|
||||
}
|
||||
|
||||
PerformanceActorsConnection.prototype = {
|
||||
|
||||
/**
|
||||
* Initializes a connection to the profiler and other miscellaneous actors.
|
||||
* If already open, nothing happens.
|
||||
@ -224,10 +224,9 @@ PerformanceFront.prototype = {
|
||||
// for all targets and interacts with the whole platform, so we don't want
|
||||
// to affect other clients by stopping (or restarting) it.
|
||||
if (!isActive) {
|
||||
// Extend the options so that protocol.js doesn't modify
|
||||
// the source object.
|
||||
let options = extend({}, this._customPerformanceOptions);
|
||||
yield this._request("profiler", "startProfiler", options);
|
||||
// Extend the profiler options so that protocol.js doesn't modify the original.
|
||||
let profilerOptions = extend({}, this._customProfilerOptions);
|
||||
yield this._request("profiler", "startProfiler", profilerOptions);
|
||||
this._profilingStartTime = 0;
|
||||
this.emit("profiler-activated");
|
||||
} else {
|
||||
@ -237,9 +236,9 @@ PerformanceFront.prototype = {
|
||||
|
||||
// The timeline actor is target-dependent, so just make sure
|
||||
// it's recording.
|
||||
|
||||
// Return start time from timeline actor
|
||||
let startTime = yield this._request("timeline", "start", options);
|
||||
|
||||
// Return only the start time from the timeline actor.
|
||||
return { startTime };
|
||||
}),
|
||||
|
||||
@ -273,7 +272,7 @@ PerformanceFront.prototype = {
|
||||
*
|
||||
* Used in tests and for older backend implementations.
|
||||
*/
|
||||
_customPerformanceOptions: {
|
||||
_customProfilerOptions: {
|
||||
entries: 1000000,
|
||||
interval: 1,
|
||||
features: ["js"]
|
||||
|
102
browser/devtools/performance/modules/io.js
Normal file
102
browser/devtools/performance/modules/io.js
Normal file
@ -0,0 +1,102 @@
|
||||
/* 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.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_VERSION = 1;
|
||||
|
||||
/**
|
||||
* 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_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(file);
|
||||
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 (recordingData.version != PERF_TOOL_SERIALIZER_VERSION) {
|
||||
deferred.reject(new Error("Unsupported recording data file version."));
|
||||
return;
|
||||
}
|
||||
deferred.resolve(recordingData);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
};
|
||||
|
||||
exports.PerformanceIO = PerformanceIO;
|
@ -5,6 +5,7 @@
|
||||
|
||||
EXTRA_JS_MODULES.devtools.performance += [
|
||||
'modules/front.js',
|
||||
'modules/io.js',
|
||||
'panel.js'
|
||||
]
|
||||
|
||||
|
@ -16,9 +16,11 @@ devtools.lazyRequireGetter(this, "EventEmitter",
|
||||
"devtools/toolkit/event-emitter");
|
||||
devtools.lazyRequireGetter(this, "DevToolsUtils",
|
||||
"devtools/toolkit/DevToolsUtils");
|
||||
|
||||
devtools.lazyRequireGetter(this, "L10N",
|
||||
"devtools/profiler/global", true);
|
||||
|
||||
devtools.lazyRequireGetter(this, "PerformanceIO",
|
||||
"devtools/performance/io", true);
|
||||
devtools.lazyRequireGetter(this, "MarkersOverview",
|
||||
"devtools/timeline/markers-overview", true);
|
||||
devtools.lazyRequireGetter(this, "MemoryOverview",
|
||||
@ -39,16 +41,24 @@ devtools.lazyImporter(this, "LineGraphWidget",
|
||||
|
||||
// Events emitted by various objects in the panel.
|
||||
const EVENTS = {
|
||||
// Emitted by the PerformanceView on record button click
|
||||
UI_START_RECORDING: "Performance:UI:StartRecording",
|
||||
UI_STOP_RECORDING: "Performance:UI:StopRecording",
|
||||
|
||||
// Emitted by the PerformanceView on import or export button click
|
||||
UI_IMPORT_RECORDING: "Performance:UI:ImportRecording",
|
||||
UI_EXPORT_RECORDING: "Performance:UI:ExportRecording",
|
||||
|
||||
// When a recording is started or stopped via the PerformanceController
|
||||
RECORDING_STARTED: "Performance:RecordingStarted",
|
||||
RECORDING_STOPPED: "Performance:RecordingStopped",
|
||||
|
||||
// When the PerformanceController has new recording data.
|
||||
TIMELINE_DATA: "Performance:TimelineData",
|
||||
// When a recording is imported or exported via the PerformanceController
|
||||
RECORDING_IMPORTED: "Performance:RecordingImported",
|
||||
RECORDING_EXPORTED: "Performance:RecordingExported",
|
||||
|
||||
// Emitted by the PerformanceView on record button click
|
||||
UI_START_RECORDING: "Performance:UI:StartRecording",
|
||||
UI_STOP_RECORDING: "Performance:UI:StopRecording",
|
||||
// When the PerformanceController has new recording data
|
||||
TIMELINE_DATA: "Performance:TimelineData",
|
||||
|
||||
// Emitted by the OverviewView when more data has been rendered
|
||||
OVERVIEW_RENDERED: "Performance:UI:OverviewRendered",
|
||||
@ -75,6 +85,11 @@ const EVENTS = {
|
||||
WATERFALL_RENDERED: "Performance:UI:WaterfallRendered"
|
||||
};
|
||||
|
||||
// Constant defining the end time for a recording that hasn't finished
|
||||
// or is not yet available.
|
||||
const RECORDING_IN_PROGRESS = -1;
|
||||
const RECORDING_UNAVAILABLE = null;
|
||||
|
||||
/**
|
||||
* The current target and the profiler connection, set by this tool's host.
|
||||
*/
|
||||
@ -128,11 +143,13 @@ let PerformanceController = {
|
||||
* Permanent storage for the markers and the memory measurements streamed by
|
||||
* the backend, along with the start and end timestamps.
|
||||
*/
|
||||
_startTime: 0,
|
||||
_endTime: 0,
|
||||
_localStartTime: RECORDING_UNAVAILABLE,
|
||||
_startTime: RECORDING_UNAVAILABLE,
|
||||
_endTime: RECORDING_UNAVAILABLE,
|
||||
_markers: [],
|
||||
_memory: [],
|
||||
_ticks: [],
|
||||
_profilerData: {},
|
||||
|
||||
/**
|
||||
* Listen for events emitted by the current tab target and
|
||||
@ -141,10 +158,15 @@ let PerformanceController = {
|
||||
initialize: function() {
|
||||
this.startRecording = this.startRecording.bind(this);
|
||||
this.stopRecording = this.stopRecording.bind(this);
|
||||
this.importRecording = this.importRecording.bind(this);
|
||||
this.exportRecording = this.exportRecording.bind(this);
|
||||
this._onTimelineData = this._onTimelineData.bind(this);
|
||||
|
||||
PerformanceView.on(EVENTS.UI_START_RECORDING, this.startRecording);
|
||||
PerformanceView.on(EVENTS.UI_STOP_RECORDING, this.stopRecording);
|
||||
PerformanceView.on(EVENTS.UI_EXPORT_RECORDING, this.exportRecording);
|
||||
PerformanceView.on(EVENTS.UI_IMPORT_RECORDING, this.importRecording);
|
||||
|
||||
gFront.on("ticks", this._onTimelineData); // framerate
|
||||
gFront.on("markers", this._onTimelineData); // timeline markers
|
||||
gFront.on("memory", this._onTimelineData); // timeline memory
|
||||
@ -156,6 +178,9 @@ let PerformanceController = {
|
||||
destroy: function() {
|
||||
PerformanceView.off(EVENTS.UI_START_RECORDING, this.startRecording);
|
||||
PerformanceView.off(EVENTS.UI_STOP_RECORDING, this.stopRecording);
|
||||
PerformanceView.off(EVENTS.UI_EXPORT_RECORDING, this.exportRecording);
|
||||
PerformanceView.off(EVENTS.UI_IMPORT_RECORDING, this.importRecording);
|
||||
|
||||
gFront.off("ticks", this._onTimelineData);
|
||||
gFront.off("markers", this._onTimelineData);
|
||||
gFront.off("memory", this._onTimelineData);
|
||||
@ -178,12 +203,12 @@ let PerformanceController = {
|
||||
});
|
||||
|
||||
this._startTime = startTime;
|
||||
this._endTime = startTime;
|
||||
this._endTime = RECORDING_IN_PROGRESS;
|
||||
this._markers = [];
|
||||
this._memory = [];
|
||||
this._ticks = [];
|
||||
|
||||
this.emit(EVENTS.RECORDING_STARTED, this._startTime);
|
||||
this.emit(EVENTS.RECORDING_STARTED);
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -195,24 +220,76 @@ let PerformanceController = {
|
||||
|
||||
// If `endTime` is not yielded from timeline actor (< Fx36), fake it.
|
||||
if (!results.endTime) {
|
||||
results.endTime = this._startTime + this.getInterval().localElapsedTime;
|
||||
results.endTime = this._startTime + this.getLocalElapsedTime();
|
||||
}
|
||||
|
||||
this._endTime = results.endTime;
|
||||
this._profilerData = results.profilerData;
|
||||
this._markers = this._markers.sort((a,b) => (a.start > b.start));
|
||||
|
||||
this.emit(EVENTS.RECORDING_STOPPED, results);
|
||||
this.emit(EVENTS.RECORDING_STOPPED);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Saves the current recording to a file.
|
||||
*
|
||||
* @param nsILocalFile file
|
||||
* The file to stream the data into.
|
||||
*/
|
||||
exportRecording: Task.async(function*(_, file) {
|
||||
let recordingData = this.getAllData();
|
||||
yield PerformanceIO.saveRecordingToFile(recordingData, file);
|
||||
|
||||
this.emit(EVENTS.RECORDING_EXPORTED, recordingData);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Loads a recording from a file, replacing the current one.
|
||||
* XXX: Handle multiple recordings, bug 1111004.
|
||||
*
|
||||
* @param nsILocalFile file
|
||||
* The file to import the data from.
|
||||
*/
|
||||
importRecording: Task.async(function*(_, file) {
|
||||
let recordingData = yield PerformanceIO.loadRecordingFromFile(file);
|
||||
|
||||
this._startTime = recordingData.interval.startTime;
|
||||
this._endTime = recordingData.interval.endTime;
|
||||
this._markers = recordingData.markers;
|
||||
this._memory = recordingData.memory;
|
||||
this._ticks = recordingData.ticks;
|
||||
this._profilerData = recordingData.profilerData;
|
||||
|
||||
this.emit(EVENTS.RECORDING_IMPORTED, recordingData);
|
||||
|
||||
// Flush the current recording.
|
||||
this.emit(EVENTS.RECORDING_STARTED);
|
||||
this.emit(EVENTS.RECORDING_STOPPED);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Gets the amount of time elapsed locally after starting a recording.
|
||||
*/
|
||||
getLocalElapsedTime: function() {
|
||||
return performance.now() - this._localStartTime;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the time interval for the current recording.
|
||||
* @return object
|
||||
*/
|
||||
getInterval: function() {
|
||||
let localElapsedTime = performance.now() - this._localStartTime;
|
||||
let startTime = this._startTime;
|
||||
let endTime = this._endTime;
|
||||
return { localElapsedTime, startTime, endTime };
|
||||
|
||||
// Compute an approximate ending time for the current recording. This is
|
||||
// needed to ensure that the view updates even when new data is
|
||||
// not being generated.
|
||||
if (endTime == RECORDING_IN_PROGRESS) {
|
||||
endTime = startTime + this.getLocalElapsedTime();
|
||||
}
|
||||
|
||||
return { startTime, endTime };
|
||||
},
|
||||
|
||||
/**
|
||||
@ -239,6 +316,26 @@ let PerformanceController = {
|
||||
return this._ticks;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the profiler data in this recording.
|
||||
* @return array
|
||||
*/
|
||||
getProfilerData: function() {
|
||||
return this._profilerData;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets all the data in this recording.
|
||||
*/
|
||||
getAllData: function() {
|
||||
let interval = this.getInterval();
|
||||
let markers = this.getMarkers();
|
||||
let memory = this.getMemory();
|
||||
let ticks = this.getTicks();
|
||||
let profilerData = this.getProfilerData();
|
||||
return { interval, markers, memory, ticks, profilerData };
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired whenever the PerformanceFront emits markers, memory or ticks.
|
||||
*/
|
||||
|
@ -12,12 +12,18 @@ let PerformanceView = {
|
||||
*/
|
||||
initialize: function () {
|
||||
this._recordButton = $("#record-button");
|
||||
this._importButton = $("#import-button");
|
||||
this._exportButton = $("#export-button");
|
||||
|
||||
this._onRecordButtonClick = this._onRecordButtonClick.bind(this);
|
||||
this._onImportButtonClick = this._onImportButtonClick.bind(this);
|
||||
this._onExportButtonClick = this._onExportButtonClick.bind(this);
|
||||
this._lockRecordButton = this._lockRecordButton.bind(this);
|
||||
this._unlockRecordButton = this._unlockRecordButton.bind(this);
|
||||
|
||||
this._recordButton.addEventListener("click", this._onRecordButtonClick);
|
||||
this._importButton.addEventListener("click", this._onImportButtonClick);
|
||||
this._exportButton.addEventListener("click", this._onExportButtonClick);
|
||||
|
||||
// Bind to controller events to unlock the record button
|
||||
PerformanceController.on(EVENTS.RECORDING_STARTED, this._unlockRecordButton);
|
||||
@ -34,6 +40,9 @@ let PerformanceView = {
|
||||
*/
|
||||
destroy: function () {
|
||||
this._recordButton.removeEventListener("click", this._onRecordButtonClick);
|
||||
this._importButton.removeEventListener("click", this._onImportButtonClick);
|
||||
this._exportButton.removeEventListener("click", this._onExportButtonClick);
|
||||
|
||||
PerformanceController.off(EVENTS.RECORDING_STARTED, this._unlockRecordButton);
|
||||
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._unlockRecordButton);
|
||||
|
||||
@ -71,6 +80,37 @@ let PerformanceView = {
|
||||
this._lockRecordButton();
|
||||
this.emit(EVENTS.UI_START_RECORDING);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for clicking the import button.
|
||||
*/
|
||||
_onImportButtonClick: function(e) {
|
||||
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||
fp.init(window, L10N.getStr("recordingsList.saveDialogTitle"), Ci.nsIFilePicker.modeOpen);
|
||||
fp.appendFilter(L10N.getStr("recordingsList.saveDialogJSONFilter"), "*.json");
|
||||
fp.appendFilter(L10N.getStr("recordingsList.saveDialogAllFilter"), "*.*");
|
||||
|
||||
if (fp.show() == Ci.nsIFilePicker.returnOK) {
|
||||
this.emit(EVENTS.UI_IMPORT_RECORDING, fp.file);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for clicking the export button.
|
||||
*/
|
||||
_onExportButtonClick: function(e) {
|
||||
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||
fp.init(window, L10N.getStr("recordingsList.saveDialogTitle"), Ci.nsIFilePicker.modeSave);
|
||||
fp.appendFilter(L10N.getStr("recordingsList.saveDialogJSONFilter"), "*.json");
|
||||
fp.appendFilter(L10N.getStr("recordingsList.saveDialogAllFilter"), "*.*");
|
||||
fp.defaultString = "profile.json";
|
||||
|
||||
fp.open({ done: result => {
|
||||
if (result != Ci.nsIFilePicker.returnCancel) {
|
||||
this.emit(EVENTS.UI_EXPORT_RECORDING, fp.file);
|
||||
}
|
||||
}});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -36,6 +36,9 @@
|
||||
<toolbarbutton id="import-button"
|
||||
class="devtools-toolbarbutton"
|
||||
label="&profilerUI.importButton;"/>
|
||||
<toolbarbutton id="export-button"
|
||||
class="devtools-toolbarbutton"
|
||||
label="&profilerUI.exportButton;"/>
|
||||
</hbox>
|
||||
</toolbar>
|
||||
|
||||
|
@ -9,36 +9,32 @@ support-files =
|
||||
# that need to be moved over to performance tool
|
||||
|
||||
[browser_perf-aaa-run-first-leaktest.js]
|
||||
[browser_perf-front.js]
|
||||
[browser_perf-front-basic-timeline-01.js]
|
||||
[browser_perf-data-massaging-01.js]
|
||||
[browser_perf-data-samples.js]
|
||||
[browser_perf-details-calltree-render-01.js]
|
||||
[browser_perf-details-calltree-render-02.js]
|
||||
[browser_perf-details-waterfall-render-01.js]
|
||||
[browser_perf-details.js]
|
||||
[browser_perf-front-basic-profiler-01.js]
|
||||
# bug 1077464
|
||||
#[browser_perf-front-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]
|
||||
# bug 1077464
|
||||
#[browser_perf-front-profiler-05.js]
|
||||
# bug 1077464
|
||||
#[browser_perf-front-profiler-05.js] bug 1077464
|
||||
#[browser_perf-front-profiler-06.js]
|
||||
# needs shared connection with profiler's shared connection
|
||||
#[browser_perf-shared-connection-01.js]
|
||||
[browser_perf-shared-connection-02.js]
|
||||
[browser_perf-shared-connection-03.js]
|
||||
# bug 1077464
|
||||
#[browser_perf-shared-connection-04.js]
|
||||
[browser_perf-data-samples.js]
|
||||
[browser_perf-data-massaging-01.js]
|
||||
[browser_perf-ui-recording.js]
|
||||
[browser_perf-front.js]
|
||||
[browser_perf-jump-to-debugger-01.js]
|
||||
[browser_perf-jump-to-debugger-02.js]
|
||||
[browser_perf-overview-render-01.js]
|
||||
[browser_perf-overview-render-02.js]
|
||||
[browser_perf-overview-selection-01.js]
|
||||
[browser_perf-overview-selection-02.js]
|
||||
[browser_perf-overview-selection-03.js]
|
||||
|
||||
[browser_perf-details.js]
|
||||
[browser_perf-jump-to-debugger-01.js]
|
||||
[browser_perf-jump-to-debugger-02.js]
|
||||
[browser_perf-details-calltree-render-01.js]
|
||||
[browser_perf-details-calltree-render-02.js]
|
||||
[browser_perf-details-waterfall-render-01.js]
|
||||
[browser_perf-shared-connection-02.js]
|
||||
[browser_perf-shared-connection-03.js]
|
||||
# [browser_perf-shared-connection-04.js] bug 1077464
|
||||
[browser_perf-ui-recording.js]
|
||||
[browser_perf_recordings-io-01.js]
|
||||
[browser_perf_recordings-io-02.js]
|
||||
[browser_perf_recordings-io-03.js]
|
||||
|
@ -0,0 +1,63 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the performance tool is able to save and load recordings.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, PerformanceController } = panel.panelWin;
|
||||
|
||||
yield startRecording(panel);
|
||||
yield stopRecording(panel);
|
||||
|
||||
// Verify original recording.
|
||||
|
||||
let originalData = PerformanceController.getAllData();
|
||||
ok(originalData, "The original recording is not empty.");
|
||||
|
||||
// Save recording.
|
||||
|
||||
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("", file);
|
||||
|
||||
yield exported;
|
||||
ok(true, "The recording data appears to have been successfully saved.");
|
||||
|
||||
// Import recording.
|
||||
|
||||
let rerendered = waitForWidgetsRendered(panel);
|
||||
let imported = once(PerformanceController, EVENTS.RECORDING_IMPORTED);
|
||||
yield PerformanceController.importRecording("", file);
|
||||
|
||||
yield imported;
|
||||
ok(true, "The recording data appears to have been successfully imported.");
|
||||
|
||||
yield rerendered;
|
||||
ok(true, "The imported data was re-rendered.");
|
||||
|
||||
// Verify imported recording.
|
||||
|
||||
let importedData = PerformanceController.getAllData();
|
||||
|
||||
is(importedData.startTime, originalData.startTime,
|
||||
"The impored data is identical to the original data (1).");
|
||||
is(importedData.endTime, originalData.endTime,
|
||||
"The impored data is identical to the original data (2).");
|
||||
|
||||
is(importedData.markers.toSource(), originalData.markers.toSource(),
|
||||
"The impored data is identical to the original data (3).");
|
||||
is(importedData.memory.toSource(), originalData.memory.toSource(),
|
||||
"The impored data is identical to the original data (4).");
|
||||
is(importedData.ticks.toSource(), originalData.ticks.toSource(),
|
||||
"The impored data is identical to the original data (5).");
|
||||
is(importedData.profilerData.toSource(), originalData.profilerData.toSource(),
|
||||
"The impored data is identical to the original data (6).");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -0,0 +1,25 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the performance tool gracefully handles loading bogus files.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, PerformanceController } = panel.panelWin;
|
||||
|
||||
let file = FileUtils.getFile("TmpD", ["tmpprofile.json"]);
|
||||
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
|
||||
|
||||
try {
|
||||
yield PerformanceController.importRecording("", file);
|
||||
ok(false, "The recording succeeded unexpectedly.");
|
||||
} catch (e) {
|
||||
is(e.message, "Could not read recording data file.");
|
||||
ok(true, "The recording was cancelled.");
|
||||
}
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -0,0 +1,54 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the performance tool gracefully handles loading files that are JSON,
|
||||
* but don't contain the appropriate recording data.
|
||||
*/
|
||||
|
||||
let { FileUtils } = Cu.import("resource://gre/modules/FileUtils.jsm", {});
|
||||
let { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, PerformanceController } = panel.panelWin;
|
||||
|
||||
let file = FileUtils.getFile("TmpD", ["tmpprofile.json"]);
|
||||
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
|
||||
yield asyncCopy({ bogus: "data" }, file);
|
||||
|
||||
try {
|
||||
yield PerformanceController.importRecording("", file);
|
||||
ok(false, "The recording succeeded unexpectedly.");
|
||||
} catch (e) {
|
||||
is(e.message, "Unrecognized recording data file.");
|
||||
ok(true, "The recording was cancelled.");
|
||||
}
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
||||
|
||||
function getUnicodeConverter() {
|
||||
let className = "@mozilla.org/intl/scriptableunicodeconverter";
|
||||
let converter = Cc[className].createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
return converter;
|
||||
}
|
||||
|
||||
function asyncCopy(data, file) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
let string = JSON.stringify(data);
|
||||
let inputStream = getUnicodeConverter().convertToInputStream(string);
|
||||
let outputStream = FileUtils.openSafeFileOutputStream(file);
|
||||
|
||||
NetUtil.asyncCopy(inputStream, outputStream, status => {
|
||||
if (!Components.isSuccessCode(status)) {
|
||||
deferred.reject(new Error("Could not save data to file."));
|
||||
}
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
@ -279,6 +279,19 @@ function* stopRecording(panel) {
|
||||
"The record button should not be locked.");
|
||||
}
|
||||
|
||||
function waitForWidgetsRendered(panel) {
|
||||
let { EVENTS, OverviewView, CallTreeView, WaterfallView } = panel.panelWin;
|
||||
|
||||
return Promise.all([
|
||||
once(OverviewView, EVENTS.FRAMERATE_GRAPH_RENDERED),
|
||||
once(OverviewView, EVENTS.MARKERS_GRAPH_RENDERED),
|
||||
once(OverviewView, EVENTS.MEMORY_GRAPH_RENDERED),
|
||||
once(OverviewView, EVENTS.OVERVIEW_RENDERED),
|
||||
once(CallTreeView, EVENTS.CALL_TREE_RENDERED),
|
||||
once(WaterfallView, EVENTS.WATERFALL_RENDERED)
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until a predicate returns true.
|
||||
*
|
||||
|
@ -12,28 +12,32 @@ let CallTreeView = {
|
||||
*/
|
||||
initialize: function () {
|
||||
this._callTree = $(".call-tree-cells-container");
|
||||
this._onRecordingStopped = this._onRecordingStopped.bind(this);
|
||||
this._onRangeChange = this._onRangeChange.bind(this);
|
||||
this._onLink = this._onLink.bind(this);
|
||||
this._stop = this._stop.bind(this);
|
||||
|
||||
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
|
||||
OverviewView.on(EVENTS.OVERVIEW_RANGE_SELECTED, this._onRangeChange);
|
||||
OverviewView.on(EVENTS.OVERVIEW_RANGE_CLEARED, this._onRangeChange);
|
||||
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._stop);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unbinds events.
|
||||
*/
|
||||
destroy: function () {
|
||||
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
|
||||
OverviewView.off(EVENTS.OVERVIEW_RANGE_SELECTED, this._onRangeChange);
|
||||
OverviewView.off(EVENTS.OVERVIEW_RANGE_CLEARED, this._onRangeChange);
|
||||
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._stop);
|
||||
},
|
||||
|
||||
/**
|
||||
* Method for handling all the set up for rendering a new call tree.
|
||||
*/
|
||||
render: function (profilerData, beginAt, endAt, options={}) {
|
||||
// Empty recordings might yield no profiler data.
|
||||
if (profilerData.profile == null) {
|
||||
return;
|
||||
}
|
||||
let threadNode = this._prepareCallTree(profilerData, beginAt, endAt, options);
|
||||
this._populateCallTree(threadNode, options);
|
||||
this.emit(EVENTS.CALL_TREE_RENDERED);
|
||||
@ -42,8 +46,8 @@ let CallTreeView = {
|
||||
/**
|
||||
* Called when recording is stopped.
|
||||
*/
|
||||
_stop: function (_, { profilerData }) {
|
||||
this._profilerData = profilerData;
|
||||
_onRecordingStopped: function () {
|
||||
let profilerData = PerformanceController.getProfilerData();
|
||||
this.render(profilerData);
|
||||
},
|
||||
|
||||
@ -53,8 +57,9 @@ let CallTreeView = {
|
||||
_onRangeChange: function (_, params) {
|
||||
// When a range is cleared, we'll have no beginAt/endAt data,
|
||||
// so the rebuild will just render all the data again.
|
||||
let profilerData = PerformanceController.getProfilerData();
|
||||
let { beginAt, endAt } = params || {};
|
||||
this.render(this._profilerData, beginAt, endAt);
|
||||
this.render(profilerData, beginAt, endAt);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -122,18 +127,19 @@ let viewSourceInDebugger = Task.async(function *(url, line) {
|
||||
// source immediately. Otherwise, initialize it and wait for the sources
|
||||
// to be added first.
|
||||
let debuggerAlreadyOpen = gToolbox.getPanel("jsdebugger");
|
||||
|
||||
let { panelWin: dbg } = yield gToolbox.selectTool("jsdebugger");
|
||||
|
||||
if (!debuggerAlreadyOpen) {
|
||||
yield new Promise((resolve) => dbg.once(dbg.EVENTS.SOURCES_ADDED, () => resolve(dbg)));
|
||||
yield dbg.once(dbg.EVENTS.SOURCES_ADDED);
|
||||
}
|
||||
|
||||
let { DebuggerView } = dbg;
|
||||
let item = DebuggerView.Sources.getItemForAttachment(a => a.source.url === url);
|
||||
let { Sources } = DebuggerView;
|
||||
|
||||
let item = Sources.getItemForAttachment(a => a.source.url === url);
|
||||
if (item) {
|
||||
return DebuggerView.setEditorLocation(item.attachment.source.actor, line, { noDebug: true });
|
||||
}
|
||||
return Promise.reject();
|
||||
|
||||
return Promise.reject("Couldn't find the specified source in the debugger.");
|
||||
});
|
||||
|
@ -11,8 +11,8 @@ let WaterfallView = {
|
||||
* Sets up the view with event binding.
|
||||
*/
|
||||
initialize: Task.async(function *() {
|
||||
this._start = this._start.bind(this);
|
||||
this._stop = this._stop.bind(this);
|
||||
this._onRecordingStarted = this._onRecordingStarted.bind(this);
|
||||
this._onRecordingStopped = this._onRecordingStopped.bind(this);
|
||||
this._onMarkerSelected = this._onMarkerSelected.bind(this);
|
||||
this._onResize = this._onResize.bind(this);
|
||||
|
||||
@ -23,8 +23,8 @@ let WaterfallView = {
|
||||
this.graph.on("unselected", this._onMarkerSelected);
|
||||
this.markerDetails.on("resize", this._onResize);
|
||||
|
||||
PerformanceController.on(EVENTS.RECORDING_STARTED, this._start);
|
||||
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._stop);
|
||||
PerformanceController.on(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
|
||||
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
|
||||
|
||||
this.graph.recalculateBounds();
|
||||
}),
|
||||
@ -37,8 +37,8 @@ let WaterfallView = {
|
||||
this.graph.off("unselected", this._onMarkerSelected);
|
||||
this.markerDetails.off("resize", this._onResize);
|
||||
|
||||
PerformanceController.off(EVENTS.RECORDING_STARTED, this._start);
|
||||
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._stop);
|
||||
PerformanceController.off(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
|
||||
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -55,14 +55,14 @@ let WaterfallView = {
|
||||
/**
|
||||
* Called when recording starts.
|
||||
*/
|
||||
_start: function (_, { startTime }) {
|
||||
_onRecordingStarted: function () {
|
||||
this.graph.clearView();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when recording stops.
|
||||
*/
|
||||
_stop: function (_, { endTime }) {
|
||||
_onRecordingStopped: function () {
|
||||
this.render();
|
||||
},
|
||||
|
||||
@ -88,7 +88,6 @@ let WaterfallView = {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Convenient way of emitting events from the view.
|
||||
*/
|
||||
|
@ -32,6 +32,7 @@ let DetailsView = {
|
||||
|
||||
yield CallTreeView.initialize();
|
||||
yield WaterfallView.initialize();
|
||||
|
||||
this.selectView(DEFAULT_DETAILS_SUBVIEW);
|
||||
}),
|
||||
|
||||
|
@ -21,15 +21,15 @@ const GRAPH_SCROLL_EVENTS_DRAIN = 50; // ms
|
||||
|
||||
/**
|
||||
* View handler for the overview panel's time view, displaying
|
||||
* framerate over time.
|
||||
* framerate, markers and memory over time.
|
||||
*/
|
||||
let OverviewView = {
|
||||
/**
|
||||
* Sets up the view with event binding.
|
||||
*/
|
||||
initialize: Task.async(function *() {
|
||||
this._start = this._start.bind(this);
|
||||
this._stop = this._stop.bind(this);
|
||||
this._onRecordingStarted = this._onRecordingStarted.bind(this);
|
||||
this._onRecordingStopped = this._onRecordingStopped.bind(this);
|
||||
this._onRecordingTick = this._onRecordingTick.bind(this);
|
||||
this._onGraphMouseUp = this._onGraphMouseUp.bind(this);
|
||||
this._onGraphScroll = this._onGraphScroll.bind(this);
|
||||
@ -45,8 +45,8 @@ let OverviewView = {
|
||||
this.memoryOverview.on("mouseup", this._onGraphMouseUp);
|
||||
this.memoryOverview.on("scroll", this._onGraphScroll);
|
||||
|
||||
PerformanceController.on(EVENTS.RECORDING_STARTED, this._start);
|
||||
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._stop);
|
||||
PerformanceController.on(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
|
||||
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -61,8 +61,8 @@ let OverviewView = {
|
||||
this.memoryOverview.off("scroll", this._onGraphScroll);
|
||||
|
||||
clearNamedTimeout("graph-scroll");
|
||||
PerformanceController.off(EVENTS.RECORDING_STARTED, this._start);
|
||||
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._stop);
|
||||
PerformanceController.off(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
|
||||
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -112,12 +112,6 @@ let OverviewView = {
|
||||
let memory = PerformanceController.getMemory();
|
||||
let timestamps = PerformanceController.getTicks();
|
||||
|
||||
// Compute an approximate ending time for the view. This is
|
||||
// needed to ensure that the view updates even when new data is
|
||||
// not being generated.
|
||||
let fakeTime = interval.startTime + interval.localElapsedTime;
|
||||
interval.endTime = fakeTime;
|
||||
|
||||
this.markersOverview.setData({ interval, markers });
|
||||
this.emit(EVENTS.MARKERS_GRAPH_RENDERED);
|
||||
|
||||
@ -133,7 +127,7 @@ let OverviewView = {
|
||||
|
||||
/**
|
||||
* Called at most every OVERVIEW_UPDATE_INTERVAL milliseconds
|
||||
* and uses data fetched from `_onTimelineData` to render
|
||||
* and uses data fetched from the controller to render
|
||||
* data into all the corresponding overview graphs.
|
||||
*/
|
||||
_onRecordingTick: Task.async(function *() {
|
||||
@ -167,7 +161,7 @@ let OverviewView = {
|
||||
|
||||
/**
|
||||
* Listener handling the "scroll" event for the framerate graph.
|
||||
* Fires an event to be handled elsewhere.
|
||||
* Fires a debounced event to be handled elsewhere.
|
||||
*/
|
||||
_onGraphScroll: function () {
|
||||
setNamedTimeout("graph-scroll", GRAPH_SCROLL_EVENTS_DRAIN, () => {
|
||||
@ -189,7 +183,7 @@ let OverviewView = {
|
||||
/**
|
||||
* Called when recording starts.
|
||||
*/
|
||||
_start: function () {
|
||||
_onRecordingStarted: function () {
|
||||
this._timeoutId = setTimeout(this._onRecordingTick, OVERVIEW_UPDATE_INTERVAL);
|
||||
|
||||
this.framerateGraph.dropSelection();
|
||||
@ -201,7 +195,7 @@ let OverviewView = {
|
||||
/**
|
||||
* Called when recording stops.
|
||||
*/
|
||||
_stop: function () {
|
||||
_onRecordingStopped: function () {
|
||||
clearTimeout(this._timeoutId);
|
||||
this._timeoutId = null;
|
||||
|
||||
|
@ -2101,7 +2101,11 @@ this.CanvasGraphUtils = {
|
||||
* @return number
|
||||
*/
|
||||
function map(value, istart, istop, ostart, ostop) {
|
||||
return ostart + (ostop - ostart) * ((value - istart) / (istop - istart));
|
||||
let ratio = istop - istart;
|
||||
if (ratio == 0) {
|
||||
return value;
|
||||
}
|
||||
return ostart + (ostop - ostart) * ((value - istart) / ratio);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -33,6 +33,10 @@
|
||||
- on a button that opens a dialog to import a saved profile data file. -->
|
||||
<!ENTITY profilerUI.importButton "Import…">
|
||||
|
||||
<!-- LOCALIZATION NOTE (profilerUI.exportButton): This string is displayed
|
||||
- on a button that opens a dialog to export a saved profile data file. -->
|
||||
<!ENTITY profilerUI.exportButton "Save">
|
||||
|
||||
<!-- LOCALIZATION NOTE (profilerUI.clearButton): This string is displayed
|
||||
- on a button that remvoes all the recordings. -->
|
||||
<!ENTITY profilerUI.clearButton "Clear">
|
||||
|
Loading…
Reference in New Issue
Block a user