• 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, !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 lineNumber;
100            if (this.profileNode.lineNumber > 0)
101                lineNumber = this.profileNode.lineNumber;
102            var urlElement = WebInspector.linkifyResourceAsNode(this.profileNode.url, "scripts", lineNumber, "profile-node-file");
103            cell.insertBefore(urlElement, cell.firstChild);
104        }
105
106        return cell;
107    },
108
109    select: function(supressSelectedEvent)
110    {
111        WebInspector.DataGridNode.prototype.select.call(this, supressSelectedEvent);
112        this.profileView._dataGridNodeSelected(this);
113    },
114
115    deselect: function(supressDeselectedEvent)
116    {
117        WebInspector.DataGridNode.prototype.deselect.call(this, supressDeselectedEvent);
118        this.profileView._dataGridNodeDeselected(this);
119    },
120
121    sort: function(/*Function*/ comparator, /*Boolean*/ force)
122    {
123        var gridNodeGroups = [[this]];
124
125        for (var gridNodeGroupIndex = 0; gridNodeGroupIndex < gridNodeGroups.length; ++gridNodeGroupIndex) {
126            var gridNodes = gridNodeGroups[gridNodeGroupIndex];
127            var count = gridNodes.length;
128
129            for (var index = 0; index < count; ++index) {
130                var gridNode = gridNodes[index];
131
132                // If the grid node is collapsed, then don't sort children (save operation for later).
133                // If the grid node has the same sorting as previously, then there is no point in sorting it again.
134                if (!force && (!gridNode.expanded || gridNode.lastComparator === comparator)) {
135                    if (gridNode.children.length)
136                        gridNode.shouldRefreshChildren = true;
137                    continue;
138                }
139
140                gridNode.lastComparator = comparator;
141
142                var children = gridNode.children;
143                var childCount = children.length;
144
145                if (childCount) {
146                    children.sort(comparator);
147
148                    for (var childIndex = 0; childIndex < childCount; ++childIndex)
149                        children[childIndex]._recalculateSiblings(childIndex);
150
151                    gridNodeGroups.push(children);
152                }
153            }
154        }
155    },
156
157    insertChild: function(/*ProfileDataGridNode*/ profileDataGridNode, index)
158    {
159        WebInspector.DataGridNode.prototype.insertChild.call(this, profileDataGridNode, index);
160
161        this.childrenByCallUID[profileDataGridNode.callUID] = profileDataGridNode;
162    },
163
164    removeChild: function(/*ProfileDataGridNode*/ profileDataGridNode)
165    {
166        WebInspector.DataGridNode.prototype.removeChild.call(this, profileDataGridNode);
167
168        delete this.childrenByCallUID[profileDataGridNode.callUID];
169    },
170
171    removeChildren: function(/*ProfileDataGridNode*/ profileDataGridNode)
172    {
173        WebInspector.DataGridNode.prototype.removeChildren.call(this);
174
175        this.childrenByCallUID = {};
176    },
177
178    findChild: function(/*Node*/ node)
179    {
180        if (!node)
181            return null;
182        return this.childrenByCallUID[node.callUID];
183    },
184
185    get averageTime()
186    {
187        return this.selfTime / Math.max(1, this.numberOfCalls);
188    },
189
190    get averagePercent()
191    {
192        return this.averageTime / this.tree.totalTime * 100.0;
193    },
194
195    get selfPercent()
196    {
197        return this.selfTime / this.tree.totalTime * 100.0;
198    },
199
200    get totalPercent()
201    {
202        return this.totalTime / this.tree.totalTime * 100.0;
203    },
204
205    get _parent()
206    {
207        return this.parent !== this.dataGrid ? this.parent : this.tree;
208    },
209
210    _populate: function(event)
211    {
212        this._sharedPopulate();
213
214        if (this._parent) {
215            var currentComparator = this._parent.lastComparator;
216
217            if (currentComparator)
218                this.sort(currentComparator, true);
219        }
220
221        if (this.removeEventListener)
222            this.removeEventListener("populate", this._populate, this);
223    },
224
225    // When focusing and collapsing we modify lots of nodes in the tree.
226    // This allows us to restore them all to their original state when we revert.
227    _save: function()
228    {
229        if (this._savedChildren)
230            return;
231
232        this._savedSelfTime = this.selfTime;
233        this._savedTotalTime = this.totalTime;
234        this._savedNumberOfCalls = this.numberOfCalls;
235
236        this._savedChildren = this.children.slice();
237    },
238
239    // When focusing and collapsing we modify lots of nodes in the tree.
240    // This allows us to restore them all to their original state when we revert.
241    _restore: function()
242    {
243        if (!this._savedChildren)
244            return;
245
246        this.selfTime = this._savedSelfTime;
247        this.totalTime = this._savedTotalTime;
248        this.numberOfCalls = this._savedNumberOfCalls;
249
250        this.removeChildren();
251
252        var children = this._savedChildren;
253        var count = children.length;
254
255        for (var index = 0; index < count; ++index) {
256            children[index]._restore();
257            this.appendChild(children[index]);
258        }
259    },
260
261    _merge: function(child, shouldAbsorb)
262    {
263        this.selfTime += child.selfTime;
264
265        if (!shouldAbsorb) {
266            this.totalTime += child.totalTime;
267            this.numberOfCalls += child.numberOfCalls;
268        }
269
270        var children = this.children.slice();
271
272        this.removeChildren();
273
274        var count = children.length;
275
276        for (var index = 0; index < count; ++index) {
277            if (!shouldAbsorb || children[index] !== child)
278                this.appendChild(children[index]);
279        }
280
281        children = child.children.slice();
282        count = children.length;
283
284        for (var index = 0; index < count; ++index) {
285            var orphanedChild = children[index],
286                existingChild = this.childrenByCallUID[orphanedChild.callUID];
287
288            if (existingChild)
289                existingChild._merge(orphanedChild, false);
290            else
291                this.appendChild(orphanedChild);
292        }
293    }
294}
295
296WebInspector.ProfileDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;
297
298WebInspector.ProfileDataGridTree = function(profileView, profileNode)
299{
300    this.tree = this;
301    this.children = [];
302
303    this.profileView = profileView;
304
305    this.totalTime = profileNode.totalTime;
306    this.lastComparator = null;
307
308    this.childrenByCallUID = {};
309}
310
311WebInspector.ProfileDataGridTree.prototype = {
312    get expanded()
313    {
314        return true;
315    },
316
317    appendChild: function(child)
318    {
319        this.insertChild(child, this.children.length);
320    },
321
322    insertChild: function(child, index)
323    {
324        this.children.splice(index, 0, child);
325        this.childrenByCallUID[child.callUID] = child;
326    },
327
328    removeChildren: function()
329    {
330        this.children = [];
331        this.childrenByCallUID = {};
332    },
333
334    findChild: WebInspector.ProfileDataGridNode.prototype.findChild,
335    sort: WebInspector.ProfileDataGridNode.prototype.sort,
336
337    _save: function()
338    {
339        if (this._savedChildren)
340            return;
341
342        this._savedTotalTime = this.totalTime;
343        this._savedChildren = this.children.slice();
344    },
345
346    restore: function()
347    {
348        if (!this._savedChildren)
349            return;
350
351        this.children = this._savedChildren;
352        this.totalTime = this._savedTotalTime;
353
354        var children = this.children;
355        var count = children.length;
356
357        for (var index = 0; index < count; ++index)
358            children[index]._restore();
359
360        this._savedChildren = null;
361    }
362}
363
364WebInspector.ProfileDataGridTree.propertyComparators = [{}, {}];
365
366WebInspector.ProfileDataGridTree.propertyComparator = function(/*String*/ property, /*Boolean*/ isAscending)
367{
368    var comparator = this.propertyComparators[(isAscending ? 1 : 0)][property];
369
370    if (!comparator) {
371        if (isAscending) {
372            comparator = function(lhs, rhs)
373            {
374                if (lhs[property] < rhs[property])
375                    return -1;
376
377                if (lhs[property] > rhs[property])
378                    return 1;
379
380                return 0;
381            }
382        } else {
383            comparator = function(lhs, rhs)
384            {
385                if (lhs[property] > rhs[property])
386                    return -1;
387
388                if (lhs[property] < rhs[property])
389                    return 1;
390
391                return 0;
392            }
393        }
394
395        this.propertyComparators[(isAscending ? 1 : 0)][property] = comparator;
396    }
397
398    return comparator;
399}
400