mmap.js revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
1// Copyright 2014 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
5mmap = new (function() {
6
7this.COL_START = 0;
8this.COL_END = 1;
9this.COL_LEN = 2;
10this.COL_PROT = 3;
11this.COL_RSS = 4;
12this.COL_PRIV_DIRTY = 5;
13this.COL_PRIV_CLEAN = 6;
14this.COL_SHARED_DIRTY = 7;
15this.COL_SHARED_CLEAN = 8;
16this.COL_FILE = 9;
17this.COL_OFFSET = 10;
18this.COL_RESIDENT = 11;
19this.SHOW_COLUMNS = [this.COL_START, this.COL_END, this.COL_LEN, this.COL_PROT,
20    this.COL_RSS, this.COL_PRIV_DIRTY, this.COL_PRIV_CLEAN,
21    this.COL_SHARED_DIRTY, this.COL_SHARED_CLEAN, this.COL_FILE];
22
23this.PAGE_SIZE = 4096;
24
25this.mapsData_ = null;
26this.mapsTable_ = null;
27this.mapsFilter_ = null;
28this.shouldUpdateProfileAfterDump_ = false;
29
30this.onDomReady_ = function() {
31  $('#mm-lookup-addr').on('change', this.lookupAddress.bind(this));
32  $('#mm-filter-file').on('change', this.applyMapsTableFilters_.bind(this));
33  $('#mm-filter-prot').on('change', this.applyMapsTableFilters_.bind(this));
34  $('#mm-filter-clear').on('click', this.resetMapsTableFilters_.bind(this));
35
36  // Create the mmaps table.
37  this.mapsTable_ = new google.visualization.Table($('#mm-table')[0]);
38  google.visualization.events.addListener(
39      this.mapsTable_, 'select', this.onMmapTableRowSelect_.bind(this));
40  $('#mm-table').on('dblclick', this.onMmapTableDblClick_.bind(this));
41};
42
43this.dumpMmaps = function(targetProcUri, updateProfile) {
44  if (!targetProcUri)
45    return;
46  this.shouldUpdateProfileAfterDump_ = !!updateProfile;
47  webservice.ajaxRequest('/dump/mmap/' + targetProcUri,
48                         this.onDumpAjaxResponse_.bind(this));
49  rootUi.showDialog('Dumping memory maps for ' + targetProcUri + '...');
50};
51
52this.dumpMmapsFromStorage = function(archiveName, snapshot) {
53  webservice.ajaxRequest('/storage/' + archiveName + '/' + snapshot + '/mmaps',
54                         this.onDumpAjaxResponse_.bind(this));
55  rootUi.showDialog('Loading memory maps from archive ...');
56};
57
58this.onDumpAjaxResponse_ = function(data) {
59  $('#mm-filter-file').val('');
60  $('#mm-filter-prot').val('');
61  this.mapsData_ = new google.visualization.DataTable(data.table);
62  this.mapsFilter_ = new google.visualization.DataView(this.mapsData_);
63  this.mapsFilter_.setColumns(this.SHOW_COLUMNS);
64  this.applyMapsTableFilters_();
65  rootUi.hideDialog();
66  if (this.shouldUpdateProfileAfterDump_)
67    profiler.profileCachedMmapDump(data.id);
68  shouldUpdateProfileAfterDump_ = false;
69};
70
71this.applyMapsTableFilters_ = function() {
72  // Filters the rows according to the user-provided file and prot regexps.
73  if (!this.mapsFilter_)
74    return;
75
76  var fileRx = $('#mm-filter-file').val();
77  var protRx = $('#mm-filter-prot').val();
78  var rows = [];
79  var totPrivDirty = 0;
80  var totPrivClean = 0;
81  var totSharedDirty = 0;
82  var totSharedClean = 0;
83
84  for (var row = 0; row < this.mapsData_.getNumberOfRows(); ++row) {
85     mappedFile = this.mapsData_.getValue(row, this.COL_FILE);
86     protFlags = this.mapsData_.getValue(row, this.COL_PROT);
87     if (!mappedFile.match(fileRx) || !protFlags.match(protRx))
88      continue;
89    rows.push(row);
90    totPrivDirty += this.mapsData_.getValue(row, this.COL_PRIV_DIRTY);
91    totPrivClean += this.mapsData_.getValue(row, this.COL_PRIV_CLEAN);
92    totSharedDirty += this.mapsData_.getValue(row,this.COL_SHARED_DIRTY);
93    totSharedClean += this.mapsData_.getValue(row, this.COL_SHARED_CLEAN);
94  }
95
96  this.mapsFilter_.setRows(rows);
97  $('#mm-totals-priv-dirty').text(totPrivDirty);
98  $('#mm-totals-priv-clean').text(totPrivClean);
99  $('#mm-totals-shared-dirty').text(totSharedDirty);
100  $('#mm-totals-shared-clean').text(totSharedClean);
101  this.redraw();
102};
103
104this.resetMapsTableFilters_ = function() {
105  $('#mm-filter-file').val('');
106  $('#mm-filter-prot').val('');
107  this.applyMapsTableFilters_();
108};
109
110this.onMmapTableRowSelect_ = function() {
111  // Update the memory totals for the selected rows.
112  if (!this.mapsFilter_)
113    return;
114
115  var totPrivDirty = 0;
116  var totPrivClean = 0;
117  var totSharedDirty = 0;
118  var totSharedClean = 0;
119
120  this.mapsTable_.getSelection().forEach(function(sel) {
121    var row = sel.row;
122    totPrivDirty += this.mapsFilter_.getValue(row, this.COL_PRIV_DIRTY);
123    totPrivClean += this.mapsFilter_.getValue(row, this.COL_PRIV_CLEAN);
124    totSharedDirty += this.mapsFilter_.getValue(row,this.COL_SHARED_DIRTY);
125    totSharedClean += this.mapsFilter_.getValue(row, this.COL_SHARED_CLEAN);
126  }, this);
127  $('#mm-selected-priv-dirty').text(totPrivDirty);
128  $('#mm-selected-priv-clean').text(totPrivClean);
129  $('#mm-selected-shared-dirty').text(totSharedDirty);
130  $('#mm-selected-shared-clean').text(totSharedClean);
131};
132
133this.onMmapTableDblClick_ = function() {
134  // Show resident pages for the selected mapping.
135  var PAGES_PER_ROW = 16;
136
137  if (!this.mapsData_)
138    return;
139
140  var sel = this.mapsTable_.getSelection();
141  if (sel.length == 0)
142    return;
143
144  // |sel| returns the row index in the current view, which might be filtered.
145  // Need to walk back in the mapsFilter_.getViewRows to get the actual row
146  // index in the original table.
147  var row = this.mapsFilter_.getViewRows()[sel[0].row];
148  var arr = JSON.parse(this.mapsData_.getValue(row, this.COL_RESIDENT));
149  var table = $('<table class="mm-resident-table"/>');
150  var curRow = $('<tr/>');
151  table.append(curRow);
152
153  for (var i = 0; i < arr.length; ++i) {
154    for (var j = 0; j < 8; ++j) {
155      var pageIdx = i * 8 + j;
156      var resident = !!(arr[i] & (1 << j));
157      if (pageIdx % PAGES_PER_ROW == 0) {
158        curRow = $('<tr/>');
159        table.append(curRow);
160      }
161      var hexAddr = (pageIdx * this.PAGE_SIZE).toString(16);
162      var cell = $('<td/>').text(hexAddr);
163      if (resident)
164        cell.addClass('resident')
165      curRow.append(cell);
166    }
167  }
168  rootUi.showDialog(table, 'Resident page list');
169};
170
171this.redraw = function() {
172  if (!this.mapsFilter_)
173    return;
174  this.mapsTable_.draw(this.mapsFilter_);
175};
176
177this.lookupAddress = function() {
178  // Looks up the user-provided address in the mmap table and highlights the
179  // row containing the map (if found).
180  if (!this.mapsData_)
181    return;
182
183  addr = parseInt($('#mm-lookup-addr').val(), 16);
184  $('#mm-lookup-offset').val('');
185  if (!addr)
186    return;
187
188  this.resetMapsTableFilters_();
189
190  var lbound = 0;
191  var ubound = this.mapsData_.getNumberOfRows() - 1;
192  while (lbound <= ubound) {
193    var row = ((lbound + ubound) / 2) >> 0;
194    var start = parseInt(this.mapsData_.getValue(row, this.COL_START), 16);
195    var end = parseInt(this.mapsData_.getValue(row, this.COL_END), 16);
196    if (addr < start){
197      ubound = row - 1;
198    }
199    else if (addr > end) {
200      lbound = row + 1;
201    }
202    else {
203      $('#mm-lookup-offset').val((addr - start).toString(16));
204      this.mapsTable_.setSelection([{row: row, column: null}]);
205      // Scroll to row.
206      $('#wrapper').scrollTop(
207          $('#mm-table .google-visualization-table-tr-sel').offset().top);
208      break;
209    }
210  }
211};
212
213$(document).ready(this.onDomReady_.bind(this));
214
215})();