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 32WebInspector.BottomUpProfileDataGridTree = function(/*ProfileView*/ aProfileView, /*ProfileNode*/ aProfileNode) 33{ 34 WebInspector.ProfileDataGridTree.call(this, aProfileView, aProfileNode); 35 36 // Iterate each node in pre-order. 37 var profileNodeUIDs = 0; 38 var profileNodeGroups = [[], [aProfileNode]]; 39 var visitedProfileNodesForCallUID = {}; 40 41 this._remainingNodeInfos = []; 42 43 for (var profileNodeGroupIndex = 0; profileNodeGroupIndex < profileNodeGroups.length; ++profileNodeGroupIndex) { 44 var parentProfileNodes = profileNodeGroups[profileNodeGroupIndex]; 45 var profileNodes = profileNodeGroups[++profileNodeGroupIndex]; 46 var count = profileNodes.length; 47 48 for (var index = 0; index < count; ++index) { 49 var profileNode = profileNodes[index]; 50 51 if (!profileNode.UID) 52 profileNode.UID = ++profileNodeUIDs; 53 54 if (profileNode.head && profileNode !== profileNode.head) { 55 // The total time of this ancestor is accounted for if we're in any form of recursive cycle. 56 var visitedNodes = visitedProfileNodesForCallUID[profileNode.callUID]; 57 var totalTimeAccountedFor = false; 58 59 if (!visitedNodes) { 60 visitedNodes = {} 61 visitedProfileNodesForCallUID[profileNode.callUID] = visitedNodes; 62 } else { 63 // The total time for this node has already been accounted for iff one of it's parents has already been visited. 64 // We can do this check in this style because we are traversing the tree in pre-order. 65 var parentCount = parentProfileNodes.length; 66 for (var parentIndex = 0; parentIndex < parentCount; ++parentIndex) { 67 if (visitedNodes[parentProfileNodes[parentIndex].UID]) { 68 totalTimeAccountedFor = true; 69 break; 70 } 71 } 72 } 73 74 visitedNodes[profileNode.UID] = true; 75 76 this._remainingNodeInfos.push({ ancestor:profileNode, focusNode:profileNode, totalTimeAccountedFor:totalTimeAccountedFor }); 77 } 78 79 var children = profileNode.children; 80 if (children.length) { 81 profileNodeGroups.push(parentProfileNodes.concat([profileNode])) 82 profileNodeGroups.push(children); 83 } 84 } 85 } 86 87 // Populate the top level nodes. 88 WebInspector.BottomUpProfileDataGridNode.prototype._populate.call(this); 89 90 return this; 91} 92 93WebInspector.BottomUpProfileDataGridTree.prototype = { 94 // When focusing, we keep the entire callstack up to this ancestor. 95 focus: function(/*ProfileDataGridNode*/ profileDataGridNode) 96 { 97 if (!profileDataGridNode) 98 return; 99 100 this._save(); 101 102 var currentNode = profileDataGridNode; 103 var focusNode = profileDataGridNode; 104 105 while (currentNode.parent && (currentNode instanceof WebInspector.ProfileDataGridNode)) { 106 currentNode._takePropertiesFromProfileDataGridNode(profileDataGridNode); 107 108 focusNode = currentNode; 109 currentNode = currentNode.parent; 110 111 if (currentNode instanceof WebInspector.ProfileDataGridNode) 112 currentNode._keepOnlyChild(focusNode); 113 } 114 115 this.children = [focusNode]; 116 this.totalTime = profileDataGridNode.totalTime; 117 }, 118 119 exclude: function(/*ProfileDataGridNode*/ profileDataGridNode) 120 { 121 if (!profileDataGridNode) 122 return; 123 124 this._save(); 125 126 var excludedCallUID = profileDataGridNode.callUID; 127 var excludedTopLevelChild = this.childrenByCallUID[excludedCallUID]; 128 129 // If we have a top level node that is excluded, get rid of it completely (not keeping children), 130 // since bottom up data relies entirely on the root node. 131 if (excludedTopLevelChild) 132 this.children.remove(excludedTopLevelChild); 133 134 var children = this.children; 135 var count = children.length; 136 137 for (var index = 0; index < count; ++index) 138 children[index]._exclude(excludedCallUID); 139 140 if (this.lastComparator) 141 this.sort(this.lastComparator, true); 142 } 143} 144 145WebInspector.BottomUpProfileDataGridTree.prototype.__proto__ = WebInspector.ProfileDataGridTree.prototype; 146 147WebInspector.BottomUpProfileDataGridNode = function(/*ProfileView*/ profileView, /*ProfileNode*/ profileNode, /*BottomUpProfileDataGridTree*/ owningTree) 148{ 149 // In bottom up mode, our parents are our children since we display an inverted tree. 150 // However, we don't want to show the very top parent since it is redundant. 151 var hasChildren = !!(profileNode.parent && profileNode.parent.parent); 152 153 WebInspector.ProfileDataGridNode.call(this, profileView, profileNode, owningTree, hasChildren); 154 155 this._remainingNodeInfos = []; 156} 157 158WebInspector.BottomUpProfileDataGridNode.prototype = { 159 _takePropertiesFromProfileDataGridNode: function(/*ProfileDataGridNode*/ profileDataGridNode) 160 { 161 this._save(); 162 163 this.selfTime = profileDataGridNode.selfTime; 164 this.totalTime = profileDataGridNode.totalTime; 165 this.numberOfCalls = profileDataGridNode.numberOfCalls; 166 }, 167 168 // When focusing, we keep just the members of the callstack. 169 _keepOnlyChild: function(/*ProfileDataGridNode*/ child) 170 { 171 this._save(); 172 173 this.removeChildren(); 174 this.appendChild(child); 175 }, 176 177 _exclude: function(aCallUID) 178 { 179 if (this._remainingNodeInfos) 180 this._populate(); 181 182 this._save(); 183 184 var children = this.children; 185 var index = this.children.length; 186 187 while (index--) 188 children[index]._exclude(aCallUID); 189 190 var child = this.childrenByCallUID[aCallUID]; 191 192 if (child) 193 this._merge(child, true); 194 }, 195 196 _merge: function(/*ProfileDataGridNode*/ child, /*Boolean*/ shouldAbsorb) 197 { 198 this.selfTime -= child.selfTime; 199 200 WebInspector.ProfileDataGridNode.prototype._merge.call(this, child, shouldAbsorb); 201 }, 202 203 _populate: function(event) 204 { 205 var remainingNodeInfos = this._remainingNodeInfos; 206 var count = remainingNodeInfos.length; 207 208 for (var index = 0; index < count; ++index) { 209 var nodeInfo = remainingNodeInfos[index]; 210 var ancestor = nodeInfo.ancestor; 211 var focusNode = nodeInfo.focusNode; 212 var child = this.findChild(ancestor); 213 214 // If we already have this child, then merge the data together. 215 if (child) { 216 var totalTimeAccountedFor = nodeInfo.totalTimeAccountedFor; 217 218 child.selfTime += focusNode.selfTime; 219 child.numberOfCalls += focusNode.numberOfCalls; 220 221 if (!totalTimeAccountedFor) 222 child.totalTime += focusNode.totalTime; 223 } else { 224 // If not, add it as a true ancestor. 225 // In heavy mode, we take our visual identity from ancestor node... 226 var child = new WebInspector.BottomUpProfileDataGridNode(this.profileView, ancestor, this.tree); 227 228 if (ancestor !== focusNode) { 229 // but the actual statistics from the "root" node (bottom of the callstack). 230 child.selfTime = focusNode.selfTime; 231 child.totalTime = focusNode.totalTime; 232 child.numberOfCalls = focusNode.numberOfCalls; 233 } 234 235 this.appendChild(child); 236 } 237 238 var parent = ancestor.parent; 239 if (parent && parent.parent) { 240 nodeInfo.ancestor = parent; 241 child._remainingNodeInfos.push(nodeInfo); 242 } 243 } 244 245 delete this._remainingNodeInfos; 246 247 if (this.removeEventListener) 248 this.removeEventListener("populate", this._populate, this); 249 } 250} 251 252WebInspector.BottomUpProfileDataGridNode.prototype.__proto__ = WebInspector.ProfileDataGridNode.prototype; 253