• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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