2012-12-13 14:17:00 -08:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
const Cc = Components.classes;
|
|
|
|
const Ci = Components.interfaces;
|
|
|
|
const Cu = Components.utils;
|
|
|
|
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
|
|
|
|
|
|
|
|
let EXPORTED_SYMBOLS = ["ProfilerController"];
|
|
|
|
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "DebuggerServer", function () {
|
|
|
|
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
|
|
|
return DebuggerServer;
|
|
|
|
});
|
|
|
|
|
2013-03-21 16:01:32 -07:00
|
|
|
/**
|
|
|
|
* Makes a structure representing an individual profile.
|
|
|
|
*/
|
|
|
|
function makeProfile(name) {
|
|
|
|
return {
|
|
|
|
name: name,
|
|
|
|
timeStarted: null,
|
|
|
|
timeEnded: null
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2012-12-13 14:17:00 -08:00
|
|
|
/**
|
|
|
|
* Object acting as a mediator between the ProfilerController and
|
|
|
|
* DebuggerServer.
|
|
|
|
*/
|
2013-02-06 12:10:30 -08:00
|
|
|
function ProfilerConnection(client) {
|
|
|
|
this.client = client;
|
2013-03-21 16:01:32 -07:00
|
|
|
this.startTime = 0;
|
2012-12-13 14:17:00 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
ProfilerConnection.prototype = {
|
|
|
|
actor: null,
|
2013-03-21 16:01:32 -07:00
|
|
|
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;
|
|
|
|
},
|
2012-12-13 14:17:00 -08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Connects to a debugee and executes a callback when ready.
|
|
|
|
*
|
|
|
|
* @param function aCallback
|
|
|
|
* Function to be called once we're connected to the client.
|
|
|
|
*/
|
|
|
|
connect: function PCn_connect(aCallback) {
|
2013-03-06 23:30:03 -08:00
|
|
|
this.client.listTabs(function (aResponse) {
|
|
|
|
this.actor = aResponse.profilerActor;
|
|
|
|
aCallback();
|
|
|
|
}.bind(this));
|
2012-12-13 14:17:00 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends a message to check if the profiler is currently active.
|
|
|
|
*
|
|
|
|
* @param function aCallback
|
|
|
|
* Function to be called once we have a response from
|
|
|
|
* the client. It will be called with a single argument
|
|
|
|
* containing a response object.
|
|
|
|
*/
|
|
|
|
isActive: function PCn_isActive(aCallback) {
|
|
|
|
var message = { to: this.actor, type: "isActive" };
|
|
|
|
this.client.request(message, aCallback);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends a message to start a profiler.
|
|
|
|
*
|
|
|
|
* @param function aCallback
|
|
|
|
* Function to be called once the profiler is running.
|
|
|
|
* It will be called with a single argument containing
|
|
|
|
* a response object.
|
|
|
|
*/
|
|
|
|
startProfiler: function PCn_startProfiler(aCallback) {
|
|
|
|
var message = {
|
|
|
|
to: this.actor,
|
|
|
|
type: "startProfiler",
|
|
|
|
entries: 1000000,
|
|
|
|
interval: 1,
|
|
|
|
features: ["js"],
|
|
|
|
};
|
2013-03-21 16:01:32 -07:00
|
|
|
|
|
|
|
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));
|
2012-12-13 14:17:00 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends a message to stop a profiler.
|
|
|
|
*
|
|
|
|
* @param function aCallback
|
|
|
|
* Function to be called once the profiler is idle.
|
|
|
|
* It will be called with a single argument containing
|
|
|
|
* a response object.
|
|
|
|
*/
|
|
|
|
stopProfiler: function PCn_stopProfiler(aCallback) {
|
|
|
|
var message = { to: this.actor, type: "stopProfiler" };
|
|
|
|
this.client.request(message, aCallback);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sends a message to get the generated profile data.
|
|
|
|
*
|
|
|
|
* @param function aCallback
|
|
|
|
* Function to be called once we have the data.
|
|
|
|
* It will be called with a single argument containing
|
|
|
|
* a response object.
|
|
|
|
*/
|
|
|
|
getProfileData: function PCn_getProfileData(aCallback) {
|
|
|
|
var message = { to: this.actor, type: "getProfile" };
|
|
|
|
this.client.request(message, aCallback);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cleanup.
|
|
|
|
*/
|
|
|
|
destroy: function PCn_destroy() {
|
2013-03-06 23:30:03 -08:00
|
|
|
this.client = null;
|
2012-12-13 14:17:00 -08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Object defining the profiler controller components.
|
|
|
|
*/
|
2013-02-06 12:10:30 -08:00
|
|
|
function ProfilerController(target) {
|
2013-03-06 23:30:03 -08:00
|
|
|
this.profiler = new ProfilerConnection(target.client);
|
2013-03-28 15:13:24 -07:00
|
|
|
this.profiles = new Map();
|
2013-03-21 16:01:32 -07:00
|
|
|
|
|
|
|
// Chrome debugging targets have already obtained a reference to the
|
|
|
|
// profiler actor.
|
2013-03-06 23:30:03 -08:00
|
|
|
this._connected = !!target.chrome;
|
2013-03-21 16:01:32 -07:00
|
|
|
|
2013-03-06 23:30:03 -08:00
|
|
|
if (target.chrome) {
|
|
|
|
this.profiler.actor = target.form.profilerActor;
|
2013-02-06 12:10:30 -08:00
|
|
|
}
|
2012-12-13 14:17:00 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
ProfilerController.prototype = {
|
|
|
|
/**
|
|
|
|
* Connects to the client unless we're already connected.
|
|
|
|
*
|
|
|
|
* @param function aCallback
|
|
|
|
* Function to be called once we're connected. If
|
|
|
|
* the controller is already connected, this function
|
|
|
|
* will be called immediately (synchronously).
|
|
|
|
*/
|
|
|
|
connect: function (aCallback) {
|
|
|
|
if (this._connected) {
|
2013-03-06 23:30:03 -08:00
|
|
|
return void aCallback();
|
2012-12-13 14:17:00 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
this.profiler.connect(function onConnect() {
|
|
|
|
this._connected = true;
|
|
|
|
aCallback();
|
|
|
|
}.bind(this));
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks whether the profiler is active.
|
|
|
|
*
|
|
|
|
* @param function aCallback
|
|
|
|
* Function to be called with a response from the
|
|
|
|
* client. It will be called with two arguments:
|
|
|
|
* an error object (may be null) and a boolean
|
|
|
|
* value indicating if the profiler is active or not.
|
|
|
|
*/
|
|
|
|
isActive: function PC_isActive(aCallback) {
|
|
|
|
this.profiler.isActive(function onActive(aResponse) {
|
|
|
|
aCallback(aResponse.error, aResponse.isActive);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2013-03-21 16:01:32 -07:00
|
|
|
* Checks whether the profile is currently recording.
|
2012-12-13 14:17:00 -08:00
|
|
|
*
|
2013-03-21 16:01:32 -07:00
|
|
|
* @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
|
2012-12-13 14:17:00 -08:00
|
|
|
* 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).
|
|
|
|
*/
|
2013-03-21 16:01:32 -07:00
|
|
|
start: function PC_start(name, cb) {
|
2013-03-28 15:13:24 -07:00
|
|
|
if (this.profiles.has(name)) {
|
2013-03-21 16:01:32 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let profiler = this.profiler;
|
2013-03-28 15:13:24 -07:00
|
|
|
let profile = makeProfile(name);
|
|
|
|
this.profiles.set(name, profile);
|
|
|
|
|
2013-03-21 16:01:32 -07:00
|
|
|
|
|
|
|
// 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();
|
|
|
|
});
|
2012-12-13 14:17:00 -08:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stops the profiler.
|
|
|
|
*
|
2013-03-21 16:01:32 -07:00
|
|
|
* @param string name
|
|
|
|
* Name of the profile that needs to be stopped.
|
|
|
|
* @param function cb
|
2012-12-13 14:17:00 -08:00
|
|
|
* 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).
|
|
|
|
*/
|
2013-03-21 16:01:32 -07:00
|
|
|
stop: function PC_stop(name, cb) {
|
|
|
|
let profiler = this.profiler;
|
2013-03-28 15:13:24 -07:00
|
|
|
let profile = this.profiles.get(name);
|
2013-03-21 16:01:32 -07:00
|
|
|
|
|
|
|
if (!profile || !this.isProfileRecording(profile)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let isRecording = function () {
|
2013-03-28 15:13:24 -07:00
|
|
|
for (let [ name, profile ] of this.profiles) {
|
|
|
|
if (this.isProfileRecording(profile)) {
|
2013-03-21 16:01:32 -07:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
profiler.getProfileData(function onData(aResponse) {
|
2012-12-13 14:17:00 -08:00
|
|
|
if (aResponse.error) {
|
|
|
|
Cu.reportError("Failed to fetch profile data before stopping the profiler.");
|
2013-03-21 16:01:32 -07:00
|
|
|
return void cb(aResponse.error, null);
|
2012-12-13 14:17:00 -08:00
|
|
|
}
|
|
|
|
|
2013-03-21 16:01:32 -07:00
|
|
|
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 };
|
2012-12-13 14:17:00 -08:00
|
|
|
});
|
2013-03-21 16:01:32 -07:00
|
|
|
|
|
|
|
onStop(data);
|
|
|
|
});
|
2012-12-13 14:17:00 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cleanup.
|
|
|
|
*/
|
2013-03-21 16:01:32 -07:00
|
|
|
destroy: function PC_destroy() {
|
2012-12-13 14:17:00 -08:00
|
|
|
this.profiler.destroy();
|
|
|
|
this.profiler = null;
|
|
|
|
}
|
|
|
|
};
|