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 */ 38WebInspector.BottomUpProfileDataGridNode = function(profileNode, owningTree) 39{ 40 WebInspector.ProfileDataGridNode.call(this, profileNode, owningTree, this._willHaveChildren(profileNode)); 41 42 this._remainingNodeInfos = []; 43} 44 45WebInspector.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 */ 166WebInspector.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 229WebInspector.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