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