| 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 |