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