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"];
|
|
|
|
|
2013-05-28 12:51:42 -07:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "DebuggerServer", function () {
|
|
|
|
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
|
|
|
return DebuggerServer;
|
|
|
|
});
|
2012-12-13 14:17:00 -08:00
|
|
|
|
2013-04-09 12:00:30 -07:00
|
|
|
/**
|
|
|
|
* Data structure that contains information that has
|
|
|
|
* to be shared between separate ProfilerController
|
|
|
|
* instances.
|
|
|
|
*/
|
|
|
|
const sharedData = {
|
2013-05-28 12:51:42 -07:00
|
|
|
startTime: 0,
|
2013-04-09 12:00:30 -07:00
|
|
|
data: new WeakMap(),
|
|
|
|
};
|
|
|
|
|
2013-03-21 16:01:32 -07:00
|
|
|
/**
|
|
|
|
* Makes a structure representing an individual profile.
|
|
|
|
*/
|
2013-05-28 12:51:42 -07:00
|
|
|
function makeProfile(name) {
|
2013-03-21 16:01:32 -07:00
|
|
|
return {
|
|
|
|
name: name,
|
2013-05-28 12:51:42 -07:00
|
|
|
timeStarted: null,
|
|
|
|
timeEnded: null
|
2013-03-21 16:01:32 -07:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2013-04-09 12:00:30 -07:00
|
|
|
// Three functions below all operate with sharedData
|
|
|
|
// structure defined above. They should be self-explanatory.
|
|
|
|
|
|
|
|
function addTarget(target) {
|
|
|
|
sharedData.data.set(target, new Map());
|
2012-12-13 14:17:00 -08:00
|
|
|
}
|
|
|
|
|
2013-04-09 12:00:30 -07:00
|
|
|
function getProfiles(target) {
|
|
|
|
return sharedData.data.get(target);
|
|
|
|
}
|
2013-03-21 16:01:32 -07:00
|
|
|
|
2013-05-28 12:51:42 -07:00
|
|
|
function getCurrentTime() {
|
|
|
|
return (new Date()).getTime() - sharedData.startTime;
|
|
|
|
}
|
|
|
|
|
2013-04-09 12:00:30 -07:00
|
|
|
/**
|
|
|
|
* Object to control the JavaScript Profiler over the remote
|
|
|
|
* debugging protocol.
|
|
|
|
*
|
|
|
|
* @param Target target
|
|
|
|
* A target object as defined in Target.jsm
|
|
|
|
*/
|
|
|
|
function ProfilerController(target) {
|
|
|
|
this.target = target;
|
|
|
|
this.client = target.client;
|
|
|
|
this.isConnected = false;
|
2012-12-13 14:17:00 -08:00
|
|
|
|
2013-04-09 12:00:30 -07:00
|
|
|
addTarget(target);
|
2012-12-13 14:17:00 -08:00
|
|
|
|
2013-04-09 12:00:30 -07:00
|
|
|
// Chrome debugging targets have already obtained a reference
|
|
|
|
// to the profiler actor.
|
|
|
|
if (target.chrome) {
|
|
|
|
this.isConnected = true;
|
|
|
|
this.actor = target.form.profilerActor;
|
|
|
|
}
|
|
|
|
};
|
2012-12-13 14:17:00 -08:00
|
|
|
|
2013-04-09 12:00:30 -07:00
|
|
|
ProfilerController.prototype = {
|
2012-12-13 14:17:00 -08:00
|
|
|
/**
|
2013-04-09 12:00:30 -07:00
|
|
|
* Return a map of profile results for the current target.
|
2012-12-13 14:17:00 -08:00
|
|
|
*
|
2013-04-09 12:00:30 -07:00
|
|
|
* @return Map
|
2012-12-13 14:17:00 -08:00
|
|
|
*/
|
2013-04-09 12:00:30 -07:00
|
|
|
get profiles() {
|
|
|
|
return getProfiles(this.target);
|
2012-12-13 14:17:00 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2013-04-09 12:00:30 -07:00
|
|
|
* Checks whether the profile is currently recording.
|
2012-12-13 14:17:00 -08:00
|
|
|
*
|
2013-04-09 12:00:30 -07:00
|
|
|
* @param object profile
|
|
|
|
* An object made by calling makeProfile function.
|
|
|
|
* @return boolean
|
2012-12-13 14:17:00 -08:00
|
|
|
*/
|
2013-04-09 12:00:30 -07:00
|
|
|
isProfileRecording: function PC_isProfileRecording(profile) {
|
|
|
|
return profile.timeStarted !== null && profile.timeEnded === null;
|
2012-12-13 14:17:00 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Connects to the client unless we're already connected.
|
|
|
|
*
|
2013-04-09 12:00:30 -07:00
|
|
|
* @param function cb
|
2012-12-13 14:17:00 -08:00
|
|
|
* Function to be called once we're connected. If
|
|
|
|
* the controller is already connected, this function
|
|
|
|
* will be called immediately (synchronously).
|
|
|
|
*/
|
2013-05-28 12:51:42 -07:00
|
|
|
connect: function (cb) {
|
2013-04-09 12:00:30 -07:00
|
|
|
if (this.isConnected) {
|
|
|
|
return void cb();
|
2012-12-13 14:17:00 -08:00
|
|
|
}
|
|
|
|
|
2013-04-09 12:00:30 -07:00
|
|
|
this.client.listTabs((resp) => {
|
|
|
|
this.actor = resp.profilerActor;
|
|
|
|
this.isConnected = true;
|
2013-05-28 12:51:42 -07:00
|
|
|
cb();
|
|
|
|
})
|
2012-12-13 14:17:00 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2013-04-09 12:00:30 -07:00
|
|
|
* Adds actor and type information to data and sends the request over
|
|
|
|
* the remote debugging protocol.
|
2012-12-13 14:17:00 -08:00
|
|
|
*
|
2013-04-09 12:00:30 -07:00
|
|
|
* @param string type
|
|
|
|
* Method to call on the other side
|
|
|
|
* @param object data
|
|
|
|
* Data to send with the request
|
|
|
|
* @param function cb
|
|
|
|
* A callback function
|
2012-12-13 14:17:00 -08:00
|
|
|
*/
|
2013-04-09 12:00:30 -07:00
|
|
|
request: function (type, data, cb) {
|
|
|
|
data.to = this.actor;
|
|
|
|
data.type = type;
|
|
|
|
this.client.request(data, cb);
|
2012-12-13 14:17:00 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2013-04-09 12:00:30 -07:00
|
|
|
* Checks whether the profiler is active.
|
2012-12-13 14:17:00 -08:00
|
|
|
*
|
2013-04-09 12:00:30 -07:00
|
|
|
* @param function cb
|
|
|
|
* 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.
|
2013-03-21 16:01:32 -07:00
|
|
|
*/
|
2013-04-09 12:00:30 -07:00
|
|
|
isActive: function (cb) {
|
2013-05-28 12:51:42 -07:00
|
|
|
this.request("isActive", {}, (resp) => cb(resp.error, resp.isActive));
|
2013-03-21 16:01:32 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2013-05-28 12:51:42 -07:00
|
|
|
this.isActive((err, isActive) => {
|
2013-03-21 16:01:32 -07:00
|
|
|
if (isActive) {
|
2013-05-28 12:51:42 -07:00
|
|
|
profile.timeStarted = getCurrentTime();
|
2013-03-21 16:01:32 -07:00
|
|
|
return void cb();
|
|
|
|
}
|
|
|
|
|
2013-04-09 12:00:30 -07:00
|
|
|
let params = {
|
|
|
|
entries: 1000000,
|
|
|
|
interval: 1,
|
|
|
|
features: ["js"],
|
|
|
|
};
|
|
|
|
|
|
|
|
this.request("startProfiler", params, (resp) => {
|
|
|
|
if (resp.error) {
|
|
|
|
return void cb(resp.error);
|
2013-03-21 16:01:32 -07:00
|
|
|
}
|
|
|
|
|
2013-05-28 12:51:42 -07:00
|
|
|
sharedData.startTime = (new Date()).getTime();
|
|
|
|
profile.timeStarted = getCurrentTime();
|
2013-03-21 16:01:32 -07:00
|
|
|
cb();
|
|
|
|
});
|
2012-12-13 14:17:00 -08:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2013-04-09 12:00:30 -07:00
|
|
|
* Stops the profiler. NOTE, that we don't stop the actual
|
|
|
|
* SPS Profiler here. It will be stopped as soon as all
|
|
|
|
* clients disconnect from the profiler actor.
|
2012-12-13 14:17:00 -08:00
|
|
|
*
|
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) {
|
2013-04-09 12:00:30 -07:00
|
|
|
if (!this.profiles.has(name)) {
|
2013-03-21 16:01:32 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-04-09 12:00:30 -07:00
|
|
|
let profile = this.profiles.get(name);
|
|
|
|
if (!this.isProfileRecording(profile)) {
|
|
|
|
return;
|
|
|
|
}
|
2013-03-21 16:01:32 -07:00
|
|
|
|
2013-04-09 12:00:30 -07:00
|
|
|
this.request("getProfile", {}, (resp) => {
|
|
|
|
if (resp.error) {
|
|
|
|
Cu.reportError("Failed to fetch profile data.");
|
|
|
|
return void cb(resp.error, null);
|
2013-03-21 16:01:32 -07:00
|
|
|
}
|
|
|
|
|
2013-04-09 12:00:30 -07:00
|
|
|
let data = resp.profile;
|
2013-05-28 12:51:42 -07:00
|
|
|
profile.timeEnded = getCurrentTime();
|
2012-12-13 14:17:00 -08:00
|
|
|
|
2013-04-09 12:00:30 -07:00
|
|
|
// Filter out all samples that fall out of current
|
|
|
|
// profile's range.
|
2013-03-21 16:01:32 -07:00
|
|
|
|
2013-04-09 12:00:30 -07:00
|
|
|
data.threads = data.threads.map((thread) => {
|
|
|
|
let samples = thread.samples.filter((sample) => {
|
2013-03-21 16:01:32 -07:00
|
|
|
return sample.time >= profile.timeStarted;
|
|
|
|
});
|
2013-04-09 12:00:30 -07:00
|
|
|
|
2013-03-21 16:01:32 -07:00
|
|
|
return { samples: samples };
|
2012-12-13 14:17:00 -08:00
|
|
|
});
|
2013-03-21 16:01:32 -07:00
|
|
|
|
2013-04-09 12:00:30 -07:00
|
|
|
cb(null, data);
|
2013-03-21 16:01:32 -07:00
|
|
|
});
|
2012-12-13 14:17:00 -08:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Cleanup.
|
|
|
|
*/
|
2013-03-21 16:01:32 -07:00
|
|
|
destroy: function PC_destroy() {
|
2013-04-09 12:00:30 -07:00
|
|
|
this.client = null;
|
|
|
|
this.target = null;
|
|
|
|
this.actor = null;
|
2012-12-13 14:17:00 -08:00
|
|
|
}
|
|
|
|
};
|