Home | History | Annotate | Download | only in front-end
      1 /*
      2  * Copyright (C) 2010 Google 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 are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 WebInspector.HeapSnapshotView = function(parent, profile)
     32 {
     33     WebInspector.View.call(this);
     34 
     35     this.element.addStyleClass("heap-snapshot-view");
     36 
     37     this.parent = parent;
     38     this.parent.addEventListener("profile added", this._updateBaseOptions, this);
     39 
     40     this.showCountAsPercent = false;
     41     this.showSizeAsPercent = false;
     42     this.showCountDeltaAsPercent = false;
     43     this.showSizeDeltaAsPercent = false;
     44 
     45     this.categories = {
     46         code: new WebInspector.ResourceCategory("code", WebInspector.UIString("Code"), "rgb(255,121,0)"),
     47         data: new WebInspector.ResourceCategory("data", WebInspector.UIString("Objects"), "rgb(47,102,236)")
     48     };
     49 
     50     var summaryContainer = document.createElement("div");
     51     summaryContainer.id = "heap-snapshot-summary-container";
     52 
     53     this.countsSummaryBar = new WebInspector.SummaryBar(this.categories);
     54     this.countsSummaryBar.element.className = "heap-snapshot-summary";
     55     this.countsSummaryBar.calculator = new WebInspector.HeapSummaryCountCalculator();
     56     var countsLabel = document.createElement("div");
     57     countsLabel.className = "heap-snapshot-summary-label";
     58     countsLabel.textContent = WebInspector.UIString("Count");
     59     this.countsSummaryBar.element.appendChild(countsLabel);
     60     summaryContainer.appendChild(this.countsSummaryBar.element);
     61 
     62     this.sizesSummaryBar = new WebInspector.SummaryBar(this.categories);
     63     this.sizesSummaryBar.element.className = "heap-snapshot-summary";
     64     this.sizesSummaryBar.calculator = new WebInspector.HeapSummarySizeCalculator();
     65     var sizesLabel = document.createElement("label");
     66     sizesLabel.className = "heap-snapshot-summary-label";
     67     sizesLabel.textContent = WebInspector.UIString("Size");
     68     this.sizesSummaryBar.element.appendChild(sizesLabel);
     69     summaryContainer.appendChild(this.sizesSummaryBar.element);
     70 
     71     this.element.appendChild(summaryContainer);
     72 
     73     var columns = {
     74         cons: { title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true },
     75         count: { title: WebInspector.UIString("Count"), width: "54px", sortable: true },
     76         size: { title: WebInspector.UIString("Size"), width: "72px", sort: "descending", sortable: true },
     77         // \xb1 is a "plus-minus" sign.
     78         countDelta: { title: WebInspector.UIString("\xb1 Count"), width: "72px", sortable: true },
     79         sizeDelta: { title: WebInspector.UIString("\xb1 Size"), width: "72px", sortable: true }
     80     };
     81 
     82     this.dataGrid = new WebInspector.DataGrid(columns);
     83     this.dataGrid.addEventListener("sorting changed", this._sortData, this);
     84     this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true);
     85     this.element.appendChild(this.dataGrid.element);
     86 
     87     this.profile = profile;
     88 
     89     this.baseSelectElement = document.createElement("select");
     90     this.baseSelectElement.className = "status-bar-item";
     91     this.baseSelectElement.addEventListener("change", this._changeBase.bind(this), false);
     92     this._updateBaseOptions();
     93 
     94     this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item status-bar-item");
     95     this.percentButton.addEventListener("click", this._percentClicked.bind(this), false);
     96 
     97     this._loadProfile(this.profile, profileCallback.bind(this));
     98 
     99     function profileCallback(profile)
    100     {
    101         var list = this._profiles();
    102         var profileIndex;
    103         for (var i = 0; i < list.length; ++i)
    104             if (list[i].uid === profile.uid) {
    105                 profileIndex = i;
    106                 break;
    107             }
    108         if (profileIndex > 0)
    109             this.baseSelectElement.selectedIndex = profileIndex - 1;
    110         else
    111             this.baseSelectElement.selectedIndex = profileIndex;
    112         this._resetDataGridList(resetCompleted.bind(this));
    113     }
    114 
    115     function resetCompleted()
    116     {
    117         this.refresh();
    118         this._updatePercentButton();
    119     }
    120 }
    121 
    122 WebInspector.HeapSnapshotView.prototype = {
    123     get statusBarItems()
    124     {
    125         return [this.baseSelectElement, this.percentButton.element];
    126     },
    127 
    128     get profile()
    129     {
    130         return this._profile;
    131     },
    132 
    133     set profile(profile)
    134     {
    135         this._profile = profile;
    136     },
    137 
    138     show: function(parentElement)
    139     {
    140         WebInspector.View.prototype.show.call(this, parentElement);
    141         this.dataGrid.updateWidths();
    142     },
    143 
    144     hide: function()
    145     {
    146         WebInspector.View.prototype.hide.call(this);
    147         this._currentSearchResultIndex = -1;
    148     },
    149 
    150     resize: function()
    151     {
    152         if (this.dataGrid)
    153             this.dataGrid.updateWidths();
    154     },
    155 
    156     refresh: function()
    157     {
    158         this.dataGrid.removeChildren();
    159 
    160         var children = this.snapshotDataGridList.children;
    161         var count = children.length;
    162         for (var index = 0; index < count; ++index)
    163             this.dataGrid.appendChild(children[index]);
    164 
    165         this._updateSummaryGraph();
    166     },
    167 
    168     refreshShowAsPercents: function()
    169     {
    170         this._updatePercentButton();
    171         this.refreshVisibleData();
    172     },
    173 
    174     _deleteSearchMatchedFlags: function(node)
    175     {
    176         delete node._searchMatchedConsColumn;
    177         delete node._searchMatchedCountColumn;
    178         delete node._searchMatchedSizeColumn;
    179         delete node._searchMatchedCountDeltaColumn;
    180         delete node._searchMatchedSizeDeltaColumn;
    181     },
    182 
    183     searchCanceled: function()
    184     {
    185         if (this._searchResults) {
    186             for (var i = 0; i < this._searchResults.length; ++i) {
    187                 var profileNode = this._searchResults[i].profileNode;
    188                 this._deleteSearchMatchedFlags(profileNode);
    189                 profileNode.refresh();
    190             }
    191         }
    192 
    193         delete this._searchFinishedCallback;
    194         this._currentSearchResultIndex = -1;
    195         this._searchResults = [];
    196     },
    197 
    198     performSearch: function(query, finishedCallback)
    199     {
    200         // Call searchCanceled since it will reset everything we need before doing a new search.
    201         this.searchCanceled();
    202 
    203         query = query.trim();
    204 
    205         if (!query.length)
    206             return;
    207 
    208         this._searchFinishedCallback = finishedCallback;
    209 
    210         var helper = WebInspector.HeapSnapshotView.SearchHelper;
    211 
    212         var operationAndNumber = helper.parseOperationAndNumber(query);
    213         var operation = operationAndNumber[0];
    214         var queryNumber = operationAndNumber[1];
    215 
    216         var percentUnits = helper.percents.test(query);
    217         var megaBytesUnits = helper.megaBytes.test(query);
    218         var kiloBytesUnits = helper.kiloBytes.test(query);
    219         var bytesUnits = helper.bytes.test(query);
    220 
    221         var queryNumberBytes = (megaBytesUnits ? (queryNumber * 1024 * 1024) : (kiloBytesUnits ? (queryNumber * 1024) : queryNumber));
    222 
    223         function matchesQuery(heapSnapshotDataGridNode)
    224         {
    225             WebInspector.HeapSnapshotView.prototype._deleteSearchMatchedFlags(heapSnapshotDataGridNode);
    226 
    227             if (percentUnits) {
    228                 heapSnapshotDataGridNode._searchMatchedCountColumn = operation(heapSnapshotDataGridNode.countPercent, queryNumber);
    229                 heapSnapshotDataGridNode._searchMatchedSizeColumn = operation(heapSnapshotDataGridNode.sizePercent, queryNumber);
    230                 heapSnapshotDataGridNode._searchMatchedCountDeltaColumn = operation(heapSnapshotDataGridNode.countDeltaPercent, queryNumber);
    231                 heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn = operation(heapSnapshotDataGridNode.sizeDeltaPercent, queryNumber);
    232             } else if (megaBytesUnits || kiloBytesUnits || bytesUnits) {
    233                 heapSnapshotDataGridNode._searchMatchedSizeColumn = operation(heapSnapshotDataGridNode.size, queryNumberBytes);
    234                 heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn = operation(heapSnapshotDataGridNode.sizeDelta, queryNumberBytes);
    235             } else {
    236                 heapSnapshotDataGridNode._searchMatchedCountColumn = operation(heapSnapshotDataGridNode.count, queryNumber);
    237                 heapSnapshotDataGridNode._searchMatchedCountDeltaColumn = operation(heapSnapshotDataGridNode.countDelta, queryNumber);
    238             }
    239 
    240             if (heapSnapshotDataGridNode.constructorName.hasSubstring(query, true))
    241                 heapSnapshotDataGridNode._searchMatchedConsColumn = true;
    242 
    243             if (heapSnapshotDataGridNode._searchMatchedConsColumn ||
    244                 heapSnapshotDataGridNode._searchMatchedCountColumn ||
    245                 heapSnapshotDataGridNode._searchMatchedSizeColumn ||
    246                 heapSnapshotDataGridNode._searchMatchedCountDeltaColumn ||
    247                 heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn) {
    248                 heapSnapshotDataGridNode.refresh();
    249                 return true;
    250             }
    251 
    252             return false;
    253         }
    254 
    255         var current = this.snapshotDataGridList.children[0];
    256         var depth = 0;
    257         var info = {};
    258 
    259         // The second and subsequent levels of heap snapshot nodes represent retainers,
    260         // so recursive expansion will be infinite, since a graph is being traversed.
    261         // So default to a recursion cap of 2 levels.
    262         const maxDepth = 2;
    263 
    264         while (current) {
    265             if (matchesQuery(current))
    266                 this._searchResults.push({ profileNode: current });
    267             current = current.traverseNextNode(false, null, (depth >= maxDepth), info);
    268             depth += info.depthChange;
    269         }
    270 
    271         finishedCallback(this, this._searchResults.length);
    272     },
    273 
    274     // FIXME: move these methods to a superclass, inherit both views from it.
    275     jumpToFirstSearchResult: WebInspector.CPUProfileView.prototype.jumpToFirstSearchResult,
    276     jumpToLastSearchResult: WebInspector.CPUProfileView.prototype.jumpToLastSearchResult,
    277     jumpToNextSearchResult: WebInspector.CPUProfileView.prototype.jumpToNextSearchResult,
    278     jumpToPreviousSearchResult: WebInspector.CPUProfileView.prototype.jumpToPreviousSearchResult,
    279     showingFirstSearchResult: WebInspector.CPUProfileView.prototype.showingFirstSearchResult,
    280     showingLastSearchResult: WebInspector.CPUProfileView.prototype.showingLastSearchResult,
    281     _jumpToSearchResult: WebInspector.CPUProfileView.prototype._jumpToSearchResult,
    282 
    283     refreshVisibleData: function()
    284     {
    285         var child = this.dataGrid.children[0];
    286         while (child) {
    287             child.refresh();
    288             child = child.traverseNextNode(false, null, true);
    289         }
    290         this._updateSummaryGraph();
    291     },
    292 
    293     _changeBase: function()
    294     {
    295         if (this.baseSnapshot.uid === this._profiles()[this.baseSelectElement.selectedIndex].uid)
    296             return;
    297 
    298         this._resetDataGridList(resetCompleted.bind(this));
    299 
    300         function resetCompleted() {
    301             this.refresh();
    302 
    303             if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
    304                 return;
    305 
    306             // The current search needs to be performed again. First negate out previous match
    307             // count by calling the search finished callback with a negative number of matches.
    308             // Then perform the search again with the same query and callback.
    309             this._searchFinishedCallback(this, -this._searchResults.length);
    310             this.performSearch(this.currentQuery, this._searchFinishedCallback);
    311         }
    312     },
    313 
    314     _createSnapshotDataGridList: function()
    315     {
    316         if (this._snapshotDataGridList)
    317           delete this._snapshotDataGridList;
    318 
    319         this._snapshotDataGridList = new WebInspector.HeapSnapshotDataGridList(this, this.baseSnapshot.entries, this.profile.entries);
    320         return this._snapshotDataGridList;
    321     },
    322 
    323     _profiles: function()
    324     {
    325         return WebInspector.panels.profiles.getProfiles(WebInspector.HeapSnapshotProfileType.TypeId);
    326     },
    327 
    328     _loadProfile: function(profile, callback)
    329     {
    330         WebInspector.panels.profiles.loadHeapSnapshot(profile.uid, callback);
    331     },
    332 
    333     processLoadedSnapshot: function(profile, loadedSnapshot)
    334     {
    335         var snapshot = WebInspector.HeapSnapshotView.prototype._convertSnapshot(loadedSnapshot);
    336         profile.children = snapshot.children;
    337         profile.entries = snapshot.entries;
    338         profile.lowlevels = snapshot.lowlevels;
    339         WebInspector.HeapSnapshotView.prototype._prepareProfile(profile);
    340     },
    341 
    342     _mouseDownInDataGrid: function(event)
    343     {
    344         if (event.detail < 2)
    345             return;
    346 
    347         var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
    348         if (!cell || (!cell.hasStyleClass("count-column") && !cell.hasStyleClass("size-column") && !cell.hasStyleClass("countDelta-column") && !cell.hasStyleClass("sizeDelta-column")))
    349             return;
    350 
    351         if (cell.hasStyleClass("count-column"))
    352             this.showCountAsPercent = !this.showCountAsPercent;
    353         else if (cell.hasStyleClass("size-column"))
    354             this.showSizeAsPercent = !this.showSizeAsPercent;
    355         else if (cell.hasStyleClass("countDelta-column"))
    356             this.showCountDeltaAsPercent = !this.showCountDeltaAsPercent;
    357         else if (cell.hasStyleClass("sizeDelta-column"))
    358             this.showSizeDeltaAsPercent = !this.showSizeDeltaAsPercent;
    359 
    360         this.refreshShowAsPercents();
    361 
    362         event.preventDefault();
    363         event.stopPropagation();
    364     },
    365 
    366     get _isShowingAsPercent()
    367     {
    368         return this.showCountAsPercent && this.showSizeAsPercent && this.showCountDeltaAsPercent && this.showSizeDeltaAsPercent;
    369     },
    370 
    371     _percentClicked: function(event)
    372     {
    373         var currentState = this._isShowingAsPercent;
    374         this.showCountAsPercent = !currentState;
    375         this.showSizeAsPercent = !currentState;
    376         this.showCountDeltaAsPercent = !currentState;
    377         this.showSizeDeltaAsPercent = !currentState;
    378         this.refreshShowAsPercents();
    379     },
    380 
    381     _convertSnapshot: function(loadedSnapshot)
    382     {
    383         var snapshot = new WebInspector.HeapSnapshot(loadedSnapshot);
    384         var result = {lowlevels: {}, entries: {}, children: {}};
    385         var rootEdgesIter = snapshot.rootNode.edges;
    386         for (var iter = rootEdgesIter; iter.hasNext(); iter.next()) {
    387             var node = iter.edge.node;
    388             if (node.isHidden)
    389                 result.lowlevels[node.name] = {count: node.instancesCount, size: node.selfSize, type: node.name};
    390             else if (node.instancesCount)
    391                 result.entries[node.name] = {constructorName: node.name, count: node.instancesCount, size: node.selfSize};
    392             else {
    393                 var entry = {constructorName: node.name};
    394                 for (var innerIter = node.edges; innerIter.hasNext(); innerIter.next()) {
    395                     var edge = innerIter.edge;
    396                     entry[edge.nodeIndex] = {constructorName: edge.node.name, count: edge.name};
    397                 }
    398                 result.children[rootEdgesIter.edge.nodeIndex] = entry;
    399             }
    400         }
    401         return result;
    402     },
    403 
    404     _prepareProfile: function(profile)
    405     {
    406         for (var profileEntry in profile.entries)
    407             profile.entries[profileEntry].retainers = {};
    408         profile.clusters = {};
    409 
    410         for (var addr in profile.children) {
    411             var retainer = profile.children[addr];
    412             var retainerId = retainer.constructorName + ":" + addr;
    413             for (var childAddr in retainer) {
    414                 if (childAddr === "constructorName") continue;
    415                 var item = retainer[childAddr];
    416                 var itemId = item.constructorName + ":" + childAddr;
    417                 if ((item.constructorName === "Object" || item.constructorName === "Array")) {
    418                     if (!(itemId in profile.clusters))
    419                         profile.clusters[itemId] = { constructorName: itemId, retainers: {} };
    420                     mergeRetainers(profile.clusters[itemId], item);
    421                 }
    422                 mergeRetainers(profile.entries[item.constructorName], item);
    423             }
    424         }
    425 
    426         function mergeRetainers(entry, item)
    427         {
    428             if (!(retainer.constructorName in entry.retainers))
    429                entry.retainers[retainer.constructorName] = { constructorName: retainer.constructorName, count: 0, clusters: {} };
    430             var retainerEntry = entry.retainers[retainer.constructorName];
    431             retainerEntry.count += item.count;
    432             if (retainer.constructorName === "Object" || retainer.constructorName === "Array")
    433                 retainerEntry.clusters[retainerId] = true;
    434         }
    435     },
    436 
    437     _resetDataGridList: function(callback)
    438     {
    439         this._loadProfile(this._profiles()[this.baseSelectElement.selectedIndex], profileLoaded.bind(this));
    440 
    441         function profileLoaded(profile)
    442         {
    443             this.baseSnapshot = profile;
    444             var lastComparator = WebInspector.HeapSnapshotDataGridList.propertyComparator("size", false);
    445             if (this.snapshotDataGridList)
    446                 lastComparator = this.snapshotDataGridList.lastComparator;
    447             this.snapshotDataGridList = this._createSnapshotDataGridList();
    448             this.snapshotDataGridList.sort(lastComparator, true);
    449             callback();
    450         }
    451     },
    452 
    453     _sortData: function()
    454     {
    455         var sortAscending = this.dataGrid.sortOrder === "ascending";
    456         var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
    457         var sortProperty = {
    458             cons: ["constructorName", null],
    459             count: ["count", "constructorName"],
    460             size: ["size", "constructorName"],
    461             countDelta: [this.showCountDeltaAsPercent ? "countDeltaPercent" : "countDelta", "constructorName"],
    462             sizeDelta: [this.showSizeDeltaAsPercent ? "sizeDeltaPercent" : "sizeDelta", "constructorName"]
    463         }[sortColumnIdentifier];
    464 
    465         this.snapshotDataGridList.sort(WebInspector.HeapSnapshotDataGridList.propertyComparator(sortProperty[0], sortProperty[1], sortAscending));
    466 
    467         this.refresh();
    468     },
    469 
    470     _updateBaseOptions: function()
    471     {
    472         var list = this._profiles();
    473         // We're assuming that snapshots can only be added.
    474         if (this.baseSelectElement.length === list.length)
    475             return;
    476 
    477         for (var i = this.baseSelectElement.length, n = list.length; i < n; ++i) {
    478             var baseOption = document.createElement("option");
    479             var title = list[i].title;
    480             if (!title.indexOf(UserInitiatedProfileName))
    481                 title = WebInspector.UIString("Snapshot %d", title.substring(UserInitiatedProfileName.length + 1));
    482             baseOption.label = WebInspector.UIString("Compared to %s", title);
    483             this.baseSelectElement.appendChild(baseOption);
    484         }
    485     },
    486 
    487     _updatePercentButton: function()
    488     {
    489         if (this._isShowingAsPercent) {
    490             this.percentButton.title = WebInspector.UIString("Show absolute counts and sizes.");
    491             this.percentButton.toggled = true;
    492         } else {
    493             this.percentButton.title = WebInspector.UIString("Show counts and sizes as percentages.");
    494             this.percentButton.toggled = false;
    495         }
    496     },
    497 
    498     _updateSummaryGraph: function()
    499     {
    500         this.countsSummaryBar.calculator.showAsPercent = this._isShowingAsPercent;
    501         this.countsSummaryBar.update(this.profile.lowlevels);
    502 
    503         this.sizesSummaryBar.calculator.showAsPercent = this._isShowingAsPercent;
    504         this.sizesSummaryBar.update(this.profile.lowlevels);
    505     }
    506 };
    507 
    508 WebInspector.HeapSnapshotView.prototype.__proto__ = WebInspector.View.prototype;
    509 
    510 WebInspector.HeapSnapshotView.SearchHelper = {
    511     // In comparators, we assume that a value from a node is passed as the first parameter.
    512     operations: {
    513         LESS: function (a, b) { return a !== null && a < b; },
    514         LESS_OR_EQUAL: function (a, b) { return a !== null && a <= b; },
    515         EQUAL: function (a, b) { return a !== null && a === b; },
    516         GREATER_OR_EQUAL: function (a, b) { return a !== null && a >= b; },
    517         GREATER: function (a, b) { return a !== null && a > b; }
    518     },
    519 
    520     operationParsers: {
    521         LESS: /^<(\d+)/,
    522         LESS_OR_EQUAL: /^<=(\d+)/,
    523         GREATER_OR_EQUAL: /^>=(\d+)/,
    524         GREATER: /^>(\d+)/
    525     },
    526 
    527     parseOperationAndNumber: function(query)
    528     {
    529         var operations = WebInspector.HeapSnapshotView.SearchHelper.operations;
    530         var parsers = WebInspector.HeapSnapshotView.SearchHelper.operationParsers;
    531         for (var operation in parsers) {
    532             var match = query.match(parsers[operation]);
    533             if (match !== null)
    534                 return [operations[operation], parseFloat(match[1])];
    535         }
    536         return [operations.EQUAL, parseFloat(query)];
    537     },
    538 
    539     percents: /%$/,
    540 
    541     megaBytes: /MB$/i,
    542 
    543     kiloBytes: /KB$/i,
    544 
    545     bytes: /B$/i
    546 }
    547 
    548 WebInspector.HeapSummaryCalculator = function(lowLevelField)
    549 {
    550     this.total = 1;
    551     this.lowLevelField = lowLevelField;
    552 }
    553 
    554 WebInspector.HeapSummaryCalculator.prototype = {
    555     computeSummaryValues: function(lowLevels)
    556     {
    557         var highLevels = { data: 0, code: 0 };
    558         this.total = 0;
    559         for (var item in lowLevels) {
    560             var highItem = this._highFromLow(item);
    561             if (highItem) {
    562                 var value = lowLevels[item][this.lowLevelField];
    563                 highLevels[highItem] += value;
    564                 this.total += value;
    565             }
    566         }
    567         var result = { categoryValues: highLevels };
    568         if (!this.showAsPercent)
    569             result.total = this.total;
    570         return result;
    571     },
    572 
    573     formatValue: function(value)
    574     {
    575         if (this.showAsPercent)
    576             return WebInspector.UIString("%.2f%%", value / this.total * 100.0);
    577         else
    578             return this._valueToString(value);
    579     },
    580 
    581     get showAsPercent()
    582     {
    583         return this._showAsPercent;
    584     },
    585 
    586     set showAsPercent(x)
    587     {
    588         this._showAsPercent = x;
    589     }
    590 }
    591 
    592 WebInspector.HeapSummaryCountCalculator = function()
    593 {
    594     WebInspector.HeapSummaryCalculator.call(this, "count");
    595 }
    596 
    597 WebInspector.HeapSummaryCountCalculator.prototype = {
    598     _highFromLow: function(type)
    599     {
    600         if (type === "CODE_TYPE" || type === "SHARED_FUNCTION_INFO_TYPE" || type === "SCRIPT_TYPE") return "code";
    601         if (type === "STRING_TYPE" || type === "HEAP_NUMBER_TYPE" || type.match(/^JS_/)) return "data";
    602         return null;
    603     },
    604 
    605     _valueToString: function(value)
    606     {
    607         return value.toString();
    608     }
    609 }
    610 
    611 WebInspector.HeapSummaryCountCalculator.prototype.__proto__ = WebInspector.HeapSummaryCalculator.prototype;
    612 
    613 WebInspector.HeapSummarySizeCalculator = function()
    614 {
    615     WebInspector.HeapSummaryCalculator.call(this, "size");
    616 }
    617 
    618 WebInspector.HeapSummarySizeCalculator.prototype = {
    619     _highFromLow: function(type)
    620     {
    621         if (type === "CODE_TYPE" || type === "SHARED_FUNCTION_INFO_TYPE" || type === "SCRIPT_TYPE")
    622             return "code";
    623         if (type === "STRING_TYPE" || type === "HEAP_NUMBER_TYPE" || type.match(/^JS_/) || type.match(/_ARRAY_TYPE$/))
    624             return "data";
    625         return null;
    626     },
    627 
    628     _valueToString: Number.bytesToString
    629 }
    630 
    631 WebInspector.HeapSummarySizeCalculator.prototype.__proto__ = WebInspector.HeapSummaryCalculator.prototype;
    632 
    633 WebInspector.HeapSnapshotDataGridNodeWithRetainers = function(owningTree)
    634 {
    635     this.tree = owningTree;
    636 
    637     WebInspector.DataGridNode.call(this, null, this._hasRetainers);
    638 
    639     this.addEventListener("populate", this._populate, this);
    640 };
    641 
    642 WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype = {
    643     isEmptySet: function(set)
    644     {
    645         for (var x in set)
    646             return false;
    647         return true;
    648     },
    649 
    650     get _hasRetainers()
    651     {
    652         return !this.isEmptySet(this.retainers);
    653     },
    654 
    655     get _parent()
    656     {
    657         // For top-level nodes, return owning tree as a parent, not data grid.
    658         return this.parent !== this.dataGrid ? this.parent : this.tree;
    659     },
    660 
    661     _populate: function(event)
    662     {
    663         function appendDiffEntry(baseItem, snapshotItem)
    664         {
    665             this.appendChild(new WebInspector.HeapSnapshotDataGridRetainerNode(this.snapshotView, baseItem, snapshotItem, this.tree));
    666         }
    667 
    668         this.produceDiff(this.baseRetainers, this.retainers, appendDiffEntry.bind(this));
    669 
    670         if (this._parent) {
    671             var currentComparator = this._parent.lastComparator;
    672             if (currentComparator)
    673                 this.sort(currentComparator, true);
    674         }
    675 
    676         this.removeEventListener("populate", this._populate, this);
    677     },
    678 
    679     produceDiff: function(baseEntries, currentEntries, callback)
    680     {
    681         for (var item in currentEntries)
    682             callback(baseEntries[item], currentEntries[item]);
    683 
    684         for (item in baseEntries) {
    685             if (!(item in currentEntries))
    686                 callback(baseEntries[item], null);
    687         }
    688     },
    689 
    690     sort: function(comparator, force) {
    691         if (!force && this.lastComparator === comparator)
    692             return;
    693 
    694         this.children.sort(comparator);
    695         var childCount = this.children.length;
    696         for (var childIndex = 0; childIndex < childCount; ++childIndex)
    697             this.children[childIndex]._recalculateSiblings(childIndex);
    698         for (var i = 0; i < this.children.length; ++i) {
    699             var child = this.children[i];
    700             if (!force && (!child.expanded || child.lastComparator === comparator))
    701                 continue;
    702             child.sort(comparator, force);
    703         }
    704         this.lastComparator = comparator;
    705     },
    706 
    707     signForDelta: function(delta) {
    708         if (delta === 0)
    709             return "";
    710         if (delta > 0)
    711             return "+";
    712         else
    713             return "\u2212";  // Math minus sign, same width as plus.
    714     },
    715 
    716     showDeltaAsPercent: function(value)
    717     {
    718         if (value === Number.POSITIVE_INFINITY)
    719             return WebInspector.UIString("new");
    720         else if (value === Number.NEGATIVE_INFINITY)
    721             return WebInspector.UIString("deleted");
    722         if (value > 1000.0)
    723             return WebInspector.UIString("%s >1000%%", this.signForDelta(value));
    724         return WebInspector.UIString("%s%.2f%%", this.signForDelta(value), Math.abs(value));
    725     },
    726 
    727     getTotalCount: function()
    728     {
    729         if (!this._count) {
    730             this._count = 0;
    731             for (var i = 0, n = this.children.length; i < n; ++i)
    732                 this._count += this.children[i].count;
    733         }
    734         return this._count;
    735     },
    736 
    737     getTotalSize: function()
    738     {
    739         if (!this._size) {
    740             this._size = 0;
    741             for (var i = 0, n = this.children.length; i < n; ++i)
    742                 this._size += this.children[i].size;
    743         }
    744         return this._size;
    745     },
    746 
    747     get countPercent()
    748     {
    749         return this.count / this._parent.getTotalCount() * 100.0;
    750     },
    751 
    752     get sizePercent()
    753     {
    754         return this.size / this._parent.getTotalSize() * 100.0;
    755     },
    756 
    757     get countDeltaPercent()
    758     {
    759         if (this.baseCount > 0) {
    760             if (this.count > 0)
    761                 return this.countDelta / this.baseCount * 100.0;
    762             else
    763                 return Number.NEGATIVE_INFINITY;
    764         } else
    765             return Number.POSITIVE_INFINITY;
    766     },
    767 
    768     get sizeDeltaPercent()
    769     {
    770         if (this.baseSize > 0) {
    771             if (this.size > 0)
    772                 return this.sizeDelta / this.baseSize * 100.0;
    773             else
    774                 return Number.NEGATIVE_INFINITY;
    775         } else
    776             return Number.POSITIVE_INFINITY;
    777     },
    778 
    779     get data()
    780     {
    781         var data = {};
    782 
    783         data["cons"] = this.constructorName;
    784 
    785         if (this.snapshotView.showCountAsPercent)
    786             data["count"] = WebInspector.UIString("%.2f%%", this.countPercent);
    787         else
    788             data["count"] = this.count;
    789 
    790         if (this.size !== null) {
    791             if (this.snapshotView.showSizeAsPercent)
    792                 data["size"] = WebInspector.UIString("%.2f%%", this.sizePercent);
    793             else
    794                 data["size"] = Number.bytesToString(this.size);
    795         } else
    796             data["size"] = "";
    797 
    798         if (this.snapshotView.showCountDeltaAsPercent)
    799             data["countDelta"] = this.showDeltaAsPercent(this.countDeltaPercent);
    800         else
    801             data["countDelta"] = WebInspector.UIString("%s%d", this.signForDelta(this.countDelta), Math.abs(this.countDelta));
    802 
    803         if (this.sizeDelta !== null) {
    804             if (this.snapshotView.showSizeDeltaAsPercent)
    805                 data["sizeDelta"] = this.showDeltaAsPercent(this.sizeDeltaPercent);
    806             else
    807                 data["sizeDelta"] = WebInspector.UIString("%s%s", this.signForDelta(this.sizeDelta), Number.bytesToString(Math.abs(this.sizeDelta)));
    808         } else
    809             data["sizeDelta"] = "";
    810 
    811         return data;
    812     },
    813 
    814     createCell: function(columnIdentifier)
    815     {
    816         var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);
    817 
    818         if ((columnIdentifier === "cons" && this._searchMatchedConsColumn) ||
    819             (columnIdentifier === "count" && this._searchMatchedCountColumn) ||
    820             (columnIdentifier === "size" && this._searchMatchedSizeColumn) ||
    821             (columnIdentifier === "countDelta" && this._searchMatchedCountDeltaColumn) ||
    822             (columnIdentifier === "sizeDelta" && this._searchMatchedSizeDeltaColumn))
    823             cell.addStyleClass("highlight");
    824 
    825         return cell;
    826     }
    827 };
    828 
    829 WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.__proto__ = WebInspector.DataGridNode.prototype;
    830 
    831 WebInspector.HeapSnapshotDataGridNode = function(snapshotView, baseEntry, snapshotEntry, owningTree)
    832 {
    833     this.snapshotView = snapshotView;
    834 
    835     if (!snapshotEntry)
    836         snapshotEntry = { constructorName: baseEntry.constructorName, count: 0, size: 0, retainers: {} };
    837     this.constructorName = snapshotEntry.constructorName;
    838     this.count = snapshotEntry.count;
    839     this.size = snapshotEntry.size;
    840     this.retainers = snapshotEntry.retainers;
    841 
    842     if (!baseEntry)
    843         baseEntry = { count: 0, size: 0, retainers: {} };
    844     this.baseCount = baseEntry.count;
    845     this.countDelta = this.count - this.baseCount;
    846     this.baseSize = baseEntry.size;
    847     this.sizeDelta = this.size - this.baseSize;
    848     this.baseRetainers = baseEntry.retainers;
    849 
    850     WebInspector.HeapSnapshotDataGridNodeWithRetainers.call(this, owningTree);
    851 };
    852 
    853 WebInspector.HeapSnapshotDataGridNode.prototype.__proto__ = WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype;
    854 
    855 WebInspector.HeapSnapshotDataGridList = function(snapshotView, baseEntries, snapshotEntries)
    856 {
    857     this.tree = this;
    858     this.snapshotView = snapshotView;
    859     this.children = [];
    860     this.lastComparator = null;
    861     this.populateChildren(baseEntries, snapshotEntries);
    862 };
    863 
    864 WebInspector.HeapSnapshotDataGridList.prototype = {
    865     appendChild: function(child)
    866     {
    867         this.insertChild(child, this.children.length);
    868     },
    869 
    870     insertChild: function(child, index)
    871     {
    872         this.children.splice(index, 0, child);
    873     },
    874 
    875     removeChildren: function()
    876     {
    877         this.children = [];
    878     },
    879 
    880     populateChildren: function(baseEntries, snapshotEntries)
    881     {
    882         function appendListEntry(baseItem, snapshotItem)
    883         {
    884             this.appendChild(new WebInspector.HeapSnapshotDataGridNode(this.snapshotView, baseItem, snapshotItem, this));
    885         }
    886         this.produceDiff(baseEntries, snapshotEntries, appendListEntry.bind(this));
    887     },
    888 
    889     produceDiff: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.produceDiff,
    890     sort: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.sort,
    891     getTotalCount: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.getTotalCount,
    892     getTotalSize: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.getTotalSize
    893 };
    894 
    895 WebInspector.HeapSnapshotDataGridList.propertyComparators = [{}, {}];
    896 
    897 WebInspector.HeapSnapshotDataGridList.propertyComparator = function(property, property2, isAscending)
    898 {
    899     var propertyHash = property + "#" + property2;
    900     var comparator = this.propertyComparators[(isAscending ? 1 : 0)][propertyHash];
    901     if (!comparator) {
    902         comparator = function(lhs, rhs) {
    903             var l = lhs[property], r = rhs[property];
    904             var result = 0;
    905             if (l !== null && r !== null) {
    906                 result = l < r ? -1 : (l > r ? 1 : 0);
    907             }
    908             if (result !== 0 || property2 === null) {
    909                 return isAscending ? result : -result;
    910             } else {
    911                 l = lhs[property2];
    912                 r = rhs[property2];
    913                 return l < r ? -1 : (l > r ? 1 : 0);
    914             }
    915         };
    916         this.propertyComparators[(isAscending ? 1 : 0)][propertyHash] = comparator;
    917     }
    918     return comparator;
    919 };
    920 
    921 WebInspector.HeapSnapshotDataGridRetainerNode = function(snapshotView, baseEntry, snapshotEntry, owningTree)
    922 {
    923     this.snapshotView = snapshotView;
    924 
    925     if (!snapshotEntry)
    926         snapshotEntry = { constructorName: baseEntry.constructorName, count: 0, clusters: {} };
    927     this.constructorName = snapshotEntry.constructorName;
    928     this.count = snapshotEntry.count;
    929     this.retainers = this._calculateRetainers(this.snapshotView.profile, snapshotEntry.clusters);
    930 
    931     if (!baseEntry)
    932         baseEntry = { count: 0, clusters: {} };
    933     this.baseCount = baseEntry.count;
    934     this.countDelta = this.count - this.baseCount;
    935     this.baseRetainers = this._calculateRetainers(this.snapshotView.baseSnapshot, baseEntry.clusters);
    936 
    937     this.size = null;
    938     this.sizeDelta = null;
    939 
    940     WebInspector.HeapSnapshotDataGridNodeWithRetainers.call(this, owningTree);
    941 }
    942 
    943 WebInspector.HeapSnapshotDataGridRetainerNode.prototype = {
    944     get sizePercent()
    945     {
    946         return null;
    947     },
    948 
    949     get sizeDeltaPercent()
    950     {
    951         return null;
    952     },
    953 
    954     _calculateRetainers: function(snapshot, clusters)
    955     {
    956         var retainers = {};
    957         if (this.isEmptySet(clusters)) {
    958             if (this.constructorName in snapshot.entries)
    959                 return snapshot.entries[this.constructorName].retainers;
    960         } else {
    961             // In case when an entry is retained by clusters, we need to gather up the list
    962             // of retainers by merging retainers of every cluster.
    963             // E.g. having such a tree:
    964             //   A
    965             //     Object:1  10
    966             //       X       3
    967             //       Y       4
    968             //     Object:2  5
    969             //       X       6
    970             //
    971             // will result in a following retainers list: X 9, Y 4.
    972             for (var clusterName in clusters) {
    973                 if (clusterName in snapshot.clusters) {
    974                     var clusterRetainers = snapshot.clusters[clusterName].retainers;
    975                     for (var clusterRetainer in clusterRetainers) {
    976                         var clusterRetainerEntry = clusterRetainers[clusterRetainer];
    977                         if (!(clusterRetainer in retainers))
    978                             retainers[clusterRetainer] = { constructorName: clusterRetainerEntry.constructorName, count: 0, clusters: {} };
    979                         retainers[clusterRetainer].count += clusterRetainerEntry.count;
    980                         for (var clusterRetainerCluster in clusterRetainerEntry.clusters)
    981                             retainers[clusterRetainer].clusters[clusterRetainerCluster] = true;
    982                     }
    983                 }
    984             }
    985         }
    986         return retainers;
    987     }
    988 };
    989 
    990 WebInspector.HeapSnapshotDataGridRetainerNode.prototype.__proto__ = WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype;
    991 
    992 
    993 WebInspector.HeapSnapshotProfileType = function()
    994 {
    995     WebInspector.ProfileType.call(this, WebInspector.HeapSnapshotProfileType.TypeId, WebInspector.UIString("HEAP SNAPSHOTS"));
    996 }
    997 
    998 WebInspector.HeapSnapshotProfileType.TypeId = "HEAP";
    999 
   1000 WebInspector.HeapSnapshotProfileType.prototype = {
   1001     get buttonTooltip()
   1002     {
   1003         return WebInspector.UIString("Take heap snapshot.");
   1004     },
   1005 
   1006     get buttonStyle()
   1007     {
   1008         return "heap-snapshot-status-bar-item status-bar-item";
   1009     },
   1010 
   1011     buttonClicked: function()
   1012     {
   1013         ProfilerAgent.takeHeapSnapshot(false);
   1014     },
   1015 
   1016     get welcomeMessage()
   1017     {
   1018         return WebInspector.UIString("Get a heap snapshot by pressing the %s button on the status bar.");
   1019     },
   1020 
   1021     createSidebarTreeElementForProfile: function(profile)
   1022     {
   1023         return new WebInspector.ProfileSidebarTreeElement(profile, WebInspector.UIString("Snapshot %d"), "heap-snapshot-sidebar-tree-item");
   1024     },
   1025 
   1026     createView: function(profile)
   1027     {
   1028         return new WebInspector.HeapSnapshotView(WebInspector.panels.profiles, profile);
   1029     }
   1030 }
   1031 
   1032 WebInspector.HeapSnapshotProfileType.prototype.__proto__ = WebInspector.ProfileType.prototype;
   1033