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