2011-01-13 Mikhail Naganov <mnaganov@chromium.org>
[WebKit-https.git] / Source / WebCore / inspector / front-end / ProfileDataGridTree.js
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 WebInspector.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
48 WebInspector.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
296 WebInspector.ProfileDataGridNode.prototype.__proto__ = WebInspector.DataGridNode.prototype;
297
298 WebInspector.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
311 WebInspector.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
364 WebInspector.ProfileDataGridTree.propertyComparators = [{}, {}];
365
366 WebInspector.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 }