Home | History | Annotate | Download | only in front-end
      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 }
    400