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 |