1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/devtools/server/actors/profiler.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,250 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +var startedProfilers = 0; 1.11 +var startTime = 0; 1.12 + 1.13 +function getCurrentTime() { 1.14 + return (new Date()).getTime() - startTime; 1.15 +} 1.16 + 1.17 +/** 1.18 + * Creates a ProfilerActor. ProfilerActor provides remote access to the 1.19 + * built-in profiler module. 1.20 + * 1.21 + * ProfilerActor.onGetProfile returns a JavaScript object with data 1.22 + * generated by our built-in profiler moduele. It has the following 1.23 + * format: 1.24 + * 1.25 + * { 1.26 + * libs: string, 1.27 + * meta: { 1.28 + * interval: number, 1.29 + * platform: string, 1.30 + * (...) 1.31 + * }, 1.32 + * threads: [ 1.33 + * { 1.34 + * samples: [ 1.35 + * { 1.36 + * frames: [ 1.37 + * { 1.38 + * line: number, 1.39 + * location: string 1.40 + * } 1.41 + * ], 1.42 + * name: string 1.43 + * responsiveness: number (in ms) 1.44 + * time: number (nspr time) 1.45 + * } 1.46 + * ] 1.47 + * } 1.48 + * ] 1.49 + * } 1.50 + * 1.51 + */ 1.52 +function ProfilerActor(aConnection) 1.53 +{ 1.54 + this._profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); 1.55 + this._started = false; 1.56 + this._observedEvents = []; 1.57 +} 1.58 + 1.59 +ProfilerActor.prototype = { 1.60 + actorPrefix: "profiler", 1.61 + 1.62 + disconnect: function() { 1.63 + for (var event of this._observedEvents) { 1.64 + Services.obs.removeObserver(this, event); 1.65 + } 1.66 + 1.67 + this.stopProfiler(); 1.68 + this._profiler = null; 1.69 + }, 1.70 + 1.71 + stopProfiler: function() { 1.72 + // We stop the profiler only after the last client has 1.73 + // stopped profiling. Otherwise there's a problem where 1.74 + // we stop the profiler as soon as you close the devtools 1.75 + // panel in one tab even though there might be other 1.76 + // profiler instances running in other tabs. 1.77 + if (!this._started) { 1.78 + return; 1.79 + } 1.80 + this._started = false; 1.81 + startedProfilers -= 1; 1.82 + if (startedProfilers <= 0) { 1.83 + this._profiler.StopProfiler(); 1.84 + } 1.85 + }, 1.86 + 1.87 + onStartProfiler: function(aRequest) { 1.88 + this._profiler.StartProfiler(aRequest.entries, aRequest.interval, 1.89 + aRequest.features, aRequest.features.length); 1.90 + this._started = true; 1.91 + startedProfilers += 1; 1.92 + startTime = (new Date()).getTime(); 1.93 + return { "msg": "profiler started" } 1.94 + }, 1.95 + onStopProfiler: function(aRequest) { 1.96 + this.stopProfiler(); 1.97 + return { "msg": "profiler stopped" } 1.98 + }, 1.99 + onGetProfileStr: function(aRequest) { 1.100 + var profileStr = this._profiler.GetProfile(); 1.101 + return { "profileStr": profileStr } 1.102 + }, 1.103 + onGetProfile: function(aRequest) { 1.104 + var profile = this._profiler.getProfileData(); 1.105 + return { "profile": profile, "currentTime": getCurrentTime() } 1.106 + }, 1.107 + onIsActive: function(aRequest) { 1.108 + var isActive = this._profiler.IsActive(); 1.109 + var currentTime = isActive ? getCurrentTime() : null; 1.110 + return { "isActive": isActive, "currentTime": currentTime } 1.111 + }, 1.112 + onGetResponsivenessTimes: function(aRequest) { 1.113 + var times = this._profiler.GetResponsivenessTimes({}); 1.114 + return { "responsivenessTimes": times } 1.115 + }, 1.116 + onGetFeatures: function(aRequest) { 1.117 + var features = this._profiler.GetFeatures([]); 1.118 + return { "features": features } 1.119 + }, 1.120 + onGetSharedLibraryInformation: function(aRequest) { 1.121 + var sharedLibraries = this._profiler.getSharedLibraryInformation(); 1.122 + return { "sharedLibraryInformation": sharedLibraries } 1.123 + }, 1.124 + onRegisterEventNotifications: function(aRequest) { 1.125 + let registered = []; 1.126 + for (var event of aRequest.events) { 1.127 + if (this._observedEvents.indexOf(event) != -1) 1.128 + continue; 1.129 + Services.obs.addObserver(this, event, false); 1.130 + this._observedEvents.push(event); 1.131 + registered.push(event); 1.132 + } 1.133 + return { registered: registered } 1.134 + }, 1.135 + onUnregisterEventNotifications: function(aRequest) { 1.136 + let unregistered = []; 1.137 + for (var event of aRequest.events) { 1.138 + let idx = this._observedEvents.indexOf(event); 1.139 + if (idx == -1) 1.140 + continue; 1.141 + Services.obs.removeObserver(this, event); 1.142 + this._observedEvents.splice(idx, 1); 1.143 + unregistered.push(event); 1.144 + } 1.145 + return { unregistered: unregistered } 1.146 + }, 1.147 + observe: DevToolsUtils.makeInfallible(function(aSubject, aTopic, aData) { 1.148 + /* 1.149 + * this.conn.send can only transmit acyclic values. However, it is 1.150 + * idiomatic for wrapped JS objects like aSubject (and possibly aData?) 1.151 + * to have a 'wrappedJSObject' property pointing to themselves. 1.152 + * 1.153 + * this.conn.send also assumes that it can retain the object it is 1.154 + * passed to be handled on later event ticks; and that it's okay to 1.155 + * freeze it. Since we don't really know what aSubject and aData are, 1.156 + * we need to pass this.conn.send a copy of them, not the originals. 1.157 + * 1.158 + * We break the cycle and make the copy by JSON.stringifying those 1.159 + * values with a replacer that omits properties known to introduce 1.160 + * cycles, and then JSON.parsing the result. This spends processor 1.161 + * time, but it's simple. 1.162 + */ 1.163 + function cycleBreaker(key, value) { 1.164 + if (key === 'wrappedJSObject') { 1.165 + return undefined; 1.166 + } 1.167 + return value; 1.168 + } 1.169 + 1.170 + /* 1.171 + * If these values are objects with a non-null 'wrappedJSObject' property 1.172 + * and aren't Xrays, use their .wrappedJSObject. Otherwise, use the value 1.173 + * unchanged. 1.174 + */ 1.175 + aSubject = (aSubject && !Cu.isXrayWrapper(aSubject) && aSubject.wrappedJSObject) || aSubject; 1.176 + aData = (aData && !Cu.isXrayWrapper(aData) && aData.wrappedJSObject) || aData; 1.177 + 1.178 + let subj = JSON.parse(JSON.stringify(aSubject, cycleBreaker)); 1.179 + let data = JSON.parse(JSON.stringify(aData, cycleBreaker)); 1.180 + 1.181 + let send = (extra) => { 1.182 + data = data || {}; 1.183 + 1.184 + if (extra) 1.185 + data.extra = extra; 1.186 + 1.187 + this.conn.send({ 1.188 + from: this.actorID, 1.189 + type: "eventNotification", 1.190 + event: aTopic, 1.191 + subject: subj, 1.192 + data: data 1.193 + }); 1.194 + } 1.195 + 1.196 + if (aTopic !== "console-api-profiler") 1.197 + return void send(); 1.198 + 1.199 + // If the event was generated from console.profile or 1.200 + // console.profileEnd we need to start the profiler 1.201 + // right away and only then notify our client. Otherwise, 1.202 + // we'll lose precious samples. 1.203 + 1.204 + let name = subj.arguments[0]; 1.205 + 1.206 + if (subj.action === "profile") { 1.207 + let resp = this.onIsActive(); 1.208 + 1.209 + if (resp.isActive) { 1.210 + return void send({ 1.211 + name: name, 1.212 + currentTime: resp.currentTime, 1.213 + action: "profile" 1.214 + }); 1.215 + } 1.216 + 1.217 + this.onStartProfiler({ 1.218 + entries: 1000000, 1.219 + interval: 1, 1.220 + features: ["js"] 1.221 + }); 1.222 + 1.223 + return void send({ currentTime: 0, action: "profile", name: name }); 1.224 + } 1.225 + 1.226 + if (subj.action === "profileEnd") { 1.227 + let resp = this.onGetProfile(); 1.228 + resp.action = "profileEnd"; 1.229 + resp.name = name; 1.230 + send(resp); 1.231 + } 1.232 + 1.233 + return undefined; // Otherwise xpcshell tests fail. 1.234 + }, "ProfilerActor.prototype.observe"), 1.235 +}; 1.236 + 1.237 +/** 1.238 + * The request types this actor can handle. 1.239 + */ 1.240 +ProfilerActor.prototype.requestTypes = { 1.241 + "startProfiler": ProfilerActor.prototype.onStartProfiler, 1.242 + "stopProfiler": ProfilerActor.prototype.onStopProfiler, 1.243 + "getProfileStr": ProfilerActor.prototype.onGetProfileStr, 1.244 + "getProfile": ProfilerActor.prototype.onGetProfile, 1.245 + "isActive": ProfilerActor.prototype.onIsActive, 1.246 + "getResponsivenessTimes": ProfilerActor.prototype.onGetResponsivenessTimes, 1.247 + "getFeatures": ProfilerActor.prototype.onGetFeatures, 1.248 + "getSharedLibraryInformation": ProfilerActor.prototype.onGetSharedLibraryInformation, 1.249 + "registerEventNotifications": ProfilerActor.prototype.onRegisterEventNotifications, 1.250 + "unregisterEventNotifications": ProfilerActor.prototype.onUnregisterEventNotifications 1.251 +}; 1.252 + 1.253 +DebuggerServer.addGlobalActor(ProfilerActor, "profilerActor");