1033 lines
37 KiB
JavaScript
1033 lines
37 KiB
JavaScript
/*
|
|
* Copyright (C) 2010 Google Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the following disclaimer
|
|
* in the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* * Neither the name of Google Inc. nor the names of its
|
|
* contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
WebInspector.HeapSnapshotView = function(parent, profile)
|
|
{
|
|
WebInspector.View.call(this);
|
|
|
|
this.element.addStyleClass("heap-snapshot-view");
|
|
|
|
this.parent = parent;
|
|
this.parent.addEventListener("profile added", this._updateBaseOptions, this);
|
|
|
|
this.showCountAsPercent = false;
|
|
this.showSizeAsPercent = false;
|
|
this.showCountDeltaAsPercent = false;
|
|
this.showSizeDeltaAsPercent = false;
|
|
|
|
this.categories = {
|
|
code: new WebInspector.ResourceCategory("code", WebInspector.UIString("Code"), "rgb(255,121,0)"),
|
|
data: new WebInspector.ResourceCategory("data", WebInspector.UIString("Objects"), "rgb(47,102,236)")
|
|
};
|
|
|
|
var summaryContainer = document.createElement("div");
|
|
summaryContainer.id = "heap-snapshot-summary-container";
|
|
|
|
this.countsSummaryBar = new WebInspector.SummaryBar(this.categories);
|
|
this.countsSummaryBar.element.className = "heap-snapshot-summary";
|
|
this.countsSummaryBar.calculator = new WebInspector.HeapSummaryCountCalculator();
|
|
var countsLabel = document.createElement("div");
|
|
countsLabel.className = "heap-snapshot-summary-label";
|
|
countsLabel.textContent = WebInspector.UIString("Count");
|
|
this.countsSummaryBar.element.appendChild(countsLabel);
|
|
summaryContainer.appendChild(this.countsSummaryBar.element);
|
|
|
|
this.sizesSummaryBar = new WebInspector.SummaryBar(this.categories);
|
|
this.sizesSummaryBar.element.className = "heap-snapshot-summary";
|
|
this.sizesSummaryBar.calculator = new WebInspector.HeapSummarySizeCalculator();
|
|
var sizesLabel = document.createElement("label");
|
|
sizesLabel.className = "heap-snapshot-summary-label";
|
|
sizesLabel.textContent = WebInspector.UIString("Size");
|
|
this.sizesSummaryBar.element.appendChild(sizesLabel);
|
|
summaryContainer.appendChild(this.sizesSummaryBar.element);
|
|
|
|
this.element.appendChild(summaryContainer);
|
|
|
|
var columns = {
|
|
cons: { title: WebInspector.UIString("Constructor"), disclosure: true, sortable: true },
|
|
count: { title: WebInspector.UIString("Count"), width: "54px", sortable: true },
|
|
size: { title: WebInspector.UIString("Size"), width: "72px", sort: "descending", sortable: true },
|
|
// \xb1 is a "plus-minus" sign.
|
|
countDelta: { title: WebInspector.UIString("\xb1 Count"), width: "72px", sortable: true },
|
|
sizeDelta: { title: WebInspector.UIString("\xb1 Size"), width: "72px", sortable: true }
|
|
};
|
|
|
|
this.dataGrid = new WebInspector.DataGrid(columns);
|
|
this.dataGrid.addEventListener("sorting changed", this._sortData, this);
|
|
this.dataGrid.element.addEventListener("mousedown", this._mouseDownInDataGrid.bind(this), true);
|
|
this.element.appendChild(this.dataGrid.element);
|
|
|
|
this.profile = profile;
|
|
|
|
this.baseSelectElement = document.createElement("select");
|
|
this.baseSelectElement.className = "status-bar-item";
|
|
this.baseSelectElement.addEventListener("change", this._changeBase.bind(this), false);
|
|
this._updateBaseOptions();
|
|
|
|
this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item status-bar-item");
|
|
this.percentButton.addEventListener("click", this._percentClicked.bind(this), false);
|
|
|
|
this._loadProfile(this.profile, profileCallback.bind(this));
|
|
|
|
function profileCallback(profile)
|
|
{
|
|
var list = this._profiles();
|
|
var profileIndex;
|
|
for (var i = 0; i < list.length; ++i)
|
|
if (list[i].uid === profile.uid) {
|
|
profileIndex = i;
|
|
break;
|
|
}
|
|
if (profileIndex > 0)
|
|
this.baseSelectElement.selectedIndex = profileIndex - 1;
|
|
else
|
|
this.baseSelectElement.selectedIndex = profileIndex;
|
|
this._resetDataGridList(resetCompleted.bind(this));
|
|
}
|
|
|
|
function resetCompleted()
|
|
{
|
|
this.refresh();
|
|
this._updatePercentButton();
|
|
}
|
|
}
|
|
|
|
WebInspector.HeapSnapshotView.prototype = {
|
|
get statusBarItems()
|
|
{
|
|
return [this.baseSelectElement, this.percentButton.element];
|
|
},
|
|
|
|
get profile()
|
|
{
|
|
return this._profile;
|
|
},
|
|
|
|
set profile(profile)
|
|
{
|
|
this._profile = profile;
|
|
},
|
|
|
|
show: function(parentElement)
|
|
{
|
|
WebInspector.View.prototype.show.call(this, parentElement);
|
|
this.dataGrid.updateWidths();
|
|
},
|
|
|
|
hide: function()
|
|
{
|
|
WebInspector.View.prototype.hide.call(this);
|
|
this._currentSearchResultIndex = -1;
|
|
},
|
|
|
|
resize: function()
|
|
{
|
|
if (this.dataGrid)
|
|
this.dataGrid.updateWidths();
|
|
},
|
|
|
|
refresh: function()
|
|
{
|
|
this.dataGrid.removeChildren();
|
|
|
|
var children = this.snapshotDataGridList.children;
|
|
var count = children.length;
|
|
for (var index = 0; index < count; ++index)
|
|
this.dataGrid.appendChild(children[index]);
|
|
|
|
this._updateSummaryGraph();
|
|
},
|
|
|
|
refreshShowAsPercents: function()
|
|
{
|
|
this._updatePercentButton();
|
|
this.refreshVisibleData();
|
|
},
|
|
|
|
_deleteSearchMatchedFlags: function(node)
|
|
{
|
|
delete node._searchMatchedConsColumn;
|
|
delete node._searchMatchedCountColumn;
|
|
delete node._searchMatchedSizeColumn;
|
|
delete node._searchMatchedCountDeltaColumn;
|
|
delete node._searchMatchedSizeDeltaColumn;
|
|
},
|
|
|
|
searchCanceled: function()
|
|
{
|
|
if (this._searchResults) {
|
|
for (var i = 0; i < this._searchResults.length; ++i) {
|
|
var profileNode = this._searchResults[i].profileNode;
|
|
this._deleteSearchMatchedFlags(profileNode);
|
|
profileNode.refresh();
|
|
}
|
|
}
|
|
|
|
delete this._searchFinishedCallback;
|
|
this._currentSearchResultIndex = -1;
|
|
this._searchResults = [];
|
|
},
|
|
|
|
performSearch: function(query, finishedCallback)
|
|
{
|
|
// Call searchCanceled since it will reset everything we need before doing a new search.
|
|
this.searchCanceled();
|
|
|
|
query = query.trim();
|
|
|
|
if (!query.length)
|
|
return;
|
|
|
|
this._searchFinishedCallback = finishedCallback;
|
|
|
|
var helper = WebInspector.HeapSnapshotView.SearchHelper;
|
|
|
|
var operationAndNumber = helper.parseOperationAndNumber(query);
|
|
var operation = operationAndNumber[0];
|
|
var queryNumber = operationAndNumber[1];
|
|
|
|
var percentUnits = helper.percents.test(query);
|
|
var megaBytesUnits = helper.megaBytes.test(query);
|
|
var kiloBytesUnits = helper.kiloBytes.test(query);
|
|
var bytesUnits = helper.bytes.test(query);
|
|
|
|
var queryNumberBytes = (megaBytesUnits ? (queryNumber * 1024 * 1024) : (kiloBytesUnits ? (queryNumber * 1024) : queryNumber));
|
|
|
|
function matchesQuery(heapSnapshotDataGridNode)
|
|
{
|
|
WebInspector.HeapSnapshotView.prototype._deleteSearchMatchedFlags(heapSnapshotDataGridNode);
|
|
|
|
if (percentUnits) {
|
|
heapSnapshotDataGridNode._searchMatchedCountColumn = operation(heapSnapshotDataGridNode.countPercent, queryNumber);
|
|
heapSnapshotDataGridNode._searchMatchedSizeColumn = operation(heapSnapshotDataGridNode.sizePercent, queryNumber);
|
|
heapSnapshotDataGridNode._searchMatchedCountDeltaColumn = operation(heapSnapshotDataGridNode.countDeltaPercent, queryNumber);
|
|
heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn = operation(heapSnapshotDataGridNode.sizeDeltaPercent, queryNumber);
|
|
} else if (megaBytesUnits || kiloBytesUnits || bytesUnits) {
|
|
heapSnapshotDataGridNode._searchMatchedSizeColumn = operation(heapSnapshotDataGridNode.size, queryNumberBytes);
|
|
heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn = operation(heapSnapshotDataGridNode.sizeDelta, queryNumberBytes);
|
|
} else {
|
|
heapSnapshotDataGridNode._searchMatchedCountColumn = operation(heapSnapshotDataGridNode.count, queryNumber);
|
|
heapSnapshotDataGridNode._searchMatchedCountDeltaColumn = operation(heapSnapshotDataGridNode.countDelta, queryNumber);
|
|
}
|
|
|
|
if (heapSnapshotDataGridNode.constructorName.hasSubstring(query, true))
|
|
heapSnapshotDataGridNode._searchMatchedConsColumn = true;
|
|
|
|
if (heapSnapshotDataGridNode._searchMatchedConsColumn ||
|
|
heapSnapshotDataGridNode._searchMatchedCountColumn ||
|
|
heapSnapshotDataGridNode._searchMatchedSizeColumn ||
|
|
heapSnapshotDataGridNode._searchMatchedCountDeltaColumn ||
|
|
heapSnapshotDataGridNode._searchMatchedSizeDeltaColumn) {
|
|
heapSnapshotDataGridNode.refresh();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
var current = this.snapshotDataGridList.children[0];
|
|
var depth = 0;
|
|
var info = {};
|
|
|
|
// The second and subsequent levels of heap snapshot nodes represent retainers,
|
|
// so recursive expansion will be infinite, since a graph is being traversed.
|
|
// So default to a recursion cap of 2 levels.
|
|
var maxDepth = 2;
|
|
|
|
while (current) {
|
|
if (matchesQuery(current))
|
|
this._searchResults.push({ profileNode: current });
|
|
current = current.traverseNextNode(false, null, (depth >= maxDepth), info);
|
|
depth += info.depthChange;
|
|
}
|
|
|
|
finishedCallback(this, this._searchResults.length);
|
|
},
|
|
|
|
// FIXME: move these methods to a superclass, inherit both views from it.
|
|
jumpToFirstSearchResult: WebInspector.CPUProfileView.prototype.jumpToFirstSearchResult,
|
|
jumpToLastSearchResult: WebInspector.CPUProfileView.prototype.jumpToLastSearchResult,
|
|
jumpToNextSearchResult: WebInspector.CPUProfileView.prototype.jumpToNextSearchResult,
|
|
jumpToPreviousSearchResult: WebInspector.CPUProfileView.prototype.jumpToPreviousSearchResult,
|
|
showingFirstSearchResult: WebInspector.CPUProfileView.prototype.showingFirstSearchResult,
|
|
showingLastSearchResult: WebInspector.CPUProfileView.prototype.showingLastSearchResult,
|
|
_jumpToSearchResult: WebInspector.CPUProfileView.prototype._jumpToSearchResult,
|
|
|
|
refreshVisibleData: function()
|
|
{
|
|
var child = this.dataGrid.children[0];
|
|
while (child) {
|
|
child.refresh();
|
|
child = child.traverseNextNode(false, null, true);
|
|
}
|
|
this._updateSummaryGraph();
|
|
},
|
|
|
|
_changeBase: function()
|
|
{
|
|
if (this.baseSnapshot.uid === this._profiles()[this.baseSelectElement.selectedIndex].uid)
|
|
return;
|
|
|
|
this._resetDataGridList(resetCompleted.bind(this));
|
|
|
|
function resetCompleted() {
|
|
this.refresh();
|
|
|
|
if (!this.currentQuery || !this._searchFinishedCallback || !this._searchResults)
|
|
return;
|
|
|
|
// The current search needs to be performed again. First negate out previous match
|
|
// count by calling the search finished callback with a negative number of matches.
|
|
// Then perform the search again with the same query and callback.
|
|
this._searchFinishedCallback(this, -this._searchResults.length);
|
|
this.performSearch(this.currentQuery, this._searchFinishedCallback);
|
|
}
|
|
},
|
|
|
|
_createSnapshotDataGridList: function()
|
|
{
|
|
if (this._snapshotDataGridList)
|
|
delete this._snapshotDataGridList;
|
|
|
|
this._snapshotDataGridList = new WebInspector.HeapSnapshotDataGridList(this, this.baseSnapshot.entries, this.profile.entries);
|
|
return this._snapshotDataGridList;
|
|
},
|
|
|
|
_profiles: function()
|
|
{
|
|
return WebInspector.panels.profiles.getProfiles(WebInspector.HeapSnapshotProfileType.TypeId);
|
|
},
|
|
|
|
_loadProfile: function(profile, callback)
|
|
{
|
|
WebInspector.panels.profiles.loadHeapSnapshot(profile.uid, callback);
|
|
},
|
|
|
|
processLoadedSnapshot: function(profile, loadedSnapshot)
|
|
{
|
|
var snapshot = WebInspector.HeapSnapshotView.prototype._convertSnapshot(loadedSnapshot);
|
|
profile.children = snapshot.children;
|
|
profile.entries = snapshot.entries;
|
|
profile.lowlevels = snapshot.lowlevels;
|
|
WebInspector.HeapSnapshotView.prototype._prepareProfile(profile);
|
|
},
|
|
|
|
_mouseDownInDataGrid: function(event)
|
|
{
|
|
if (event.detail < 2)
|
|
return;
|
|
|
|
var cell = event.target.enclosingNodeOrSelfWithNodeName("td");
|
|
if (!cell || (!cell.hasStyleClass("count-column") && !cell.hasStyleClass("size-column") && !cell.hasStyleClass("countDelta-column") && !cell.hasStyleClass("sizeDelta-column")))
|
|
return;
|
|
|
|
if (cell.hasStyleClass("count-column"))
|
|
this.showCountAsPercent = !this.showCountAsPercent;
|
|
else if (cell.hasStyleClass("size-column"))
|
|
this.showSizeAsPercent = !this.showSizeAsPercent;
|
|
else if (cell.hasStyleClass("countDelta-column"))
|
|
this.showCountDeltaAsPercent = !this.showCountDeltaAsPercent;
|
|
else if (cell.hasStyleClass("sizeDelta-column"))
|
|
this.showSizeDeltaAsPercent = !this.showSizeDeltaAsPercent;
|
|
|
|
this.refreshShowAsPercents();
|
|
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
},
|
|
|
|
get _isShowingAsPercent()
|
|
{
|
|
return this.showCountAsPercent && this.showSizeAsPercent && this.showCountDeltaAsPercent && this.showSizeDeltaAsPercent;
|
|
},
|
|
|
|
_percentClicked: function(event)
|
|
{
|
|
var currentState = this._isShowingAsPercent;
|
|
this.showCountAsPercent = !currentState;
|
|
this.showSizeAsPercent = !currentState;
|
|
this.showCountDeltaAsPercent = !currentState;
|
|
this.showSizeDeltaAsPercent = !currentState;
|
|
this.refreshShowAsPercents();
|
|
},
|
|
|
|
_convertSnapshot: function(loadedSnapshot)
|
|
{
|
|
var snapshot = new WebInspector.HeapSnapshot(loadedSnapshot);
|
|
var result = {lowlevels: {}, entries: {}, children: {}};
|
|
var rootEdgesIter = snapshot.rootNode.edges;
|
|
for (var iter = rootEdgesIter; iter.hasNext(); iter.next()) {
|
|
var node = iter.edge.node;
|
|
if (node.isHidden)
|
|
result.lowlevels[node.name] = {count: node.instancesCount, size: node.selfSize, type: node.name};
|
|
else if (node.instancesCount)
|
|
result.entries[node.name] = {constructorName: node.name, count: node.instancesCount, size: node.selfSize};
|
|
else {
|
|
var entry = {constructorName: node.name};
|
|
for (var innerIter = node.edges; innerIter.hasNext(); innerIter.next()) {
|
|
var edge = innerIter.edge;
|
|
entry[edge.nodeIndex] = {constructorName: edge.node.name, count: edge.name};
|
|
}
|
|
result.children[rootEdgesIter.edge.nodeIndex] = entry;
|
|
}
|
|
}
|
|
return result;
|
|
},
|
|
|
|
_prepareProfile: function(profile)
|
|
{
|
|
for (var profileEntry in profile.entries)
|
|
profile.entries[profileEntry].retainers = {};
|
|
profile.clusters = {};
|
|
|
|
for (var addr in profile.children) {
|
|
var retainer = profile.children[addr];
|
|
var retainerId = retainer.constructorName + ":" + addr;
|
|
for (var childAddr in retainer) {
|
|
if (childAddr === "constructorName") continue;
|
|
var item = retainer[childAddr];
|
|
var itemId = item.constructorName + ":" + childAddr;
|
|
if ((item.constructorName === "Object" || item.constructorName === "Array")) {
|
|
if (!(itemId in profile.clusters))
|
|
profile.clusters[itemId] = { constructorName: itemId, retainers: {} };
|
|
mergeRetainers(profile.clusters[itemId], item);
|
|
}
|
|
mergeRetainers(profile.entries[item.constructorName], item);
|
|
}
|
|
}
|
|
|
|
function mergeRetainers(entry, item)
|
|
{
|
|
if (!(retainer.constructorName in entry.retainers))
|
|
entry.retainers[retainer.constructorName] = { constructorName: retainer.constructorName, count: 0, clusters: {} };
|
|
var retainerEntry = entry.retainers[retainer.constructorName];
|
|
retainerEntry.count += item.count;
|
|
if (retainer.constructorName === "Object" || retainer.constructorName === "Array")
|
|
retainerEntry.clusters[retainerId] = true;
|
|
}
|
|
},
|
|
|
|
_resetDataGridList: function(callback)
|
|
{
|
|
this._loadProfile(this._profiles()[this.baseSelectElement.selectedIndex], profileLoaded.bind(this));
|
|
|
|
function profileLoaded(profile)
|
|
{
|
|
this.baseSnapshot = profile;
|
|
var lastComparator = WebInspector.HeapSnapshotDataGridList.propertyComparator("size", false);
|
|
if (this.snapshotDataGridList)
|
|
lastComparator = this.snapshotDataGridList.lastComparator;
|
|
this.snapshotDataGridList = this._createSnapshotDataGridList();
|
|
this.snapshotDataGridList.sort(lastComparator, true);
|
|
callback();
|
|
}
|
|
},
|
|
|
|
_sortData: function()
|
|
{
|
|
var sortAscending = this.dataGrid.sortOrder === "ascending";
|
|
var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier;
|
|
var sortProperty = {
|
|
cons: ["constructorName", null],
|
|
count: ["count", "constructorName"],
|
|
size: ["size", "constructorName"],
|
|
countDelta: [this.showCountDeltaAsPercent ? "countDeltaPercent" : "countDelta", "constructorName"],
|
|
sizeDelta: [this.showSizeDeltaAsPercent ? "sizeDeltaPercent" : "sizeDelta", "constructorName"]
|
|
}[sortColumnIdentifier];
|
|
|
|
this.snapshotDataGridList.sort(WebInspector.HeapSnapshotDataGridList.propertyComparator(sortProperty[0], sortProperty[1], sortAscending));
|
|
|
|
this.refresh();
|
|
},
|
|
|
|
_updateBaseOptions: function()
|
|
{
|
|
var list = this._profiles();
|
|
// We're assuming that snapshots can only be added.
|
|
if (this.baseSelectElement.length === list.length)
|
|
return;
|
|
|
|
for (var i = this.baseSelectElement.length, n = list.length; i < n; ++i) {
|
|
var baseOption = document.createElement("option");
|
|
var title = list[i].title;
|
|
if (!title.indexOf(UserInitiatedProfileName))
|
|
title = WebInspector.UIString("Snapshot %d", title.substring(UserInitiatedProfileName.length + 1));
|
|
baseOption.label = WebInspector.UIString("Compared to %s", title);
|
|
this.baseSelectElement.appendChild(baseOption);
|
|
}
|
|
},
|
|
|
|
_updatePercentButton: function()
|
|
{
|
|
if (this._isShowingAsPercent) {
|
|
this.percentButton.title = WebInspector.UIString("Show absolute counts and sizes.");
|
|
this.percentButton.toggled = true;
|
|
} else {
|
|
this.percentButton.title = WebInspector.UIString("Show counts and sizes as percentages.");
|
|
this.percentButton.toggled = false;
|
|
}
|
|
},
|
|
|
|
_updateSummaryGraph: function()
|
|
{
|
|
this.countsSummaryBar.calculator.showAsPercent = this._isShowingAsPercent;
|
|
this.countsSummaryBar.update(this.profile.lowlevels);
|
|
|
|
this.sizesSummaryBar.calculator.showAsPercent = this._isShowingAsPercent;
|
|
this.sizesSummaryBar.update(this.profile.lowlevels);
|
|
}
|
|
};
|
|
|
|
WebInspector.HeapSnapshotView.prototype.__proto__ = WebInspector.View.prototype;
|
|
|
|
WebInspector.HeapSnapshotView.SearchHelper = {
|
|
// In comparators, we assume that a value from a node is passed as the first parameter.
|
|
operations: {
|
|
LESS: function (a, b) { return a !== null && a < b; },
|
|
LESS_OR_EQUAL: function (a, b) { return a !== null && a <= b; },
|
|
EQUAL: function (a, b) { return a !== null && a === b; },
|
|
GREATER_OR_EQUAL: function (a, b) { return a !== null && a >= b; },
|
|
GREATER: function (a, b) { return a !== null && a > b; }
|
|
},
|
|
|
|
operationParsers: {
|
|
LESS: /^<(\d+)/,
|
|
LESS_OR_EQUAL: /^<=(\d+)/,
|
|
GREATER_OR_EQUAL: /^>=(\d+)/,
|
|
GREATER: /^>(\d+)/
|
|
},
|
|
|
|
parseOperationAndNumber: function(query)
|
|
{
|
|
var operations = WebInspector.HeapSnapshotView.SearchHelper.operations;
|
|
var parsers = WebInspector.HeapSnapshotView.SearchHelper.operationParsers;
|
|
for (var operation in parsers) {
|
|
var match = query.match(parsers[operation]);
|
|
if (match !== null)
|
|
return [operations[operation], parseFloat(match[1])];
|
|
}
|
|
return [operations.EQUAL, parseFloat(query)];
|
|
},
|
|
|
|
percents: /%$/,
|
|
|
|
megaBytes: /MB$/i,
|
|
|
|
kiloBytes: /KB$/i,
|
|
|
|
bytes: /B$/i
|
|
}
|
|
|
|
WebInspector.HeapSummaryCalculator = function(lowLevelField)
|
|
{
|
|
this.total = 1;
|
|
this.lowLevelField = lowLevelField;
|
|
}
|
|
|
|
WebInspector.HeapSummaryCalculator.prototype = {
|
|
computeSummaryValues: function(lowLevels)
|
|
{
|
|
var highLevels = { data: 0, code: 0 };
|
|
this.total = 0;
|
|
for (var item in lowLevels) {
|
|
var highItem = this._highFromLow(item);
|
|
if (highItem) {
|
|
var value = lowLevels[item][this.lowLevelField];
|
|
highLevels[highItem] += value;
|
|
this.total += value;
|
|
}
|
|
}
|
|
var result = { categoryValues: highLevels };
|
|
if (!this.showAsPercent)
|
|
result.total = this.total;
|
|
return result;
|
|
},
|
|
|
|
formatValue: function(value)
|
|
{
|
|
if (this.showAsPercent)
|
|
return WebInspector.UIString("%.2f%%", value / this.total * 100.0);
|
|
else
|
|
return this._valueToString(value);
|
|
},
|
|
|
|
get showAsPercent()
|
|
{
|
|
return this._showAsPercent;
|
|
},
|
|
|
|
set showAsPercent(x)
|
|
{
|
|
this._showAsPercent = x;
|
|
}
|
|
}
|
|
|
|
WebInspector.HeapSummaryCountCalculator = function()
|
|
{
|
|
WebInspector.HeapSummaryCalculator.call(this, "count");
|
|
}
|
|
|
|
WebInspector.HeapSummaryCountCalculator.prototype = {
|
|
_highFromLow: function(type)
|
|
{
|
|
if (type === "CODE_TYPE" || type === "SHARED_FUNCTION_INFO_TYPE" || type === "SCRIPT_TYPE") return "code";
|
|
if (type === "STRING_TYPE" || type === "HEAP_NUMBER_TYPE" || type.match(/^JS_/)) return "data";
|
|
return null;
|
|
},
|
|
|
|
_valueToString: function(value)
|
|
{
|
|
return value.toString();
|
|
}
|
|
}
|
|
|
|
WebInspector.HeapSummaryCountCalculator.prototype.__proto__ = WebInspector.HeapSummaryCalculator.prototype;
|
|
|
|
WebInspector.HeapSummarySizeCalculator = function()
|
|
{
|
|
WebInspector.HeapSummaryCalculator.call(this, "size");
|
|
}
|
|
|
|
WebInspector.HeapSummarySizeCalculator.prototype = {
|
|
_highFromLow: function(type)
|
|
{
|
|
if (type === "CODE_TYPE" || type === "SHARED_FUNCTION_INFO_TYPE" || type === "SCRIPT_TYPE")
|
|
return "code";
|
|
if (type === "STRING_TYPE" || type === "HEAP_NUMBER_TYPE" || type.match(/^JS_/) || type.match(/_ARRAY_TYPE$/))
|
|
return "data";
|
|
return null;
|
|
},
|
|
|
|
_valueToString: Number.bytesToString
|
|
}
|
|
|
|
WebInspector.HeapSummarySizeCalculator.prototype.__proto__ = WebInspector.HeapSummaryCalculator.prototype;
|
|
|
|
WebInspector.HeapSnapshotDataGridNodeWithRetainers = function(owningTree)
|
|
{
|
|
this.tree = owningTree;
|
|
|
|
WebInspector.DataGridNode.call(this, null, this._hasRetainers);
|
|
|
|
this.addEventListener("populate", this._populate, this);
|
|
};
|
|
|
|
WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype = {
|
|
isEmptySet: function(set)
|
|
{
|
|
for (var x in set)
|
|
return false;
|
|
return true;
|
|
},
|
|
|
|
get _hasRetainers()
|
|
{
|
|
return !this.isEmptySet(this.retainers);
|
|
},
|
|
|
|
get _parent()
|
|
{
|
|
// For top-level nodes, return owning tree as a parent, not data grid.
|
|
return this.parent !== this.dataGrid ? this.parent : this.tree;
|
|
},
|
|
|
|
_populate: function(event)
|
|
{
|
|
function appendDiffEntry(baseItem, snapshotItem)
|
|
{
|
|
this.appendChild(new WebInspector.HeapSnapshotDataGridRetainerNode(this.snapshotView, baseItem, snapshotItem, this.tree));
|
|
}
|
|
|
|
this.produceDiff(this.baseRetainers, this.retainers, appendDiffEntry.bind(this));
|
|
|
|
if (this._parent) {
|
|
var currentComparator = this._parent.lastComparator;
|
|
if (currentComparator)
|
|
this.sort(currentComparator, true);
|
|
}
|
|
|
|
this.removeEventListener("populate", this._populate, this);
|
|
},
|
|
|
|
produceDiff: function(baseEntries, currentEntries, callback)
|
|
{
|
|
for (var item in currentEntries)
|
|
callback(baseEntries[item], currentEntries[item]);
|
|
|
|
for (item in baseEntries) {
|
|
if (!(item in currentEntries))
|
|
callback(baseEntries[item], null);
|
|
}
|
|
},
|
|
|
|
sort: function(comparator, force) {
|
|
if (!force && this.lastComparator === comparator)
|
|
return;
|
|
|
|
this.children.sort(comparator);
|
|
var childCount = this.children.length;
|
|
for (var childIndex = 0; childIndex < childCount; ++childIndex)
|
|
this.children[childIndex]._recalculateSiblings(childIndex);
|
|
for (var i = 0; i < this.children.length; ++i) {
|
|
var child = this.children[i];
|
|
if (!force && (!child.expanded || child.lastComparator === comparator))
|
|
continue;
|
|
child.sort(comparator, force);
|
|
}
|
|
this.lastComparator = comparator;
|
|
},
|
|
|
|
signForDelta: function(delta) {
|
|
if (delta === 0)
|
|
return "";
|
|
if (delta > 0)
|
|
return "+";
|
|
else
|
|
return "\u2212"; // Math minus sign, same width as plus.
|
|
},
|
|
|
|
showDeltaAsPercent: function(value)
|
|
{
|
|
if (value === Number.POSITIVE_INFINITY)
|
|
return WebInspector.UIString("new");
|
|
else if (value === Number.NEGATIVE_INFINITY)
|
|
return WebInspector.UIString("deleted");
|
|
if (value > 1000.0)
|
|
return WebInspector.UIString("%s >1000%%", this.signForDelta(value));
|
|
return WebInspector.UIString("%s%.2f%%", this.signForDelta(value), Math.abs(value));
|
|
},
|
|
|
|
getTotalCount: function()
|
|
{
|
|
if (!this._count) {
|
|
this._count = 0;
|
|
for (var i = 0, n = this.children.length; i < n; ++i)
|
|
this._count += this.children[i].count;
|
|
}
|
|
return this._count;
|
|
},
|
|
|
|
getTotalSize: function()
|
|
{
|
|
if (!this._size) {
|
|
this._size = 0;
|
|
for (var i = 0, n = this.children.length; i < n; ++i)
|
|
this._size += this.children[i].size;
|
|
}
|
|
return this._size;
|
|
},
|
|
|
|
get countPercent()
|
|
{
|
|
return this.count / this._parent.getTotalCount() * 100.0;
|
|
},
|
|
|
|
get sizePercent()
|
|
{
|
|
return this.size / this._parent.getTotalSize() * 100.0;
|
|
},
|
|
|
|
get countDeltaPercent()
|
|
{
|
|
if (this.baseCount > 0) {
|
|
if (this.count > 0)
|
|
return this.countDelta / this.baseCount * 100.0;
|
|
else
|
|
return Number.NEGATIVE_INFINITY;
|
|
} else
|
|
return Number.POSITIVE_INFINITY;
|
|
},
|
|
|
|
get sizeDeltaPercent()
|
|
{
|
|
if (this.baseSize > 0) {
|
|
if (this.size > 0)
|
|
return this.sizeDelta / this.baseSize * 100.0;
|
|
else
|
|
return Number.NEGATIVE_INFINITY;
|
|
} else
|
|
return Number.POSITIVE_INFINITY;
|
|
},
|
|
|
|
get data()
|
|
{
|
|
var data = {};
|
|
|
|
data["cons"] = this.constructorName;
|
|
|
|
if (this.snapshotView.showCountAsPercent)
|
|
data["count"] = WebInspector.UIString("%.2f%%", this.countPercent);
|
|
else
|
|
data["count"] = this.count;
|
|
|
|
if (this.size !== null) {
|
|
if (this.snapshotView.showSizeAsPercent)
|
|
data["size"] = WebInspector.UIString("%.2f%%", this.sizePercent);
|
|
else
|
|
data["size"] = Number.bytesToString(this.size);
|
|
} else
|
|
data["size"] = "";
|
|
|
|
if (this.snapshotView.showCountDeltaAsPercent)
|
|
data["countDelta"] = this.showDeltaAsPercent(this.countDeltaPercent);
|
|
else
|
|
data["countDelta"] = WebInspector.UIString("%s%d", this.signForDelta(this.countDelta), Math.abs(this.countDelta));
|
|
|
|
if (this.sizeDelta !== null) {
|
|
if (this.snapshotView.showSizeDeltaAsPercent)
|
|
data["sizeDelta"] = this.showDeltaAsPercent(this.sizeDeltaPercent);
|
|
else
|
|
data["sizeDelta"] = WebInspector.UIString("%s%s", this.signForDelta(this.sizeDelta), Number.bytesToString(Math.abs(this.sizeDelta)));
|
|
} else
|
|
data["sizeDelta"] = "";
|
|
|
|
return data;
|
|
},
|
|
|
|
createCell: function(columnIdentifier)
|
|
{
|
|
var cell = WebInspector.DataGridNode.prototype.createCell.call(this, columnIdentifier);
|
|
|
|
if ((columnIdentifier === "cons" && this._searchMatchedConsColumn) ||
|
|
(columnIdentifier === "count" && this._searchMatchedCountColumn) ||
|
|
(columnIdentifier === "size" && this._searchMatchedSizeColumn) ||
|
|
(columnIdentifier === "countDelta" && this._searchMatchedCountDeltaColumn) ||
|
|
(columnIdentifier === "sizeDelta" && this._searchMatchedSizeDeltaColumn))
|
|
cell.addStyleClass("highlight");
|
|
|
|
return cell;
|
|
}
|
|
};
|
|
|
|
WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.__proto__ = WebInspector.DataGridNode.prototype;
|
|
|
|
WebInspector.HeapSnapshotDataGridNode = function(snapshotView, baseEntry, snapshotEntry, owningTree)
|
|
{
|
|
this.snapshotView = snapshotView;
|
|
|
|
if (!snapshotEntry)
|
|
snapshotEntry = { constructorName: baseEntry.constructorName, count: 0, size: 0, retainers: {} };
|
|
this.constructorName = snapshotEntry.constructorName;
|
|
this.count = snapshotEntry.count;
|
|
this.size = snapshotEntry.size;
|
|
this.retainers = snapshotEntry.retainers;
|
|
|
|
if (!baseEntry)
|
|
baseEntry = { count: 0, size: 0, retainers: {} };
|
|
this.baseCount = baseEntry.count;
|
|
this.countDelta = this.count - this.baseCount;
|
|
this.baseSize = baseEntry.size;
|
|
this.sizeDelta = this.size - this.baseSize;
|
|
this.baseRetainers = baseEntry.retainers;
|
|
|
|
WebInspector.HeapSnapshotDataGridNodeWithRetainers.call(this, owningTree);
|
|
};
|
|
|
|
WebInspector.HeapSnapshotDataGridNode.prototype.__proto__ = WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype;
|
|
|
|
WebInspector.HeapSnapshotDataGridList = function(snapshotView, baseEntries, snapshotEntries)
|
|
{
|
|
this.tree = this;
|
|
this.snapshotView = snapshotView;
|
|
this.children = [];
|
|
this.lastComparator = null;
|
|
this.populateChildren(baseEntries, snapshotEntries);
|
|
};
|
|
|
|
WebInspector.HeapSnapshotDataGridList.prototype = {
|
|
appendChild: function(child)
|
|
{
|
|
this.insertChild(child, this.children.length);
|
|
},
|
|
|
|
insertChild: function(child, index)
|
|
{
|
|
this.children.splice(index, 0, child);
|
|
},
|
|
|
|
removeChildren: function()
|
|
{
|
|
this.children = [];
|
|
},
|
|
|
|
populateChildren: function(baseEntries, snapshotEntries)
|
|
{
|
|
function appendListEntry(baseItem, snapshotItem)
|
|
{
|
|
this.appendChild(new WebInspector.HeapSnapshotDataGridNode(this.snapshotView, baseItem, snapshotItem, this));
|
|
}
|
|
this.produceDiff(baseEntries, snapshotEntries, appendListEntry.bind(this));
|
|
},
|
|
|
|
produceDiff: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.produceDiff,
|
|
sort: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.sort,
|
|
getTotalCount: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.getTotalCount,
|
|
getTotalSize: WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype.getTotalSize
|
|
};
|
|
|
|
WebInspector.HeapSnapshotDataGridList.propertyComparators = [{}, {}];
|
|
|
|
WebInspector.HeapSnapshotDataGridList.propertyComparator = function(property, property2, isAscending)
|
|
{
|
|
var propertyHash = property + "#" + property2;
|
|
var comparator = this.propertyComparators[(isAscending ? 1 : 0)][propertyHash];
|
|
if (!comparator) {
|
|
comparator = function(lhs, rhs) {
|
|
var l = lhs[property], r = rhs[property];
|
|
var result = 0;
|
|
if (l !== null && r !== null) {
|
|
result = l < r ? -1 : (l > r ? 1 : 0);
|
|
}
|
|
if (result !== 0 || property2 === null) {
|
|
return isAscending ? result : -result;
|
|
} else {
|
|
l = lhs[property2];
|
|
r = rhs[property2];
|
|
return l < r ? -1 : (l > r ? 1 : 0);
|
|
}
|
|
};
|
|
this.propertyComparators[(isAscending ? 1 : 0)][propertyHash] = comparator;
|
|
}
|
|
return comparator;
|
|
};
|
|
|
|
WebInspector.HeapSnapshotDataGridRetainerNode = function(snapshotView, baseEntry, snapshotEntry, owningTree)
|
|
{
|
|
this.snapshotView = snapshotView;
|
|
|
|
if (!snapshotEntry)
|
|
snapshotEntry = { constructorName: baseEntry.constructorName, count: 0, clusters: {} };
|
|
this.constructorName = snapshotEntry.constructorName;
|
|
this.count = snapshotEntry.count;
|
|
this.retainers = this._calculateRetainers(this.snapshotView.profile, snapshotEntry.clusters);
|
|
|
|
if (!baseEntry)
|
|
baseEntry = { count: 0, clusters: {} };
|
|
this.baseCount = baseEntry.count;
|
|
this.countDelta = this.count - this.baseCount;
|
|
this.baseRetainers = this._calculateRetainers(this.snapshotView.baseSnapshot, baseEntry.clusters);
|
|
|
|
this.size = null;
|
|
this.sizeDelta = null;
|
|
|
|
WebInspector.HeapSnapshotDataGridNodeWithRetainers.call(this, owningTree);
|
|
}
|
|
|
|
WebInspector.HeapSnapshotDataGridRetainerNode.prototype = {
|
|
get sizePercent()
|
|
{
|
|
return null;
|
|
},
|
|
|
|
get sizeDeltaPercent()
|
|
{
|
|
return null;
|
|
},
|
|
|
|
_calculateRetainers: function(snapshot, clusters)
|
|
{
|
|
var retainers = {};
|
|
if (this.isEmptySet(clusters)) {
|
|
if (this.constructorName in snapshot.entries)
|
|
return snapshot.entries[this.constructorName].retainers;
|
|
} else {
|
|
// In case when an entry is retained by clusters, we need to gather up the list
|
|
// of retainers by merging retainers of every cluster.
|
|
// E.g. having such a tree:
|
|
// A
|
|
// Object:1 10
|
|
// X 3
|
|
// Y 4
|
|
// Object:2 5
|
|
// X 6
|
|
//
|
|
// will result in a following retainers list: X 9, Y 4.
|
|
for (var clusterName in clusters) {
|
|
if (clusterName in snapshot.clusters) {
|
|
var clusterRetainers = snapshot.clusters[clusterName].retainers;
|
|
for (var clusterRetainer in clusterRetainers) {
|
|
var clusterRetainerEntry = clusterRetainers[clusterRetainer];
|
|
if (!(clusterRetainer in retainers))
|
|
retainers[clusterRetainer] = { constructorName: clusterRetainerEntry.constructorName, count: 0, clusters: {} };
|
|
retainers[clusterRetainer].count += clusterRetainerEntry.count;
|
|
for (var clusterRetainerCluster in clusterRetainerEntry.clusters)
|
|
retainers[clusterRetainer].clusters[clusterRetainerCluster] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return retainers;
|
|
}
|
|
};
|
|
|
|
WebInspector.HeapSnapshotDataGridRetainerNode.prototype.__proto__ = WebInspector.HeapSnapshotDataGridNodeWithRetainers.prototype;
|
|
|
|
|
|
WebInspector.HeapSnapshotProfileType = function()
|
|
{
|
|
WebInspector.ProfileType.call(this, WebInspector.HeapSnapshotProfileType.TypeId, WebInspector.UIString("HEAP SNAPSHOTS"));
|
|
}
|
|
|
|
WebInspector.HeapSnapshotProfileType.TypeId = "HEAP";
|
|
|
|
WebInspector.HeapSnapshotProfileType.prototype = {
|
|
get buttonTooltip()
|
|
{
|
|
return WebInspector.UIString("Take heap snapshot.");
|
|
},
|
|
|
|
get buttonStyle()
|
|
{
|
|
return "heap-snapshot-status-bar-item status-bar-item";
|
|
},
|
|
|
|
buttonClicked: function()
|
|
{
|
|
InspectorBackend.takeHeapSnapshot(false);
|
|
},
|
|
|
|
get welcomeMessage()
|
|
{
|
|
return WebInspector.UIString("Get a heap snapshot by pressing the %s button on the status bar.");
|
|
},
|
|
|
|
createSidebarTreeElementForProfile: function(profile)
|
|
{
|
|
return new WebInspector.ProfileSidebarTreeElement(profile, WebInspector.UIString("Snapshot %d"), "heap-snapshot-sidebar-tree-item");
|
|
},
|
|
|
|
createView: function(profile)
|
|
{
|
|
return new WebInspector.HeapSnapshotView(WebInspector.panels.profiles, profile);
|
|
}
|
|
}
|
|
|
|
WebInspector.HeapSnapshotProfileType.prototype.__proto__ = WebInspector.ProfileType.prototype;
|