Home | History | Annotate | Download | only in tcmalloc
      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