diff options
author | primiano@chromium.org <primiano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-23 13:03:44 +0000 |
---|---|---|
committer | primiano@chromium.org <primiano@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-23 13:03:44 +0000 |
commit | 408f1e11721894d1375ba041f5405fcacfddb204 (patch) | |
tree | a8120aba0f0dc846718f735f83bbbbda9e3b9b46 /tools/memory_inspector | |
parent | 32011ffedcc1559c923ea88aba75b8a8c9da8359 (diff) | |
download | chromium_src-408f1e11721894d1375ba041f5405fcacfddb204.zip chromium_src-408f1e11721894d1375ba041f5405fcacfddb204.tar.gz chromium_src-408f1e11721894d1375ba041f5405fcacfddb204.tar.bz2 |
Add native_heap support to the memory_inspector frontend.
This is a follow-up to crrev.com/239543009/ and adds the frontend code
(both server-side and client side) to support native heaps and
symbolization.
BUG=340294
NOTRY=true
Review URL: https://codereview.chromium.org/237743006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@265621 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'tools/memory_inspector')
11 files changed, 318 insertions, 37 deletions
diff --git a/tools/memory_inspector/memory_inspector/frontends/background_tasks.py b/tools/memory_inspector/memory_inspector/frontends/background_tasks.py index 09323ba..1d6d602 100644 --- a/tools/memory_inspector/memory_inspector/frontends/background_tasks.py +++ b/tools/memory_inspector/memory_inspector/frontends/background_tasks.py @@ -78,6 +78,7 @@ def TracerMain_(log, storage_path, backend_name, device_id, pid, interval, datetime_str = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M') archive_name = '%s - %s - %s' % (datetime_str, device.name, process.name) archive = storage.OpenArchive(archive_name, create=True) + heaps_to_symbolize = [] for i in xrange(1, count + 1): # [1, count] range is easier to handle. process = device.GetProcess(pid) @@ -96,10 +97,22 @@ def TracerMain_(log, storage_path, backend_name, device_id, pid, interval, nheap = process.DumpNativeHeap() log.put((completion, 'Dumped %d native allocs' % len(nheap.allocations))) archive.StoreNativeHeap(nheap) + heaps_to_symbolize += [nheap] if i < count: time.sleep(interval) + log.put((90, 'Symbolizing')) + symbols = backend.ExtractSymbols(heaps_to_symbolize, + device.settings['native_symbol_paths'] or '') + + expected_symbols_count = len(set.union( + *[set(x.stack_frames.iterkeys()) for x in heaps_to_symbolize])) + log.put((99, 'Symbolization complete. Got %d symbols (%.1f%%).' % ( + len(symbols), 100.0 * len(symbols) / expected_symbols_count))) + + archive.StoreSymbols(symbols) + log.put((100, 'Trace complete.')) return 0 diff --git a/tools/memory_inspector/memory_inspector/frontends/www_content/css/nheap.css b/tools/memory_inspector/memory_inspector/frontends/www_content/css/nheap.css new file mode 100644 index 0000000..358237c --- /dev/null +++ b/tools/memory_inspector/memory_inspector/frontends/www_content/css/nheap.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. */ + +#nheap-table dl { + line-height: 1em; + -webkit-user-select: text; +} + +#nheap-table dt { +} + +#nheap-table dd { + display: inline; + float: right; +} diff --git a/tools/memory_inspector/memory_inspector/frontends/www_content/css/processes.css b/tools/memory_inspector/memory_inspector/frontends/www_content/css/processes.css index f8d99b22..0aa1f9d 100644 --- a/tools/memory_inspector/memory_inspector/frontends/www_content/css/processes.css +++ b/tools/memory_inspector/memory_inspector/frontends/www_content/css/processes.css @@ -15,4 +15,9 @@ clear: both; font-size: 0.9em; margin-bottom: 1em; +} + +#ps-tracer-dialog > div > :last-child { + float: right; + width: 60%; }
\ No newline at end of file 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 e1df30c..514fa18 100644 --- a/tools/memory_inspector/memory_inspector/frontends/www_content/index.html +++ b/tools/memory_inspector/memory_inspector/frontends/www_content/index.html @@ -11,6 +11,7 @@ <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="/css/mmap.css" rel="stylesheet" type="text/css"> + <link href="/css/nheap.css" rel="stylesheet" type="text/css"> <link href="/css/processes.css" rel="stylesheet" type="text/css"> <link href="/css/profiler.css" rel="stylesheet" type="text/css"> <link href="/css/rootUi.css" rel="stylesheet" type="text/css"> @@ -24,6 +25,7 @@ </script> <script src="/js/devices.js"></script> <script src="/js/mmap.js"></script> + <script src="/js/nheap.js"></script> <script src="/js/processes.js"></script> <script src="/js/profiler.js"></script> <script src="/js/rootUi.js"></script> @@ -40,6 +42,7 @@ <li><a href="#tabs-ps">Processes</a></li> <li><a href="#tabs-prof">Profiler</a></li> <li><a href="#tabs-mm">Memory maps table</a></li> + <li><a href="#tabs-nheap">Native S.Traces</a></li> <li><a href="#tabs-storage">Archived traces</a></li> <li><a href="#tabs-settings">Settings</a></li> </ul> @@ -89,6 +92,10 @@ <label for="ps-tracer-snapshots">Num snapshots</label> <input type="text" id="ps-tracer-snapshots" value="1"> </div> + <div> + <input type="checkbox" id="ps-tracer-bt" class="ui-widget-content"> + <label for="ps-tracer-bt">Detailed (w/ backtraces)</label> + </div> </div> </div> @@ -159,12 +166,26 @@ <div id="mm-table"></div> </div> + <div id="tabs-nheap"> + <div id="nheap-toolbar" class="ui-widget-header ui-corner-all"> + <label>Totals: </label> + <input type="text" id="nheap-totals" values="0 KB" readonly> + <label>Selected: </label> + <input type="text" id="nheap-selected" values="0 KB" readonly> + <label>Filter: </label> + <input type="text" id="nheap-filter"> + </div> + <div id="nheap-table"></div> + </div> + <div id="tabs-storage"> <div id="storage-toolbar" class="ui-widget-header ui-corner-all"> <label>Group:</label> <button id="storage-profile-mmaps">Profile memory maps</button> + <button id="storage-profile-native">Profile native allocations</button> <label>Single snapshot:</label> <button id="storage-dump-mmaps">Show memory maps</button> + <button id="storage-dump-nheap">Show native heap</button> </div> <div id="storage-table"></div> </div> diff --git a/tools/memory_inspector/memory_inspector/frontends/www_content/js/devices.js b/tools/memory_inspector/memory_inspector/frontends/www_content/js/devices.js index 9d3ac04..2686ef15 100644 --- a/tools/memory_inspector/memory_inspector/frontends/www_content/js/devices.js +++ b/tools/memory_inspector/memory_inspector/frontends/www_content/js/devices.js @@ -75,15 +75,20 @@ this.onDeviceSelectionChange_ = function() { if (!this.selDeviceUri_) return; - // Initialize device and start processes / OS stats (it is a POST request). + this.initializeSelectedDevice(false); +}; + +this.initializeSelectedDevice = function(enableNativeTracing) { webservice.ajaxRequest( '/initialize/' + this.selDeviceUri_, this.onDeviceInitializationComplete_.bind(this), null, // default error handler. - {}); + {enableNativeTracing: (enableNativeTracing ? '1' : '')}); }; -this.onDeviceInitializationComplete_ = function() { +this.onDeviceInitializationComplete_ = function(data) { + this.devices_[this.selDeviceUri_].isNativeTracingEnabled = + data.isNativeTracingEnabled; processes.startPsTable(); processes.startDeviceStats(); }; diff --git a/tools/memory_inspector/memory_inspector/frontends/www_content/js/nheap.js b/tools/memory_inspector/memory_inspector/frontends/www_content/js/nheap.js new file mode 100644 index 0000000..005a42a --- /dev/null +++ b/tools/memory_inspector/memory_inspector/frontends/www_content/js/nheap.js @@ -0,0 +1,86 @@ +// 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. + +nheap = new (function() { + +this.COL_STACKTRACE = 3; +this.COL_TOTAL = 0; + +this.nheapData_ = null; +this.nheapTable_ = null; +this.nheapFilter_ = null; + +this.onDomReady_ = function() { + // Create the mmaps table. + this.nheapTable_ = new google.visualization.Table($('#nheap-table')[0]); + google.visualization.events.addListener( + this.nheapTable_, 'select', this.onNheapTableRowSelect_.bind(this)); + $('#nheap-filter').on('change', this.applyTableFilters_.bind(this)); +}; + +this.dumpNheapFromStorage = function(archiveName, snapshot) { + webservice.ajaxRequest('/storage/' + archiveName + '/' + snapshot + '/nheap', + this.onDumpAjaxResponse_.bind(this)); + rootUi.showDialog('Loading native heap allocs from archive ...'); + this.resetTableFilters_(); +}; + +this.onDumpAjaxResponse_ = function(data) { + this.nheapData_ = new google.visualization.DataTable(data); // TODO remove .table form mmap + this.nheapFilter_ = new google.visualization.DataView(this.nheapData_); + this.applyTableFilters_(); + rootUi.hideDialog(); +}; + +this.resetTableFilters_ = function() { + $('#nheap-filter').val(''); +} + +this.applyTableFilters_ = function() { + // Filters the rows according to the user-provided file and prot regexps. + if (!this.nheapFilter_) + return; + + var rx = $('#nheap-filter').val(); + var rows = []; + var total = 0; + + for (var row = 0; row < this.nheapData_.getNumberOfRows(); ++row) { + stackTrace = this.nheapData_.getValue(row, this.COL_STACKTRACE); + if (!stackTrace.match(rx)) + continue; + rows.push(row); + total += this.nheapData_.getValue(row, this.COL_TOTAL); + } + + $('#nheap-totals').val(Math.floor(total / 1024) + ' KB'); + this.nheapFilter_.setRows(rows); + this.redraw(); +}; + +this.onNheapTableRowSelect_ = function() { + if (!this.nheapFilter_) + return; + + var total = 0; + + this.nheapTable_.getSelection().forEach(function(sel) { + var row = sel.row; + total += this.nheapFilter_.getValue(row, this.COL_TOTAL); + }, this); + + $('#nheap-selected').val(Math.floor(total / 1024) + ' KB'); +}; + +this.redraw = function() { + if (!this.nheapFilter_) + return; + this.nheapTable_.draw(this.nheapFilter_, {allowHtml: true, + page: 'enable', + pageSize: 25}); +}; + +$(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 4c38c03..deac58b 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 @@ -87,12 +87,23 @@ this.showTracingDialog_ = function() { this.startTracingSelectedProcess_ = function() { if (!this.selProcUri_) return alert('The process ' + this.selProcUri_ + ' died.'); + var traceNativeHeap = $('#ps-tracer-bt').prop('checked'); $('#ps-tracer-dialog').dialog('close'); + if (traceNativeHeap && !devices.getSelectedDevice().isNativeTracingEnabled) { + var shouldProvision = confirm('Native heap tracing is not enabled.\n' + + 'Do you want to enable it (will cause a reboot on Android)?'); + if (shouldProvision) { + devices.initializeSelectedDevice(true); + alert('Wait device to complete reboot and then retry.'); + return; + } + } + var postArgs = {interval: $('#ps-tracer-period').val(), count: $('#ps-tracer-snapshots').val(), - traceNativeHeap: false}; + traceNativeHeap: traceNativeHeap}; webservice.ajaxRequest('/tracer/start/' + this.selProcUri_, this.onStartTracerAjaxResponse_.bind(this), diff --git a/tools/memory_inspector/memory_inspector/frontends/www_content/js/profiler.js b/tools/memory_inspector/memory_inspector/frontends/www_content/js/profiler.js index ea8841d1..1920aa7 100644 --- a/tools/memory_inspector/memory_inspector/frontends/www_content/js/profiler.js +++ b/tools/memory_inspector/memory_inspector/frontends/www_content/js/profiler.js @@ -65,7 +65,7 @@ this.profileCachedMmapDump = function(mmapDumpId) { }; this.profileArchivedMmaps = function(archiveName, snapshots) { - // Creates a profile using the data from the storage. + // Creates a mmap profile using the data from the storage. webservice.ajaxRequest('/profile/create', // This is a POST request. this.onProfileAjaxResponse_.bind(this), null, // use the default error handler. @@ -76,6 +76,19 @@ this.profileArchivedMmaps = function(archiveName, snapshots) { ruleset: $('#prof-ruleset').val()}); }; +this.profileArchivedNHeaps = function(archiveName, snapshots) { + // Creates a native-heap profile using the data from the storage. + webservice.ajaxRequest('/profile/create', // This is a POST request. + this.onProfileAjaxResponse_.bind(this), + null, // use the default error handler. + {type: 'nheap', + source: 'archive', + archive: archiveName, + snapshots: snapshots, + ruleset: 'heuristic'}); + // TODO(primiano): Next CLs: support custom rules, not just the heuristic one. +}; + this.onProfileAjaxResponse_ = function(data) { // This AJAX response contains a summary of the profile requested via the // /profile endpoint, which consists of: 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 5da2a04..d135188 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 @@ -34,6 +34,8 @@ this.onTabChange_ = function(_, ui) { return profiler.redraw(); case 'mm': return mmap.redraw(); + case 'nheap': + return nheap.redraw(); case 'settings': return settings.reload(); case 'storage': diff --git a/tools/memory_inspector/memory_inspector/frontends/www_content/js/storage.js b/tools/memory_inspector/memory_inspector/frontends/www_content/js/storage.js index f67af86..1c9c336 100644 --- a/tools/memory_inspector/memory_inspector/frontends/www_content/js/storage.js +++ b/tools/memory_inspector/memory_inspector/frontends/www_content/js/storage.js @@ -15,6 +15,8 @@ this.onDomReady_ = function() { .click(this.dumpMmapForSelectedSnapshot_.bind(this)); $('#storage-profile-native').button({icons:{primary: 'ui-icon-image'}}) .click(this.profileNativeForSelectedSnapshots.bind(this)); + $('#storage-dump-nheap').button({icons:{primary: 'ui-icon-calculator'}}) + .click(this.dumpNheapForSelectedSnapshot_.bind(this)); // Create the table. this.table_ = new google.visualization.Table($('#storage-table')[0]); @@ -64,10 +66,53 @@ this.dumpMmapForSelectedSnapshot_ = function() { rootUi.showTab('mm'); }; +this.dumpNheapForSelectedSnapshot_ = function() { + var sel = this.table_.getSelection(); + if (sel.length != 1) { + alert('Please select only one snapshot.') + return; + } + + var row = sel[0].row; + if (!this.checkHasNativeHapDump_(row)) + return; + nheap.dumpNheapFromStorage(this.tableData_.getValue(row, 0), + this.tableData_.getValue(row, 1)) + rootUi.showTab('nheap'); +}; + this.profileNativeForSelectedSnapshots = function() { + // Generates a native heap profile for the selected snapshots. + var sel = this.table_.getSelection(); + if (!sel.length || !this.tableData_) + return; + var archiveName = null; + var snapshots = []; + for (var i = 0; i < sel.length; ++i) { + var row = sel[i].row; + var curArchive = this.tableData_.getValue(row, 0); + if (archiveName && curArchive != archiveName) { + alert('All the selected snapshots must belong to the same archive!'); + return; + } + if (!this.checkHasNativeHapDump_(row)) + return; + archiveName = curArchive; + snapshots.push(this.tableData_.getValue(row, 1)); + } + profiler.profileArchivedNHeaps(archiveName, snapshots); + rootUi.showTab('prof'); }; +this.checkHasNativeHapDump_ = function(row) { + if (!this.tableData_.getValue(row, 3)) { + alert('The selected snapshot doesn\'t have a heap dump!'); + return false; + } + return true; +} + this.redraw = function() { if (!this.tableData_) return; diff --git a/tools/memory_inspector/memory_inspector/frontends/www_server.py b/tools/memory_inspector/memory_inspector/frontends/www_server.py index d1a54ae..3d120d4 100644 --- a/tools/memory_inspector/memory_inspector/frontends/www_server.py +++ b/tools/memory_inspector/memory_inspector/frontends/www_server.py @@ -18,6 +18,7 @@ The following HTTP status code are returned by the server: This typically happens when the target device is disconnected. """ +import cgi import collections import datetime import dateutil.parser @@ -33,6 +34,7 @@ import wsgiref.simple_server from memory_inspector.core import backends from memory_inspector.core import memory_map from memory_inspector.classification import mmap_classifier +from memory_inspector.classification import native_heap_classifier from memory_inspector.data import serialization from memory_inspector.data import file_storage from memory_inspector.frontends import background_tasks @@ -154,8 +156,10 @@ def _InitializeDevice(args, req_vars): # pylint: disable=W0613 if not device: return _HTTP_GONE, [], 'Device not found' device.Initialize() + if req_vars['enableNativeTracing']: + device.EnableNativeTracing(True) return _HTTP_OK, [], { - 'isNativeTracingEnabled': device.IsNativeTracingEnabled()} + 'isNativeTracingEnabled': device.IsNativeTracingEnabled()} @AjaxHandler(r'/ajax/profile/create', 'POST') @@ -173,41 +177,58 @@ def _CreateProfile(args, req_vars): # pylint: disable=W0613 # Step 1: collect the memory dumps, according to what the client specified in # the 'type' and 'source' POST arguments. - # Case 1: Generate a profile from a set of mmap dumps. - if req_vars['type'] == 'mmap': - classifier = mmap_classifier - # Case 1a: Use a cached mmap dumps. - if req_vars['source'] == 'cache': - dumps[0] = _GetCacheObject(req_vars['id']) - # Case 1b: Load mem dumps from an archive. - elif req_vars['source'] == 'archive': - archive = _persistent_storage.OpenArchive(req_vars['archive']) - if not archive: - return _HTTP_GONE, [], 'Cannot open archive %s' % req_vars['archive'] - first_timestamp = None - for timestamp_str in req_vars['snapshots']: - timestamp = dateutil.parser.parse(timestamp_str) - first_timestamp = timestamp if not first_timestamp else first_timestamp - time_delta = int((timestamp - first_timestamp).total_seconds()) + # Case 1a: The client requests to load data from an archive. + if req_vars['source'] == 'archive': + archive = _persistent_storage.OpenArchive(req_vars['archive']) + if not archive: + return _HTTP_GONE, [], 'Cannot open archive %s' % req_vars['archive'] + first_timestamp = None + for timestamp_str in req_vars['snapshots']: + timestamp = dateutil.parser.parse(timestamp_str) + first_timestamp = first_timestamp or timestamp + time_delta = int((timestamp - first_timestamp).total_seconds()) + if req_vars['type'] == 'mmap': dumps[time_delta] = archive.LoadMemMaps(timestamp) + elif req_vars['type'] == 'nheap': + dumps[time_delta] = archive.LoadNativeHeap(timestamp) - # TODO(primiano): Add support for native_heap types. + # Case 1b: Use a dump recently cached (only mmap, via _DumpMmapsForProcess). + elif req_vars['source'] == 'cache': + assert(req_vars['type'] == 'mmap'), 'Only cached mmap dumps are supported.' + dumps[0] = _GetCacheObject(req_vars['id']) - # Step 2: Load the rule-set specified by the client in the 'ruleset' POST arg. - # Also, perform some basic sanity checking. - rules_path = os.path.join(memory_inspector.ROOT_DIR, 'classification_rules', - req_vars['ruleset']) - if not classifier: - return _HTTP_GONE, [], 'Classifier %s not supported.' % req_vars['type'] if not dumps: return _HTTP_GONE, [], 'No memory dumps could be retrieved' - if not os.path.isfile(rules_path): - return _HTTP_GONE, [], 'Cannot find the rule-set %s' % rules_path - with open(rules_path) as f: - rules = mmap_classifier.LoadRules(f.read()) - # Step 3: Aggregate the data using the desired classifier and generate the - # profile dictionary (which will be kept cached here in the server). + # Initialize the classifier (mmap or nheap) and prepare symbols for nheap. + if req_vars['type'] == 'mmap': + classifier = mmap_classifier + elif req_vars['type'] == 'nheap': + classifier = native_heap_classifier + if not archive.HasSymbols(): + return _HTTP_GONE, [], 'No symbols in archive %s' % req_vars['archive'] + symbols = archive.LoadSymbols() + for nheap in dumps.itervalues(): + nheap.SymbolizeUsingSymbolDB(symbols) + + if not classifier: + return _HTTP_GONE, [], 'Classifier %s not supported.' % req_vars['type'] + + # Step 2: Load the rule-set specified by the client in the 'ruleset' POST arg. + if req_vars['ruleset'] == 'heuristic': + assert(req_vars['type'] == 'nheap'), ( + 'heuristic rules are supported only for nheap') + rules = native_heap_classifier.InferHeuristicRulesFromHeap(dumps[0]) + else: + rules_path = os.path.join( + memory_inspector.ROOT_DIR, 'classification_rules', req_vars['ruleset']) + if not os.path.isfile(rules_path): + return _HTTP_GONE, [], 'Cannot find the rule-set %s' % rules_path + with open(rules_path) as f: + rules = classifier.LoadRules(f.read()) + + # Step 3: Aggregate the dump data using the classifier and generate the + # profile data (which will be kept cached here in the server). # The resulting profile will consist of 1+ snapshots (depending on the number # dumps the client has requested to process) and a number of 1+ metrics # (depending on the buckets' keys returned by the classifier). @@ -223,8 +244,6 @@ def _CreateProfile(args, req_vars): # pylint: disable=W0613 profile_id = _CacheObject(snapshots) first_snapshot = next(snapshots.itervalues()) - - # |metrics| is the key set of any of the aggregated result return _HTTP_OK, [], {'id': profile_id, 'times': snapshots.keys(), 'metrics': first_snapshot.keys, @@ -531,6 +550,51 @@ def _LoadMmapsFromStorage(args, req_vars): # pylint: disable=W0613 return _HTTP_OK, [], {'table': _ConvertMmapToGTable(mmap)} +@AjaxHandler(r'/ajax/storage/(.+)/(.+)/nheap') +def _LoadNheapFromStorage(args, req_vars): + """Returns a Google Charts DataTable dictionary for the nheap.""" + archive = _persistent_storage.OpenArchive(args[0]) + if not archive: + return _HTTP_GONE, [], 'Cannot open archive %s' % req_vars['archive'] + + timestamp = dateutil.parser.parse(args[1]) + if not archive.HasNativeHeap(timestamp): + return _HTTP_GONE, [], 'No native heap dump for snapshot %s' % timestamp + + nheap = archive.LoadNativeHeap(timestamp) + symbols = archive.LoadSymbols() + nheap.SymbolizeUsingSymbolDB(symbols) + + resp = { + 'cols': [ + {'label': 'Total size [KB]', 'type':'number'}, + {'label': 'Alloc size [B]', 'type':'number'}, + {'label': 'Count', 'type':'number'}, + {'label': 'Stack Trace', 'type':'string'}, + ], + 'rows': []} + for alloc in nheap.allocations: + strace = '<dl>' + for frame in alloc.stack_trace.frames: + # Use the fallback libname.so+0xaddr if symbol info is not available. + symbol_name = frame.symbol.name if frame.symbol else '??' + source_info = (str(frame.symbol.source_info[0]) if + frame.symbol and frame.symbol.source_info else frame.raw_address) + strace += '<dd title="%s">%s</dd><dt>%s</dt>' % ( + cgi.escape(source_info), + cgi.escape(os.path.basename(source_info)), + cgi.escape(symbol_name)) + strace += '</dl>' + + resp['rows'] += [{'c': [ + {'v': alloc.total_size, 'f': alloc.total_size / 1024}, + {'v': alloc.size, 'f': None}, + {'v': alloc.count, 'f': None}, + {'v': strace, 'f': None}, + ]}] + return _HTTP_OK, [], resp + + # /ajax/tracer/start/Android/device-id/pid @AjaxHandler(r'/ajax/tracer/start/(\w+)/(\w+)/(\d+)', 'POST') def _StartTracer(args, req_vars): |