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