blob: 77cd1a4aac2faeddefaff733684e6e9ff986611c [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright (c) 2013 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<link rel="stylesheet" href="/extras/tcmalloc/tcmalloc_snapshot_view.css">
<link rel="import" href="/core/analysis/object_snapshot_view.html">
<link rel="import" href="/base/units/util.html">
<script>
'use strict';
tr.exportTo('tr.e.tcmalloc', function() {
/*
* Displays a heap memory snapshot in a human readable form.
* @constructor
*/
var TcmallocSnapshotView = tr.b.ui.define(
'heap-snapshot-view',
tr.c.analysis.ObjectSnapshotView);
TcmallocSnapshotView.prototype = {
__proto__: tr.c.analysis.ObjectSnapshotView.prototype,
decorate: function() {
this.classList.add('tcmalloc-snapshot-view');
},
updateContents: function() {
var snapshot = this.objectSnapshot_;
if (!snapshot || !snapshot.heap_) {
this.textContent = 'No heap found.';
return;
}
// Clear old snapshot view.
this.textContent = '';
// Note: "total" may actually be less than the largest allocation bin.
// This might happen if one stack is doing a lot of allocation, then
// passing off to another stack for deallocation. That stack will
// have a high "current bytes" count and the other one might be
// negative or zero. So "total" may be smaller than the largest trace.
var subhead = document.createElement('div');
subhead.textContent = 'Retaining ' +
this.getByteString_(snapshot.total_.currentBytes) + ' in ' +
snapshot.total_.currentAllocs +
' allocations. Showing > 0.1 MB.';
subhead.className = 'subhead';
this.appendChild(subhead);
// Build a nested tree-view of allocations
var myList = this.buildAllocList_(snapshot.heap_, false);
this.appendChild(myList);
},
/**
* Creates a nested list with clickable entries.
* @param {Object} heapEntry The current trace heap entry.
* @param {boolean} hide Whether this list is hidden by default.
* @return {Element} A <ul> list element.
*/
buildAllocList_: function(heapEntry, hide) {
var myList = document.createElement('ul');
myList.hidden = hide;
var keys = Object.keys(heapEntry.children);
keys.sort(function(a, b) {
// Sort from large to small.
return heapEntry.children[b].currentBytes -
heapEntry.children[a].currentBytes;
});
for (var i = 0; i < keys.length; i++) {
var traceName = keys[i];
var trace = heapEntry.children[traceName];
// Don't show small nodes - they make things harder to see.
if (trace.currentBytes < 100 * 1024)
continue;
var childCount = Object.keys(trace.children).length;
var isLeaf = childCount == 0;
var myItem = this.buildItem_(
traceName, isLeaf, trace.currentBytes, trace.currentAllocs);
myList.appendChild(myItem);
// Build a nested <ul> list of my children.
if (childCount > 0)
myItem.appendChild(this.buildAllocList_(trace, true));
}
return myList;
},
/*
* Returns a <li> for an allocation traceName of size bytes.
*/
buildItem_: function(traceName, isLeaf, bytes, allocs) {
var myItem = document.createElement('li');
myItem.className = 'trace-item';
myItem.id = traceName;
var byteDiv = document.createElement('div');
byteDiv.textContent = this.getByteString_(bytes);
byteDiv.className = 'trace-bytes';
myItem.appendChild(byteDiv);
if (traceName.length == 0) {
// The empty trace name indicates that the allocations occurred at
// this trace level, not in a sub-trace. This looks weird as the
// empty string, so replace it with something non-empty and don't
// give that line an expander.
traceName = '(here)';
} else if (traceName.indexOf('..') == 0) {
// Tasks in RunTask have special handling. They show the path of the
// filename. Convert '../../foo.cc' into 'RunTask from foo.cc'.
var lastSlash = traceName.lastIndexOf('/');
if (lastSlash != -1)
traceName = 'Task from ' + traceName.substr(lastSlash + 1);
}
var traceDiv = document.createElement('div');
traceDiv.textContent = traceName;
traceDiv.className = 'trace-name';
myItem.appendChild(traceDiv);
// Don't allow leaf nodes to be expanded.
if (isLeaf)
return myItem;
// Expand the element when it is clicked.
var self = this;
myItem.addEventListener('click', function(event) {
// Allow click on the +/- image (li) or child divs.
if (this == event.target || this == event.target.parentElement) {
this.classList.toggle('expanded');
var child = this.querySelector('ul');
child.hidden = !child.hidden;
// Highlight this stack trace in the timeline view.
self.onItemClicked_(this);
}
});
myItem.classList.add('collapsed');
return myItem;
},
onItemClicked_: function(traceItem) {
// Compute the full stack trace the user just clicked.
var traces = [];
while (traceItem.classList.contains('trace-item')) {
var traceNameDiv = traceItem.firstElementChild.nextElementSibling;
traces.unshift(traceNameDiv.textContent);
var traceNameUl = traceItem.parentElement;
traceItem = traceNameUl.parentElement;
}
// Tell the instance that this stack trace is selected.
var instance = this.objectSnapshot_.objectInstance;
instance.selectedTraces = traces;
// Invalid the viewport to cause a redraw.
var trackView = document.querySelector('.timeline-track-view');
trackView.viewport_.dispatchChangeEvent();
},
/*
* Returns a human readable string for a size in bytes.
*/
getByteString_: function(bytes) {
var mb = bytes / 1024 / 1024;
return mb.toFixed(1) + ' MB';
}
};
tr.c.analysis.ObjectSnapshotView.register(
TcmallocSnapshotView,
{typeName: 'memory::Heap'});
return {
TcmallocSnapshotView: TcmallocSnapshotView
};
});
</script>