| <!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> |