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