2 * Copyright (C) 2009 280 North Inc. All Rights Reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
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.
32 WebInspector.BottomUpProfileDataGridNode = function(/*ProfileView*/ profileView, /*ProfileNode*/ profileNode, /*BottomUpProfileDataGridTree*/ owningTree)
34 WebInspector.ProfileDataGridNode.call(this, profileView, profileNode, owningTree, this._willHaveChildren(profileNode));
36 this._remainingNodeInfos = [];
39 WebInspector.BottomUpProfileDataGridNode.prototype = {
40 _takePropertiesFromProfileDataGridNode: function(/*ProfileDataGridNode*/ profileDataGridNode)
44 this.selfTime = profileDataGridNode.selfTime;
45 this.totalTime = profileDataGridNode.totalTime;
46 this.numberOfCalls = profileDataGridNode.numberOfCalls;
49 // When focusing, we keep just the members of the callstack.
50 _keepOnlyChild: function(/*ProfileDataGridNode*/ child)
54 this.removeChildren();
55 this.appendChild(child);
58 _exclude: function(aCallUID)
60 if (this._remainingNodeInfos)
65 var children = this.children;
66 var index = this.children.length;
69 children[index]._exclude(aCallUID);
71 var child = this.childrenByCallUID[aCallUID];
74 this._merge(child, true);
79 WebInspector.ProfileDataGridNode.prototype._restore();
81 if (!this.children.length)
82 this.hasChildren = this._willHaveChildren();
85 _merge: function(/*ProfileDataGridNode*/ child, /*Boolean*/ shouldAbsorb)
87 this.selfTime -= child.selfTime;
89 WebInspector.ProfileDataGridNode.prototype._merge.call(this, child, shouldAbsorb);
92 _sharedPopulate: function()
94 var remainingNodeInfos = this._remainingNodeInfos;
95 var count = remainingNodeInfos.length;
97 for (var index = 0; index < count; ++index) {
98 var nodeInfo = remainingNodeInfos[index];
99 var ancestor = nodeInfo.ancestor;
100 var focusNode = nodeInfo.focusNode;
101 var child = this.findChild(ancestor);
103 // If we already have this child, then merge the data together.
105 var totalTimeAccountedFor = nodeInfo.totalTimeAccountedFor;
107 child.selfTime += focusNode.selfTime;
108 child.numberOfCalls += focusNode.numberOfCalls;
110 if (!totalTimeAccountedFor)
111 child.totalTime += focusNode.totalTime;
113 // If not, add it as a true ancestor.
114 // In heavy mode, we take our visual identity from ancestor node...
115 var child = new WebInspector.BottomUpProfileDataGridNode(this.profileView, ancestor, this.tree);
117 if (ancestor !== focusNode) {
118 // but the actual statistics from the "root" node (bottom of the callstack).
119 child.selfTime = focusNode.selfTime;
120 child.totalTime = focusNode.totalTime;
121 child.numberOfCalls = focusNode.numberOfCalls;
124 this.appendChild(child);
127 var parent = ancestor.parent;
128 if (parent && parent.parent) {
129 nodeInfo.ancestor = parent;
130 child._remainingNodeInfos.push(nodeInfo);
134 delete this._remainingNodeInfos;
137 _willHaveChildren: function(profileNode)
139 profileNode = profileNode || this.profileNode;
140 // In bottom up mode, our parents are our children since we display an inverted tree.
141 // However, we don't want to show the very top parent since it is redundant.
142 return !!(profileNode.parent && profileNode.parent.parent);
146 WebInspector.BottomUpProfileDataGridNode.prototype.__proto__ = WebInspector.ProfileDataGridNode.prototype;
148 WebInspector.BottomUpProfileDataGridTree = function(/*ProfileView*/ aProfileView, /*ProfileNode*/ aProfileNode)
150 WebInspector.ProfileDataGridTree.call(this, aProfileView, aProfileNode);
152 // Iterate each node in pre-order.
153 var profileNodeUIDs = 0;
154 var profileNodeGroups = [[], [aProfileNode]];
155 var visitedProfileNodesForCallUID = {};
157 this._remainingNodeInfos = [];
159 for (var profileNodeGroupIndex = 0; profileNodeGroupIndex < profileNodeGroups.length; ++profileNodeGroupIndex) {
160 var parentProfileNodes = profileNodeGroups[profileNodeGroupIndex];
161 var profileNodes = profileNodeGroups[++profileNodeGroupIndex];
162 var count = profileNodes.length;
164 for (var index = 0; index < count; ++index) {
165 var profileNode = profileNodes[index];
167 if (!profileNode.UID)
168 profileNode.UID = ++profileNodeUIDs;
170 if (profileNode.head && profileNode !== profileNode.head) {
171 // The total time of this ancestor is accounted for if we're in any form of recursive cycle.
172 var visitedNodes = visitedProfileNodesForCallUID[profileNode.callUID];
173 var totalTimeAccountedFor = false;
177 visitedProfileNodesForCallUID[profileNode.callUID] = visitedNodes;
179 // The total time for this node has already been accounted for iff one of it's parents has already been visited.
180 // We can do this check in this style because we are traversing the tree in pre-order.
181 var parentCount = parentProfileNodes.length;
182 for (var parentIndex = 0; parentIndex < parentCount; ++parentIndex) {
183 if (visitedNodes[parentProfileNodes[parentIndex].UID]) {
184 totalTimeAccountedFor = true;
190 visitedNodes[profileNode.UID] = true;
192 this._remainingNodeInfos.push({ ancestor:profileNode, focusNode:profileNode, totalTimeAccountedFor:totalTimeAccountedFor });
195 var children = profileNode.children;
196 if (children.length) {
197 profileNodeGroups.push(parentProfileNodes.concat([profileNode]))
198 profileNodeGroups.push(children);
203 // Populate the top level nodes.
204 WebInspector.BottomUpProfileDataGridNode.prototype._populate.call(this);
209 WebInspector.BottomUpProfileDataGridTree.prototype = {
210 // When focusing, we keep the entire callstack up to this ancestor.
211 focus: function(/*ProfileDataGridNode*/ profileDataGridNode)
213 if (!profileDataGridNode)
218 var currentNode = profileDataGridNode;
219 var focusNode = profileDataGridNode;
221 while (currentNode.parent && (currentNode instanceof WebInspector.ProfileDataGridNode)) {
222 currentNode._takePropertiesFromProfileDataGridNode(profileDataGridNode);
224 focusNode = currentNode;
225 currentNode = currentNode.parent;
227 if (currentNode instanceof WebInspector.ProfileDataGridNode)
228 currentNode._keepOnlyChild(focusNode);
231 this.children = [focusNode];
232 this.totalTime = profileDataGridNode.totalTime;
235 exclude: function(/*ProfileDataGridNode*/ profileDataGridNode)
237 if (!profileDataGridNode)
242 var excludedCallUID = profileDataGridNode.callUID;
243 var excludedTopLevelChild = this.childrenByCallUID[excludedCallUID];
245 // If we have a top level node that is excluded, get rid of it completely (not keeping children),
246 // since bottom up data relies entirely on the root node.
247 if (excludedTopLevelChild)
248 this.children.remove(excludedTopLevelChild);
250 var children = this.children;
251 var count = children.length;
253 for (var index = 0; index < count; ++index)
254 children[index]._exclude(excludedCallUID);
256 if (this.lastComparator)
257 this.sort(this.lastComparator, true);
260 _sharedPopulate: WebInspector.BottomUpProfileDataGridNode.prototype._sharedPopulate
263 WebInspector.BottomUpProfileDataGridTree.prototype.__proto__ = WebInspector.ProfileDataGridTree.prototype;