|
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"); |