|
1 /* |
|
2 * Copyright (C) 2010 Google Inc. All rights reserved. |
|
3 * |
|
4 * Redistribution and use in source and binary forms, with or without |
|
5 * modification, are permitted provided that the following conditions are |
|
6 * met: |
|
7 * |
|
8 * * Redistributions of source code must retain the above copyright |
|
9 * notice, this list of conditions and the following disclaimer. |
|
10 * * Redistributions in binary form must reproduce the above |
|
11 * copyright notice, this list of conditions and the following disclaimer |
|
12 * in the documentation and/or other materials provided with the |
|
13 * distribution. |
|
14 * * Neither the name of Google Inc. nor the names of its |
|
15 * contributors may be used to endorse or promote products derived from |
|
16 * this software without specific prior written permission. |
|
17 * |
|
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
29 */ |
|
30 |
|
31 /** |
|
32 * @fileoverview Profiler processor is used to process log file produced |
|
33 * by V8 and produce an internal profile representation which is used |
|
34 * for building profile views in "Profiles" tab. |
|
35 */ |
|
36 |
|
37 |
|
38 /** |
|
39 * Creates a Profile View builder object compatible with WebKit Profiler UI. |
|
40 * |
|
41 * @param {number} samplingRate Number of ms between profiler ticks. |
|
42 * @constructor |
|
43 */ |
|
44 devtools.profiler.WebKitViewBuilder = function(samplingRate) |
|
45 { |
|
46 devtools.profiler.ViewBuilder.call(this, samplingRate); |
|
47 }; |
|
48 devtools.profiler.WebKitViewBuilder.prototype.__proto__ = devtools.profiler.ViewBuilder.prototype; |
|
49 |
|
50 |
|
51 /** |
|
52 * @override |
|
53 */ |
|
54 devtools.profiler.WebKitViewBuilder.prototype.createViewNode = function(funcName, totalTime, selfTime, head) |
|
55 { |
|
56 return new devtools.profiler.WebKitViewNode(funcName, totalTime, selfTime, head); |
|
57 }; |
|
58 |
|
59 |
|
60 /** |
|
61 * Constructs a Profile View node object for displaying in WebKit Profiler UI. |
|
62 * |
|
63 * @param {string} internalFuncName A fully qualified function name. |
|
64 * @param {number} totalTime Amount of time that application spent in the |
|
65 * corresponding function and its descendants (not that depending on |
|
66 * profile they can be either callees or callers.) |
|
67 * @param {number} selfTime Amount of time that application spent in the |
|
68 * corresponding function only. |
|
69 * @param {devtools.profiler.ProfileView.Node} head Profile view head. |
|
70 * @constructor |
|
71 */ |
|
72 devtools.profiler.WebKitViewNode = function(internalFuncName, totalTime, selfTime, head) |
|
73 { |
|
74 devtools.profiler.ProfileView.Node.call(this, internalFuncName, totalTime, selfTime, head); |
|
75 this.initFuncInfo_(); |
|
76 this.callUID = internalFuncName; |
|
77 }; |
|
78 devtools.profiler.WebKitViewNode.prototype.__proto__ = devtools.profiler.ProfileView.Node.prototype; |
|
79 |
|
80 |
|
81 /** |
|
82 * RegEx for stripping V8's prefixes of compiled functions. |
|
83 */ |
|
84 devtools.profiler.WebKitViewNode.FUNC_NAME_STRIP_RE = /^(?:LazyCompile|Function|Callback): (.*)$/; |
|
85 |
|
86 |
|
87 /** |
|
88 * RegEx for extracting script source URL and line number. |
|
89 */ |
|
90 devtools.profiler.WebKitViewNode.FUNC_NAME_PARSE_RE = /^((?:get | set )?[^ ]+) (.*):(\d+)( \{\d+\})?$/; |
|
91 |
|
92 |
|
93 /** |
|
94 * Inits "functionName", "url", and "lineNumber" fields using "internalFuncName" |
|
95 * field. |
|
96 * @private |
|
97 */ |
|
98 devtools.profiler.WebKitViewNode.prototype.initFuncInfo_ = function() |
|
99 { |
|
100 var nodeAlias = devtools.profiler.WebKitViewNode; |
|
101 this.functionName = this.internalFuncName; |
|
102 |
|
103 var strippedName = nodeAlias.FUNC_NAME_STRIP_RE.exec(this.functionName); |
|
104 if (strippedName) |
|
105 this.functionName = strippedName[1]; |
|
106 |
|
107 var parsedName = nodeAlias.FUNC_NAME_PARSE_RE.exec(this.functionName); |
|
108 if (parsedName) { |
|
109 this.functionName = parsedName[1]; |
|
110 if (parsedName[4]) |
|
111 this.functionName += parsedName[4]; |
|
112 this.url = parsedName[2]; |
|
113 this.lineNumber = parsedName[3]; |
|
114 } else { |
|
115 this.url = ''; |
|
116 this.lineNumber = 0; |
|
117 } |
|
118 }; |
|
119 |
|
120 |
|
121 /** |
|
122 * Ancestor of a profile object that leaves out only JS-related functions. |
|
123 * @constructor |
|
124 */ |
|
125 devtools.profiler.JsProfile = function() |
|
126 { |
|
127 devtools.profiler.Profile.call(this); |
|
128 }; |
|
129 devtools.profiler.JsProfile.prototype.__proto__ = devtools.profiler.Profile.prototype; |
|
130 |
|
131 |
|
132 /** |
|
133 * RegExp that leaves only non-native JS functions. |
|
134 * @type {RegExp} |
|
135 */ |
|
136 devtools.profiler.JsProfile.JS_NON_NATIVE_RE = new RegExp( |
|
137 "^" + |
|
138 "(?:Callback:)|" + |
|
139 "(?:Script: (?!native))|" + |
|
140 "(?:(?:LazyCompile|Function): [^ ]*(?: (?!native )[^ ]+:\\d+)?$)"); |
|
141 |
|
142 |
|
143 /** |
|
144 * @override |
|
145 */ |
|
146 devtools.profiler.JsProfile.prototype.skipThisFunction = function(name) |
|
147 { |
|
148 return !devtools.profiler.JsProfile.JS_NON_NATIVE_RE.test(name); |
|
149 }; |
|
150 |
|
151 |
|
152 /** |
|
153 * Profiler processor. Consumes profiler log and builds profile views. |
|
154 * FIXME: change field naming style to use trailing underscore. |
|
155 * |
|
156 * @param {function(devtools.profiler.ProfileView)} newProfileCallback Callback |
|
157 * that receives a new processed profile. |
|
158 * @constructor |
|
159 */ |
|
160 devtools.profiler.Processor = function() |
|
161 { |
|
162 var dispatches = { |
|
163 "code-creation": { |
|
164 parsers: [null, this.createAddressParser("code"), parseInt, null], |
|
165 processor: this.processCodeCreation_, backrefs: true, |
|
166 needsProfile: true }, |
|
167 "code-move": { parsers: [this.createAddressParser("code"), |
|
168 this.createAddressParser("code-move-to")], |
|
169 processor: this.processCodeMove_, backrefs: true, |
|
170 needsProfile: true }, |
|
171 "code-delete": { parsers: [this.createAddressParser("code")], |
|
172 processor: this.processCodeDelete_, backrefs: true, |
|
173 needsProfile: true }, |
|
174 "function-creation": { parsers: [this.createAddressParser("code"), |
|
175 this.createAddressParser("function-obj")], |
|
176 processor: this.processFunctionCreation_, backrefs: true }, |
|
177 "function-move": { parsers: [this.createAddressParser("code"), |
|
178 this.createAddressParser("code-move-to")], |
|
179 processor: this.processFunctionMove_, backrefs: true }, |
|
180 "function-delete": { parsers: [this.createAddressParser("code")], |
|
181 processor: this.processFunctionDelete_, backrefs: true }, |
|
182 "tick": { parsers: [this.createAddressParser("code"), |
|
183 this.createAddressParser("stack"), parseInt, "var-args"], |
|
184 processor: this.processTick_, backrefs: true, needProfile: true }, |
|
185 "profiler": { parsers: [null, "var-args"], |
|
186 processor: this.processProfiler_, needsProfile: false }, |
|
187 "heap-sample-begin": { parsers: [null, null, parseInt], |
|
188 processor: this.processHeapSampleBegin_ }, |
|
189 "heap-sample-stats": { parsers: [null, null, parseInt, parseInt], |
|
190 processor: this.processHeapSampleStats_ }, |
|
191 "heap-sample-item": { parsers: [null, parseInt, parseInt], |
|
192 processor: this.processHeapSampleItem_ }, |
|
193 "heap-js-cons-item": { parsers: [null, parseInt, parseInt], |
|
194 processor: this.processHeapJsConsItem_ }, |
|
195 "heap-js-ret-item": { parsers: [null, "var-args"], |
|
196 processor: this.processHeapJsRetItem_ }, |
|
197 "heap-sample-end": { parsers: [null, null], |
|
198 processor: this.processHeapSampleEnd_ }, |
|
199 // Not used in DevTools Profiler. |
|
200 "shared-library": null, |
|
201 // Obsolete row types. |
|
202 "code-allocate": null, |
|
203 "begin-code-region": null, |
|
204 "end-code-region": null}; |
|
205 |
|
206 if (devtools.profiler.Profile.VERSION === 2) { |
|
207 dispatches["tick"] = { parsers: [this.createAddressParser("code"), |
|
208 this.createAddressParser("stack"), |
|
209 this.createAddressParser("func"), parseInt, "var-args"], |
|
210 processor: this.processTickV2_, backrefs: true }; |
|
211 } |
|
212 |
|
213 devtools.profiler.LogReader.call(this, dispatches); |
|
214 |
|
215 /** |
|
216 * Callback that is called when a new profile is encountered in the log. |
|
217 * @type {function()} |
|
218 */ |
|
219 this.startedProfileProcessing_ = null; |
|
220 |
|
221 /** |
|
222 * Callback that is called periodically to display processing status. |
|
223 * @type {function()} |
|
224 */ |
|
225 this.profileProcessingStatus_ = null; |
|
226 |
|
227 /** |
|
228 * Callback that is called when a profile has been processed and is ready |
|
229 * to be shown. |
|
230 * @type {function(devtools.profiler.ProfileView)} |
|
231 */ |
|
232 this.finishedProfileProcessing_ = null; |
|
233 |
|
234 /** |
|
235 * The current profile. |
|
236 * @type {devtools.profiler.JsProfile} |
|
237 */ |
|
238 this.currentProfile_ = null; |
|
239 |
|
240 /** |
|
241 * Builder of profile views. Created during "profiler,begin" event processing. |
|
242 * @type {devtools.profiler.WebKitViewBuilder} |
|
243 */ |
|
244 this.viewBuilder_ = null; |
|
245 |
|
246 /** |
|
247 * Next profile id. |
|
248 * @type {number} |
|
249 */ |
|
250 this.profileId_ = 1; |
|
251 |
|
252 /** |
|
253 * Counter for processed ticks. |
|
254 * @type {number} |
|
255 */ |
|
256 this.ticksCount_ = 0; |
|
257 |
|
258 /** |
|
259 * Interval id for updating processing status. |
|
260 * @type {number} |
|
261 */ |
|
262 this.processingInterval_ = null; |
|
263 |
|
264 /** |
|
265 * The current heap snapshot. |
|
266 * @type {string} |
|
267 */ |
|
268 this.currentHeapSnapshot_ = null; |
|
269 |
|
270 /** |
|
271 * Next heap snapshot id. |
|
272 * @type {number} |
|
273 */ |
|
274 this.heapSnapshotId_ = 1; |
|
275 }; |
|
276 devtools.profiler.Processor.prototype.__proto__ = devtools.profiler.LogReader.prototype; |
|
277 |
|
278 |
|
279 /** |
|
280 * @override |
|
281 */ |
|
282 devtools.profiler.Processor.prototype.printError = function(str) |
|
283 { |
|
284 debugPrint(str); |
|
285 }; |
|
286 |
|
287 |
|
288 /** |
|
289 * @override |
|
290 */ |
|
291 devtools.profiler.Processor.prototype.skipDispatch = function(dispatch) |
|
292 { |
|
293 return dispatch.needsProfile && this.currentProfile_ === null; |
|
294 }; |
|
295 |
|
296 |
|
297 /** |
|
298 * Sets profile processing callbacks. |
|
299 * |
|
300 * @param {function()} started Started processing callback. |
|
301 * @param {function(devtools.profiler.ProfileView)} finished Finished |
|
302 * processing callback. |
|
303 */ |
|
304 devtools.profiler.Processor.prototype.setCallbacks = function(started, processing, finished) |
|
305 { |
|
306 this.startedProfileProcessing_ = started; |
|
307 this.profileProcessingStatus_ = processing; |
|
308 this.finishedProfileProcessing_ = finished; |
|
309 }; |
|
310 |
|
311 |
|
312 /** |
|
313 * An address for the fake "(program)" entry. WebKit's visualisation |
|
314 * has assumptions on how the top of the call tree should look like, |
|
315 * and we need to add a fake entry as the topmost function. This |
|
316 * address is chosen because it's the end address of the first memory |
|
317 * page, which is never used for code or data, but only as a guard |
|
318 * page for catching AV errors. |
|
319 * |
|
320 * @type {number} |
|
321 */ |
|
322 devtools.profiler.Processor.PROGRAM_ENTRY = 0xffff; |
|
323 /** |
|
324 * @type {string} |
|
325 */ |
|
326 devtools.profiler.Processor.PROGRAM_ENTRY_STR = "0xffff"; |
|
327 |
|
328 |
|
329 /** |
|
330 * Sets new profile callback. |
|
331 * @param {function(devtools.profiler.ProfileView)} callback Callback function. |
|
332 */ |
|
333 devtools.profiler.Processor.prototype.setNewProfileCallback = function(callback) |
|
334 { |
|
335 this.newProfileCallback_ = callback; |
|
336 }; |
|
337 |
|
338 |
|
339 devtools.profiler.Processor.prototype.processProfiler_ = function(state, params) |
|
340 { |
|
341 switch (state) { |
|
342 case "resume": |
|
343 if (this.currentProfile_ === null) { |
|
344 this.currentProfile_ = new devtools.profiler.JsProfile(); |
|
345 // see the comment for devtools.profiler.Processor.PROGRAM_ENTRY |
|
346 this.currentProfile_.addCode("Function", "(program)", devtools.profiler.Processor.PROGRAM_ENTRY, 1); |
|
347 if (this.startedProfileProcessing_) |
|
348 this.startedProfileProcessing_(); |
|
349 this.ticksCount_ = 0; |
|
350 var self = this; |
|
351 if (this.profileProcessingStatus_) { |
|
352 this.processingInterval_ = window.setInterval( |
|
353 function() { self.profileProcessingStatus_(self.ticksCount_); }, |
|
354 1000); |
|
355 } |
|
356 } |
|
357 break; |
|
358 case "pause": |
|
359 if (this.currentProfile_ !== null) { |
|
360 window.clearInterval(this.processingInterval_); |
|
361 this.processingInterval_ = null; |
|
362 if (this.finishedProfileProcessing_) |
|
363 this.finishedProfileProcessing_(this.createProfileForView()); |
|
364 this.currentProfile_ = null; |
|
365 } |
|
366 break; |
|
367 case "begin": |
|
368 var samplingRate = NaN; |
|
369 if (params.length > 0) |
|
370 samplingRate = parseInt(params[0]); |
|
371 if (isNaN(samplingRate)) |
|
372 samplingRate = 1; |
|
373 this.viewBuilder_ = new devtools.profiler.WebKitViewBuilder(samplingRate); |
|
374 break; |
|
375 // These events are valid but aren't used. |
|
376 case "compression": |
|
377 case "end": break; |
|
378 default: |
|
379 throw new Error("unknown profiler state: " + state); |
|
380 } |
|
381 }; |
|
382 |
|
383 |
|
384 devtools.profiler.Processor.prototype.processCodeCreation_ = function(type, start, size, name) |
|
385 { |
|
386 this.currentProfile_.addCode(this.expandAlias(type), name, start, size); |
|
387 }; |
|
388 |
|
389 |
|
390 devtools.profiler.Processor.prototype.processCodeMove_ = function(from, to) |
|
391 { |
|
392 this.currentProfile_.moveCode(from, to); |
|
393 }; |
|
394 |
|
395 |
|
396 devtools.profiler.Processor.prototype.processCodeDelete_ = function(start) |
|
397 { |
|
398 this.currentProfile_.deleteCode(start); |
|
399 }; |
|
400 |
|
401 |
|
402 devtools.profiler.Processor.prototype.processFunctionCreation_ = function(functionAddr, codeAddr) |
|
403 { |
|
404 this.currentProfile_.addCodeAlias(functionAddr, codeAddr); |
|
405 }; |
|
406 |
|
407 |
|
408 devtools.profiler.Processor.prototype.processFunctionMove_ = function(from, to) |
|
409 { |
|
410 this.currentProfile_.safeMoveDynamicCode(from, to); |
|
411 }; |
|
412 |
|
413 |
|
414 devtools.profiler.Processor.prototype.processFunctionDelete_ = function(start) |
|
415 { |
|
416 this.currentProfile_.safeDeleteDynamicCode(start); |
|
417 }; |
|
418 |
|
419 |
|
420 // TODO(mnaganov): Remove after next V8 roll. |
|
421 devtools.profiler.Processor.prototype.processTick_ = function(pc, sp, vmState, stack) |
|
422 { |
|
423 // see the comment for devtools.profiler.Processor.PROGRAM_ENTRY |
|
424 stack.push(devtools.profiler.Processor.PROGRAM_ENTRY_STR); |
|
425 this.currentProfile_.recordTick(this.processStack(pc, stack)); |
|
426 this.ticksCount_++; |
|
427 }; |
|
428 |
|
429 |
|
430 devtools.profiler.Processor.prototype.processTickV2_ = function(pc, sp, func, vmState, stack) |
|
431 { |
|
432 // see the comment for devtools.profiler.Processor.PROGRAM_ENTRY |
|
433 stack.push(devtools.profiler.Processor.PROGRAM_ENTRY_STR); |
|
434 |
|
435 |
|
436 if (func) { |
|
437 var funcEntry = this.currentProfile_.findEntry(func); |
|
438 if (!funcEntry || !funcEntry.isJSFunction || !funcEntry.isJSFunction()) |
|
439 func = 0; |
|
440 else { |
|
441 var currEntry = this.currentProfile_.findEntry(pc); |
|
442 if (!currEntry || !currEntry.isJSFunction || currEntry.isJSFunction()) { |
|
443 func = 0; |
|
444 } |
|
445 } |
|
446 } |
|
447 |
|
448 this.currentProfile_.recordTick(this.processStack(pc, func, stack)); |
|
449 this.ticksCount_++; |
|
450 }; |
|
451 |
|
452 |
|
453 devtools.profiler.Processor.prototype.processHeapSampleBegin_ = function(space, state, ticks) |
|
454 { |
|
455 if (space !== "Heap") return; |
|
456 this.currentHeapSnapshot_ = { |
|
457 number: this.heapSnapshotId_++, |
|
458 entries: {}, |
|
459 clusters: {}, |
|
460 lowlevels: {}, |
|
461 ticks: ticks |
|
462 }; |
|
463 }; |
|
464 |
|
465 |
|
466 devtools.profiler.Processor.prototype.processHeapSampleStats_ = function(space, state, capacity, used) |
|
467 { |
|
468 if (space !== "Heap") return; |
|
469 }; |
|
470 |
|
471 |
|
472 devtools.profiler.Processor.prototype.processHeapSampleItem_ = function(item, number, size) |
|
473 { |
|
474 if (!this.currentHeapSnapshot_) return; |
|
475 this.currentHeapSnapshot_.lowlevels[item] = { |
|
476 type: item, count: number, size: size |
|
477 }; |
|
478 }; |
|
479 |
|
480 |
|
481 devtools.profiler.Processor.prototype.processHeapJsConsItem_ = function(item, number, size) |
|
482 { |
|
483 if (!this.currentHeapSnapshot_) return; |
|
484 this.currentHeapSnapshot_.entries[item] = { |
|
485 cons: item, count: number, size: size, retainers: {} |
|
486 }; |
|
487 }; |
|
488 |
|
489 |
|
490 devtools.profiler.Processor.prototype.processHeapJsRetItem_ = function(item, retainersArray) |
|
491 { |
|
492 if (!this.currentHeapSnapshot_) return; |
|
493 var rawRetainers = {}; |
|
494 for (var i = 0, n = retainersArray.length; i < n; ++i) { |
|
495 var entry = retainersArray[i].split(";"); |
|
496 rawRetainers[entry[0]] = parseInt(entry[1], 10); |
|
497 } |
|
498 |
|
499 function mergeRetainers(entry) { |
|
500 for (var rawRetainer in rawRetainers) { |
|
501 var consName = rawRetainer.indexOf(":") !== -1 ? rawRetainer.split(":")[0] : rawRetainer; |
|
502 if (!(consName in entry.retainers)) |
|
503 entry.retainers[consName] = { cons: consName, count: 0, clusters: {} }; |
|
504 var retainer = entry.retainers[consName]; |
|
505 retainer.count += rawRetainers[rawRetainer]; |
|
506 if (consName !== rawRetainer) |
|
507 retainer.clusters[rawRetainer] = true; |
|
508 } |
|
509 } |
|
510 |
|
511 if (item.indexOf(":") !== -1) { |
|
512 // Array, Function, or Object instances cluster case. |
|
513 if (!(item in this.currentHeapSnapshot_.clusters)) { |
|
514 this.currentHeapSnapshot_.clusters[item] = { |
|
515 cons: item, retainers: {} |
|
516 }; |
|
517 } |
|
518 mergeRetainers(this.currentHeapSnapshot_.clusters[item]); |
|
519 item = item.split(":")[0]; |
|
520 } |
|
521 mergeRetainers(this.currentHeapSnapshot_.entries[item]); |
|
522 }; |
|
523 |
|
524 |
|
525 devtools.profiler.Processor.prototype.processHeapSampleEnd_ = function(space, state) |
|
526 { |
|
527 if (space !== "Heap") return; |
|
528 var snapshot = this.currentHeapSnapshot_; |
|
529 this.currentHeapSnapshot_ = null; |
|
530 WebInspector.panels.profiles.addSnapshot(snapshot); |
|
531 }; |
|
532 |
|
533 |
|
534 /** |
|
535 * Creates a profile for further displaying in ProfileView. |
|
536 */ |
|
537 devtools.profiler.Processor.prototype.createProfileForView = function() |
|
538 { |
|
539 var profile = this.viewBuilder_.buildView(this.currentProfile_.getTopDownProfile()); |
|
540 profile.uid = this.profileId_++; |
|
541 profile.title = UserInitiatedProfileName + "." + profile.uid; |
|
542 return profile; |
|
543 }; |