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