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