summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2018-07-24 17:43:47 -0700
committerandroid-build-merger <android-build-merger@google.com>2018-07-24 17:43:47 -0700
commitd1a90c07eeddd06365c51f7cd95828eaa731cd70 (patch)
treee5702224c54ae662babae2053677714aa1746536
parent2cfb8062239e276813c0ed66db4bcdf93e1e2809 (diff)
parentf2307ae2a0799e300fdc67d8c1d10c7ce3a78f1d (diff)
downloadextras-d1a90c07eeddd06365c51f7cd95828eaa731cd70.tar.gz
Merge "simpleperf: add --binary_filter option in report_html.py."
am: f2307ae2a0 Change-Id: I5f9e6ad5f57f697b9d55bc1870523b9f2a0672a1
-rw-r--r--simpleperf/scripts/report_html.js13
-rwxr-xr-xsimpleperf/scripts/report_html.py55
-rw-r--r--simpleperf/scripts/test.py11
-rw-r--r--simpleperf/scripts/utils.py108
4 files changed, 115 insertions, 72 deletions
diff --git a/simpleperf/scripts/report_html.js b/simpleperf/scripts/report_html.js
index 4fc2a4a3..720f72f8 100644
--- a/simpleperf/scripts/report_html.js
+++ b/simpleperf/scripts/report_html.js
@@ -602,7 +602,7 @@ class SampleTableWeightSelectorView {
if (e.clickEvent) {
let button = $(e.clickEvent.target);
let newOption = button.attr('key');
- if (this.curOption != newOption) {
+ if (newOption && this.curOption != newOption) {
this.curOption = newOption;
divContainer.find(`#${id}`).text(options.get(this.curOption));
onSelectChange();
@@ -638,7 +638,7 @@ class SampleTableView {
this.div = $('<div>', {id: this.id}).appendTo(divContainer);
this.eventInfo = eventInfo;
this.selectorView = null;
- this.tableView = null;
+ this.tableDiv = null;
}
drawAsync(totalProgress) {
@@ -647,6 +647,7 @@ class SampleTableView {
this.div.empty();
this.selectorView = new SampleTableWeightSelectorView(
this.div, this.eventInfo, () => this.onSampleWeightChange());
+ this.tableDiv = $('<div>').appendTo(this.div);
}))
.then(() => this._drawSampleTable(totalProgress));
}
@@ -657,7 +658,7 @@ class SampleTableView {
let data = [];
return createPromise()
.then(wait(() => {
- this.div.find('table').remove();
+ this.tableDiv.empty();
let getSampleWeight = this.selectorView.getSampleWeightFunction();
let sampleWeightSuffix = this.selectorView.getSampleWeightSuffix();
// Draw a table of 'Total', 'Self', 'Samples', 'Process', 'Thread', 'Library',
@@ -665,7 +666,7 @@ class SampleTableView {
let valueSuffix = sampleWeightSuffix.length > 0 ? `(in${sampleWeightSuffix})` : '';
let titles = ['Total' + valueSuffix, 'Self' + valueSuffix, 'Samples', 'Process',
'Thread', 'Library', 'Function', 'HideKey'];
- this.div.append(`
+ this.tableDiv.append(`
<table cellspacing="0" class="table table-striped table-bordered"
style="width:100%">
<thead>${getTableRow(titles, 'th')}</thead>
@@ -691,7 +692,7 @@ class SampleTableView {
}))
.then(addProgress(totalProgress / 2))
.then(wait(() => {
- let table = this.div.find('table');
+ let table = this.tableDiv.find('table');
let dataTable = table.DataTable({
lengthMenu: [10, 20, 50, 100, -1],
order: [0, 'desc'],
@@ -1039,7 +1040,7 @@ class SampleWeightSelectorView {
if (e.clickEvent) {
let button = $(e.clickEvent.target);
let newOption = button.attr('key');
- if (this.curOption != newOption) {
+ if (newOption && this.curOption != newOption) {
this.curOption = newOption;
divContainer.find(`#${id}`).text(options.get(this.curOption));
onSelectChange();
diff --git a/simpleperf/scripts/report_html.py b/simpleperf/scripts/report_html.py
index c28a4299..c3ab0df2 100755
--- a/simpleperf/scripts/report_html.py
+++ b/simpleperf/scripts/report_html.py
@@ -635,7 +635,7 @@ class RecordData(object):
self.events[event_name] = EventScope(event_name)
return self.events[event_name]
- def add_source_code(self, source_dirs):
+ def add_source_code(self, source_dirs, filter_lib):
""" Collect source code information:
1. Find line ranges for each function in FunctionSet.
2. Find line for each addr in FunctionScope.addr_hit_map.
@@ -647,17 +647,19 @@ class RecordData(object):
if function.func_name == 'unknown':
continue
lib_name = self.libs.get_lib_name(function.lib_id)
- addr2line.add_addr(lib_name, function.start_addr, function.start_addr)
- addr2line.add_addr(lib_name, function.start_addr,
- function.start_addr + function.addr_len - 1)
+ if filter_lib(lib_name):
+ addr2line.add_addr(lib_name, function.start_addr, function.start_addr)
+ addr2line.add_addr(lib_name, function.start_addr,
+ function.start_addr + function.addr_len - 1)
# Request line for each addr in FunctionScope.addr_hit_map.
for event in self.events.values():
for lib in event.libraries:
lib_name = self.libs.get_lib_name(lib.lib_id)
- for function in lib.functions.values():
- func_addr = self.functions.id_to_func[function.func_id].start_addr
- for addr in function.addr_hit_map:
- addr2line.add_addr(lib_name, func_addr, addr)
+ if filter_lib(lib_name):
+ for function in lib.functions.values():
+ func_addr = self.functions.id_to_func[function.func_id].start_addr
+ for addr in function.addr_hit_map:
+ addr2line.add_addr(lib_name, func_addr, addr)
addr2line.convert_addrs_to_lines()
# Set line range for each function.
@@ -665,6 +667,8 @@ class RecordData(object):
if function.func_name == 'unknown':
continue
dso = addr2line.get_dso(self.libs.get_lib_name(function.lib_id))
+ if not dso:
+ continue
start_source = addr2line.get_addr_source(dso, function.start_addr)
end_source = addr2line.get_addr_source(dso, function.start_addr + function.addr_len - 1)
if not start_source or not end_source:
@@ -681,6 +685,8 @@ class RecordData(object):
for event in self.events.values():
for lib in event.libraries:
dso = addr2line.get_dso(self.libs.get_lib_name(lib.lib_id))
+ if not dso:
+ continue
for function in lib.functions.values():
for addr in function.addr_hit_map:
source = addr2line.get_addr_source(dso, addr)
@@ -697,18 +703,29 @@ class RecordData(object):
# Collect needed source code in SourceFileSet.
self.source_files.load_source_code(source_dirs)
- def add_disassembly(self):
+ def add_disassembly(self, filter_lib):
""" Collect disassembly information:
1. Use objdump to collect disassembly for each function in FunctionSet.
2. Set flag to dump addr_hit_map when generating record info.
"""
objdump = Objdump(self.ndk_path, self.binary_cache_path)
- for function in self.functions.id_to_func.values():
+ cur_lib_name = None
+ dso_info = None
+ for function in sorted(self.functions.id_to_func.values(), key=lambda a: a.lib_id):
if function.func_name == 'unknown':
continue
lib_name = self.libs.get_lib_name(function.lib_id)
- code = objdump.disassemble_code(lib_name, function.start_addr, function.addr_len)
- function.disassembly = code
+ if lib_name != cur_lib_name:
+ cur_lib_name = lib_name
+ if filter_lib(lib_name):
+ dso_info = objdump.get_dso_info(lib_name)
+ else:
+ dso_info = None
+ if dso_info:
+ log_info('Disassemble %s' % dso_info[0])
+ if dso_info:
+ code = objdump.disassemble_code(dso_info, function.start_addr, function.addr_len)
+ function.disassembly = code
self.gen_addr_hit_map_in_record_info = True
@@ -866,6 +883,8 @@ def main():
parser.add_argument('--add_source_code', action='store_true', help='Add source code.')
parser.add_argument('--source_dirs', nargs='+', help='Source code directories.')
parser.add_argument('--add_disassembly', action='store_true', help='Add disassembled code.')
+ parser.add_argument('--binary_filter', nargs='+', help="""Annotate source code and disassembly
+ only for selected binaries.""")
parser.add_argument('--ndk_path', nargs=1, help='Find tools in the ndk path.')
parser.add_argument('--no_browser', action='store_true', help="Don't open report in browser.")
parser.add_argument('--show_art_frames', action='store_true',
@@ -892,10 +911,18 @@ def main():
for record_file in args.record_file:
record_data.load_record_file(record_file, args.show_art_frames)
record_data.limit_percents(args.min_func_percent, args.min_callchain_percent)
+
+ def filter_lib(lib_name):
+ if not args.binary_filter:
+ return True
+ for binary in args.binary_filter:
+ if binary in lib_name:
+ return True
+ return False
if args.add_source_code:
- record_data.add_source_code(args.source_dirs)
+ record_data.add_source_code(args.source_dirs, filter_lib)
if args.add_disassembly:
- record_data.add_disassembly()
+ record_data.add_disassembly(filter_lib)
# 3. Generate report html.
report_generator = ReportGenerator(args.report_path)
diff --git a/simpleperf/scripts/test.py b/simpleperf/scripts/test.py
index 7eed5112..8424085b 100644
--- a/simpleperf/scripts/test.py
+++ b/simpleperf/scripts/test.py
@@ -577,6 +577,8 @@ class TestExampleWithNative(TestExampleBase):
def test_report_html(self):
self.common_test_report_html()
+ self.run_cmd(['report_html.py', '--add_source_code', '--source_dirs', 'testdata',
+ '--add_disassembly', '--binary_filter', "libnative-lib.so"])
class TestExampleWithNativeRoot(TestExampleBase):
@@ -1063,11 +1065,12 @@ class TestTools(unittest.TestCase):
}
objdump = Objdump(None, binary_cache_path)
for dso_path in test_map:
- dso_info = test_map[dso_path]
- disassemble_code = objdump.disassemble_code(dso_path, dso_info['start_addr'],
- dso_info['len'])
+ dso = test_map[dso_path]
+ dso_info = objdump.get_dso_info(dso_path)
+ self.assertIsNotNone(dso_info)
+ disassemble_code = objdump.disassemble_code(dso_info, dso['start_addr'], dso['len'])
self.assertTrue(disassemble_code)
- for item in dso_info['expected_items']:
+ for item in dso['expected_items']:
self.assertTrue(item in disassemble_code)
def test_readelf(self):
diff --git a/simpleperf/scripts/utils.py b/simpleperf/scripts/utils.py
index 81f29a33..22554830 100644
--- a/simpleperf/scripts/utils.py
+++ b/simpleperf/scripts/utils.py
@@ -354,6 +354,13 @@ def open_report_in_browser(report_path):
# webbrowser.get() doesn't work well on darwin/windows.
webbrowser.open_new_tab(report_path)
+def is_elf_file(path):
+ if os.path.isfile(path):
+ with open(path, 'rb') as fh:
+ data = fh.read(4)
+ if len(data) == 4 and bytes_to_str(data) == '\x7fELF':
+ return True
+ return False
def find_real_dso_path(dso_path_in_record_file, binary_cache_path):
""" Given the path of a shared library in perf.data, find its real path in the file system. """
@@ -361,9 +368,9 @@ def find_real_dso_path(dso_path_in_record_file, binary_cache_path):
return None
if binary_cache_path:
tmp_path = os.path.join(binary_cache_path, dso_path_in_record_file[1:])
- if os.path.isfile(tmp_path):
+ if is_elf_file(tmp_path):
return tmp_path
- if os.path.isfile(dso_path_in_record_file):
+ if is_elf_file(dso_path_in_record_file):
return dso_path_in_record_file
return None
@@ -559,19 +566,20 @@ class Objdump(object):
self.readelf = ReadElf(ndk_path)
self.objdump_paths = {}
- def disassemble_code(self, dso_path, start_addr, addr_len):
- """ Disassemble [start_addr, start_addr + addr_len] of dso_path.
- Return a list of pair (disassemble_code_line, addr).
- """
- # 1. Find real path.
+ def get_dso_info(self, dso_path):
real_path = find_real_dso_path(dso_path, self.binary_cache_path)
- if real_path is None:
+ if not real_path:
return None
-
- # 2. Get path of objdump.
arch = self.readelf.get_arch(real_path)
if arch == 'unknown':
return None
+ return (real_path, arch)
+
+ def disassemble_code(self, dso_info, start_addr, addr_len):
+ """ Disassemble [start_addr, start_addr + addr_len] of dso_path.
+ Return a list of pair (disassemble_code_line, addr).
+ """
+ real_path, arch = dso_info
objdump_path = self.objdump_paths.get(arch)
if not objdump_path:
objdump_path = find_tool_path('objdump', self.ndk_path, arch)
@@ -614,53 +622,57 @@ class ReadElf(object):
def get_arch(self, elf_file_path):
""" Get arch of an elf file. """
- try:
- output = subprocess.check_output([self.readelf_path, '-h', elf_file_path])
- if output.find('AArch64') != -1:
- return 'arm64'
- if output.find('ARM') != -1:
- return 'arm'
- if output.find('X86-64') != -1:
- return 'x86_64'
- if output.find('80386') != -1:
- return 'x86'
- except subprocess.CalledProcessError:
- pass
+ if is_elf_file(elf_file_path):
+ try:
+ output = subprocess.check_output([self.readelf_path, '-h', elf_file_path])
+ output = bytes_to_str(output)
+ if output.find('AArch64') != -1:
+ return 'arm64'
+ if output.find('ARM') != -1:
+ return 'arm'
+ if output.find('X86-64') != -1:
+ return 'x86_64'
+ if output.find('80386') != -1:
+ return 'x86'
+ except subprocess.CalledProcessError:
+ pass
return 'unknown'
def get_build_id(self, elf_file_path):
""" Get build id of an elf file. """
- try:
- output = subprocess.check_output([self.readelf_path, '-n', elf_file_path])
- output = bytes_to_str(output)
- result = re.search(r'Build ID:\s*(\S+)', output)
- if result:
- build_id = result.group(1)
- if len(build_id) < 40:
- build_id += '0' * (40 - len(build_id))
- else:
- build_id = build_id[:40]
- build_id = '0x' + build_id
- return build_id
- except subprocess.CalledProcessError:
- pass
+ if is_elf_file(elf_file_path):
+ try:
+ output = subprocess.check_output([self.readelf_path, '-n', elf_file_path])
+ output = bytes_to_str(output)
+ result = re.search(r'Build ID:\s*(\S+)', output)
+ if result:
+ build_id = result.group(1)
+ if len(build_id) < 40:
+ build_id += '0' * (40 - len(build_id))
+ else:
+ build_id = build_id[:40]
+ build_id = '0x' + build_id
+ return build_id
+ except subprocess.CalledProcessError:
+ pass
return ""
def get_sections(self, elf_file_path):
""" Get sections of an elf file. """
section_names = []
- try:
- output = subprocess.check_output([self.readelf_path, '-SW', elf_file_path])
- output = bytes_to_str(output)
- for line in output.split('\n'):
- # Parse line like:" [ 1] .note.android.ident NOTE 0000000000400190 ...".
- result = re.search(r'^\s+\[\s*\d+\]\s(.+?)\s', line)
- if result:
- section_name = result.group(1).strip()
- if section_name:
- section_names.append(section_name)
- except subprocess.CalledProcessError:
- pass
+ if is_elf_file(elf_file_path):
+ try:
+ output = subprocess.check_output([self.readelf_path, '-SW', elf_file_path])
+ output = bytes_to_str(output)
+ for line in output.split('\n'):
+ # Parse line like:" [ 1] .note.android.ident NOTE 0000000000400190 ...".
+ result = re.search(r'^\s+\[\s*\d+\]\s(.+?)\s', line)
+ if result:
+ section_name = result.group(1).strip()
+ if section_name:
+ section_names.append(section_name)
+ except subprocess.CalledProcessError:
+ pass
return section_names
def extant_dir(arg):