| /* |
| * 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._getProfiles(); |
| 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. |
| const 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._getProfiles()[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; |
| }, |
| |
| _getProfiles: function() |
| { |
| return WebInspector.panels.profiles.getProfiles(WebInspector.HeapSnapshotProfileType.TypeId); |
| }, |
| |
| _loadProfile: function(profile, callback) |
| { |
| if (profile._loaded) { |
| callback(profile); |
| return; |
| } |
| |
| InspectorBackend.getProfile(profile.typeId, profile.uid, loadedCallback.bind(this)); |
| |
| function loadedCallback(loadedSnapshot) { |
| profile.children = loadedSnapshot.head.children; |
| profile.entries = loadedSnapshot.head.entries; |
| profile.lowlevels = loadedSnapshot.head.lowlevels; |
| this._prepareProfile(profile); |
| profile._loaded = true; |
| this.parent.updateProfile(profile); |
| callback(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(); |
| }, |
| |
| _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._getProfiles()[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: ["cons", 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 = this._getProfiles(); |
| // 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]; |
| 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 = { 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(); |
| }, |
| |
| 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; |