OLD | NEW |
| (Empty) |
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 /** | |
6 * @fileoverview Profiler processor is used to process log file produced | |
7 * by V8 and produce an internal profile representation which is used | |
8 * for building profile views in 'Profiles' tab. | |
9 */ | |
10 goog.provide('devtools.profiler.Processor'); | |
11 | |
12 | |
13 /** | |
14 * Creates a Profile View builder object compatible with WebKit Profiler UI. | |
15 * | |
16 * @param {number} samplingRate Number of ms between profiler ticks. | |
17 * @constructor | |
18 */ | |
19 devtools.profiler.WebKitViewBuilder = function(samplingRate) { | |
20 devtools.profiler.ViewBuilder.call(this, samplingRate); | |
21 }; | |
22 goog.inherits(devtools.profiler.WebKitViewBuilder, | |
23 devtools.profiler.ViewBuilder); | |
24 | |
25 | |
26 /** | |
27 * @override | |
28 */ | |
29 devtools.profiler.WebKitViewBuilder.prototype.createViewNode = function( | |
30 funcName, totalTime, selfTime, head) { | |
31 return new devtools.profiler.WebKitViewNode( | |
32 funcName, totalTime, selfTime, head); | |
33 }; | |
34 | |
35 | |
36 /** | |
37 * Constructs a Profile View node object for displaying in WebKit Profiler UI. | |
38 * | |
39 * @param {string} internalFuncName A fully qualified function name. | |
40 * @param {number} totalTime Amount of time that application spent in the | |
41 * corresponding function and its descendants (not that depending on | |
42 * profile they can be either callees or callers.) | |
43 * @param {number} selfTime Amount of time that application spent in the | |
44 * corresponding function only. | |
45 * @param {devtools.profiler.ProfileView.Node} head Profile view head. | |
46 * @constructor | |
47 */ | |
48 devtools.profiler.WebKitViewNode = function( | |
49 internalFuncName, totalTime, selfTime, head) { | |
50 devtools.profiler.ProfileView.Node.call(this, | |
51 internalFuncName, totalTime, selfTime, head); | |
52 this.initFuncInfo_(); | |
53 this.callUID = internalFuncName; | |
54 }; | |
55 goog.inherits(devtools.profiler.WebKitViewNode, | |
56 devtools.profiler.ProfileView.Node); | |
57 | |
58 | |
59 /** | |
60 * RegEx for stripping V8's prefixes of compiled functions. | |
61 */ | |
62 devtools.profiler.WebKitViewNode.FUNC_NAME_STRIP_RE = | |
63 /^(?:LazyCompile|Function): (.*)$/; | |
64 | |
65 | |
66 /** | |
67 * RegEx for extracting script source URL and line number. | |
68 */ | |
69 devtools.profiler.WebKitViewNode.FUNC_NAME_PARSE_RE = | |
70 /^([^ ]+) (.*):(\d+)( \{\d+\})?$/; | |
71 | |
72 | |
73 /** | |
74 * Inits 'functionName', 'url', and 'lineNumber' fields using 'internalFuncName' | |
75 * field. | |
76 * @private | |
77 */ | |
78 devtools.profiler.WebKitViewNode.prototype.initFuncInfo_ = function() { | |
79 var nodeAlias = devtools.profiler.WebKitViewNode; | |
80 this.functionName = this.internalFuncName; | |
81 | |
82 var strippedName = nodeAlias.FUNC_NAME_STRIP_RE.exec(this.functionName); | |
83 if (strippedName) { | |
84 this.functionName = strippedName[1]; | |
85 } | |
86 | |
87 var parsedName = nodeAlias.FUNC_NAME_PARSE_RE.exec(this.functionName); | |
88 if (parsedName) { | |
89 this.functionName = parsedName[1]; | |
90 if (parsedName[4]) { | |
91 this.functionName += parsedName[4]; | |
92 } | |
93 this.url = parsedName[2]; | |
94 this.lineNumber = parsedName[3]; | |
95 } else { | |
96 this.url = ''; | |
97 this.lineNumber = 0; | |
98 } | |
99 }; | |
100 | |
101 | |
102 /** | |
103 * Ancestor of a profile object that leaves out only JS-related functions. | |
104 * @constructor | |
105 */ | |
106 devtools.profiler.JsProfile = function() { | |
107 devtools.profiler.Profile.call(this); | |
108 }; | |
109 goog.inherits(devtools.profiler.JsProfile, devtools.profiler.Profile); | |
110 | |
111 | |
112 /** | |
113 * RegExp that leaves only JS functions. | |
114 * @type {RegExp} | |
115 */ | |
116 devtools.profiler.JsProfile.JS_FUNC_RE = /^(LazyCompile|Function|Script):/; | |
117 | |
118 /** | |
119 * RegExp that filters out native code (ending with "native src.js:xxx"). | |
120 * @type {RegExp} | |
121 */ | |
122 devtools.profiler.JsProfile.JS_NATIVE_FUNC_RE = /\ native\ \w+\.js:\d+$/; | |
123 | |
124 /** | |
125 * RegExp that filters out native scripts. | |
126 * @type {RegExp} | |
127 */ | |
128 devtools.profiler.JsProfile.JS_NATIVE_SCRIPT_RE = /^Script:\ native/; | |
129 | |
130 | |
131 /** | |
132 * @override | |
133 */ | |
134 devtools.profiler.JsProfile.prototype.skipThisFunction = function(name) { | |
135 return !devtools.profiler.JsProfile.JS_FUNC_RE.test(name) || | |
136 // To profile V8's natives comment out two lines below and '||' above. | |
137 devtools.profiler.JsProfile.JS_NATIVE_FUNC_RE.test(name) || | |
138 devtools.profiler.JsProfile.JS_NATIVE_SCRIPT_RE.test(name); | |
139 }; | |
140 | |
141 | |
142 /** | |
143 * Profiler processor. Consumes profiler log and builds profile views. | |
144 * | |
145 * @param {function(devtools.profiler.ProfileView)} newProfileCallback Callback | |
146 * that receives a new processed profile. | |
147 * @constructor | |
148 */ | |
149 devtools.profiler.Processor = function() { | |
150 devtools.profiler.LogReader.call(this, { | |
151 'code-creation': { | |
152 parsers: [null, this.createAddressParser('code'), parseInt, null], | |
153 processor: this.processCodeCreation_, backrefs: true, | |
154 needsProfile: true }, | |
155 'code-move': { parsers: [this.createAddressParser('code'), | |
156 this.createAddressParser('code-move-to')], | |
157 processor: this.processCodeMove_, backrefs: true, | |
158 needsProfile: true }, | |
159 'code-delete': { parsers: [this.createAddressParser('code')], | |
160 processor: this.processCodeDelete_, backrefs: true, | |
161 needsProfile: true }, | |
162 'tick': { parsers: [this.createAddressParser('code'), | |
163 this.createAddressParser('stack'), parseInt, 'var-args'], | |
164 processor: this.processTick_, backrefs: true, needProfile: true }, | |
165 'profiler': { parsers: [null, 'var-args'], | |
166 processor: this.processProfiler_, needsProfile: false }, | |
167 'heap-sample-begin': { parsers: [null, null, parseInt], | |
168 processor: this.processHeapSampleBegin_ }, | |
169 'heap-sample-stats': { parsers: [null, null, parseInt, parseInt], | |
170 processor: this.processHeapSampleStats_ }, | |
171 'heap-sample-item': { parsers: [null, parseInt, parseInt], | |
172 processor: this.processHeapSampleItem_ }, | |
173 'heap-js-cons-item': { parsers: [null, parseInt, parseInt], | |
174 processor: this.processHeapJsConsItem_ }, | |
175 'heap-js-ret-item': { parsers: [null, 'var-args'], | |
176 processor: this.processHeapJsRetItem_ }, | |
177 'heap-sample-end': { parsers: [null, null], | |
178 processor: this.processHeapSampleEnd_ }, | |
179 // Not used in DevTools Profiler. | |
180 'shared-library': null, | |
181 // Obsolete row types. | |
182 'code-allocate': null, | |
183 'begin-code-region': null, | |
184 'end-code-region': null}); | |
185 | |
186 | |
187 /** | |
188 * Callback that is called when a new profile is encountered in the log. | |
189 * @type {function()} | |
190 */ | |
191 this.startedProfileProcessing_ = null; | |
192 | |
193 /** | |
194 * Callback that is called periodically to display processing status. | |
195 * @type {function()} | |
196 */ | |
197 this.profileProcessingStatus_ = null; | |
198 | |
199 /** | |
200 * Callback that is called when a profile has been processed and is ready | |
201 * to be shown. | |
202 * @type {function(devtools.profiler.ProfileView)} | |
203 */ | |
204 this.finishedProfileProcessing_ = null; | |
205 | |
206 /** | |
207 * The current profile. | |
208 * @type {devtools.profiler.JsProfile} | |
209 */ | |
210 this.currentProfile_ = null; | |
211 | |
212 /** | |
213 * Builder of profile views. Created during "profiler,begin" event processing. | |
214 * @type {devtools.profiler.WebKitViewBuilder} | |
215 */ | |
216 this.viewBuilder_ = null; | |
217 | |
218 /** | |
219 * Next profile id. | |
220 * @type {number} | |
221 */ | |
222 this.profileId_ = 1; | |
223 | |
224 /** | |
225 * Counter for processed ticks. | |
226 * @type {number} | |
227 */ | |
228 this.ticksCount_ = 0; | |
229 | |
230 /** | |
231 * The current heap snapshot. | |
232 * @type {string} | |
233 */ | |
234 this.currentHeapSnapshot_ = null; | |
235 | |
236 /** | |
237 * Next heap snapshot id. | |
238 * @type {number} | |
239 */ | |
240 this.heapSnapshotId_ = 1; | |
241 }; | |
242 goog.inherits(devtools.profiler.Processor, devtools.profiler.LogReader); | |
243 | |
244 | |
245 /** | |
246 * @override | |
247 */ | |
248 devtools.profiler.Processor.prototype.printError = function(str) { | |
249 debugPrint(str); | |
250 }; | |
251 | |
252 | |
253 /** | |
254 * @override | |
255 */ | |
256 devtools.profiler.Processor.prototype.skipDispatch = function(dispatch) { | |
257 return dispatch.needsProfile && this.currentProfile_ == null; | |
258 }; | |
259 | |
260 | |
261 /** | |
262 * Sets profile processing callbacks. | |
263 * | |
264 * @param {function()} started Started processing callback. | |
265 * @param {function(devtools.profiler.ProfileView)} finished Finished | |
266 * processing callback. | |
267 */ | |
268 devtools.profiler.Processor.prototype.setCallbacks = function( | |
269 started, processing, finished) { | |
270 this.startedProfileProcessing_ = started; | |
271 this.profileProcessingStatus_ = processing; | |
272 this.finishedProfileProcessing_ = finished; | |
273 }; | |
274 | |
275 | |
276 /** | |
277 * An address for the fake "(program)" entry. WebKit's visualisation | |
278 * has assumptions on how the top of the call tree should look like, | |
279 * and we need to add a fake entry as the topmost function. This | |
280 * address is chosen because it's the end address of the first memory | |
281 * page, which is never used for code or data, but only as a guard | |
282 * page for catching AV errors. | |
283 * | |
284 * @type {number} | |
285 */ | |
286 devtools.profiler.Processor.PROGRAM_ENTRY = 0xffff; | |
287 /** | |
288 * @type {string} | |
289 */ | |
290 devtools.profiler.Processor.PROGRAM_ENTRY_STR = '0xffff'; | |
291 | |
292 | |
293 /** | |
294 * Sets new profile callback. | |
295 * @param {function(devtools.profiler.ProfileView)} callback Callback function. | |
296 */ | |
297 devtools.profiler.Processor.prototype.setNewProfileCallback = function( | |
298 callback) { | |
299 this.newProfileCallback_ = callback; | |
300 }; | |
301 | |
302 | |
303 devtools.profiler.Processor.prototype.processProfiler_ = function( | |
304 state, params) { | |
305 var processingInterval = null; | |
306 switch (state) { | |
307 case 'resume': | |
308 if (this.currentProfile_ == null) { | |
309 this.currentProfile_ = new devtools.profiler.JsProfile(); | |
310 // see the comment for devtools.profiler.Processor.PROGRAM_ENTRY | |
311 this.currentProfile_.addCode( | |
312 'Function', '(program)', | |
313 devtools.profiler.Processor.PROGRAM_ENTRY, 1); | |
314 if (this.startedProfileProcessing_) { | |
315 this.startedProfileProcessing_(); | |
316 } | |
317 this.ticksCount_ = 0; | |
318 var self = this; | |
319 if (this.profileProcessingStatus_) { | |
320 processingInterval = window.setInterval( | |
321 function() { self.profileProcessingStatus_(self.ticksCount_); }, | |
322 1000); | |
323 } | |
324 } | |
325 break; | |
326 case 'pause': | |
327 if (this.currentProfile_ != null) { | |
328 window.clearInterval(processingInterval); | |
329 if (this.finishedProfileProcessing_) { | |
330 this.finishedProfileProcessing_(this.createProfileForView()); | |
331 } | |
332 this.currentProfile_ = null; | |
333 } | |
334 break; | |
335 case 'begin': | |
336 var samplingRate = NaN; | |
337 if (params.length > 0) { | |
338 samplingRate = parseInt(params[0]); | |
339 } | |
340 if (isNaN(samplingRate)) { | |
341 samplingRate = 1; | |
342 } | |
343 this.viewBuilder_ = new devtools.profiler.WebKitViewBuilder(samplingRate); | |
344 break; | |
345 // These events are valid but aren't used. | |
346 case 'compression': | |
347 case 'end': break; | |
348 default: | |
349 throw new Error('unknown profiler state: ' + state); | |
350 } | |
351 }; | |
352 | |
353 | |
354 devtools.profiler.Processor.prototype.processCodeCreation_ = function( | |
355 type, start, size, name) { | |
356 this.currentProfile_.addCode(this.expandAlias(type), name, start, size); | |
357 }; | |
358 | |
359 | |
360 devtools.profiler.Processor.prototype.processCodeMove_ = function(from, to) { | |
361 this.currentProfile_.moveCode(from, to); | |
362 }; | |
363 | |
364 | |
365 devtools.profiler.Processor.prototype.processCodeDelete_ = function(start) { | |
366 this.currentProfile_.deleteCode(start); | |
367 }; | |
368 | |
369 | |
370 devtools.profiler.Processor.prototype.processTick_ = function( | |
371 pc, sp, vmState, stack) { | |
372 // see the comment for devtools.profiler.Processor.PROGRAM_ENTRY | |
373 stack.push(devtools.profiler.Processor.PROGRAM_ENTRY_STR); | |
374 this.currentProfile_.recordTick(this.processStack(pc, stack)); | |
375 this.ticksCount_++; | |
376 }; | |
377 | |
378 | |
379 devtools.profiler.Processor.prototype.processHeapSampleBegin_ = function( | |
380 space, state, ticks) { | |
381 if (space != 'Heap') return; | |
382 this.currentHeapSnapshot_ = { | |
383 number: this.heapSnapshotId_++, | |
384 entries: {}, | |
385 clusters: {}, | |
386 lowlevels: {}, | |
387 ticks: ticks | |
388 }; | |
389 }; | |
390 | |
391 | |
392 devtools.profiler.Processor.prototype.processHeapSampleStats_ = function( | |
393 space, state, capacity, used) { | |
394 if (space != 'Heap') return; | |
395 this.currentHeapSnapshot_.capacity = capacity; | |
396 this.currentHeapSnapshot_.used = used; | |
397 }; | |
398 | |
399 | |
400 devtools.profiler.Processor.prototype.processHeapSampleItem_ = function( | |
401 item, number, size) { | |
402 if (!this.currentHeapSnapshot_) return; | |
403 this.currentHeapSnapshot_.lowlevels[item] = { | |
404 type: item, count: number, size: size | |
405 }; | |
406 }; | |
407 | |
408 | |
409 devtools.profiler.Processor.prototype.processHeapJsConsItem_ = function( | |
410 item, number, size) { | |
411 if (!this.currentHeapSnapshot_) return; | |
412 this.currentHeapSnapshot_.entries[item] = { | |
413 cons: item, count: number, size: size, retainers: {} | |
414 }; | |
415 }; | |
416 | |
417 | |
418 devtools.profiler.Processor.prototype.processHeapJsRetItem_ = function( | |
419 item, retainersArray) { | |
420 if (!this.currentHeapSnapshot_) return; | |
421 var rawRetainers = {}; | |
422 for (var i = 0, n = retainersArray.length; i < n; ++i) { | |
423 var entry = retainersArray[i].split(';'); | |
424 rawRetainers[entry[0]] = parseInt(entry[1], 10); | |
425 } | |
426 | |
427 function mergeRetainers(entry) { | |
428 for (var rawRetainer in rawRetainers) { | |
429 var consName = rawRetainer.indexOf(':') != -1 ? | |
430 rawRetainer.split(':')[0] : rawRetainer; | |
431 if (!(consName in entry.retainers)) | |
432 entry.retainers[consName] = { cons: consName, count: 0, clusters: {} }; | |
433 var retainer = entry.retainers[consName]; | |
434 retainer.count += rawRetainers[rawRetainer]; | |
435 if (consName != rawRetainer) | |
436 retainer.clusters[rawRetainer] = true; | |
437 } | |
438 } | |
439 | |
440 if (item.indexOf(':') != -1) { | |
441 // Array, Function, or Object instances cluster case. | |
442 if (!(item in this.currentHeapSnapshot_.clusters)) { | |
443 this.currentHeapSnapshot_.clusters[item] = { | |
444 cons: item, retainers: {} | |
445 }; | |
446 } | |
447 mergeRetainers(this.currentHeapSnapshot_.clusters[item]); | |
448 item = item.split(':')[0]; | |
449 } | |
450 mergeRetainers(this.currentHeapSnapshot_.entries[item]); | |
451 }; | |
452 | |
453 | |
454 devtools.profiler.Processor.prototype.processHeapSampleEnd_ = function( | |
455 space, state) { | |
456 if (space != 'Heap') return; | |
457 var snapshot = this.currentHeapSnapshot_; | |
458 this.currentHeapSnapshot_ = null; | |
459 // For some reason, 'used' from 'heap-sample-stats' sometimes differ from | |
460 // the sum of objects sizes. To avoid discrepancy, we re-calculate 'used'. | |
461 snapshot.used = 0; | |
462 for (var item in snapshot.lowlevels) { | |
463 snapshot.used += snapshot.lowlevels[item].size; | |
464 } | |
465 WebInspector.panels.profiles.addSnapshot(snapshot); | |
466 }; | |
467 | |
468 | |
469 /** | |
470 * Creates a profile for further displaying in ProfileView. | |
471 */ | |
472 devtools.profiler.Processor.prototype.createProfileForView = function() { | |
473 var profile = this.viewBuilder_.buildView( | |
474 this.currentProfile_.getTopDownProfile()); | |
475 profile.uid = this.profileId_++; | |
476 profile.title = UserInitiatedProfileName + '.' + profile.uid; | |
477 return profile; | |
478 }; | |
OLD | NEW |