1<!DOCTYPE html> 2<!-- 3Copyright 2015 The Chromium Authors. All rights reserved. 4Use of this source code is governed by a BSD-style license that can be 5found in the LICENSE file. 6--> 7 8<link rel="import" href="/tracing/base/color_scheme.html"> 9<link rel="import" href="/tracing/base/iteration_helpers.html"> 10<link rel="import" href="/tracing/base/multi_dimensional_view.html"> 11<link rel="import" href="/tracing/ui/analysis/memory_dump_sub_view_util.html"> 12<link rel="import" href="/tracing/ui/analysis/stacked_pane.html"> 13<link rel="import" href="/tracing/ui/base/dom_helpers.html"> 14<link rel='import' href='/tracing/ui/base/info_bar.html'> 15<link rel="import" href="/tracing/ui/base/table.html"> 16<link rel="import" href="/tracing/value/numeric.html"> 17<link rel="import" href="/tracing/value/unit.html"> 18 19<polymer-element name="tr-ui-a-memory-dump-heap-details-pane" 20 extends="tr-ui-a-stacked-pane"> 21 <template> 22 <style> 23 :host { 24 display: flex; 25 flex-direction: column; 26 } 27 28 #header { 29 flex: 0 0 auto; 30 display: flex; 31 flex-direction: row; 32 align-items: center; 33 34 background-color: #eee; 35 border-bottom: 1px solid #8e8e8e; 36 border-top: 1px solid white; 37 } 38 39 #label { 40 flex: 1 1 auto; 41 padding: 8px; 42 font-size: 15px; 43 font-weight: bold; 44 } 45 46 #view_mode_container { 47 display: none; 48 flex: 0 0 auto; 49 padding: 5px; 50 font-size: 15px; 51 } 52 53 #contents { 54 flex: 1 0 auto; 55 align-self: stretch; 56 font-size: 12px; 57 } 58 59 #info_text { 60 padding: 8px; 61 color: #666; 62 font-style: italic; 63 text-align: center; 64 } 65 66 #table { 67 display: none; /* Hide until memory allocator dumps are set. */ 68 flex: 1 0 auto; 69 align-self: stretch; 70 } 71 </style> 72 <div id="header"> 73 <div id="label">Heap details</div> 74 <div id="view_mode_container"> 75 <span>View mode:</span> 76 <!-- View mode selector (added in Polymer.ready()) --> 77 </div> 78 </div> 79 <div id="contents"> 80 <tr-ui-b-info-bar id="info_bar" class="info-bar-hidden"> 81 </tr-ui-b-info-bar> 82 <div id="info_text">No heap dump selected</div> 83 <tr-ui-b-table id="table"></tr-ui-b-table> 84 </div> 85 </template> 86</polymer-element> 87<script> 88'use strict'; 89 90tr.exportTo('tr.ui.analysis', function() { 91 92 var ScalarNumeric = tr.v.ScalarNumeric; 93 var sizeInBytes_smallerIsBetter = 94 tr.v.Unit.byName.sizeInBytes_smallerIsBetter; 95 96 /** @{enum} */ 97 var RowDimension = { 98 ROOT: -1, 99 STACK_FRAME: 0, 100 OBJECT_TYPE: 1 101 }; 102 103 var LATIN_SMALL_LETTER_F_WITH_HOOK = String.fromCharCode(0x0192); 104 var CIRCLED_LATIN_CAPITAL_LETTER_T = String.fromCharCode(0x24C9); 105 106 /** @{constructor} */ 107 function HeapDumpNodeTitleColumn(title) { 108 tr.ui.analysis.TitleColumn.call(this, title); 109 } 110 111 HeapDumpNodeTitleColumn.prototype = { 112 __proto__: tr.ui.analysis.TitleColumn.prototype, 113 114 formatTitle: function(row) { 115 var title = row.title; 116 var dimension = row.dimension; 117 switch (dimension) { 118 case RowDimension.ROOT: 119 return title; 120 121 case RowDimension.STACK_FRAME: 122 case RowDimension.OBJECT_TYPE: 123 return this.formatSubRow_(title, dimension); 124 125 default: 126 throw new Error('Invalid row dimension: ' + row.dimension); 127 } 128 }, 129 130 cmp: function(rowA, rowB) { 131 if (rowA.dimension !== rowB.dimension) 132 return rowA.dimension - rowB.dimension; 133 return tr.ui.analysis.TitleColumn.prototype.cmp.call(this, rowA, rowB); 134 }, 135 136 formatSubRow_: function(title, dimension) { 137 var titleEl = document.createElement('span'); 138 139 var symbolEl = document.createElement('span'); 140 var symbolColorName; 141 if (dimension === RowDimension.STACK_FRAME) { 142 symbolEl.textContent = LATIN_SMALL_LETTER_F_WITH_HOOK; 143 symbolEl.title = 'Stack frame'; 144 symbolColorName = 'heap_dump_stack_frame'; 145 } else { 146 symbolEl.textContent = CIRCLED_LATIN_CAPITAL_LETTER_T; 147 symbolEl.title = 'Object type'; 148 symbolColorName = 'heap_dump_object_type'; 149 } 150 symbolEl.style.color = 151 tr.b.ColorScheme.getColorForReservedNameAsString(symbolColorName); 152 symbolEl.style.paddingRight = '4px'; 153 symbolEl.style.cursor = 'help'; 154 symbolEl.style.weight = 'bold'; 155 titleEl.appendChild(symbolEl); 156 157 titleEl.appendChild(document.createTextNode(title)); 158 159 return titleEl; 160 } 161 }; 162 163 var COLUMN_RULES = [ 164 { 165 importance: 0, 166 columnConstructor: tr.ui.analysis.NumericMemoryColumn 167 } 168 ]; 169 170 Polymer('tr-ui-a-memory-dump-heap-details-pane', { 171 created: function() { 172 this.heapDumps_ = undefined; 173 this.aggregationMode_ = undefined; 174 this.viewMode_ = undefined; 175 }, 176 177 ready: function() { 178 this.$.table.selectionMode = tr.ui.b.TableFormat.SelectionMode.ROW; 179 this.$.info_bar.message = 'Note: Values displayed in the heavy view ' + 180 'are lower bounds (except for the root).'; 181 182 this.$.view_mode_container.appendChild(tr.ui.b.createSelector( 183 this, 'viewMode', 'memoryDumpHeapDetailsPane.viewMode', 184 tr.b.MultiDimensionalViewType.TOP_DOWN_TREE_VIEW, 185 [ 186 { 187 label: 'Top-down (Tree)', 188 value: tr.b.MultiDimensionalViewType.TOP_DOWN_TREE_VIEW 189 }, 190 { 191 label: 'Top-down (Heavy)', 192 value: tr.b.MultiDimensionalViewType.TOP_DOWN_HEAVY_VIEW 193 }, 194 { 195 label: 'Bottom-up (Heavy)', 196 value: tr.b.MultiDimensionalViewType.BOTTOM_UP_HEAVY_VIEW 197 } 198 ])); 199 }, 200 201 /** 202 * Sets the heap dumps and schedules rebuilding the pane. 203 * 204 * The provided value should be a chronological list of heap dumps. All 205 * dumps are assumed to belong to the same process and belong to the same 206 * allocator. Example: 207 * 208 * [ 209 * tr.model.HeapDump {}, // Heap dump at timestamp 1. 210 * undefined, // Heap dump not provided at timestamp 2. 211 * tr.model.HeapDump {}, // Heap dump at timestamp 3. 212 * ] 213 */ 214 set heapDumps(heapDumps) { 215 this.heapDumps_ = heapDumps; 216 this.scheduleRebuildPane_(); 217 }, 218 219 get heapDumps() { 220 return this.heapDumps_; 221 }, 222 223 set aggregationMode(aggregationMode) { 224 this.aggregationMode_ = aggregationMode; 225 this.scheduleRebuildPane_(); 226 }, 227 228 get aggregationMode() { 229 return this.aggregationMode_; 230 }, 231 232 set viewMode(viewMode) { 233 this.viewMode_ = viewMode; 234 this.scheduleRebuildPane_(); 235 }, 236 237 get viewMode() { 238 return this.viewMode_; 239 }, 240 241 get heavyView() { 242 switch (this.viewMode) { 243 case tr.b.MultiDimensionalViewType.TOP_DOWN_HEAVY_VIEW: 244 case tr.b.MultiDimensionalViewType.BOTTOM_UP_HEAVY_VIEW: 245 return true; 246 default: 247 return false; 248 } 249 }, 250 251 rebuildPane_: function() { 252 if (this.heapDumps_ === undefined || 253 this.heapDumps_.length === 0) { 254 // Show the info text (hide the table and the view mode selector). 255 this.$.info_text.style.display = 'block'; 256 this.$.table.style.display = 'none'; 257 this.$.view_mode_container.style.display = 'none'; 258 this.$.info_bar.visible = false; 259 260 this.$.table.clear(); 261 this.$.table.rebuild(); 262 return; 263 } 264 265 // Show the table and the view mode selector (hide the info text). 266 this.$.info_text.style.display = 'none'; 267 this.$.table.style.display = 'block'; 268 this.$.view_mode_container.style.display = 'block'; 269 270 // Show the info bar if in heavy view mode. 271 this.$.info_bar.visible = this.heavyView; 272 273 var stackFrameTrees = this.createStackFrameTrees_(this.heapDumps_); 274 var rows = this.createRows_(stackFrameTrees); 275 var columns = this.createColumns_(rows); 276 277 this.$.table.tableRows = rows; 278 this.$.table.tableColumns = columns; 279 this.$.table.rebuild(); 280 tr.ui.analysis.expandTableRowsRecursively(this.$.table); 281 }, 282 283 createStackFrameTrees_: function(heapDumps) { 284 return heapDumps.map(function(heapDump) { 285 if (heapDump === undefined) 286 return undefined; 287 288 var builder = new tr.b.MultiDimensionalViewBuilder(2 /* dimensions */); 289 290 // Build the heap tree. 291 heapDump.entries.forEach(function(entry) { 292 var leafStackFrame = entry.leafStackFrame; 293 var stackTracePath = leafStackFrame === undefined ? 294 [] : leafStackFrame.getUserFriendlyStackTrace().reverse(); 295 296 var objectTypeName = entry.objectTypeName; 297 var objectTypeNamePath = objectTypeName === undefined ? 298 [] : [objectTypeName]; 299 300 builder.addPath([stackTracePath, objectTypeNamePath], entry.size, 301 tr.b.MultiDimensionalViewBuilder.ValueKind.TOTAL); 302 }, this); 303 304 return builder.buildView(this.viewMode); 305 }, this); 306 }, 307 308 createRows_: function(stackFrameTrees) { 309 var definedHeapDump = tr.b.findFirstInArray(this.heapDumps); 310 if (definedHeapDump === undefined) 311 return []; 312 313 // The title of the root row is the name of the allocator. 314 var rootRowTitle = definedHeapDump.allocatorName; 315 return [this.createHeapRowRecursively_( 316 stackFrameTrees, RowDimension.ROOT, rootRowTitle)]; 317 }, 318 319 createHeapRowRecursively_: function(nodes, dimension, title) { 320 // Transform a chronological list of stack frame tree nodes into a 321 // dictionary of cells (where each cell contains a chronological list 322 // of the values of its numeric). 323 var cells = tr.ui.analysis.createCells(nodes, function(node) { 324 return { 325 'Size': new ScalarNumeric(sizeInBytes_smallerIsBetter, node.total) 326 }; 327 }); 328 329 var row = { 330 dimension: dimension, 331 title: title, 332 contexts: nodes, 333 cells: cells 334 }; 335 336 // Recursively create sub-rows for children (if applicable). 337 var stackFrameSubRows = this.createHeapDimensionSubRowsRecursively_( 338 nodes, RowDimension.STACK_FRAME); 339 var objectTypeSubRows = this.createHeapDimensionSubRowsRecursively_( 340 nodes, RowDimension.OBJECT_TYPE); 341 var subRows = stackFrameSubRows.concat(objectTypeSubRows); 342 if (subRows.length > 0) 343 row.subRows = subRows; 344 345 return row; 346 }, 347 348 createHeapDimensionSubRowsRecursively_: function(nodes, dimension) { 349 // Sub-row name (list index) -> Timestamp (list index) -> Child 350 // MultiDimensionalViewNode. 351 var dimensionGroupedChildNodes = tr.b.dictionaryValues( 352 tr.b.invertArrayOfDicts(nodes, function(node) { 353 var childDict = {}; 354 var displayedChildrenTotal = 0; 355 var hasDisplayedChildren = false; 356 for (var child of node.children[dimension].values()) { 357 // Don't show lower-bound sub-rows in tree-view. 358 if (!this.heavyView && child.isLowerBound) 359 continue; 360 childDict[child.title[dimension]] = child; 361 displayedChildrenTotal += child.total; 362 hasDisplayedChildren = true; 363 } 364 365 // Add '<other>' node if necessary in tree-view. 366 if (!this.heavyView && displayedChildrenTotal < node.total && 367 hasDisplayedChildren) { 368 var otherTitle = node.title.slice(); 369 otherTitle[dimension] = '<other>'; 370 childDict['<other>'] = { 371 title: otherTitle, 372 total: node.total - displayedChildrenTotal, 373 children: [new Map(), new Map()] 374 }; 375 } 376 377 return childDict; 378 }, this)); 379 380 // Sub-row name (list index) -> Sub-row. 381 return dimensionGroupedChildNodes.map(function(subRowNodes) { 382 var subRowTitle = tr.b.findFirstInArray(subRowNodes).title[dimension]; 383 return this.createHeapRowRecursively_( 384 subRowNodes, dimension, subRowTitle); 385 }, this); 386 }, 387 388 createColumns_: function(rows) { 389 var titleColumn = new HeapDumpNodeTitleColumn('Stack frame'); 390 titleColumn.width = '500px'; 391 392 var numericColumns = tr.ui.analysis.MemoryColumn.fromRows( 393 rows, 'cells', this.aggregationMode_, COLUMN_RULES); 394 tr.ui.analysis.MemoryColumn.spaceEqually(numericColumns); 395 396 var columns = [titleColumn].concat(numericColumns); 397 return columns; 398 } 399 }); 400 401 return { 402 RowDimension: RowDimension // Exported for testing. 403 }; 404}); 405</script> 406