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.
7 tvcm.requireStylesheet('tcmalloc.tcmalloc_snapshot_view');
9 tvcm.require('tracing.analysis.object_snapshot_view');
10 tvcm.require('tracing.analysis.util');
12 tvcm.exportTo('tcmalloc', function() {
14 var tsRound = tracing.analysis.tsRound;
17 * Displays a heap memory snapshot in a human readable form.
20 var TcmallocSnapshotView = tvcm.ui.define(
22 tracing.analysis.ObjectSnapshotView);
24 TcmallocSnapshotView.prototype = {
25 __proto__: tracing.analysis.ObjectSnapshotView.prototype,
27 decorate: function() {
28 this.classList.add('tcmalloc-snapshot-view');
31 updateContents: function() {
32 var snapshot = this.objectSnapshot_;
33 if (!snapshot || !snapshot.heap_) {
34 this.textContent = 'No heap found.';
37 // Clear old snapshot view.
38 this.textContent = '';
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);
53 // Build a nested tree-view of allocations
54 var myList = this.buildAllocList_(snapshot.heap_, false);
55 this.appendChild(myList);
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.
64 buildAllocList_: function(heapEntry, hide) {
65 var myList = document.createElement('ul');
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;
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)
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.
86 myItem.appendChild(this.buildAllocList_(trace, true));
92 * Returns a <li> for an allocation traceName of size bytes.
94 buildItem_: function(traceName, isLeaf, bytes, allocs) {
95 var myItem = document.createElement('li');
96 myItem.className = 'trace-item';
97 myItem.id = traceName;
99 var byteDiv = document.createElement('div');
100 byteDiv.textContent = this.getByteString_(bytes);
101 byteDiv.className = 'trace-bytes';
102 myItem.appendChild(byteDiv);
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('/');
115 traceName = 'Task from ' + traceName.substr(lastSlash + 1);
117 var traceDiv = document.createElement('div');
118 traceDiv.textContent = traceName;
119 traceDiv.className = 'trace-name';
120 myItem.appendChild(traceDiv);
122 // Don't allow leaf nodes to be expanded.
126 // Expand the element when it is clicked.
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);
138 myItem.classList.add('collapsed');
142 onItemClicked_: function(traceItem) {
143 // Compute the full stack trace the user just clicked.
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;
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();
160 * Returns a human readable string for a size in bytes.
162 getByteString_: function(bytes) {
163 var mb = bytes / 1024 / 1024;
164 return mb.toFixed(1) + ' MB';
168 tracing.analysis.ObjectSnapshotView.register(
169 'memory::Heap', TcmallocSnapshotView);
172 TcmallocSnapshotView: TcmallocSnapshotView