summaryrefslogtreecommitdiffstats
path: root/tools/memory_inspector
diff options
context:
space:
mode:
authorprimiano@chromium.org <primiano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-13 15:20:12 +0000
committerprimiano@chromium.org <primiano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-03-13 15:20:12 +0000
commit438c69e18ab05ea3701b4bdf51cffdd7d1982e5a (patch)
tree32c8fcd9629da098882b0f7d89119b22dfcf0263 /tools/memory_inspector
parentc2d2ef92677c79d67d637e5bb613f059c5aca18b (diff)
downloadchromium_src-438c69e18ab05ea3701b4bdf51cffdd7d1982e5a.zip
chromium_src-438c69e18ab05ea3701b4bdf51cffdd7d1982e5a.tar.gz
chromium_src-438c69e18ab05ea3701b4bdf51cffdd7d1982e5a.tar.bz2
Add mmap table and resident page UI to memory_inspector.
This wires-up the raw mmap dump (through memdump), introducing: - A new /ajax/dump/mmap endpoint to the server. - The HTML/JS ui to show the mmap table and the resident page list. BUG=340294 NOTRY=true Review URL: https://codereview.chromium.org/196973008 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@256836 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'tools/memory_inspector')
-rw-r--r--tools/memory_inspector/memory_inspector/frontends/www_content/index.html42
-rw-r--r--tools/memory_inspector/memory_inspector/frontends/www_content/js/mmap.js192
-rw-r--r--tools/memory_inspector/memory_inspector/frontends/www_content/js/processes.js5
-rw-r--r--tools/memory_inspector/memory_inspector/frontends/www_content/js/rootUi.js14
-rw-r--r--tools/memory_inspector/memory_inspector/frontends/www_content/mmap.css16
-rw-r--r--tools/memory_inspector/memory_inspector/frontends/www_content/rootUi.css4
-rw-r--r--tools/memory_inspector/memory_inspector/frontends/www_server.py43
7 files changed, 301 insertions, 15 deletions
diff --git a/tools/memory_inspector/memory_inspector/frontends/www_content/index.html b/tools/memory_inspector/memory_inspector/frontends/www_content/index.html
index 22d130c..18baadb 100644
--- a/tools/memory_inspector/memory_inspector/frontends/www_content/index.html
+++ b/tools/memory_inspector/memory_inspector/frontends/www_content/index.html
@@ -10,6 +10,7 @@
<title>Memory Inspector</title>
<link href='//fonts.googleapis.com/css?family=Coda' rel='stylesheet' type='text/css'>
<link href="//ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/themes/flick/jquery-ui.css" rel="stylesheet">
+ <link href="/mmap.css" rel="stylesheet" type="text/css">
<link href="/rootUi.css" rel="stylesheet" type="text/css">
<link href="/settings.css" rel="stylesheet" type="text/css">
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
@@ -20,6 +21,7 @@
{ packages: ['corechart', 'table', 'orgchart', 'treemap'] });
</script>
<script src="/js/devices.js"></script>
+ <script src="/js/mmap.js"></script>
<script src="/js/rootUi.js"></script>
<script src="/js/processes.js"></script>
<script src="/js/settings.js"></script>
@@ -92,19 +94,33 @@
<header>
<b>Filters: </b>
<span>
- Prot. flags: <input type="text" id="mm-filter-prot" />
- File name: <input type="text" id="mm-filter-file" />
+ <input type="button" id="mm-filter-clear" value="Reset">
+ Prot. flags: <input type="text" id="mm-filter-prot">
+ File name: <input type="text" id="mm-filter-file">
+ <i>(Just press enter to apply filters)</i>
</span>
</header>
<header>
- <b>Totals: </b>
- <b>Priv dirty (Kb): </b><span id="memmaps-totals-priv-dirty">0</span>
- <b>Priv clean (Kb): </b><span id="memmaps-totals-priv-clean">0</span>
- <b>Shared dirty (Kb): </b><span id="memmaps-totals-shared-dirty">0</span>
- <b>Shared clean (Kb): </b><span id="memmaps-totals-shared-clean">0</span>
+ <b>Lookup addr: </b>
+ <input type="text" id="mm-lookup-addr">
+ <span><b>Offset in mapping:</b></span>
+ <span id="mm-lookup-offset">0</span>
+ </span>
+ </header>
+ <hr>
+ <header>
+ <b>Totals [Kb]: </b>
+ <b>Priv dirty: </b><span id="mm-totals-priv-dirty">0</span>
+ <b>Priv clean: </b><span id="mm-totals-priv-clean">0</span>
+ <b>Shared dirty: </b><span id="mm-totals-shared-dirty">0</span>
+ <b>Shared clean: </b><span id="mm-totals-shared-clean">0</span>
</header>
<header>
- Note: totals from this filtered table might not match the totals in the treemap, as table filtering is not hierarchical.
+ <b>Selected [Kb]: </b>
+ <b>Priv dirty: </b><span id="mm-selected-priv-dirty">0</span>
+ <b>Priv clean: </b><span id="mm-selected-priv-clean">0</span>
+ <b>Shared dirty: </b><span id="mm-selected-shared-dirty">0</span>
+ <b>Shared clean: </b><span id="mm-selected-shared-clean">0</span>
</header>
<div id="mm-table"></div>
</div>
@@ -130,17 +146,17 @@
</div>
<div id="tabs-archive">
- <input type="button" value="Refresh" id="archive-refresh" />
- <input type="button" value="Analyze Memory maps" id="archive-classify_mmaps" />
- <input type="button" value="Analyze Native Heap" id="archive-classify_native" />
+ <input type="button" value="Refresh" id="archive-refresh">
+ <input type="button" value="Analyze Memory maps" id="archive-classify_mmaps">
+ <input type="button" value="Analyze Native Heap" id="archive-classify_native">
<div id="archive">
</div>
</div>
<div id="tabs-settings">
<header>
- <input type="button" value="Reload" id="settings-load" />
- <input type="button" value="Store" id="settings-store" />
+ <input type="button" value="Reload" id="settings-load">
+ <input type="button" value="Store" id="settings-store">
</header>
<div id="settings-container">
</div>
diff --git a/tools/memory_inspector/memory_inspector/frontends/www_content/js/mmap.js b/tools/memory_inspector/memory_inspector/frontends/www_content/js/mmap.js
new file mode 100644
index 0000000..400d24f
--- /dev/null
+++ b/tools/memory_inspector/memory_inspector/frontends/www_content/js/mmap.js
@@ -0,0 +1,192 @@
+// Copyright 2014 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.
+
+mmap = new (function() {
+
+this.AJAX_BASE_URL_ = '/ajax';
+this.COL_START = 0;
+this.COL_END = 1;
+this.COL_LEN = 2;
+this.COL_PROT = 3;
+this.COL_PRIV_DIRTY = 4;
+this.COL_PRIV_CLEAN = 5;
+this.COL_SHARED_DIRTY = 6;
+this.COL_SHARED_CLEAN = 7;
+this.COL_FILE = 8;
+this.COL_RESIDENT = 10;
+this.SHOW_COLUMNS = [0, 1, 2, 3, 4, 5, 6, 7, 8];
+this.PAGE_SIZE = 4096;
+
+this.mapsData_ = null;
+this.mapsTable_ = null;
+this.mapsFilter_ = null;
+
+this.onDomReady_ = function() {
+ $('#mm-lookup-addr').on('change', this.lookupAddress.bind(this));
+ $('#mm-filter-file').on('change', this.applyMapsTableFilters_.bind(this));
+ $('#mm-filter-prot').on('change', this.applyMapsTableFilters_.bind(this));
+ $('#mm-filter-clear').on('click', this.resetMapsTableFilters_.bind(this));
+};
+
+this.dumpMmaps = function(targetProcUri) {
+ if (!targetProcUri)
+ return;
+ webservice.ajaxRequest('/dump/mmap/' + targetProcUri,
+ this.onDumpAjaxResponse_.bind(this));
+ rootUi.showDialog('Dumping memory maps for ' + targetProcUri + '...');
+};
+
+this.onDumpAjaxResponse_ = function(data) {
+ $('#mm-filter-file').val('');
+ $('#mm-filter-prot').val('');
+ this.mapsData_ = new google.visualization.DataTable(data);
+ this.mapsFilter_ = new google.visualization.DataView(this.mapsData_);
+ this.mapsFilter_.setColumns(this.SHOW_COLUMNS);
+ this.mapsTable_ = new google.visualization.Table($('#mm-table')[0]);
+ google.visualization.events.addListener(
+ this.mapsTable_, 'select', this.onMmapTableRowSelect_.bind(this));
+ $('#mm-table').on('dblclick', this.onMmapTableDblClick_.bind(this));
+ rootUi.showTab('mm-table');
+ this.applyMapsTableFilters_();
+ rootUi.hideDialog();
+};
+
+this.applyMapsTableFilters_ = function() {
+ // Filters the rows according to the user-provided file and prot regexps.
+ if (!this.mapsFilter_)
+ return;
+
+ var fileRx = $('#mm-filter-file').val();
+ var protRx = $('#mm-filter-prot').val();
+ var rows = [];
+ var totPrivDirty = 0;
+ var totPrivClean = 0;
+ var totSharedDirty = 0;
+ var totSharedClean = 0;
+
+ for (var row = 0; row < this.mapsData_.getNumberOfRows(); ++row) {
+ mappedFile = this.mapsData_.getValue(row, this.COL_FILE);
+ protFlags = this.mapsData_.getValue(row, this.COL_PROT);
+ if (!mappedFile.match(fileRx) || !protFlags.match(protRx))
+ continue;
+ rows.push(row);
+ totPrivDirty += this.mapsData_.getValue(row, this.COL_PRIV_DIRTY);
+ totPrivClean += this.mapsData_.getValue(row, this.COL_PRIV_CLEAN);
+ totSharedDirty += this.mapsData_.getValue(row,this.COL_SHARED_DIRTY);
+ totSharedClean += this.mapsData_.getValue(row, this.COL_SHARED_CLEAN);
+ }
+ this.mapsFilter_.setRows(rows);
+ this.mapsTable_.draw(this.mapsFilter_);
+ $('#mm-totals-priv-dirty').text(totPrivDirty);
+ $('#mm-totals-priv-clean').text(totPrivClean);
+ $('#mm-totals-shared-dirty').text(totSharedDirty);
+ $('#mm-totals-shared-clean').text(totSharedClean);
+};
+
+this.resetMapsTableFilters_ = function() {
+ $('#mm-filter-file').val('');
+ $('#mm-filter-prot').val('');
+ this.applyMapsTableFilters_();
+};
+
+this.onMmapTableRowSelect_ = function() {
+ // Update the memory totals for the selected rows.
+ if (!this.mapsFilter_)
+ return;
+
+ var totPrivDirty = 0;
+ var totPrivClean = 0;
+ var totSharedDirty = 0;
+ var totSharedClean = 0;
+
+ this.mapsTable_.getSelection().forEach(function(sel) {
+ var row = sel.row;
+ totPrivDirty += this.mapsFilter_.getValue(row, this.COL_PRIV_DIRTY);
+ totPrivClean += this.mapsFilter_.getValue(row, this.COL_PRIV_CLEAN);
+ totSharedDirty += this.mapsFilter_.getValue(row,this.COL_SHARED_DIRTY);
+ totSharedClean += this.mapsFilter_.getValue(row, this.COL_SHARED_CLEAN);
+ }, this);
+ $('#mm-selected-priv-dirty').text(totPrivDirty);
+ $('#mm-selected-priv-clean').text(totPrivClean);
+ $('#mm-selected-shared-dirty').text(totSharedDirty);
+ $('#mm-selected-shared-clean').text(totSharedClean);
+};
+
+this.onMmapTableDblClick_ = function() {
+ // Show resident pages for the selected mapping.
+ var PAGES_PER_ROW = 16;
+
+ if (!this.mapsData_)
+ return;
+
+ var sel = this.mapsTable_.getSelection();
+ if (sel.length == 0)
+ return;
+
+ // |sel| returns the row index in the current view, which might be filtered.
+ // Need to walk back in the mapsFilter_.getViewRows to get the actual row
+ // index in the original table.
+ var row = this.mapsFilter_.getViewRows()[sel[0].row];
+ var arr = JSON.parse(this.mapsData_.getValue(row, this.COL_RESIDENT));
+ var table = $('<table class="mm-resident-table"/>');
+ var curRow = $('<tr/>');
+ table.append(curRow);
+
+ for (var i = 0; i < arr.length; ++i) {
+ for (var j = 0; j < 8; ++j) {
+ var pageIdx = i * 8 + j;
+ var resident = !!(arr[i] & (1 << j));
+ if (pageIdx % PAGES_PER_ROW == 0) {
+ curRow = $('<tr/>');
+ table.append(curRow);
+ }
+ var hexAddr = (pageIdx * this.PAGE_SIZE).toString(16);
+ var cell = $('<td/>').text(hexAddr);
+ if (resident)
+ cell.addClass('resident')
+ curRow.append(cell);
+ }
+ }
+ rootUi.showDialog(table, 'Resident page list');
+};
+
+this.lookupAddress = function() {
+ // Looks up the user-provided address in the mmap table and highlights the
+ // row containing the map (if found).
+ if (!this.mapsData_)
+ return;
+
+ addr = parseInt($('#mm-lookup-addr').val(), 16);
+ $('#mm-lookup-offset').text('');
+ if (!addr)
+ return;
+
+ this.resetMapsTableFilters_();
+
+ var lbound = 0;
+ var ubound = this.mapsData_.getNumberOfRows() - 1;
+ while (lbound <= ubound) {
+ var row = ((lbound + ubound) / 2) >> 0;
+ var start = parseInt(this.mapsData_.getValue(row, this.COL_START), 16);
+ var end = parseInt(this.mapsData_.getValue(row, this.COL_END), 16);
+ if (addr < start){
+ ubound = row - 1;
+ }
+ else if (addr > end) {
+ lbound = row + 1;
+ }
+ else {
+ $('#mm-lookup-offset').text((addr - start).toString(16));
+ this.mapsTable_.setSelection([{row: row, column: null}]);
+ // Scroll to row.
+ $('#wrapper').scrollTop(
+ $('#mm-table .google-visualization-table-tr-sel').offset().top);
+ break;
+ }
+ }
+};
+
+$(document).ready(this.onDomReady_.bind(this));
+
+})(); \ No newline at end of file
diff --git a/tools/memory_inspector/memory_inspector/frontends/www_content/js/processes.js b/tools/memory_inspector/memory_inspector/frontends/www_content/js/processes.js
index 08cc48a..fc7b98e 100644
--- a/tools/memory_inspector/memory_inspector/frontends/www_content/js/processes.js
+++ b/tools/memory_inspector/memory_inspector/frontends/www_content/js/processes.js
@@ -39,6 +39,7 @@ this.refreshPsTable = function() {
this.psTable_ = new google.visualization.Table($('#ps-table')[0]);
google.visualization.events.addListener(
this.psTable_, 'select', this.onPsTableRowSelect_.bind(this));
+ $('#ps-table').on('dblclick', this.onPsTableDblClick_.bind(this));
};
var showAllParam = $('#ps-show_all').prop('checked') ? '?all=1' : '';
@@ -71,6 +72,10 @@ this.onPsTableRowSelect_ = function() {
this.startSelectedProcessStats();
};
+this.onPsTableDblClick_ = function() {
+ mmap.dumpMmaps(this.getSelectedProcessURI());
+};
+
this.onPsAjaxResponse_ = function(data) {
// Redraw table preserving sorting info.
var sort = this.psTable_.getSortInfo() || {column: -1, ascending: false};
diff --git a/tools/memory_inspector/memory_inspector/frontends/www_content/js/rootUi.js b/tools/memory_inspector/memory_inspector/frontends/www_content/js/rootUi.js
index a9e85de..a2620eb 100644
--- a/tools/memory_inspector/memory_inspector/frontends/www_content/js/rootUi.js
+++ b/tools/memory_inspector/memory_inspector/frontends/www_content/js/rootUi.js
@@ -36,13 +36,23 @@ this.showTab = function(tabId) {
$('#tabs').tabs('option', 'active', index - 1);
};
-this.showDialog = function(message) {
+this.showDialog = function(content, title) {
var dialog = $("#message_dialog");
+ title = title || '';
if (dialog.length == 0) {
dialog = $('<div id="message_dialog"/>');
$('body').append(dialog);
}
- $("#message_dialog").text(message).dialog({ modal: true });
+ if (typeof(content) == 'string')
+ dialog.empty().text(content);
+ else
+ dialog.empty().append(content); // Assume is a jQuery DOM object.
+
+ dialog.dialog({modal: true, title: title, height:'auto', width:'auto'});
+};
+
+this.hideDialog = function() {
+ $("#message_dialog").dialog('close');
};
$(document).ready(this.onDomReady_.bind(this));
diff --git a/tools/memory_inspector/memory_inspector/frontends/www_content/mmap.css b/tools/memory_inspector/memory_inspector/frontends/www_content/mmap.css
new file mode 100644
index 0000000..b47adb4
--- /dev/null
+++ b/tools/memory_inspector/memory_inspector/frontends/www_content/mmap.css
@@ -0,0 +1,16 @@
+/* Copyright 2014 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. */
+
+.mm-resident-table {
+ border: 1px solid #999;
+}
+
+.mm-resident-table td {
+ padding: 0.2em;
+}
+
+.mm-resident-table td.resident {
+ background: lightgreen;
+}
+
diff --git a/tools/memory_inspector/memory_inspector/frontends/www_content/rootUi.css b/tools/memory_inspector/memory_inspector/frontends/www_content/rootUi.css
index 80b805d..7654f57 100644
--- a/tools/memory_inspector/memory_inspector/frontends/www_content/rootUi.css
+++ b/tools/memory_inspector/memory_inspector/frontends/www_content/rootUi.css
@@ -141,3 +141,7 @@ input[type="text"] {
display: inline-block;
height: 20em;
}
+
+table {
+ -webkit-user-select: none;
+} \ No newline at end of file
diff --git a/tools/memory_inspector/memory_inspector/frontends/www_server.py b/tools/memory_inspector/memory_inspector/frontends/www_server.py
index fdf605d..784881f 100644
--- a/tools/memory_inspector/memory_inspector/frontends/www_server.py
+++ b/tools/memory_inspector/memory_inspector/frontends/www_server.py
@@ -100,6 +100,7 @@ class AjaxHandler(UriHandler):
('Expires', 'Fri, 19 Sep 1986 05:00:00 GMT')]
return http_code, headers + extra_headers, serialized_content
+
@AjaxHandler('/ajax/backends')
def _ListBackends(args, req_vars): # pylint: disable=W0613
return _HTTP_OK, [], [backend.name for backend in backends.ListBackends()]
@@ -120,6 +121,48 @@ def _ListDevices(args, req_vars): # pylint: disable=W0613
return _HTTP_OK, [], resp
+@AjaxHandler(r'/ajax/dump/mmap/(\w+)/(\w+)/(\d+)')
+def _DumpMmapsForProcess(args, req_vars): # pylint: disable=W0613
+ """Dumps memory maps for a process.
+
+ The response is formatted according to the Google Charts DataTable format.
+ """
+ process = _GetProcess(args)
+ if not process:
+ return _HTTP_GONE, [], 'Device not found or process died'
+ mmap = process.DumpMemoryMaps()
+ resp = {
+ 'cols': [
+ {'label': 'Start', 'type':'string'},
+ {'label': 'End', 'type':'string'},
+ {'label': 'Length Kb', 'type':'number'},
+ {'label': 'Prot', 'type':'string'},
+ {'label': 'Priv. Dirty Kb', 'type':'number'},
+ {'label': 'Priv. Clean Kb', 'type':'number'},
+ {'label': 'Shared Dirty Kb', 'type':'number'},
+ {'label': 'Shared Clean Kb', 'type':'number'},
+ {'label': 'File', 'type':'string'},
+ {'label': 'Offset', 'type':'number'},
+ {'label': 'Resident Pages', 'type':'string'},
+ ],
+ 'rows': []}
+ for entry in mmap.entries:
+ resp['rows'] += [{'c': [
+ {'v': '%08x' % entry.start, 'f': None},
+ {'v': '%08x' % entry.end, 'f': None},
+ {'v': entry.len / 1024, 'f': None},
+ {'v': entry.prot_flags, 'f': None},
+ {'v': entry.priv_dirty_bytes / 1024, 'f': None},
+ {'v': entry.priv_clean_bytes / 1024, 'f': None},
+ {'v': entry.shared_dirty_bytes / 1024, 'f': None},
+ {'v': entry.shared_clean_bytes / 1024, 'f': None},
+ {'v': entry.mapped_file, 'f': None},
+ {'v': entry.mapped_offset, 'f': None},
+ {'v': '[%s]' % (','.join(map(str, entry.resident_pages))), 'f': None},
+ ]}]
+ return _HTTP_OK, [], resp
+
+
@AjaxHandler('/ajax/initialize/(\w+)/(\w+)$') # /ajax/initialize/Android/a0b1c2
def _InitializeDevice(args, req_vars): # pylint: disable=W0613
device = _GetDevice(args)