gecko/toolkit/devtools/server/actors/profiler.js

250 lines
7.5 KiB
JavaScript

/* 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";
var startedProfilers = 0;
var startTime = 0;
function getCurrentTime() {
return (new Date()).getTime() - startTime;
}
/**
* Creates a ProfilerActor. ProfilerActor provides remote access to the
* built-in profiler module.
*
* ProfilerActor.onGetProfile returns a JavaScript object with data
* generated by our built-in profiler moduele. It has the following
* format:
*
* {
* libs: string,
* meta: {
* interval: number,
* platform: string,
* (...)
* },
* threads: [
* {
* samples: [
* {
* frames: [
* {
* line: number,
* location: string
* }
* ],
* name: string
* responsiveness: number (in ms)
* time: number (nspr time)
* }
* ]
* }
* ]
* }
*
*/
function ProfilerActor(aConnection)
{
this._profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
this._started = false;
this._observedEvents = [];
}
ProfilerActor.prototype = {
actorPrefix: "profiler",
disconnect: function() {
for (var event of this._observedEvents) {
Services.obs.removeObserver(this, event);
}
this.stopProfiler();
this._profiler = null;
},
stopProfiler: function() {
// We stop the profiler only after the last client has
// stopped profiling. Otherwise there's a problem where
// we stop the profiler as soon as you close the devtools
// panel in one tab even though there might be other
// profiler instances running in other tabs.
if (!this._started) {
return;
}
this._started = false;
startedProfilers -= 1;
if (startedProfilers <= 0) {
this._profiler.StopProfiler();
}
},
onStartProfiler: function(aRequest) {
this._profiler.StartProfiler(aRequest.entries, aRequest.interval,
aRequest.features, aRequest.features.length);
this._started = true;
startedProfilers += 1;
startTime = (new Date()).getTime();
return { "msg": "profiler started" }
},
onStopProfiler: function(aRequest) {
this.stopProfiler();
return { "msg": "profiler stopped" }
},
onGetProfileStr: function(aRequest) {
var profileStr = this._profiler.GetProfile();
return { "profileStr": profileStr }
},
onGetProfile: function(aRequest) {
var profile = this._profiler.getProfileData();
return { "profile": profile, "currentTime": getCurrentTime() }
},
onIsActive: function(aRequest) {
var isActive = this._profiler.IsActive();
var currentTime = isActive ? getCurrentTime() : null;
return { "isActive": isActive, "currentTime": currentTime }
},
onGetResponsivenessTimes: function(aRequest) {
var times = this._profiler.GetResponsivenessTimes({});
return { "responsivenessTimes": times }
},
onGetFeatures: function(aRequest) {
var features = this._profiler.GetFeatures([]);
return { "features": features }
},
onGetSharedLibraryInformation: function(aRequest) {
var sharedLibraries = this._profiler.getSharedLibraryInformation();
return { "sharedLibraryInformation": sharedLibraries }
},
onRegisterEventNotifications: function(aRequest) {
let registered = [];
for (var event of aRequest.events) {
if (this._observedEvents.indexOf(event) != -1)
continue;
Services.obs.addObserver(this, event, false);
this._observedEvents.push(event);
registered.push(event);
}
return { registered: registered }
},
onUnregisterEventNotifications: function(aRequest) {
let unregistered = [];
for (var event of aRequest.events) {
let idx = this._observedEvents.indexOf(event);
if (idx == -1)
continue;
Services.obs.removeObserver(this, event);
this._observedEvents.splice(idx, 1);
unregistered.push(event);
}
return { unregistered: unregistered }
},
observe: makeInfallible(function(aSubject, aTopic, aData) {
/*
* this.conn.send can only transmit acyclic values. However, it is
* idiomatic for wrapped JS objects like aSubject (and possibly aData?)
* to have a 'wrappedJSObject' property pointing to themselves.
*
* this.conn.send also assumes that it can retain the object it is
* passed to be handled on later event ticks; and that it's okay to
* freeze it. Since we don't really know what aSubject and aData are,
* we need to pass this.conn.send a copy of them, not the originals.
*
* We break the cycle and make the copy by JSON.stringifying those
* values with a replacer that omits properties known to introduce
* cycles, and then JSON.parsing the result. This spends processor
* time, but it's simple.
*/
function cycleBreaker(key, value) {
if (key === 'wrappedJSObject') {
return undefined;
}
return value;
}
/*
* If these values are objects with a non-null 'wrappedJSObject'
* property, use its value. Otherwise, use the value unchanged.
*/
aSubject = (aSubject && aSubject.wrappedJSObject) || aSubject;
aData = (aData && aData.wrappedJSObject) || aData;
let subj = JSON.parse(JSON.stringify(aSubject, cycleBreaker));
let data = JSON.parse(JSON.stringify(aData, cycleBreaker));
let send = (extra) => {
data = data || {};
if (extra)
data.extra = extra;
this.conn.send({
from: this.actorID,
type: "eventNotification",
event: aTopic,
subject: subj,
data: data
});
}
if (aTopic !== "console-api-profiler")
return void send();
// If the event was generated from console.profile or
// console.profileEnd we need to start the profiler
// right away and only then notify our client. Otherwise,
// we'll lose precious samples.
let name = subj.arguments[0];
if (subj.action === "profile") {
let resp = this.onIsActive();
if (resp.isActive) {
return void send({
name: name,
currentTime: resp.currentTime,
action: "profile"
});
}
this.onStartProfiler({
entries: 1000000,
interval: 1,
features: ["js"]
});
return void send({ currentTime: 0, action: "profile", name: name });
}
if (subj.action === "profileEnd") {
let resp = this.onGetProfile();
resp.action = "profileEnd";
resp.name = name;
send(resp);
}
return undefined; // Otherwise xpcshell tests fail.
}, "ProfilerActor.prototype.observe"),
};
/**
* The request types this actor can handle.
*/
ProfilerActor.prototype.requestTypes = {
"startProfiler": ProfilerActor.prototype.onStartProfiler,
"stopProfiler": ProfilerActor.prototype.onStopProfiler,
"getProfileStr": ProfilerActor.prototype.onGetProfileStr,
"getProfile": ProfilerActor.prototype.onGetProfile,
"isActive": ProfilerActor.prototype.onIsActive,
"getResponsivenessTimes": ProfilerActor.prototype.onGetResponsivenessTimes,
"getFeatures": ProfilerActor.prototype.onGetFeatures,
"getSharedLibraryInformation": ProfilerActor.prototype.onGetSharedLibraryInformation,
"registerEventNotifications": ProfilerActor.prototype.onRegisterEventNotifications,
"unregisterEventNotifications": ProfilerActor.prototype.onUnregisterEventNotifications
};
DebuggerServer.addGlobalActor(ProfilerActor, "profilerActor");