diff -r 000000000000 -r 4f2f89ce4247 WebKit/chromium/src/js/HeapProfilerPanel.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/WebKit/chromium/src/js/HeapProfilerPanel.js Fri Sep 17 09:02:29 2010 +0300 @@ -0,0 +1,966 @@ +/* + * 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. + */ + +/** + * @fileoverview Heap profiler panel implementation. + */ + +WebInspector.ProfilesPanel.prototype.addSnapshot = function(snapshot) { + snapshot.title = WebInspector.UIString("Snapshot %d", snapshot.number); + snapshot.typeId = WebInspector.HeapSnapshotProfileType.TypeId; + + var snapshots = WebInspector.HeapSnapshotProfileType.snapshots; + snapshots.push(snapshot); + + snapshot.listIndex = snapshots.length - 1; + + if (WebInspector.CPUProfile) + this.addProfileHeader(WebInspector.HeapSnapshotProfileType.TypeId, snapshot); + else + this.addProfileHeader(snapshot); + + this.dispatchEventToListeners("snapshot added"); +} + + +WebInspector.HeapSnapshotView = function(parent, profile) +{ + WebInspector.View.call(this); + + this.element.addStyleClass("heap-snapshot-view"); + + this.parent = parent; + this.parent.addEventListener("snapshot 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 }, + "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(); + if (this.profile.listIndex > 0) + this.baseSelectElement.selectedIndex = this.profile.listIndex - 1; + else + this.baseSelectElement.selectedIndex = this.profile.listIndex; + this._resetDataGridList(); + + this.percentButton = new WebInspector.StatusBarButton("", "percent-time-status-bar-item status-bar-item"); + this.percentButton.addEventListener("click", this._percentClicked.bind(this), false); + + 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); + }, + + 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 === WebInspector.HeapSnapshotProfileType.snapshots[this.baseSelectElement.selectedIndex]) + return; + + this._resetDataGridList(); + 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; + }, + + _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(); + }, + + _resetDataGridList: function() + { + this.baseSnapshot = WebInspector.HeapSnapshotProfileType.snapshots[this.baseSelectElement.selectedIndex]; + var lastComparator = WebInspector.HeapSnapshotDataGridList.propertyComparator("size", false); + if (this.snapshotDataGridList) + lastComparator = this.snapshotDataGridList.lastComparator; + this.snapshotDataGridList = this._createSnapshotDataGridList(); + this.snapshotDataGridList.sort(lastComparator, true); + }, + + _sortData: function() + { + var sortAscending = this.dataGrid.sortOrder === "ascending"; + var sortColumnIdentifier = this.dataGrid.sortColumnIdentifier; + var sortProperty = { + "cons": ["constructorName", null], + "count": ["count", null], + "size": ["size", "count"], + "countDelta": this.showCountDeltaAsPercent ? ["countDeltaPercent", null] : ["countDelta", null], + "sizeDelta": this.showSizeDeltaAsPercent ? ["sizeDeltaPercent", "countDeltaPercent"] : ["sizeDelta", "sizeDeltaPercent"] + }[sortColumnIdentifier]; + + this.snapshotDataGridList.sort(WebInspector.HeapSnapshotDataGridList.propertyComparator(sortProperty[0], sortProperty[1], sortAscending)); + + this.refresh(); + }, + + _updateBaseOptions: function() + { + var list = WebInspector.HeapSnapshotProfileType.snapshots; + // 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"); + baseOption.label = WebInspector.UIString("Compared to %s", list[i].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.HeapSnapshotSidebarTreeElement = function(snapshot) +{ + this.profile = snapshot; + + WebInspector.SidebarTreeElement.call(this, "heap-snapshot-sidebar-tree-item", "", "", snapshot, false); + + this.refreshTitles(); +}; + +WebInspector.HeapSnapshotSidebarTreeElement.prototype = { + get mainTitle() + { + if (this._mainTitle) + return this._mainTitle; + return this.profile.title; + }, + + set mainTitle(x) + { + this._mainTitle = x; + this.refreshTitles(); + } +}; + +WebInspector.HeapSnapshotSidebarTreeElement.prototype.__proto__ = WebInspector.ProfileSidebarTreeElement.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) + { + var self = this; + this.produceDiff(this.baseRetainers, this.retainers, function(baseItem, snapshotItem) { + self.appendChild(new WebInspector.HeapSnapshotDataGridRetainerNode(self.snapshotView, baseItem, snapshotItem, self.tree)); + }); + + 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 + // Math minus sign, same width as plus. + return "\u2212"; + }, + + 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 = { cons: baseEntry.cons, count: 0, size: 0, retainers: {} }; + this.constructorName = snapshotEntry.cons; + 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) + { + var self = this; + this.produceDiff(baseEntries, snapshotEntries, function(baseItem, snapshotItem) { + self.appendChild(new WebInspector.HeapSnapshotDataGridNode(self.snapshotView, baseItem, snapshotItem, self)); + }); + }, + + 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]; + if ((l === null || r === null) && property2 !== null) + l = lhs[property2], r = rhs[property2]; + var result = l < r ? -1 : (l > r ? 1 : 0); + return isAscending ? result : -result; + }; + this.propertyComparators[(isAscending ? 1 : 0)][propertyHash] = comparator; + } + return comparator; +}; + +WebInspector.HeapSnapshotDataGridRetainerNode = function(snapshotView, baseEntry, snapshotEntry, owningTree) +{ + this.snapshotView = snapshotView; + + if (!snapshotEntry) + snapshotEntry = { cons: baseEntry.cons, count: 0, clusters: {} }; + this.constructorName = snapshotEntry.cons; + 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] = { cons: clusterRetainerEntry.cons, 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.snapshots = []; + +WebInspector.HeapSnapshotProfileType.prototype = { + get buttonTooltip() + { + return WebInspector.UIString("Take heap snapshot."); + }, + + get buttonStyle() + { + return "heap-snapshot-status-bar-item status-bar-item"; + }, + + buttonClicked: function() + { + devtools.tools.getProfilerAgent().startProfiling(devtools.ProfilerAgent.ProfilerModules.PROFILER_MODULE_HEAP_SNAPSHOT); + }, + + get welcomeMessage() + { + return WebInspector.UIString("Get a heap snapshot by pressing the %s button on the status bar."); + }, + + createSidebarTreeElementForProfile: function(profile) + { + var element = new WebInspector.HeapSnapshotSidebarTreeElement(profile); + element.small = false; + return element; + }, + + createView: function(profile) + { + return new WebInspector.HeapSnapshotView(WebInspector.panels.profiles, profile); + } +} + +WebInspector.HeapSnapshotProfileType.prototype.__proto__ = WebInspector.ProfileType.prototype; + + +(function() { + var originalCreatePanels = WebInspector._createPanels; + WebInspector._createPanels = function() { + originalCreatePanels.apply(this, arguments); + if (WebInspector.panels.profiles) + WebInspector.panels.profiles.registerProfileType(new WebInspector.HeapSnapshotProfileType()); + } +})();