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 this.c_entries_ = {}; 40 this.ticks_ = []; 41}; 42 43 44/** 45 * Returns whether a function with the specified name must be skipped. 46 * Should be overriden by subclasses. 47 * 48 * @param {string} name Function name. 49 */ 50Profile.prototype.skipThisFunction = function(name) { 51 return false; 52}; 53 54 55/** 56 * Enum for profiler operations that involve looking up existing 57 * code entries. 58 * 59 * @enum {number} 60 */ 61Profile.Operation = { 62 MOVE: 0, 63 DELETE: 1, 64 TICK: 2 65}; 66 67 68/** 69 * Enum for code state regarding its dynamic optimization. 70 * 71 * @enum {number} 72 */ 73Profile.CodeState = { 74 COMPILED: 0, 75 OPTIMIZABLE: 1, 76 OPTIMIZED: 2 77}; 78 79 80/** 81 * Called whenever the specified operation has failed finding a function 82 * containing the specified address. Should be overriden by subclasses. 83 * See the Profile.Operation enum for the list of 84 * possible operations. 85 * 86 * @param {number} operation Operation. 87 * @param {number} addr Address of the unknown code. 88 * @param {number} opt_stackPos If an unknown address is encountered 89 * during stack strace processing, specifies a position of the frame 90 * containing the address. 91 */ 92Profile.prototype.handleUnknownCode = function( 93 operation, addr, opt_stackPos) { 94}; 95 96 97/** 98 * Registers a library. 99 * 100 * @param {string} name Code entry name. 101 * @param {number} startAddr Starting address. 102 * @param {number} endAddr Ending address. 103 */ 104Profile.prototype.addLibrary = function( 105 name, startAddr, endAddr) { 106 var entry = new CodeMap.CodeEntry( 107 endAddr - startAddr, name, 'SHARED_LIB'); 108 this.codeMap_.addLibrary(startAddr, entry); 109 return entry; 110}; 111 112 113/** 114 * Registers statically compiled code entry. 115 * 116 * @param {string} name Code entry name. 117 * @param {number} startAddr Starting address. 118 * @param {number} endAddr Ending address. 119 */ 120Profile.prototype.addStaticCode = function( 121 name, startAddr, endAddr) { 122 var entry = new CodeMap.CodeEntry( 123 endAddr - startAddr, name, 'CPP'); 124 this.codeMap_.addStaticCode(startAddr, entry); 125 return entry; 126}; 127 128 129/** 130 * Registers dynamic (JIT-compiled) code entry. 131 * 132 * @param {string} type Code entry type. 133 * @param {string} name Code entry name. 134 * @param {number} start Starting address. 135 * @param {number} size Code entry size. 136 */ 137Profile.prototype.addCode = function( 138 type, name, timestamp, start, size) { 139 var entry = new Profile.DynamicCodeEntry(size, type, name); 140 this.codeMap_.addCode(start, entry); 141 return entry; 142}; 143 144 145/** 146 * Registers dynamic (JIT-compiled) code entry. 147 * 148 * @param {string} type Code entry type. 149 * @param {string} name Code entry name. 150 * @param {number} start Starting address. 151 * @param {number} size Code entry size. 152 * @param {number} funcAddr Shared function object address. 153 * @param {Profile.CodeState} state Optimization state. 154 */ 155Profile.prototype.addFuncCode = function( 156 type, name, timestamp, start, size, funcAddr, state) { 157 // As code and functions are in the same address space, 158 // it is safe to put them in a single code map. 159 var func = this.codeMap_.findDynamicEntryByStartAddress(funcAddr); 160 if (!func) { 161 func = new Profile.FunctionEntry(name); 162 this.codeMap_.addCode(funcAddr, func); 163 } else if (func.name !== name) { 164 // Function object has been overwritten with a new one. 165 func.name = name; 166 } 167 var entry = this.codeMap_.findDynamicEntryByStartAddress(start); 168 if (entry) { 169 if (entry.size === size && entry.func === func) { 170 // Entry state has changed. 171 entry.state = state; 172 } 173 } else { 174 entry = new Profile.DynamicFuncCodeEntry(size, type, func, state); 175 this.codeMap_.addCode(start, entry); 176 } 177 return entry; 178}; 179 180 181/** 182 * Reports about moving of a dynamic code entry. 183 * 184 * @param {number} from Current code entry address. 185 * @param {number} to New code entry address. 186 */ 187Profile.prototype.moveCode = function(from, to) { 188 try { 189 this.codeMap_.moveCode(from, to); 190 } catch (e) { 191 this.handleUnknownCode(Profile.Operation.MOVE, from); 192 } 193}; 194 195Profile.prototype.deoptCode = function( 196 timestamp, code, inliningId, scriptOffset, bailoutType, 197 sourcePositionText, deoptReasonText) { 198}; 199 200/** 201 * Reports about deletion of a dynamic code entry. 202 * 203 * @param {number} start Starting address. 204 */ 205Profile.prototype.deleteCode = function(start) { 206 try { 207 this.codeMap_.deleteCode(start); 208 } catch (e) { 209 this.handleUnknownCode(Profile.Operation.DELETE, start); 210 } 211}; 212 213/** 214 * Adds source positions for given code. 215 */ 216Profile.prototype.addSourcePositions = function( 217 start, script, startPos, endPos, sourcePositions, inliningPositions, 218 inlinedFunctions) { 219 // CLI does not need source code => ignore. 220}; 221 222/** 223 * Adds script source code. 224 */ 225Profile.prototype.addScriptSource = function(script, source) { 226 // CLI does not need source code => ignore. 227}; 228 229/** 230 * Reports about moving of a dynamic code entry. 231 * 232 * @param {number} from Current code entry address. 233 * @param {number} to New code entry address. 234 */ 235Profile.prototype.moveFunc = function(from, to) { 236 if (this.codeMap_.findDynamicEntryByStartAddress(from)) { 237 this.codeMap_.moveCode(from, to); 238 } 239}; 240 241 242/** 243 * Retrieves a code entry by an address. 244 * 245 * @param {number} addr Entry address. 246 */ 247Profile.prototype.findEntry = function(addr) { 248 return this.codeMap_.findEntry(addr); 249}; 250 251 252/** 253 * Records a tick event. Stack must contain a sequence of 254 * addresses starting with the program counter value. 255 * 256 * @param {Array<number>} stack Stack sample. 257 */ 258Profile.prototype.recordTick = function(time_ns, vmState, stack) { 259 var processedStack = this.resolveAndFilterFuncs_(stack); 260 this.bottomUpTree_.addPath(processedStack); 261 processedStack.reverse(); 262 this.topDownTree_.addPath(processedStack); 263}; 264 265 266/** 267 * Translates addresses into function names and filters unneeded 268 * functions. 269 * 270 * @param {Array<number>} stack Stack sample. 271 */ 272Profile.prototype.resolveAndFilterFuncs_ = function(stack) { 273 var result = []; 274 var last_seen_c_function = ''; 275 var look_for_first_c_function = false; 276 for (var i = 0; i < stack.length; ++i) { 277 var entry = this.codeMap_.findEntry(stack[i]); 278 if (entry) { 279 var name = entry.getName(); 280 if (i === 0 && (entry.type === 'CPP' || entry.type === 'SHARED_LIB')) { 281 look_for_first_c_function = true; 282 } 283 if (look_for_first_c_function && entry.type === 'CPP') { 284 last_seen_c_function = name; 285 } 286 if (!this.skipThisFunction(name)) { 287 result.push(name); 288 } 289 } else { 290 this.handleUnknownCode(Profile.Operation.TICK, stack[i], i); 291 if (i === 0) result.push("UNKNOWN"); 292 } 293 if (look_for_first_c_function && 294 i > 0 && 295 (!entry || entry.type !== 'CPP') && 296 last_seen_c_function !== '') { 297 if (this.c_entries_[last_seen_c_function] === undefined) { 298 this.c_entries_[last_seen_c_function] = 0; 299 } 300 this.c_entries_[last_seen_c_function]++; 301 look_for_first_c_function = false; // Found it, we're done. 302 } 303 } 304 return result; 305}; 306 307 308/** 309 * Performs a BF traversal of the top down call graph. 310 * 311 * @param {function(CallTree.Node)} f Visitor function. 312 */ 313Profile.prototype.traverseTopDownTree = function(f) { 314 this.topDownTree_.traverse(f); 315}; 316 317 318/** 319 * Performs a BF traversal of the bottom up call graph. 320 * 321 * @param {function(CallTree.Node)} f Visitor function. 322 */ 323Profile.prototype.traverseBottomUpTree = function(f) { 324 this.bottomUpTree_.traverse(f); 325}; 326 327 328/** 329 * Calculates a top down profile for a node with the specified label. 330 * If no name specified, returns the whole top down calls tree. 331 * 332 * @param {string} opt_label Node label. 333 */ 334Profile.prototype.getTopDownProfile = function(opt_label) { 335 return this.getTreeProfile_(this.topDownTree_, opt_label); 336}; 337 338 339/** 340 * Calculates a bottom up profile for a node with the specified label. 341 * If no name specified, returns the whole bottom up calls tree. 342 * 343 * @param {string} opt_label Node label. 344 */ 345Profile.prototype.getBottomUpProfile = function(opt_label) { 346 return this.getTreeProfile_(this.bottomUpTree_, opt_label); 347}; 348 349 350/** 351 * Helper function for calculating a tree profile. 352 * 353 * @param {Profile.CallTree} tree Call tree. 354 * @param {string} opt_label Node label. 355 */ 356Profile.prototype.getTreeProfile_ = function(tree, opt_label) { 357 if (!opt_label) { 358 tree.computeTotalWeights(); 359 return tree; 360 } else { 361 var subTree = tree.cloneSubtree(opt_label); 362 subTree.computeTotalWeights(); 363 return subTree; 364 } 365}; 366 367 368/** 369 * Calculates a flat profile of callees starting from a node with 370 * the specified label. If no name specified, starts from the root. 371 * 372 * @param {string} opt_label Starting node label. 373 */ 374Profile.prototype.getFlatProfile = function(opt_label) { 375 var counters = new CallTree(); 376 var rootLabel = opt_label || CallTree.ROOT_NODE_LABEL; 377 var precs = {}; 378 precs[rootLabel] = 0; 379 var root = counters.findOrAddChild(rootLabel); 380 381 this.topDownTree_.computeTotalWeights(); 382 this.topDownTree_.traverseInDepth( 383 function onEnter(node) { 384 if (!(node.label in precs)) { 385 precs[node.label] = 0; 386 } 387 var nodeLabelIsRootLabel = node.label == rootLabel; 388 if (nodeLabelIsRootLabel || precs[rootLabel] > 0) { 389 if (precs[rootLabel] == 0) { 390 root.selfWeight += node.selfWeight; 391 root.totalWeight += node.totalWeight; 392 } else { 393 var rec = root.findOrAddChild(node.label); 394 rec.selfWeight += node.selfWeight; 395 if (nodeLabelIsRootLabel || precs[node.label] == 0) { 396 rec.totalWeight += node.totalWeight; 397 } 398 } 399 precs[node.label]++; 400 } 401 }, 402 function onExit(node) { 403 if (node.label == rootLabel || precs[rootLabel] > 0) { 404 precs[node.label]--; 405 } 406 }, 407 null); 408 409 if (!opt_label) { 410 // If we have created a flat profile for the whole program, we don't 411 // need an explicit root in it. Thus, replace the counters tree 412 // root with the node corresponding to the whole program. 413 counters.root_ = root; 414 } else { 415 // Propagate weights so percents can be calculated correctly. 416 counters.getRoot().selfWeight = root.selfWeight; 417 counters.getRoot().totalWeight = root.totalWeight; 418 } 419 return counters; 420}; 421 422 423Profile.CEntryNode = function(name, ticks) { 424 this.name = name; 425 this.ticks = ticks; 426} 427 428 429Profile.prototype.getCEntryProfile = function() { 430 var result = [new Profile.CEntryNode("TOTAL", 0)]; 431 var total_ticks = 0; 432 for (var f in this.c_entries_) { 433 var ticks = this.c_entries_[f]; 434 total_ticks += ticks; 435 result.push(new Profile.CEntryNode(f, ticks)); 436 } 437 result[0].ticks = total_ticks; // Sorting will keep this at index 0. 438 result.sort(function(n1, n2) { 439 return n2.ticks - n1.ticks || (n2.name < n1.name ? -1 : 1) 440 }); 441 return result; 442} 443 444 445/** 446 * Cleans up function entries that are not referenced by code entries. 447 */ 448Profile.prototype.cleanUpFuncEntries = function() { 449 var referencedFuncEntries = []; 450 var entries = this.codeMap_.getAllDynamicEntriesWithAddresses(); 451 for (var i = 0, l = entries.length; i < l; ++i) { 452 if (entries[i][1].constructor === Profile.FunctionEntry) { 453 entries[i][1].used = false; 454 } 455 } 456 for (var i = 0, l = entries.length; i < l; ++i) { 457 if ("func" in entries[i][1]) { 458 entries[i][1].func.used = true; 459 } 460 } 461 for (var i = 0, l = entries.length; i < l; ++i) { 462 if (entries[i][1].constructor === Profile.FunctionEntry && 463 !entries[i][1].used) { 464 this.codeMap_.deleteCode(entries[i][0]); 465 } 466 } 467}; 468 469 470/** 471 * Creates a dynamic code entry. 472 * 473 * @param {number} size Code size. 474 * @param {string} type Code type. 475 * @param {string} name Function name. 476 * @constructor 477 */ 478Profile.DynamicCodeEntry = function(size, type, name) { 479 CodeMap.CodeEntry.call(this, size, name, type); 480}; 481 482 483/** 484 * Returns node name. 485 */ 486Profile.DynamicCodeEntry.prototype.getName = function() { 487 return this.type + ': ' + this.name; 488}; 489 490 491/** 492 * Returns raw node name (without type decoration). 493 */ 494Profile.DynamicCodeEntry.prototype.getRawName = function() { 495 return this.name; 496}; 497 498 499Profile.DynamicCodeEntry.prototype.isJSFunction = function() { 500 return false; 501}; 502 503 504Profile.DynamicCodeEntry.prototype.toString = function() { 505 return this.getName() + ': ' + this.size.toString(16); 506}; 507 508 509/** 510 * Creates a dynamic code entry. 511 * 512 * @param {number} size Code size. 513 * @param {string} type Code type. 514 * @param {Profile.FunctionEntry} func Shared function entry. 515 * @param {Profile.CodeState} state Code optimization state. 516 * @constructor 517 */ 518Profile.DynamicFuncCodeEntry = function(size, type, func, state) { 519 CodeMap.CodeEntry.call(this, size, '', type); 520 this.func = func; 521 this.state = state; 522}; 523 524Profile.DynamicFuncCodeEntry.STATE_PREFIX = ["", "~", "*"]; 525 526/** 527 * Returns state. 528 */ 529Profile.DynamicFuncCodeEntry.prototype.getState = function() { 530 return Profile.DynamicFuncCodeEntry.STATE_PREFIX[this.state]; 531}; 532 533/** 534 * Returns node name. 535 */ 536Profile.DynamicFuncCodeEntry.prototype.getName = function() { 537 var name = this.func.getName(); 538 return this.type + ': ' + this.getState() + name; 539}; 540 541 542/** 543 * Returns raw node name (without type decoration). 544 */ 545Profile.DynamicFuncCodeEntry.prototype.getRawName = function() { 546 return this.func.getName(); 547}; 548 549 550Profile.DynamicFuncCodeEntry.prototype.isJSFunction = function() { 551 return true; 552}; 553 554 555Profile.DynamicFuncCodeEntry.prototype.toString = function() { 556 return this.getName() + ': ' + this.size.toString(16); 557}; 558 559 560/** 561 * Creates a shared function object entry. 562 * 563 * @param {string} name Function name. 564 * @constructor 565 */ 566Profile.FunctionEntry = function(name) { 567 CodeMap.CodeEntry.call(this, 0, name); 568}; 569 570 571/** 572 * Returns node name. 573 */ 574Profile.FunctionEntry.prototype.getName = function() { 575 var name = this.name; 576 if (name.length == 0) { 577 name = '<anonymous>'; 578 } else if (name.charAt(0) == ' ') { 579 // An anonymous function with location: " aaa.js:10". 580 name = '<anonymous>' + name; 581 } 582 return name; 583}; 584 585Profile.FunctionEntry.prototype.toString = CodeMap.CodeEntry.prototype.toString; 586 587/** 588 * Constructs a call graph. 589 * 590 * @constructor 591 */ 592function CallTree() { 593 this.root_ = new CallTree.Node( 594 CallTree.ROOT_NODE_LABEL); 595}; 596 597 598/** 599 * The label of the root node. 600 */ 601CallTree.ROOT_NODE_LABEL = ''; 602 603 604/** 605 * @private 606 */ 607CallTree.prototype.totalsComputed_ = false; 608 609 610/** 611 * Returns the tree root. 612 */ 613CallTree.prototype.getRoot = function() { 614 return this.root_; 615}; 616 617 618/** 619 * Adds the specified call path, constructing nodes as necessary. 620 * 621 * @param {Array<string>} path Call path. 622 */ 623CallTree.prototype.addPath = function(path) { 624 if (path.length == 0) { 625 return; 626 } 627 var curr = this.root_; 628 for (var i = 0; i < path.length; ++i) { 629 curr = curr.findOrAddChild(path[i]); 630 } 631 curr.selfWeight++; 632 this.totalsComputed_ = false; 633}; 634 635 636/** 637 * Finds an immediate child of the specified parent with the specified 638 * label, creates a child node if necessary. If a parent node isn't 639 * specified, uses tree root. 640 * 641 * @param {string} label Child node label. 642 */ 643CallTree.prototype.findOrAddChild = function(label) { 644 return this.root_.findOrAddChild(label); 645}; 646 647 648/** 649 * Creates a subtree by cloning and merging all subtrees rooted at nodes 650 * with a given label. E.g. cloning the following call tree on label 'A' 651 * will give the following result: 652 * 653 * <A>--<B> <B> 654 * / / 655 * <root> == clone on 'A' ==> <root>--<A> 656 * \ \ 657 * <C>--<A>--<D> <D> 658 * 659 * And <A>'s selfWeight will be the sum of selfWeights of <A>'s from the 660 * source call tree. 661 * 662 * @param {string} label The label of the new root node. 663 */ 664CallTree.prototype.cloneSubtree = function(label) { 665 var subTree = new CallTree(); 666 this.traverse(function(node, parent) { 667 if (!parent && node.label != label) { 668 return null; 669 } 670 var child = (parent ? parent : subTree).findOrAddChild(node.label); 671 child.selfWeight += node.selfWeight; 672 return child; 673 }); 674 return subTree; 675}; 676 677 678/** 679 * Computes total weights in the call graph. 680 */ 681CallTree.prototype.computeTotalWeights = function() { 682 if (this.totalsComputed_) { 683 return; 684 } 685 this.root_.computeTotalWeight(); 686 this.totalsComputed_ = true; 687}; 688 689 690/** 691 * Traverses the call graph in preorder. This function can be used for 692 * building optionally modified tree clones. This is the boilerplate code 693 * for this scenario: 694 * 695 * callTree.traverse(function(node, parentClone) { 696 * var nodeClone = cloneNode(node); 697 * if (parentClone) 698 * parentClone.addChild(nodeClone); 699 * return nodeClone; 700 * }); 701 * 702 * @param {function(CallTree.Node, *)} f Visitor function. 703 * The second parameter is the result of calling 'f' on the parent node. 704 */ 705CallTree.prototype.traverse = function(f) { 706 var pairsToProcess = new ConsArray(); 707 pairsToProcess.concat([{node: this.root_, param: null}]); 708 while (!pairsToProcess.atEnd()) { 709 var pair = pairsToProcess.next(); 710 var node = pair.node; 711 var newParam = f(node, pair.param); 712 var morePairsToProcess = []; 713 node.forEachChild(function (child) { 714 morePairsToProcess.push({node: child, param: newParam}); }); 715 pairsToProcess.concat(morePairsToProcess); 716 } 717}; 718 719 720/** 721 * Performs an indepth call graph traversal. 722 * 723 * @param {function(CallTree.Node)} enter A function called 724 * prior to visiting node's children. 725 * @param {function(CallTree.Node)} exit A function called 726 * after visiting node's children. 727 */ 728CallTree.prototype.traverseInDepth = function(enter, exit) { 729 function traverse(node) { 730 enter(node); 731 node.forEachChild(traverse); 732 exit(node); 733 } 734 traverse(this.root_); 735}; 736 737 738/** 739 * Constructs a call graph node. 740 * 741 * @param {string} label Node label. 742 * @param {CallTree.Node} opt_parent Node parent. 743 */ 744CallTree.Node = function(label, opt_parent) { 745 this.label = label; 746 this.parent = opt_parent; 747 this.children = {}; 748}; 749 750 751/** 752 * Node self weight (how many times this node was the last node in 753 * a call path). 754 * @type {number} 755 */ 756CallTree.Node.prototype.selfWeight = 0; 757 758 759/** 760 * Node total weight (includes weights of all children). 761 * @type {number} 762 */ 763CallTree.Node.prototype.totalWeight = 0; 764 765 766/** 767 * Adds a child node. 768 * 769 * @param {string} label Child node label. 770 */ 771CallTree.Node.prototype.addChild = function(label) { 772 var child = new CallTree.Node(label, this); 773 this.children[label] = child; 774 return child; 775}; 776 777 778/** 779 * Computes node's total weight. 780 */ 781CallTree.Node.prototype.computeTotalWeight = 782 function() { 783 var totalWeight = this.selfWeight; 784 this.forEachChild(function(child) { 785 totalWeight += child.computeTotalWeight(); }); 786 return this.totalWeight = totalWeight; 787}; 788 789 790/** 791 * Returns all node's children as an array. 792 */ 793CallTree.Node.prototype.exportChildren = function() { 794 var result = []; 795 this.forEachChild(function (node) { result.push(node); }); 796 return result; 797}; 798 799 800/** 801 * Finds an immediate child with the specified label. 802 * 803 * @param {string} label Child node label. 804 */ 805CallTree.Node.prototype.findChild = function(label) { 806 return this.children[label] || null; 807}; 808 809 810/** 811 * Finds an immediate child with the specified label, creates a child 812 * node if necessary. 813 * 814 * @param {string} label Child node label. 815 */ 816CallTree.Node.prototype.findOrAddChild = function(label) { 817 return this.findChild(label) || this.addChild(label); 818}; 819 820 821/** 822 * Calls the specified function for every child. 823 * 824 * @param {function(CallTree.Node)} f Visitor function. 825 */ 826CallTree.Node.prototype.forEachChild = function(f) { 827 for (var c in this.children) { 828 f(this.children[c]); 829 } 830}; 831 832 833/** 834 * Walks up from the current node up to the call tree root. 835 * 836 * @param {function(CallTree.Node)} f Visitor function. 837 */ 838CallTree.Node.prototype.walkUpToRoot = function(f) { 839 for (var curr = this; curr != null; curr = curr.parent) { 840 f(curr); 841 } 842}; 843 844 845/** 846 * Tries to find a node with the specified path. 847 * 848 * @param {Array<string>} labels The path. 849 * @param {function(CallTree.Node)} opt_f Visitor function. 850 */ 851CallTree.Node.prototype.descendToChild = function( 852 labels, opt_f) { 853 for (var pos = 0, curr = this; pos < labels.length && curr != null; pos++) { 854 var child = curr.findChild(labels[pos]); 855 if (opt_f) { 856 opt_f(child, pos); 857 } 858 curr = child; 859 } 860 return curr; 861}; 862 863function JsonProfile() { 864 this.codeMap_ = new CodeMap(); 865 this.codeEntries_ = []; 866 this.functionEntries_ = []; 867 this.ticks_ = []; 868 this.scripts_ = []; 869} 870 871JsonProfile.prototype.addLibrary = function( 872 name, startAddr, endAddr) { 873 var entry = new CodeMap.CodeEntry( 874 endAddr - startAddr, name, 'SHARED_LIB'); 875 this.codeMap_.addLibrary(startAddr, entry); 876 877 entry.codeId = this.codeEntries_.length; 878 this.codeEntries_.push({name : entry.name, type : entry.type}); 879 return entry; 880}; 881 882JsonProfile.prototype.addStaticCode = function( 883 name, startAddr, endAddr) { 884 var entry = new CodeMap.CodeEntry( 885 endAddr - startAddr, name, 'CPP'); 886 this.codeMap_.addStaticCode(startAddr, entry); 887 888 entry.codeId = this.codeEntries_.length; 889 this.codeEntries_.push({name : entry.name, type : entry.type}); 890 return entry; 891}; 892 893JsonProfile.prototype.addCode = function( 894 kind, name, timestamp, start, size) { 895 var entry = new CodeMap.CodeEntry(size, name, 'CODE'); 896 this.codeMap_.addCode(start, entry); 897 898 entry.codeId = this.codeEntries_.length; 899 this.codeEntries_.push({ 900 name : entry.name, 901 timestamp: timestamp, 902 type : entry.type, 903 kind : kind 904 }); 905 906 return entry; 907}; 908 909JsonProfile.prototype.addFuncCode = function( 910 kind, name, timestamp, start, size, funcAddr, state) { 911 // As code and functions are in the same address space, 912 // it is safe to put them in a single code map. 913 var func = this.codeMap_.findDynamicEntryByStartAddress(funcAddr); 914 if (!func) { 915 var func = new CodeMap.CodeEntry(0, name, 'SFI'); 916 this.codeMap_.addCode(funcAddr, func); 917 918 func.funcId = this.functionEntries_.length; 919 this.functionEntries_.push({name : name, codes : []}); 920 } else if (func.name !== name) { 921 // Function object has been overwritten with a new one. 922 func.name = name; 923 924 func.funcId = this.functionEntries_.length; 925 this.functionEntries_.push({name : name, codes : []}); 926 } 927 // TODO(jarin): Insert the code object into the SFI's code list. 928 var entry = this.codeMap_.findDynamicEntryByStartAddress(start); 929 if (entry) { 930 // TODO(jarin) This does not look correct, we should really 931 // update the code object (remove the old one and insert this one). 932 if (entry.size === size && entry.func === func) { 933 // Entry state has changed. 934 entry.state = state; 935 } 936 } else { 937 var entry = new CodeMap.CodeEntry(size, name, 'JS'); 938 this.codeMap_.addCode(start, entry); 939 940 entry.codeId = this.codeEntries_.length; 941 942 this.functionEntries_[func.funcId].codes.push(entry.codeId); 943 944 if (state === 0) { 945 kind = "Builtin"; 946 } else if (state === 1) { 947 kind = "Unopt"; 948 } else if (state === 2) { 949 kind = "Opt"; 950 } 951 952 this.codeEntries_.push({ 953 name : entry.name, 954 type : entry.type, 955 kind : kind, 956 func : func.funcId, 957 tm : timestamp 958 }); 959 } 960 return entry; 961}; 962 963JsonProfile.prototype.moveCode = function(from, to) { 964 try { 965 this.codeMap_.moveCode(from, to); 966 } catch (e) { 967 printErr("Move: unknown source " + from); 968 } 969}; 970 971JsonProfile.prototype.addSourcePositions = function( 972 start, script, startPos, endPos, sourcePositions, inliningPositions, 973 inlinedFunctions) { 974 var entry = this.codeMap_.findDynamicEntryByStartAddress(start); 975 if (!entry) return; 976 var codeId = entry.codeId; 977 978 // Resolve the inlined fucntions list. 979 if (inlinedFunctions.length > 0) { 980 inlinedFunctions = inlinedFunctions.substring(1).split("S"); 981 for (var i = 0; i < inlinedFunctions.length; i++) { 982 var funcAddr = parseInt(inlinedFunctions[i]); 983 var func = this.codeMap_.findDynamicEntryByStartAddress(funcAddr); 984 if (!func || func.funcId === undefined) { 985 printErr("Could not find function " + inlinedFunctions[i]); 986 inlinedFunctions[i] = null; 987 } else { 988 inlinedFunctions[i] = func.funcId; 989 } 990 } 991 } else { 992 inlinedFunctions = []; 993 } 994 995 this.codeEntries_[entry.codeId].source = { 996 script : script, 997 start : startPos, 998 end : endPos, 999 positions : sourcePositions, 1000 inlined : inliningPositions, 1001 fns : inlinedFunctions 1002 }; 1003}; 1004 1005JsonProfile.prototype.addScriptSource = function(script, url, source) { 1006 this.scripts_[script] = { 1007 name : url, 1008 source : source 1009 }; 1010}; 1011 1012 1013JsonProfile.prototype.deoptCode = function( 1014 timestamp, code, inliningId, scriptOffset, bailoutType, 1015 sourcePositionText, deoptReasonText) { 1016 let entry = this.codeMap_.findDynamicEntryByStartAddress(code); 1017 if (entry) { 1018 let codeId = entry.codeId; 1019 if (!this.codeEntries_[codeId].deopt) { 1020 // Only add the deopt if there was no deopt before. 1021 // The subsequent deoptimizations should be lazy deopts for 1022 // other on-stack activations. 1023 this.codeEntries_[codeId].deopt = { 1024 tm : timestamp, 1025 inliningId : inliningId, 1026 scriptOffset : scriptOffset, 1027 posText : sourcePositionText, 1028 reason : deoptReasonText, 1029 bailoutType : bailoutType 1030 }; 1031 } 1032 } 1033}; 1034 1035JsonProfile.prototype.deleteCode = function(start) { 1036 try { 1037 this.codeMap_.deleteCode(start); 1038 } catch (e) { 1039 printErr("Delete: unknown address " + start); 1040 } 1041}; 1042 1043JsonProfile.prototype.moveFunc = function(from, to) { 1044 if (this.codeMap_.findDynamicEntryByStartAddress(from)) { 1045 this.codeMap_.moveCode(from, to); 1046 } 1047}; 1048 1049JsonProfile.prototype.findEntry = function(addr) { 1050 return this.codeMap_.findEntry(addr); 1051}; 1052 1053JsonProfile.prototype.recordTick = function(time_ns, vmState, stack) { 1054 // TODO(jarin) Resolve the frame-less case (when top of stack is 1055 // known code). 1056 var processedStack = []; 1057 for (var i = 0; i < stack.length; i++) { 1058 var resolved = this.codeMap_.findAddress(stack[i]); 1059 if (resolved) { 1060 processedStack.push(resolved.entry.codeId, resolved.offset); 1061 } else { 1062 processedStack.push(-1, stack[i]); 1063 } 1064 } 1065 this.ticks_.push({ tm : time_ns, vm : vmState, s : processedStack }); 1066}; 1067 1068function writeJson(s) { 1069 write(JSON.stringify(s, null, 2)); 1070} 1071 1072JsonProfile.prototype.writeJson = function() { 1073 // Write out the JSON in a partially manual way to avoid creating too-large 1074 // strings in one JSON.stringify call when there are a lot of ticks. 1075 write('{\n') 1076 1077 write(' "code": '); 1078 writeJson(this.codeEntries_); 1079 write(',\n'); 1080 1081 write(' "functions": '); 1082 writeJson(this.functionEntries_); 1083 write(',\n'); 1084 1085 write(' "ticks": [\n'); 1086 for (var i = 0; i < this.ticks_.length; i++) { 1087 write(' '); 1088 writeJson(this.ticks_[i]); 1089 if (i < this.ticks_.length - 1) { 1090 write(',\n'); 1091 } else { 1092 write('\n'); 1093 } 1094 } 1095 write(' ],\n'); 1096 1097 write(' "scripts": '); 1098 writeJson(this.scripts_); 1099 1100 write('}\n'); 1101}; 1102