• 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/**
27 * @constructor
28 * @extends {WebInspector.DataGridNode}
29 * @param {!ProfilerAgent.CPUProfileNode} profileNode
30 * @param {!WebInspector.TopDownProfileDataGridTree} owningTree
31 * @param {boolean} hasChildren
32 */
33WebInspector.ProfileDataGridNode = function(profileNode, owningTree, hasChildren)
34{
35    this.profileNode = profileNode;
36
37    WebInspector.DataGridNode.call(this, null, hasChildren);
38
39    this.tree = owningTree;
40
41    this.childrenByCallUID = {};
42    this.lastComparator = null;
43
44    this.callUID = profileNode.callUID;
45    this.selfTime = profileNode.selfTime;
46    this.totalTime = profileNode.totalTime;
47    this.functionName = profileNode.functionName;
48    this._deoptReason = (!profileNode.deoptReason || profileNode.deoptReason === "no reason") ? "" : profileNode.deoptReason;
49    this.url = profileNode.url;
50}
51
52WebInspector.ProfileDataGridNode.prototype = {
53    get data()
54    {
55        function formatMilliseconds(time)
56        {
57            return WebInspector.UIString("%.1f\u2009ms", time);
58        }
59
60        var data = {};
61
62        if (this._deoptReason) {
63            var div = document.createElement("div");
64            var marker = div.createChild("span");
65            marker.className = "profile-warn-marker";
66            marker.title = WebInspector.UIString("Not optimized: %s", this._deoptReason);
67            var functionName = div.createChild("span");
68            functionName.textContent = this.functionName;
69            data["function"] = div;
70        } else
71            data["function"] = this.functionName;
72
73        if (this.tree.profileView.showSelfTimeAsPercent.get())
74            data["self"] = WebInspector.UIString("%.2f%", this.selfPercent);
75        else
76            data["self"] = formatMilliseconds(this.selfTime);
77
78        if (this.tree.profileView.showTotalTimeAsPercent.get())
79            data["total"] = WebInspector.UIString("%.2f%", this.totalPercent);
80        else
81            data["total"] = formatMilliseconds(this.totalTime);
82
83        return data;
84    },
85
86    /**
87     * @override
88     * @param {string} columnIdentifier
89     * @return {!Element}
90     */
91    createCell: function(columnIdentifier)
92    {
93        var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);
94
95        if (columnIdentifier === "self" && this._searchMatchedSelfColumn)
96            cell.classList.add("highlight");
97        else if (columnIdentifier === "total" && this._searchMatchedTotalColumn)
98            cell.classList.add("highlight");
99
100        if (columnIdentifier !== "function")
101            return cell;
102
103        if (this._deoptReason)
104            cell.classList.add("not-optimized");
105
106        if (this.profileNode._searchMatchedFunctionColumn)
107            cell.classList.add("highlight");
108
109        if (this.profileNode.scriptId !== "0") {
110            var lineNumber = this.profileNode.lineNumber ? this.profileNode.lineNumber - 1 : 0;
111            var columnNumber = this.profileNode.columnNumber ? this.profileNode.columnNumber - 1 : 0;
112            var location = new WebInspector.DebuggerModel.Location(this.profileNode.scriptId, lineNumber, columnNumber);
113            var urlElement = this.tree.profileView._linkifier.linkifyRawLocation(location, "profile-node-file");
114            if (!urlElement)
115                urlElement = this.tree.profileView._linkifier.linkifyLocation(this.profileNode.url, lineNumber, columnNumber, "profile-node-file");
116            urlElement.style.maxWidth = "75%";
117            cell.insertBefore(urlElement, cell.firstChild);
118        }
119
120        return cell;
121    },
122
123    select: function(supressSelectedEvent)
124    {
125        WebInspector.DataGridNode.prototype.select.call(this, supressSelectedEvent);
126        this.tree.profileView._dataGridNodeSelected(this);
127    },
128
129    deselect: function(supressDeselectedEvent)
130    {
131        WebInspector.DataGridNode.prototype.deselect.call(this, supressDeselectedEvent);
132        this.tree.profileView._dataGridNodeDeselected(this);
133    },
134
135    /**
136     * @param {function(!T, !T)} comparator
137     * @param {boolean} force
138     * @template T
139     */
140    sort: function(comparator, force)
141    {
142        var gridNodeGroups = [[this]];
143
144        for (var gridNodeGroupIndex = 0; gridNodeGroupIndex < gridNodeGroups.length; ++gridNodeGroupIndex) {
145            var gridNodes = gridNodeGroups[gridNodeGroupIndex];
146            var count = gridNodes.length;
147
148            for (var index = 0; index < count; ++index) {
149                var gridNode = gridNodes[index];
150
151                // If the grid node is collapsed, then don't sort children (save operation for later).
152                // If the grid node has the same sorting as previously, then there is no point in sorting it again.
153                if (!force && (!gridNode.expanded || gridNode.lastComparator === comparator)) {
154                    if (gridNode.children.length)
155                        gridNode.shouldRefreshChildren = true;
156                    continue;
157                }
158
159                gridNode.lastComparator = comparator;
160
161                var children = gridNode.children;
162                var childCount = children.length;
163
164                if (childCount) {
165                    children.sort(comparator);
166
167                    for (var childIndex = 0; childIndex < childCount; ++childIndex)
168                        children[childIndex]._recalculateSiblings(childIndex);
169
170                    gridNodeGroups.push(children);
171                }
172            }
173        }
174    },
175
176    /**
177     * @param {!WebInspector.ProfileDataGridNode} profileDataGridNode
178     * @param {number} index
179     */
180    insertChild: function(profileDataGridNode, index)
181    {
182        WebInspector.DataGridNode.prototype.insertChild.call(this, profileDataGridNode, index);
183
184        this.childrenByCallUID[profileDataGridNode.callUID] = profileDataGridNode;
185    },
186
187    /**
188     * @param {!WebInspector.ProfileDataGridNode} profileDataGridNode
189     */
190    removeChild: function(profileDataGridNode)
191    {
192        WebInspector.DataGridNode.prototype.removeChild.call(this, profileDataGridNode);
193
194        delete this.childrenByCallUID[profileDataGridNode.callUID];
195    },
196
197    removeChildren: function()
198    {
199        WebInspector.DataGridNode.prototype.removeChildren.call(this);
200
201        this.childrenByCallUID = {};
202    },
203
204    /**
205     * @param {!WebInspector.ProfileDataGridNode} node
206     */
207    findChild: function(node)
208    {
209        if (!node)
210            return null;
211        return this.childrenByCallUID[node.callUID];
212    },
213
214    get selfPercent()
215    {
216        return this.selfTime / this.tree.totalTime * 100.0;
217    },
218
219    get totalPercent()
220    {
221        return this.totalTime / this.tree.totalTime * 100.0;
222    },
223
224    get _parent()
225    {
226        return this.parent !== this.dataGrid ? this.parent : this.tree;
227    },
228
229    populate: function()
230    {
231        if (this._populated)
232            return;
233        this._populated = true;
234
235        this._sharedPopulate();
236
237        var currentComparator = this.tree.lastComparator;
238
239        if (currentComparator)
240            this.sort(currentComparator, true);
241    },
242
243    // When focusing and collapsing we modify lots of nodes in the tree.
244    // This allows us to restore them all to their original state when we revert.
245    _save: function()
246    {
247        if (this._savedChildren)
248            return;
249
250        this._savedSelfTime = this.selfTime;
251        this._savedTotalTime = this.totalTime;
252
253        this._savedChildren = this.children.slice();
254    },
255
256    // When focusing and collapsing we modify lots of nodes in the tree.
257    // This allows us to restore them all to their original state when we revert.
258    _restore: function()
259    {
260        if (!this._savedChildren)
261            return;
262
263        this.selfTime = this._savedSelfTime;
264        this.totalTime = this._savedTotalTime;
265
266        this.removeChildren();
267
268        var children = this._savedChildren;
269        var count = children.length;
270
271        for (var index = 0; index < count; ++index) {
272            children[index]._restore();
273            this.appendChild(children[index]);
274        }
275    },
276
277    _merge: function(child, shouldAbsorb)
278    {
279        this.selfTime += child.selfTime;
280
281        if (!shouldAbsorb)
282            this.totalTime += child.totalTime;
283
284        var children = this.children.slice();
285
286        this.removeChildren();
287
288        var count = children.length;
289
290        for (var index = 0; index < count; ++index) {
291            if (!shouldAbsorb || children[index] !== child)
292                this.appendChild(children[index]);
293        }
294
295        children = child.children.slice();
296        count = children.length;
297
298        for (var index = 0; index < count; ++index) {
299            var orphanedChild = children[index],
300                existingChild = this.childrenByCallUID[orphanedChild.callUID];
301
302            if (existingChild)
303                existingChild._merge(orphanedChild, false);
304            else
305                this.appendChild(orphanedChild);
306        }
307    },
308
309    __proto__: WebInspector.DataGridNode.prototype
310}
311
312/**
313 * @constructor
314 * @param {!WebInspector.CPUProfileView} profileView
315 * @param {!ProfilerAgent.CPUProfileNode} rootProfileNode
316 */
317WebInspector.ProfileDataGridTree = function(profileView, rootProfileNode)
318{
319    this.tree = this;
320    this.children = [];
321
322    this.profileView = profileView;
323
324    this.totalTime = rootProfileNode.totalTime;
325    this.lastComparator = null;
326
327    this.childrenByCallUID = {};
328}
329
330WebInspector.ProfileDataGridTree.prototype = {
331    get expanded()
332    {
333        return true;
334    },
335
336    appendChild: function(child)
337    {
338        this.insertChild(child, this.children.length);
339    },
340
341    insertChild: function(child, index)
342    {
343        this.children.splice(index, 0, child);
344        this.childrenByCallUID[child.callUID] = child;
345    },
346
347    removeChildren: function()
348    {
349        this.children = [];
350        this.childrenByCallUID = {};
351    },
352
353    findChild: WebInspector.ProfileDataGridNode.prototype.findChild,
354    sort: WebInspector.ProfileDataGridNode.prototype.sort,
355
356    _save: function()
357    {
358        if (this._savedChildren)
359            return;
360
361        this._savedTotalTime = this.totalTime;
362        this._savedChildren = this.children.slice();
363    },
364
365    restore: function()
366    {
367        if (!this._savedChildren)
368            return;
369
370        this.children = this._savedChildren;
371        this.totalTime = this._savedTotalTime;
372
373        var children = this.children;
374        var count = children.length;
375
376        for (var index = 0; index < count; ++index)
377            children[index]._restore();
378
379        this._savedChildren = null;
380    }
381}
382
383WebInspector.ProfileDataGridTree.propertyComparators = [{}, {}];
384
385/**
386 * @param {string} property
387 * @param {boolean} isAscending
388 * @return {function(!Object.<string, *>, !Object.<string, *>)}
389 */
390WebInspector.ProfileDataGridTree.propertyComparator = function(property, isAscending)
391{
392    var comparator = WebInspector.ProfileDataGridTree.propertyComparators[(isAscending ? 1 : 0)][property];
393
394    if (!comparator) {
395        if (isAscending) {
396            comparator = function(lhs, rhs)
397            {
398                if (lhs[property] < rhs[property])
399                    return -1;
400
401                if (lhs[property] > rhs[property])
402                    return 1;
403
404                return 0;
405            }
406        } else {
407            comparator = function(lhs, rhs)
408            {
409                if (lhs[property] > rhs[property])
410                    return -1;
411
412                if (lhs[property] < rhs[property])
413                    return 1;
414
415                return 0;
416            }
417        }
418
419        WebInspector.ProfileDataGridTree.propertyComparators[(isAscending ? 1 : 0)][property] = comparator;
420    }
421
422    return comparator;
423}
424