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