1 // Copyright 2009 the V8 project authors. All rights reserved. 2 // Redistribution and use in source and binary forms, with or without 3 // modification, are permitted provided that the following conditions are 4 // met: 5 // 6 // * Redistributions of source code must retain the above copyright 7 // notice, this list of conditions and the following disclaimer. 8 // * Redistributions in binary form must reproduce the above 9 // copyright notice, this list of conditions and the following 10 // disclaimer in the documentation and/or other materials provided 11 // with the distribution. 12 // * Neither the name of Google Inc. nor the names of its 13 // contributors may be used to endorse or promote products derived 14 // from this software without specific prior written permission. 15 // 16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 28 29 // Initlialize namespaces 30 var devtools = devtools || {}; 31 devtools.profiler = devtools.profiler || {}; 32 33 34 /** 35 * Creates a profile object for processing profiling-related events 36 * and calculating function execution times. 37 * 38 * @constructor 39 */ 40 devtools.profiler.Profile = function() { 41 this.codeMap_ = new devtools.profiler.CodeMap(); 42 this.topDownTree_ = new devtools.profiler.CallTree(); 43 this.bottomUpTree_ = new devtools.profiler.CallTree(); 44 }; 45 46 /** 47 * Version of profiler log. 48 */ 49 devtools.profiler.Profile.VERSION = 2; 50 51 52 /** 53 * Returns whether a function with the specified name must be skipped. 54 * Should be overriden by subclasses. 55 * 56 * @param {string} name Function name. 57 */ 58 devtools.profiler.Profile.prototype.skipThisFunction = function(name) { 59 return false; 60 }; 61 62 63 /** 64 * Enum for profiler operations that involve looking up existing 65 * code entries. 66 * 67 * @enum {number} 68 */ 69 devtools.profiler.Profile.Operation = { 70 MOVE: 0, 71 DELETE: 1, 72 TICK: 2 73 }; 74 75 76 /** 77 * Called whenever the specified operation has failed finding a function 78 * containing the specified address. Should be overriden by subclasses. 79 * See the devtools.profiler.Profile.Operation enum for the list of 80 * possible operations. 81 * 82 * @param {number} operation Operation. 83 * @param {number} addr Address of the unknown code. 84 * @param {number} opt_stackPos If an unknown address is encountered 85 * during stack strace processing, specifies a position of the frame 86 * containing the address. 87 */ 88 devtools.profiler.Profile.prototype.handleUnknownCode = function( 89 operation, addr, opt_stackPos) { 90 }; 91 92 93 /** 94 * Registers a library. 95 * 96 * @param {string} name Code entry name. 97 * @param {number} startAddr Starting address. 98 * @param {number} endAddr Ending address. 99 */ 100 devtools.profiler.Profile.prototype.addLibrary = function( 101 name, startAddr, endAddr) { 102 var entry = new devtools.profiler.CodeMap.CodeEntry( 103 endAddr - startAddr, name); 104 this.codeMap_.addLibrary(startAddr, entry); 105 return entry; 106 }; 107 108 109 /** 110 * Registers statically compiled code entry. 111 * 112 * @param {string} name Code entry name. 113 * @param {number} startAddr Starting address. 114 * @param {number} endAddr Ending address. 115 */ 116 devtools.profiler.Profile.prototype.addStaticCode = function( 117 name, startAddr, endAddr) { 118 var entry = new devtools.profiler.CodeMap.CodeEntry( 119 endAddr - startAddr, name); 120 this.codeMap_.addStaticCode(startAddr, entry); 121 return entry; 122 }; 123 124 125 /** 126 * Registers dynamic (JIT-compiled) code entry. 127 * 128 * @param {string} type Code entry type. 129 * @param {string} name Code entry name. 130 * @param {number} start Starting address. 131 * @param {number} size Code entry size. 132 */ 133 devtools.profiler.Profile.prototype.addCode = function( 134 type, name, start, size) { 135 var entry = new devtools.profiler.Profile.DynamicCodeEntry(size, type, name); 136 this.codeMap_.addCode(start, entry); 137 return entry; 138 }; 139 140 141 /** 142 * Creates an alias entry for a code entry. 143 * 144 * @param {number} aliasAddr Alias address. 145 * @param {number} addr Code entry address. 146 */ 147 devtools.profiler.Profile.prototype.addCodeAlias = function( 148 aliasAddr, addr) { 149 var entry = this.codeMap_.findDynamicEntryByStartAddress(addr); 150 if (entry) { 151 this.codeMap_.addCode(aliasAddr, entry); 152 } 153 }; 154 155 156 /** 157 * Reports about moving of a dynamic code entry. 158 * 159 * @param {number} from Current code entry address. 160 * @param {number} to New code entry address. 161 */ 162 devtools.profiler.Profile.prototype.moveCode = function(from, to) { 163 try { 164 this.codeMap_.moveCode(from, to); 165 } catch (e) { 166 this.handleUnknownCode(devtools.profiler.Profile.Operation.MOVE, from); 167 } 168 }; 169 170 171 /** 172 * Reports about deletion of a dynamic code entry. 173 * 174 * @param {number} start Starting address. 175 */ 176 devtools.profiler.Profile.prototype.deleteCode = function(start) { 177 try { 178 this.codeMap_.deleteCode(start); 179 } catch (e) { 180 this.handleUnknownCode(devtools.profiler.Profile.Operation.DELETE, start); 181 } 182 }; 183 184 185 /** 186 * Reports about moving of a dynamic code entry. 187 * 188 * @param {number} from Current code entry address. 189 * @param {number} to New code entry address. 190 */ 191 devtools.profiler.Profile.prototype.safeMoveDynamicCode = function(from, to) { 192 if (this.codeMap_.findDynamicEntryByStartAddress(from)) { 193 this.codeMap_.moveCode(from, to); 194 } 195 }; 196 197 198 /** 199 * Reports about deletion of a dynamic code entry. 200 * 201 * @param {number} start Starting address. 202 */ 203 devtools.profiler.Profile.prototype.safeDeleteDynamicCode = function(start) { 204 if (this.codeMap_.findDynamicEntryByStartAddress(start)) { 205 this.codeMap_.deleteCode(start); 206 } 207 }; 208 209 210 /** 211 * Retrieves a code entry by an address. 212 * 213 * @param {number} addr Entry address. 214 */ 215 devtools.profiler.Profile.prototype.findEntry = function(addr) { 216 return this.codeMap_.findEntry(addr); 217 }; 218 219 220 /** 221 * Records a tick event. Stack must contain a sequence of 222 * addresses starting with the program counter value. 223 * 224 * @param {Array<number>} stack Stack sample. 225 */ 226 devtools.profiler.Profile.prototype.recordTick = function(stack) { 227 var processedStack = this.resolveAndFilterFuncs_(stack); 228 this.bottomUpTree_.addPath(processedStack); 229 processedStack.reverse(); 230 this.topDownTree_.addPath(processedStack); 231 }; 232 233 234 /** 235 * Translates addresses into function names and filters unneeded 236 * functions. 237 * 238 * @param {Array<number>} stack Stack sample. 239 */ 240 devtools.profiler.Profile.prototype.resolveAndFilterFuncs_ = function(stack) { 241 var result = []; 242 for (var i = 0; i < stack.length; ++i) { 243 var entry = this.codeMap_.findEntry(stack[i]); 244 if (entry) { 245 var name = entry.getName(); 246 if (!this.skipThisFunction(name)) { 247 result.push(name); 248 } 249 } else { 250 this.handleUnknownCode( 251 devtools.profiler.Profile.Operation.TICK, stack[i], i); 252 } 253 } 254 return result; 255 }; 256 257 258 /** 259 * Performs a BF traversal of the top down call graph. 260 * 261 * @param {function(devtools.profiler.CallTree.Node)} f Visitor function. 262 */ 263 devtools.profiler.Profile.prototype.traverseTopDownTree = function(f) { 264 this.topDownTree_.traverse(f); 265 }; 266 267 268 /** 269 * Performs a BF traversal of the bottom up call graph. 270 * 271 * @param {function(devtools.profiler.CallTree.Node)} f Visitor function. 272 */ 273 devtools.profiler.Profile.prototype.traverseBottomUpTree = function(f) { 274 this.bottomUpTree_.traverse(f); 275 }; 276 277 278 /** 279 * Calculates a top down profile for a node with the specified label. 280 * If no name specified, returns the whole top down calls tree. 281 * 282 * @param {string} opt_label Node label. 283 */ 284 devtools.profiler.Profile.prototype.getTopDownProfile = function(opt_label) { 285 return this.getTreeProfile_(this.topDownTree_, opt_label); 286 }; 287 288 289 /** 290 * Calculates a bottom up profile for a node with the specified label. 291 * If no name specified, returns the whole bottom up calls tree. 292 * 293 * @param {string} opt_label Node label. 294 */ 295 devtools.profiler.Profile.prototype.getBottomUpProfile = function(opt_label) { 296 return this.getTreeProfile_(this.bottomUpTree_, opt_label); 297 }; 298 299 300 /** 301 * Helper function for calculating a tree profile. 302 * 303 * @param {devtools.profiler.Profile.CallTree} tree Call tree. 304 * @param {string} opt_label Node label. 305 */ 306 devtools.profiler.Profile.prototype.getTreeProfile_ = function(tree, opt_label) { 307 if (!opt_label) { 308 tree.computeTotalWeights(); 309 return tree; 310 } else { 311 var subTree = tree.cloneSubtree(opt_label); 312 subTree.computeTotalWeights(); 313 return subTree; 314 } 315 }; 316 317 318 /** 319 * Calculates a flat profile of callees starting from a node with 320 * the specified label. If no name specified, starts from the root. 321 * 322 * @param {string} opt_label Starting node label. 323 */ 324 devtools.profiler.Profile.prototype.getFlatProfile = function(opt_label) { 325 var counters = new devtools.profiler.CallTree(); 326 var rootLabel = opt_label || devtools.profiler.CallTree.ROOT_NODE_LABEL; 327 var precs = {}; 328 precs[rootLabel] = 0; 329 var root = counters.findOrAddChild(rootLabel); 330 331 this.topDownTree_.computeTotalWeights(); 332 this.topDownTree_.traverseInDepth( 333 function onEnter(node) { 334 if (!(node.label in precs)) { 335 precs[node.label] = 0; 336 } 337 var nodeLabelIsRootLabel = node.label == rootLabel; 338 if (nodeLabelIsRootLabel || precs[rootLabel] > 0) { 339 if (precs[rootLabel] == 0) { 340 root.selfWeight += node.selfWeight; 341 root.totalWeight += node.totalWeight; 342 } else { 343 var rec = root.findOrAddChild(node.label); 344 rec.selfWeight += node.selfWeight; 345 if (nodeLabelIsRootLabel || precs[node.label] == 0) { 346 rec.totalWeight += node.totalWeight; 347 } 348 } 349 precs[node.label]++; 350 } 351 }, 352 function onExit(node) { 353 if (node.label == rootLabel || precs[rootLabel] > 0) { 354 precs[node.label]--; 355 } 356 }, 357 null); 358 359 if (!opt_label) { 360 // If we have created a flat profile for the whole program, we don't 361 // need an explicit root in it. Thus, replace the counters tree 362 // root with the node corresponding to the whole program. 363 counters.root_ = root; 364 } else { 365 // Propagate weights so percents can be calculated correctly. 366 counters.getRoot().selfWeight = root.selfWeight; 367 counters.getRoot().totalWeight = root.totalWeight; 368 } 369 return counters; 370 }; 371 372 373 /** 374 * Creates a dynamic code entry. 375 * 376 * @param {number} size Code size. 377 * @param {string} type Code type. 378 * @param {string} name Function name. 379 * @constructor 380 */ 381 devtools.profiler.Profile.DynamicCodeEntry = function(size, type, name) { 382 devtools.profiler.CodeMap.CodeEntry.call(this, size, name); 383 this.type = type; 384 }; 385 386 387 /** 388 * Returns node name. 389 */ 390 devtools.profiler.Profile.DynamicCodeEntry.prototype.getName = function() { 391 var name = this.name; 392 if (name.length == 0) { 393 name = '<anonymous>'; 394 } else if (name.charAt(0) == ' ') { 395 // An anonymous function with location: " aaa.js:10". 396 name = '<anonymous>' + name; 397 } 398 return this.type + ': ' + name; 399 }; 400 401 402 /** 403 * Returns raw node name (without type decoration). 404 */ 405 devtools.profiler.Profile.DynamicCodeEntry.prototype.getRawName = function() { 406 return this.name; 407 }; 408 409 410 devtools.profiler.Profile.DynamicCodeEntry.prototype.isJSFunction = function() { 411 return this.type == "Function" || 412 this.type == "LazyCompile" || 413 this.type == "Script"; 414 }; 415 416 417 /** 418 * Constructs a call graph. 419 * 420 * @constructor 421 */ 422 devtools.profiler.CallTree = function() { 423 this.root_ = new devtools.profiler.CallTree.Node( 424 devtools.profiler.CallTree.ROOT_NODE_LABEL); 425 }; 426 427 428 /** 429 * The label of the root node. 430 */ 431 devtools.profiler.CallTree.ROOT_NODE_LABEL = ''; 432 433 434 /** 435 * @private 436 */ 437 devtools.profiler.CallTree.prototype.totalsComputed_ = false; 438 439 440 /** 441 * Returns the tree root. 442 */ 443 devtools.profiler.CallTree.prototype.getRoot = function() { 444 return this.root_; 445 }; 446 447 448 /** 449 * Adds the specified call path, constructing nodes as necessary. 450 * 451 * @param {Array<string>} path Call path. 452 */ 453 devtools.profiler.CallTree.prototype.addPath = function(path) { 454 if (path.length == 0) { 455 return; 456 } 457 var curr = this.root_; 458 for (var i = 0; i < path.length; ++i) { 459 curr = curr.findOrAddChild(path[i]); 460 } 461 curr.selfWeight++; 462 this.totalsComputed_ = false; 463 }; 464 465 466 /** 467 * Finds an immediate child of the specified parent with the specified 468 * label, creates a child node if necessary. If a parent node isn't 469 * specified, uses tree root. 470 * 471 * @param {string} label Child node label. 472 */ 473 devtools.profiler.CallTree.prototype.findOrAddChild = function(label) { 474 return this.root_.findOrAddChild(label); 475 }; 476 477 478 /** 479 * Creates a subtree by cloning and merging all subtrees rooted at nodes 480 * with a given label. E.g. cloning the following call tree on label 'A' 481 * will give the following result: 482 * 483 * <A>--<B> <B> 484 * / / 485 * <root> == clone on 'A' ==> <root>--<A> 486 * \ \ 487 * <C>--<A>--<D> <D> 488 * 489 * And <A>'s selfWeight will be the sum of selfWeights of <A>'s from the 490 * source call tree. 491 * 492 * @param {string} label The label of the new root node. 493 */ 494 devtools.profiler.CallTree.prototype.cloneSubtree = function(label) { 495 var subTree = new devtools.profiler.CallTree(); 496 this.traverse(function(node, parent) { 497 if (!parent && node.label != label) { 498 return null; 499 } 500 var child = (parent ? parent : subTree).findOrAddChild(node.label); 501 child.selfWeight += node.selfWeight; 502 return child; 503 }); 504 return subTree; 505 }; 506 507 508 /** 509 * Computes total weights in the call graph. 510 */ 511 devtools.profiler.CallTree.prototype.computeTotalWeights = function() { 512 if (this.totalsComputed_) { 513 return; 514 } 515 this.root_.computeTotalWeight(); 516 this.totalsComputed_ = true; 517 }; 518 519 520 /** 521 * Traverses the call graph in preorder. This function can be used for 522 * building optionally modified tree clones. This is the boilerplate code 523 * for this scenario: 524 * 525 * callTree.traverse(function(node, parentClone) { 526 * var nodeClone = cloneNode(node); 527 * if (parentClone) 528 * parentClone.addChild(nodeClone); 529 * return nodeClone; 530 * }); 531 * 532 * @param {function(devtools.profiler.CallTree.Node, *)} f Visitor function. 533 * The second parameter is the result of calling 'f' on the parent node. 534 */ 535 devtools.profiler.CallTree.prototype.traverse = function(f) { 536 var pairsToProcess = new ConsArray(); 537 pairsToProcess.concat([{node: this.root_, param: null}]); 538 while (!pairsToProcess.atEnd()) { 539 var pair = pairsToProcess.next(); 540 var node = pair.node; 541 var newParam = f(node, pair.param); 542 var morePairsToProcess = []; 543 node.forEachChild(function (child) { 544 morePairsToProcess.push({node: child, param: newParam}); }); 545 pairsToProcess.concat(morePairsToProcess); 546 } 547 }; 548 549 550 /** 551 * Performs an indepth call graph traversal. 552 * 553 * @param {function(devtools.profiler.CallTree.Node)} enter A function called 554 * prior to visiting node's children. 555 * @param {function(devtools.profiler.CallTree.Node)} exit A function called 556 * after visiting node's children. 557 */ 558 devtools.profiler.CallTree.prototype.traverseInDepth = function(enter, exit) { 559 function traverse(node) { 560 enter(node); 561 node.forEachChild(traverse); 562 exit(node); 563 } 564 traverse(this.root_); 565 }; 566 567 568 /** 569 * Constructs a call graph node. 570 * 571 * @param {string} label Node label. 572 * @param {devtools.profiler.CallTree.Node} opt_parent Node parent. 573 */ 574 devtools.profiler.CallTree.Node = function(label, opt_parent) { 575 this.label = label; 576 this.parent = opt_parent; 577 this.children = {}; 578 }; 579 580 581 /** 582 * Node self weight (how many times this node was the last node in 583 * a call path). 584 * @type {number} 585 */ 586 devtools.profiler.CallTree.Node.prototype.selfWeight = 0; 587 588 589 /** 590 * Node total weight (includes weights of all children). 591 * @type {number} 592 */ 593 devtools.profiler.CallTree.Node.prototype.totalWeight = 0; 594 595 596 /** 597 * Adds a child node. 598 * 599 * @param {string} label Child node label. 600 */ 601 devtools.profiler.CallTree.Node.prototype.addChild = function(label) { 602 var child = new devtools.profiler.CallTree.Node(label, this); 603 this.children[label] = child; 604 return child; 605 }; 606 607 608 /** 609 * Computes node's total weight. 610 */ 611 devtools.profiler.CallTree.Node.prototype.computeTotalWeight = 612 function() { 613 var totalWeight = this.selfWeight; 614 this.forEachChild(function(child) { 615 totalWeight += child.computeTotalWeight(); }); 616 return this.totalWeight = totalWeight; 617 }; 618 619 620 /** 621 * Returns all node's children as an array. 622 */ 623 devtools.profiler.CallTree.Node.prototype.exportChildren = function() { 624 var result = []; 625 this.forEachChild(function (node) { result.push(node); }); 626 return result; 627 }; 628 629 630 /** 631 * Finds an immediate child with the specified label. 632 * 633 * @param {string} label Child node label. 634 */ 635 devtools.profiler.CallTree.Node.prototype.findChild = function(label) { 636 return this.children[label] || null; 637 }; 638 639 640 /** 641 * Finds an immediate child with the specified label, creates a child 642 * node if necessary. 643 * 644 * @param {string} label Child node label. 645 */ 646 devtools.profiler.CallTree.Node.prototype.findOrAddChild = function(label) { 647 return this.findChild(label) || this.addChild(label); 648 }; 649 650 651 /** 652 * Calls the specified function for every child. 653 * 654 * @param {function(devtools.profiler.CallTree.Node)} f Visitor function. 655 */ 656 devtools.profiler.CallTree.Node.prototype.forEachChild = function(f) { 657 for (var c in this.children) { 658 f(this.children[c]); 659 } 660 }; 661 662 663 /** 664 * Walks up from the current node up to the call tree root. 665 * 666 * @param {function(devtools.profiler.CallTree.Node)} f Visitor function. 667 */ 668 devtools.profiler.CallTree.Node.prototype.walkUpToRoot = function(f) { 669 for (var curr = this; curr != null; curr = curr.parent) { 670 f(curr); 671 } 672 }; 673 674 675 /** 676 * Tries to find a node with the specified path. 677 * 678 * @param {Array<string>} labels The path. 679 * @param {function(devtools.profiler.CallTree.Node)} opt_f Visitor function. 680 */ 681 devtools.profiler.CallTree.Node.prototype.descendToChild = function( 682 labels, opt_f) { 683 for (var pos = 0, curr = this; pos < labels.length && curr != null; pos++) { 684 var child = curr.findChild(labels[pos]); 685 if (opt_f) { 686 opt_f(child, pos); 687 } 688 curr = child; 689 } 690 return curr; 691 }; 692