Home | History | Annotate | Download | only in front_end
      1 /*
      2  * Copyright (C) 2009 280 North Inc. All Rights Reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 // Bottom Up Profiling shows the entire callstack backwards:
     27 // The root node is a representation of each individual function called, and each child of that node represents
     28 // a reverse-callstack showing how many of those calls came from it. So, unlike top-down, the statistics in
     29 // each child still represent the root node. We have to be particularly careful of recursion with this mode
     30 // because a root node can represent itself AND an ancestor.
     31 
     32 /**
     33  * @constructor
     34  * @extends {WebInspector.ProfileDataGridNode}
     35  * @param {!ProfilerAgent.CPUProfileNode} profileNode
     36  * @param {!WebInspector.TopDownProfileDataGridTree} owningTree
     37  */
     38 WebInspector.BottomUpProfileDataGridNode = function(profileNode, owningTree)
     39 {
     40     WebInspector.ProfileDataGridNode.call(this, profileNode, owningTree, this._willHaveChildren(profileNode));
     41 
     42     this._remainingNodeInfos = [];
     43 }
     44 
     45 WebInspector.BottomUpProfileDataGridNode.prototype = {
     46     /**
     47      * @param {!WebInspector.ProfileDataGridNode} profileDataGridNode
     48      */
     49     _takePropertiesFromProfileDataGridNode: function(profileDataGridNode)
     50     {
     51         this._save();
     52 
     53         this.selfTime = profileDataGridNode.selfTime;
     54         this.totalTime = profileDataGridNode.totalTime;
     55     },
     56 
     57     /**
     58      * When focusing, we keep just the members of the callstack.
     59      * @param {!WebInspector.ProfileDataGridNode} child
     60      */
     61     _keepOnlyChild: function(child)
     62     {
     63         this._save();
     64 
     65         this.removeChildren();
     66         this.appendChild(child);
     67     },
     68 
     69     _exclude: function(aCallUID)
     70     {
     71         if (this._remainingNodeInfos)
     72             this.populate();
     73 
     74         this._save();
     75 
     76         var children = this.children;
     77         var index = this.children.length;
     78 
     79         while (index--)
     80             children[index]._exclude(aCallUID);
     81 
     82         var child = this.childrenByCallUID[aCallUID];
     83 
     84         if (child)
     85             this._merge(child, true);
     86     },
     87 
     88     _restore: function()
     89     {
     90         WebInspector.ProfileDataGridNode.prototype._restore();
     91 
     92         if (!this.children.length)
     93             this.hasChildren = this._willHaveChildren(this.profileNode);
     94     },
     95 
     96     /**
     97      * @param {!WebInspector.ProfileDataGridNode} child
     98      * @param {boolean} shouldAbsorb
     99      */
    100     _merge: function(child, shouldAbsorb)
    101     {
    102         this.selfTime -= child.selfTime;
    103 
    104         WebInspector.ProfileDataGridNode.prototype._merge.call(this, child, shouldAbsorb);
    105     },
    106 
    107     _sharedPopulate: function()
    108     {
    109         var remainingNodeInfos = this._remainingNodeInfos;
    110         var count = remainingNodeInfos.length;
    111 
    112         for (var index = 0; index < count; ++index) {
    113             var nodeInfo = remainingNodeInfos[index];
    114             var ancestor = nodeInfo.ancestor;
    115             var focusNode = nodeInfo.focusNode;
    116             var child = this.findChild(ancestor);
    117 
    118             // If we already have this child, then merge the data together.
    119             if (child) {
    120                 var totalTimeAccountedFor = nodeInfo.totalTimeAccountedFor;
    121 
    122                 child.selfTime += focusNode.selfTime;
    123 
    124                 if (!totalTimeAccountedFor)
    125                     child.totalTime += focusNode.totalTime;
    126             } else {
    127                 // If not, add it as a true ancestor.
    128                 // In heavy mode, we take our visual identity from ancestor node...
    129                 child = new WebInspector.BottomUpProfileDataGridNode(ancestor, this.tree);
    130 
    131                 if (ancestor !== focusNode) {
    132                     // but the actual statistics from the "root" node (bottom of the callstack).
    133                     child.selfTime = focusNode.selfTime;
    134                     child.totalTime = focusNode.totalTime;
    135                 }
    136 
    137                 this.appendChild(child);
    138             }
    139 
    140             var parent = ancestor.parent;
    141             if (parent && parent.parent) {
    142                 nodeInfo.ancestor = parent;
    143                 child._remainingNodeInfos.push(nodeInfo);
    144             }
    145         }
    146 
    147         delete this._remainingNodeInfos;
    148     },
    149 
    150     _willHaveChildren: function(profileNode)
    151     {
    152         // In bottom up mode, our parents are our children since we display an inverted tree.
    153         // However, we don't want to show the very top parent since it is redundant.
    154         return !!(profileNode.parent && profileNode.parent.parent);
    155     },
    156 
    157     __proto__: WebInspector.ProfileDataGridNode.prototype
    158 }
    159 
    160 /**
    161  * @constructor
    162  * @extends {WebInspector.ProfileDataGridTree}
    163  * @param {WebInspector.CPUProfileView} profileView
    164  * @param {ProfilerAgent.CPUProfileNode} rootProfileNode
    165  */
    166 WebInspector.BottomUpProfileDataGridTree = function(profileView, rootProfileNode)
    167 {
    168     WebInspector.ProfileDataGridTree.call(this, profileView, rootProfileNode);
    169 
    170     // Iterate each node in pre-order.
    171     var profileNodeUIDs = 0;
    172     var profileNodeGroups = [[], [rootProfileNode]];
    173     var visitedProfileNodesForCallUID = {};
    174 
    175     this._remainingNodeInfos = [];
    176 
    177     for (var profileNodeGroupIndex = 0; profileNodeGroupIndex < profileNodeGroups.length; ++profileNodeGroupIndex) {
    178         var parentProfileNodes = profileNodeGroups[profileNodeGroupIndex];
    179         var profileNodes = profileNodeGroups[++profileNodeGroupIndex];
    180         var count = profileNodes.length;
    181 
    182         for (var index = 0; index < count; ++index) {
    183             var profileNode = profileNodes[index];
    184 
    185             if (!profileNode.UID)
    186                 profileNode.UID = ++profileNodeUIDs;
    187 
    188             if (profileNode.head && profileNode !== profileNode.head) {
    189                 // The total time of this ancestor is accounted for if we're in any form of recursive cycle.
    190                 var visitedNodes = visitedProfileNodesForCallUID[profileNode.callUID];
    191                 var totalTimeAccountedFor = false;
    192 
    193                 if (!visitedNodes) {
    194                     visitedNodes = {}
    195                     visitedProfileNodesForCallUID[profileNode.callUID] = visitedNodes;
    196                 } else {
    197                     // The total time for this node has already been accounted for iff one of it's parents has already been visited.
    198                     // We can do this check in this style because we are traversing the tree in pre-order.
    199                     var parentCount = parentProfileNodes.length;
    200                     for (var parentIndex = 0; parentIndex < parentCount; ++parentIndex) {
    201                         if (visitedNodes[parentProfileNodes[parentIndex].UID]) {
    202                             totalTimeAccountedFor = true;
    203                             break;
    204                         }
    205                     }
    206                 }
    207 
    208                 visitedNodes[profileNode.UID] = true;
    209 
    210                 this._remainingNodeInfos.push({ ancestor:profileNode, focusNode:profileNode, totalTimeAccountedFor:totalTimeAccountedFor });
    211             }
    212 
    213             var children = profileNode.children;
    214             if (children.length) {
    215                 profileNodeGroups.push(parentProfileNodes.concat([profileNode]))
    216                 profileNodeGroups.push(children);
    217             }
    218         }
    219     }
    220 
    221     // Populate the top level nodes.
    222     var any = /** @type{*} */(this);
    223     var node = /** @type{WebInspector.ProfileDataGridNode} */(any);
    224     WebInspector.BottomUpProfileDataGridNode.prototype.populate.call(node);
    225 
    226     return this;
    227 }
    228 
    229 WebInspector.BottomUpProfileDataGridTree.prototype = {
    230     /**
    231      * When focusing, we keep the entire callstack up to this ancestor.
    232      * @param {!WebInspector.ProfileDataGridNode} profileDataGridNode
    233      */
    234     focus: function(profileDataGridNode)
    235     {
    236         if (!profileDataGridNode)
    237             return;
    238 
    239         this._save();
    240 
    241         var currentNode = profileDataGridNode;
    242         var focusNode = profileDataGridNode;
    243 
    244         while (currentNode.parent && (currentNode instanceof WebInspector.ProfileDataGridNode)) {
    245             currentNode._takePropertiesFromProfileDataGridNode(profileDataGridNode);
    246 
    247             focusNode = currentNode;
    248             currentNode = currentNode.parent;
    249 
    250             if (currentNode instanceof WebInspector.ProfileDataGridNode)
    251                 currentNode._keepOnlyChild(focusNode);
    252         }
    253 
    254         this.children = [focusNode];
    255         this.totalTime = profileDataGridNode.totalTime;
    256     },
    257 
    258     /**
    259      * @param {!WebInspector.ProfileDataGridNode} profileDataGridNode
    260      */
    261     exclude: function(profileDataGridNode)
    262     {
    263         if (!profileDataGridNode)
    264             return;
    265 
    266         this._save();
    267 
    268         var excludedCallUID = profileDataGridNode.callUID;
    269         var excludedTopLevelChild = this.childrenByCallUID[excludedCallUID];
    270 
    271         // If we have a top level node that is excluded, get rid of it completely (not keeping children),
    272         // since bottom up data relies entirely on the root node.
    273         if (excludedTopLevelChild)
    274             this.children.remove(excludedTopLevelChild);
    275 
    276         var children = this.children;
    277         var count = children.length;
    278 
    279         for (var index = 0; index < count; ++index)
    280             children[index]._exclude(excludedCallUID);
    281 
    282         if (this.lastComparator)
    283             this.sort(this.lastComparator, true);
    284     },
    285 
    286     _sharedPopulate: WebInspector.BottomUpProfileDataGridNode.prototype._sharedPopulate,
    287 
    288     __proto__: WebInspector.ProfileDataGridTree.prototype
    289 }
    290