toolkit/devtools/server/actors/profiler.js

branch
TOR_BUG_3246
changeset 7
129ffea94266
equal deleted inserted replaced
-1:000000000000 0:c50fac966769
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 "use strict";
6
7 var startedProfilers = 0;
8 var startTime = 0;
9
10 function getCurrentTime() {
11 return (new Date()).getTime() - startTime;
12 }
13
14 /**
15 * Creates a ProfilerActor. ProfilerActor provides remote access to the
16 * built-in profiler module.
17 *
18 * ProfilerActor.onGetProfile returns a JavaScript object with data
19 * generated by our built-in profiler moduele. It has the following
20 * format:
21 *
22 * {
23 * libs: string,
24 * meta: {
25 * interval: number,
26 * platform: string,
27 * (...)
28 * },
29 * threads: [
30 * {
31 * samples: [
32 * {
33 * frames: [
34 * {
35 * line: number,
36 * location: string
37 * }
38 * ],
39 * name: string
40 * responsiveness: number (in ms)
41 * time: number (nspr time)
42 * }
43 * ]
44 * }
45 * ]
46 * }
47 *
48 */
49 function ProfilerActor(aConnection)
50 {
51 this._profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
52 this._started = false;
53 this._observedEvents = [];
54 }
55
56 ProfilerActor.prototype = {
57 actorPrefix: "profiler",
58
59 disconnect: function() {
60 for (var event of this._observedEvents) {
61 Services.obs.removeObserver(this, event);
62 }
63
64 this.stopProfiler();
65 this._profiler = null;
66 },
67
68 stopProfiler: function() {
69 // We stop the profiler only after the last client has
70 // stopped profiling. Otherwise there's a problem where
71 // we stop the profiler as soon as you close the devtools
72 // panel in one tab even though there might be other
73 // profiler instances running in other tabs.
74 if (!this._started) {
75 return;
76 }
77 this._started = false;
78 startedProfilers -= 1;
79 if (startedProfilers <= 0) {
80 this._profiler.StopProfiler();
81 }
82 },
83
84 onStartProfiler: function(aRequest) {
85 this._profiler.StartProfiler(aRequest.entries, aRequest.interval,
86 aRequest.features, aRequest.features.length);
87 this._started = true;
88 startedProfilers += 1;
89 startTime = (new Date()).getTime();
90 return { "msg": "profiler started" }
91 },
92 onStopProfiler: function(aRequest) {
93 this.stopProfiler();
94 return { "msg": "profiler stopped" }
95 },
96 onGetProfileStr: function(aRequest) {
97 var profileStr = this._profiler.GetProfile();
98 return { "profileStr": profileStr }
99 },
100 onGetProfile: function(aRequest) {
101 var profile = this._profiler.getProfileData();
102 return { "profile": profile, "currentTime": getCurrentTime() }
103 },
104 onIsActive: function(aRequest) {
105 var isActive = this._profiler.IsActive();
106 var currentTime = isActive ? getCurrentTime() : null;
107 return { "isActive": isActive, "currentTime": currentTime }
108 },
109 onGetResponsivenessTimes: function(aRequest) {
110 var times = this._profiler.GetResponsivenessTimes({});
111 return { "responsivenessTimes": times }
112 },
113 onGetFeatures: function(aRequest) {
114 var features = this._profiler.GetFeatures([]);
115 return { "features": features }
116 },
117 onGetSharedLibraryInformation: function(aRequest) {
118 var sharedLibraries = this._profiler.getSharedLibraryInformation();
119 return { "sharedLibraryInformation": sharedLibraries }
120 },
121 onRegisterEventNotifications: function(aRequest) {
122 let registered = [];
123 for (var event of aRequest.events) {
124 if (this._observedEvents.indexOf(event) != -1)
125 continue;
126 Services.obs.addObserver(this, event, false);
127 this._observedEvents.push(event);
128 registered.push(event);
129 }
130 return { registered: registered }
131 },
132 onUnregisterEventNotifications: function(aRequest) {
133 let unregistered = [];
134 for (var event of aRequest.events) {
135 let idx = this._observedEvents.indexOf(event);
136 if (idx == -1)
137 continue;
138 Services.obs.removeObserver(this, event);
139 this._observedEvents.splice(idx, 1);
140 unregistered.push(event);
141 }
142 return { unregistered: unregistered }
143 },
144 observe: DevToolsUtils.makeInfallible(function(aSubject, aTopic, aData) {
145 /*
146 * this.conn.send can only transmit acyclic values. However, it is
147 * idiomatic for wrapped JS objects like aSubject (and possibly aData?)
148 * to have a 'wrappedJSObject' property pointing to themselves.
149 *
150 * this.conn.send also assumes that it can retain the object it is
151 * passed to be handled on later event ticks; and that it's okay to
152 * freeze it. Since we don't really know what aSubject and aData are,
153 * we need to pass this.conn.send a copy of them, not the originals.
154 *
155 * We break the cycle and make the copy by JSON.stringifying those
156 * values with a replacer that omits properties known to introduce
157 * cycles, and then JSON.parsing the result. This spends processor
158 * time, but it's simple.
159 */
160 function cycleBreaker(key, value) {
161 if (key === 'wrappedJSObject') {
162 return undefined;
163 }
164 return value;
165 }
166
167 /*
168 * If these values are objects with a non-null 'wrappedJSObject' property
169 * and aren't Xrays, use their .wrappedJSObject. Otherwise, use the value
170 * unchanged.
171 */
172 aSubject = (aSubject && !Cu.isXrayWrapper(aSubject) && aSubject.wrappedJSObject) || aSubject;
173 aData = (aData && !Cu.isXrayWrapper(aData) && aData.wrappedJSObject) || aData;
174
175 let subj = JSON.parse(JSON.stringify(aSubject, cycleBreaker));
176 let data = JSON.parse(JSON.stringify(aData, cycleBreaker));
177
178 let send = (extra) => {
179 data = data || {};
180
181 if (extra)
182 data.extra = extra;
183
184 this.conn.send({
185 from: this.actorID,
186 type: "eventNotification",
187 event: aTopic,
188 subject: subj,
189 data: data
190 });
191 }
192
193 if (aTopic !== "console-api-profiler")
194 return void send();
195
196 // If the event was generated from console.profile or
197 // console.profileEnd we need to start the profiler
198 // right away and only then notify our client. Otherwise,
199 // we'll lose precious samples.
200
201 let name = subj.arguments[0];
202
203 if (subj.action === "profile") {
204 let resp = this.onIsActive();
205
206 if (resp.isActive) {
207 return void send({
208 name: name,
209 currentTime: resp.currentTime,
210 action: "profile"
211 });
212 }
213
214 this.onStartProfiler({
215 entries: 1000000,
216 interval: 1,
217 features: ["js"]
218 });
219
220 return void send({ currentTime: 0, action: "profile", name: name });
221 }
222
223 if (subj.action === "profileEnd") {
224 let resp = this.onGetProfile();
225 resp.action = "profileEnd";
226 resp.name = name;
227 send(resp);
228 }
229
230 return undefined; // Otherwise xpcshell tests fail.
231 }, "ProfilerActor.prototype.observe"),
232 };
233
234 /**
235 * The request types this actor can handle.
236 */
237 ProfilerActor.prototype.requestTypes = {
238 "startProfiler": ProfilerActor.prototype.onStartProfiler,
239 "stopProfiler": ProfilerActor.prototype.onStopProfiler,
240 "getProfileStr": ProfilerActor.prototype.onGetProfileStr,
241 "getProfile": ProfilerActor.prototype.onGetProfile,
242 "isActive": ProfilerActor.prototype.onIsActive,
243 "getResponsivenessTimes": ProfilerActor.prototype.onGetResponsivenessTimes,
244 "getFeatures": ProfilerActor.prototype.onGetFeatures,
245 "getSharedLibraryInformation": ProfilerActor.prototype.onGetSharedLibraryInformation,
246 "registerEventNotifications": ProfilerActor.prototype.onRegisterEventNotifications,
247 "unregisterEventNotifications": ProfilerActor.prototype.onUnregisterEventNotifications
248 };
249
250 DebuggerServer.addGlobalActor(ProfilerActor, "profilerActor");

mercurial