1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 'use strict'; 6 7 base.requireStylesheet('tcmalloc.tcmalloc_snapshot_view'); 8 9 base.require('tracing.analysis.object_snapshot_view'); 10 base.require('tracing.analysis.util'); 11 12 base.exportTo('tcmalloc', function() { 13 14 var tsRound = tracing.analysis.tsRound; 15 16 /* 17 * Displays a heap memory snapshot in a human readable form. 18 * @constructor 19 */ 20 var TcmallocSnapshotView = ui.define( 21 'heap-snapshot-view', 22 tracing.analysis.ObjectSnapshotView); 23 24 TcmallocSnapshotView.prototype = { 25 __proto__: tracing.analysis.ObjectSnapshotView.prototype, 26 27 decorate: function() { 28 this.classList.add('tcmalloc-snapshot-view'); 29 }, 30 31 updateContents: function() { 32 var snapshot = this.objectSnapshot_; 33 if (!snapshot || !snapshot.heap_) { 34 this.textContent = 'No heap found.'; 35 return; 36 } 37 // Clear old snapshot view. 38 this.textContent = ''; 39 40 // Note: "total" may actually be less than the largest allocation bin. 41 // This might happen if one stack is doing a lot of allocation, then 42 // passing off to another stack for deallocation. That stack will 43 // have a high "current bytes" count and the other one might be 44 // negative or zero. So "total" may be smaller than the largest trace. 45 var subhead = document.createElement('div'); 46 subhead.textContent = 'Retaining ' + 47 this.getByteString_(snapshot.total_.currentBytes) + ' in ' + 48 snapshot.total_.currentAllocs + 49 ' allocations. Showing > 0.1 MB.'; 50 subhead.className = 'subhead'; 51 this.appendChild(subhead); 52 53 // Build a nested tree-view of allocations 54 var myList = this.buildAllocList_(snapshot.heap_, false); 55 this.appendChild(myList); 56 }, 57 58 /** 59 * Creates a nested list with clickable entries. 60 * @param {Object} heapEntry The current trace heap entry. 61 * @param {boolean} hide Whether this list is hidden by default. 62 * @return {Element} A <ul> list element. 63 */ 64 buildAllocList_: function(heapEntry, hide) { 65 var myList = document.createElement('ul'); 66 myList.hidden = hide; 67 var keys = Object.keys(heapEntry.children); 68 keys.sort(function(a, b) { 69 // Sort from large to small. 70 return heapEntry.children[b].currentBytes - 71 heapEntry.children[a].currentBytes; 72 }); 73 for (var i = 0; i < keys.length; i++) { 74 var traceName = keys[i]; 75 var trace = heapEntry.children[traceName]; 76 // Don't show small nodes - they make things harder to see. 77 if (trace.currentBytes < 100 * 1024) 78 continue; 79 var childCount = Object.keys(trace.children).length; 80 var isLeaf = childCount == 0; 81 var myItem = this.buildItem_( 82 traceName, isLeaf, trace.currentBytes, trace.currentAllocs); 83 myList.appendChild(myItem); 84 // Build a nested <ul> list of my children. 85 if (childCount > 0) 86 myItem.appendChild(this.buildAllocList_(trace, true)); 87 } 88 return myList; 89 }, 90 91 /* 92 * Returns a <li> for an allocation traceName of size bytes. 93 */ 94 buildItem_: function(traceName, isLeaf, bytes, allocs) { 95 var myItem = document.createElement('li'); 96 myItem.className = 'trace-item'; 97 myItem.id = traceName; 98 99 var byteDiv = document.createElement('div'); 100 byteDiv.textContent = this.getByteString_(bytes); 101 byteDiv.className = 'trace-bytes'; 102 myItem.appendChild(byteDiv); 103 104 if (traceName.length == 0) { 105 // The empty trace name indicates that the allocations occurred at 106 // this trace level, not in a sub-trace. This looks weird as the 107 // empty string, so replace it with something non-empty and don't 108 // give that line an expander. 109 traceName = '(here)'; 110 } else if (traceName.indexOf('..') == 0) { 111 // Tasks in RunTask have special handling. They show the path of the 112 // filename. Convert '../../foo.cc' into 'RunTask from foo.cc'. 113 var lastSlash = traceName.lastIndexOf('/'); 114 if (lastSlash != -1) 115 traceName = 'Task from ' + traceName.substr(lastSlash + 1); 116 } 117 var traceDiv = document.createElement('div'); 118 traceDiv.textContent = traceName; 119 traceDiv.className = 'trace-name'; 120 myItem.appendChild(traceDiv); 121 122 // Don't allow leaf nodes to be expanded. 123 if (isLeaf) 124 return myItem; 125 126 // Expand the element when it is clicked. 127 var self = this; 128 myItem.addEventListener('click', function(event) { 129 // Allow click on the +/- image (li) or child divs. 130 if (this == event.target || this == event.target.parentElement) { 131 this.classList.toggle('expanded'); 132 var child = this.querySelector('ul'); 133 child.hidden = !child.hidden; 134 // Highlight this stack trace in the timeline view. 135 self.onItemClicked_(this); 136 } 137 }); 138 myItem.classList.add('collapsed'); 139 return myItem; 140 }, 141 142 onItemClicked_: function(traceItem) { 143 // Compute the full stack trace the user just clicked. 144 var traces = []; 145 while (traceItem.classList.contains('trace-item')) { 146 var traceNameDiv = traceItem.firstElementChild.nextElementSibling; 147 traces.unshift(traceNameDiv.textContent); 148 var traceNameUl = traceItem.parentElement; 149 traceItem = traceNameUl.parentElement; 150 } 151 // Tell the instance that this stack trace is selected. 152 var instance = this.objectSnapshot_.objectInstance; 153 instance.selectedTraces = traces; 154 // Invalid the viewport to cause a redraw. 155 var trackView = document.querySelector('.timeline-track-view'); 156 trackView.viewport_.dispatchChangeEvent(); 157 }, 158 159 /* 160 * Returns a human readable string for a size in bytes. 161 */ 162 getByteString_: function(bytes) { 163 var mb = bytes / 1024 / 1024; 164 return mb.toFixed(1) + ' MB'; 165 } 166 }; 167 168 tracing.analysis.ObjectSnapshotView.register( 169 'memory::Heap', TcmallocSnapshotView); 170 171 return { 172 TcmallocSnapshotView: TcmallocSnapshotView 173 }; 174 175 }); 176