Home | History | Annotate | Download | only in tools
      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