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