зеркало из https://github.com/mozilla/gecko-dev.git
Bug 853650 - Support for multiple simultaneously running profiles. r=past
This commit is contained in:
Родитель
770e0ab1c9
Коммит
4f64ed4de2
|
@ -18,16 +18,39 @@ XPCOMUtils.defineLazyGetter(this, "DebuggerServer", function () {
|
|||
return DebuggerServer;
|
||||
});
|
||||
|
||||
/**
|
||||
* Makes a structure representing an individual profile.
|
||||
*/
|
||||
function makeProfile(name) {
|
||||
return {
|
||||
name: name,
|
||||
timeStarted: null,
|
||||
timeEnded: null
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Object acting as a mediator between the ProfilerController and
|
||||
* DebuggerServer.
|
||||
*/
|
||||
function ProfilerConnection(client) {
|
||||
this.client = client;
|
||||
this.startTime = 0;
|
||||
}
|
||||
|
||||
ProfilerConnection.prototype = {
|
||||
actor: null,
|
||||
startTime: null,
|
||||
|
||||
/**
|
||||
* Returns how many milliseconds have passed since the connection
|
||||
* was started (start time is specificed by the startTime property).
|
||||
*
|
||||
* @return number
|
||||
*/
|
||||
get currentTime() {
|
||||
return (new Date()).getTime() - this.startTime;
|
||||
},
|
||||
|
||||
/**
|
||||
* Connects to a debugee and executes a callback when ready.
|
||||
|
@ -71,7 +94,13 @@ ProfilerConnection.prototype = {
|
|||
interval: 1,
|
||||
features: ["js"],
|
||||
};
|
||||
this.client.request(message, aCallback);
|
||||
|
||||
this.client.request(message, function () {
|
||||
// Record the current time so we could split profiler data
|
||||
// in chunks later.
|
||||
this.startTime = (new Date()).getTime();
|
||||
aCallback.apply(null, Array.slice(arguments));
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -113,9 +142,12 @@ ProfilerConnection.prototype = {
|
|||
*/
|
||||
function ProfilerController(target) {
|
||||
this.profiler = new ProfilerConnection(target.client);
|
||||
// Chrome debugging targets have already obtained a reference to the profiler
|
||||
// actor.
|
||||
this.pool = {};
|
||||
|
||||
// Chrome debugging targets have already obtained a reference to the
|
||||
// profiler actor.
|
||||
this._connected = !!target.chrome;
|
||||
|
||||
if (target.chrome) {
|
||||
this.profiler.actor = target.form.profilerActor;
|
||||
}
|
||||
|
@ -157,44 +189,118 @@ ProfilerController.prototype = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Starts the profiler.
|
||||
* Checks whether the profile is currently recording.
|
||||
*
|
||||
* @param function aCallback
|
||||
* @param object profile
|
||||
* An object made by calling makeProfile function.
|
||||
* @return boolean
|
||||
*/
|
||||
isProfileRecording: function PC_isProfileRecording(profile) {
|
||||
return profile.timeStarted !== null && profile.timeEnded === null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a new profile and starts the profiler, if needed.
|
||||
*
|
||||
* @param string name
|
||||
* Name of the profile.
|
||||
* @param function cb
|
||||
* Function to be called once the profiler is started
|
||||
* or we get an error. It will be called with a single
|
||||
* argument: an error object (may be null).
|
||||
*/
|
||||
start: function PC_start(aCallback) {
|
||||
this.profiler.startProfiler(function onStart(aResponse) {
|
||||
aCallback(aResponse.error);
|
||||
start: function PC_start(name, cb) {
|
||||
if (this.pool[name]) {
|
||||
return;
|
||||
}
|
||||
|
||||
let profile = this.pool[name] = makeProfile(name);
|
||||
let profiler = this.profiler;
|
||||
|
||||
// If profile is already running, no need to do anything.
|
||||
if (this.isProfileRecording(profile)) {
|
||||
return void cb();
|
||||
}
|
||||
|
||||
this.isActive(function (err, isActive) {
|
||||
if (isActive) {
|
||||
profile.timeStarted = profiler.currentTime;
|
||||
return void cb();
|
||||
}
|
||||
|
||||
profiler.startProfiler(function onStart(aResponse) {
|
||||
if (aResponse.error) {
|
||||
return void cb(aResponse.error);
|
||||
}
|
||||
|
||||
profile.timeStarted = profiler.currentTime;
|
||||
cb();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Stops the profiler.
|
||||
*
|
||||
* @param function aCallback
|
||||
* @param string name
|
||||
* Name of the profile that needs to be stopped.
|
||||
* @param function cb
|
||||
* Function to be called once the profiler is stopped
|
||||
* or we get an error. It will be called with a single
|
||||
* argument: an error object (may be null).
|
||||
*/
|
||||
stop: function PC_stop(aCallback) {
|
||||
this.profiler.getProfileData(function onData(aResponse) {
|
||||
let data = aResponse.profile;
|
||||
if (aResponse.error) {
|
||||
Cu.reportError("Failed to fetch profile data before stopping the profiler.");
|
||||
stop: function PC_stop(name, cb) {
|
||||
let profiler = this.profiler;
|
||||
let profile = this.pool[name];
|
||||
|
||||
if (!profile || !this.isProfileRecording(profile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let isRecording = function () {
|
||||
for (let name in this.pool) {
|
||||
if (this.isProfileRecording(this.pool[name])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
this.profiler.stopProfiler(function onStop(aResponse) {
|
||||
aCallback(aResponse.error, data);
|
||||
return false;
|
||||
}.bind(this);
|
||||
|
||||
let onStop = function (data) {
|
||||
if (isRecording()) {
|
||||
return void cb(null, data);
|
||||
}
|
||||
|
||||
profiler.stopProfiler(function onStopProfiler(response) {
|
||||
cb(response.error, data);
|
||||
});
|
||||
}.bind(this));
|
||||
}.bind(this);
|
||||
|
||||
profiler.getProfileData(function onData(aResponse) {
|
||||
if (aResponse.error) {
|
||||
Cu.reportError("Failed to fetch profile data before stopping the profiler.");
|
||||
return void cb(aResponse.error, null);
|
||||
}
|
||||
|
||||
let data = aResponse.profile;
|
||||
profile.timeEnded = profiler.currentTime;
|
||||
|
||||
data.threads = data.threads.map(function (thread) {
|
||||
let samples = thread.samples.filter(function (sample) {
|
||||
return sample.time >= profile.timeStarted;
|
||||
});
|
||||
return { samples: samples };
|
||||
});
|
||||
|
||||
onStop(data);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Cleanup.
|
||||
*/
|
||||
destroy: function PC_destroy(aCallback) {
|
||||
destroy: function PC_destroy() {
|
||||
this.profiler.destroy();
|
||||
this.profiler = null;
|
||||
}
|
||||
|
|
|
@ -71,6 +71,8 @@ function ProfileUI(uid, panel) {
|
|||
}
|
||||
|
||||
let label = doc.querySelector("li#profile-" + this.uid + " > h1");
|
||||
let name = label.textContent.replace(/\s\*$/, "");
|
||||
|
||||
switch (event.data.status) {
|
||||
case "loaded":
|
||||
if (this.panel._runningUid !== null) {
|
||||
|
@ -89,9 +91,10 @@ function ProfileUI(uid, panel) {
|
|||
// so that it could update the UI. Also, once started, we add a
|
||||
// star to the profile name to indicate which profile is currently
|
||||
// running.
|
||||
this.panel.startProfiling(function onStart() {
|
||||
this.panel.startProfiling(name, function onStart() {
|
||||
label.textContent = name + " *";
|
||||
this.panel.broadcast(this.uid, {task: "onStarted"});
|
||||
label.textContent = label.textContent + " *";
|
||||
this.emit("started");
|
||||
}.bind(this));
|
||||
|
||||
break;
|
||||
|
@ -99,9 +102,10 @@ function ProfileUI(uid, panel) {
|
|||
// Stop profiling and, once stopped, notify the underlying page so
|
||||
// that it could update the UI and remove a star from the profile
|
||||
// name.
|
||||
this.panel.stopProfiling(function onStop() {
|
||||
this.panel.stopProfiling(name, function onStop() {
|
||||
label.textContent = name;
|
||||
this.panel.broadcast(this.uid, {task: "onStopped"});
|
||||
label.textContent = label.textContent.replace(/\s\*$/, "");
|
||||
this.emit("stopped");
|
||||
}.bind(this));
|
||||
break;
|
||||
case "disabled":
|
||||
|
@ -372,8 +376,8 @@ ProfilerPanel.prototype = {
|
|||
* A function to call once we get the message
|
||||
* that profiling had been successfuly started.
|
||||
*/
|
||||
startProfiling: function PP_startProfiling(onStart) {
|
||||
this.controller.start(function (err) {
|
||||
startProfiling: function PP_startProfiling(name, onStart) {
|
||||
this.controller.start(name, function (err) {
|
||||
if (err) {
|
||||
Cu.reportError("ProfilerController.start: " + err.message);
|
||||
return;
|
||||
|
@ -392,7 +396,7 @@ ProfilerPanel.prototype = {
|
|||
* A function to call once we get the message
|
||||
* that profiling had been successfuly stopped.
|
||||
*/
|
||||
stopProfiling: function PP_stopProfiling(onStop) {
|
||||
stopProfiling: function PP_stopProfiling(name, onStop) {
|
||||
this.controller.isActive(function (err, isActive) {
|
||||
if (err) {
|
||||
Cu.reportError("ProfilerController.isActive: " + err.message);
|
||||
|
@ -403,18 +407,19 @@ ProfilerPanel.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
this.controller.stop(function (err, data) {
|
||||
this.controller.stop(name, function (err, data) {
|
||||
if (err) {
|
||||
Cu.reportError("ProfilerController.stop: " + err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
this.activeProfile.data = data;
|
||||
this.activeProfile.parse(data, function onParsed() {
|
||||
this.emit("parsed");
|
||||
}.bind(this));
|
||||
|
||||
onStop();
|
||||
this.emit("stopped");
|
||||
this.emit("stopped", data);
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
},
|
||||
|
|
|
@ -54,31 +54,20 @@ function onParentMessage(event) {
|
|||
var profilerMessage = document.getElementById("profilerMessage");
|
||||
var msg = JSON.parse(event.data);
|
||||
|
||||
if (msg.task !== "receiveProfileData" && !msg.isCurrent) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (msg.task) {
|
||||
case "onStarted":
|
||||
if (msg.isCurrent) {
|
||||
start.style.display = "none";
|
||||
start.querySelector("button").removeAttribute("disabled");
|
||||
stop.style.display = "inline";
|
||||
} else {
|
||||
start.querySelector("button").setAttribute("disabled", true);
|
||||
var text = gStrings.getFormatStr("profiler.alreadyRunning", [msg.uid]);
|
||||
profilerMessage.textContent = text;
|
||||
profilerMessage.style.display = "block";
|
||||
notifyParent("disabled");
|
||||
}
|
||||
start.style.display = "none";
|
||||
start.querySelector("button").removeAttribute("disabled");
|
||||
stop.style.display = "inline";
|
||||
break;
|
||||
case "onStopped":
|
||||
if (msg.isCurrent) {
|
||||
stop.style.display = "none";
|
||||
stop.querySelector("button").removeAttribute("disabled");
|
||||
start.style.display = "inline";
|
||||
} else {
|
||||
start.querySelector("button").removeAttribute("disabled");
|
||||
profilerMessage.textContent = "";
|
||||
profilerMessage.style.display = "none";
|
||||
notifyParent("enabled");
|
||||
}
|
||||
stop.style.display = "none";
|
||||
stop.querySelector("button").removeAttribute("disabled");
|
||||
start.style.display = "inline";
|
||||
break;
|
||||
case "receiveProfileData":
|
||||
loadProfile(JSON.stringify(msg.rawProfile));
|
||||
|
@ -267,4 +256,4 @@ function enterProgressUI() {
|
|||
Parser.updateLogSetting();
|
||||
|
||||
return reporter;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,43 +33,44 @@ function getCleoControls(doc) {
|
|||
];
|
||||
}
|
||||
|
||||
function sendFromActiveProfile(msg) {
|
||||
let [win, doc] = getProfileInternals();
|
||||
|
||||
win.parent.postMessage({
|
||||
uid: gPanel.activeProfile.uid,
|
||||
status: msg
|
||||
}, "*");
|
||||
function sendFromProfile(uid, msg) {
|
||||
let [win, doc] = getProfileInternals(uid);
|
||||
win.parent.postMessage({ uid: uid, status: msg }, "*");
|
||||
}
|
||||
|
||||
function startProfiling() {
|
||||
gPanel.profiles.get(gUid).once("disabled", stopProfiling);
|
||||
sendFromActiveProfile("start");
|
||||
gPanel.profiles.get(gPanel.activeProfile.uid).once("started", function () {
|
||||
setTimeout(function () {
|
||||
sendFromProfile(2, "start");
|
||||
gPanel.profiles.get(2).once("started", function () setTimeout(stopProfiling, 50));
|
||||
}, 50);
|
||||
});
|
||||
sendFromProfile(gPanel.activeProfile.uid, "start");
|
||||
}
|
||||
|
||||
function stopProfiling() {
|
||||
let [win, doc] = getProfileInternals(gUid);
|
||||
let [btn, msg] = getCleoControls(doc);
|
||||
|
||||
ok(msg.textContent.match("Profile 1") !== null, "Message is visible");
|
||||
ok(btn.hasAttribute("disabled"), "Button is disabled");
|
||||
|
||||
is(gPanel.document.querySelector("li#profile-1 > h1").textContent,
|
||||
"Profile 1 *", "Profile 1 has a star next to it.");
|
||||
is(gPanel.document.querySelector("li#profile-2 > h1").textContent,
|
||||
"Profile 2", "Profile 2 doesn't have a star next to it.");
|
||||
"Profile 2 *", "Profile 2 has a star next to it.");
|
||||
|
||||
gPanel.profiles.get(gUid).once("enabled", confirmAndFinish);
|
||||
sendFromActiveProfile("stop");
|
||||
gPanel.profiles.get(gPanel.activeProfile.uid).once("stopped", function () {
|
||||
is(gPanel.document.querySelector("li#profile-1 > h1").textContent,
|
||||
"Profile 1", "Profile 1 doesn't have a star next to it anymore.");
|
||||
|
||||
sendFromProfile(2, "stop");
|
||||
gPanel.profiles.get(2).once("stopped", confirmAndFinish);
|
||||
});
|
||||
sendFromProfile(gPanel.activeProfile.uid, "stop");
|
||||
}
|
||||
|
||||
function confirmAndFinish() {
|
||||
function confirmAndFinish(ev, data) {
|
||||
let [win, doc] = getProfileInternals(gUid);
|
||||
let [btn, msg] = getCleoControls(doc);
|
||||
|
||||
ok(msg.style.display === "none", "Message is hidden");
|
||||
ok(!btn.hasAttribute("disabled"), "Button is enabled");
|
||||
|
||||
is(gPanel.document.querySelector("li#profile-1 > h1").textContent,
|
||||
"Profile 1", "Profile 1 doesn't have a star next to it.");
|
||||
is(gPanel.document.querySelector("li#profile-2 > h1").textContent,
|
||||
|
@ -80,4 +81,4 @@ function confirmAndFinish() {
|
|||
gTab = null;
|
||||
gUid = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ function test() {
|
|||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
testInactive(testStart);
|
||||
testInactive(startFirstProfile);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -32,23 +32,33 @@ function testActive(next=function(){}) {
|
|||
});
|
||||
}
|
||||
|
||||
function testStart() {
|
||||
gPanel.controller.start(function (err) {
|
||||
ok(!err, "Profiler started without errors");
|
||||
testActive(testStop);
|
||||
function startFirstProfile() {
|
||||
gPanel.controller.start("Profile 1", function (err) {
|
||||
ok(!err, "Profile 1 started without errors");
|
||||
testActive(startSecondProfile);
|
||||
});
|
||||
}
|
||||
|
||||
function testStop() {
|
||||
gPanel.controller.stop(function (err, data) {
|
||||
ok(!err, "Profiler stopped without errors");
|
||||
function startSecondProfile() {
|
||||
gPanel.controller.start("Profile 2", function (err) {
|
||||
ok(!err, "Profile 2 started without errors");
|
||||
testActive(stopFirstProfile);
|
||||
});
|
||||
}
|
||||
|
||||
function stopFirstProfile() {
|
||||
gPanel.controller.stop("Profile 1", function (err, data) {
|
||||
ok(!err, "Profile 1 stopped without errors");
|
||||
ok(data, "Profiler returned some data");
|
||||
|
||||
testInactive(function () {
|
||||
tearDown(gTab, function () {
|
||||
gTab = null;
|
||||
gPanel = null;
|
||||
});
|
||||
});
|
||||
testActive(stopSecondProfile);
|
||||
});
|
||||
}
|
||||
|
||||
function stopSecondProfile() {
|
||||
gPanel.controller.stop("Profile 2", function (err, data) {
|
||||
ok(!err, "Profile 2 stopped without errors");
|
||||
ok(data, "Profiler returned some data");
|
||||
testInactive(tearDown.call(null, gTab, function () gTab = gPanel = null));
|
||||
});
|
||||
}
|
|
@ -80,4 +80,4 @@ function tearDown(tab, callback=function(){}) {
|
|||
|
||||
finish();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче