diff options
author | Yabin Cui <yabinc@google.com> | 2021-04-19 20:58:14 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2021-04-19 20:58:14 +0000 |
commit | 416226e55eb999283ad2ba5218c61788aeecfbec (patch) | |
tree | 9ea8e7e0504b25525e0a994684bde02c62a62b1c /simpleperf | |
parent | e34d9f9f480de89e0993820dbbb6286ef02e510a (diff) | |
parent | 36e87b914232fd83c96d18a09e4d065dc0087d43 (diff) | |
download | extras-416226e55eb999283ad2ba5218c61788aeecfbec.tar.gz |
Merge "simpleperf: support proguard mapping file in scripts."
Diffstat (limited to 'simpleperf')
-rwxr-xr-x | simpleperf/scripts/inferno/inferno.py | 11 | ||||
-rwxr-xr-x | simpleperf/scripts/pprof_proto_generator.py | 6 | ||||
-rwxr-xr-x | simpleperf/scripts/purgatorio/purgatorio.py | 10 | ||||
-rwxr-xr-x | simpleperf/scripts/report_html.py | 13 | ||||
-rw-r--r-- | simpleperf/scripts/simpleperf_report_lib.py | 10 | ||||
-rwxr-xr-x | simpleperf/scripts/test/do_test.py | 7 | ||||
-rw-r--r-- | simpleperf/scripts/test/inferno_test.py | 40 | ||||
-rw-r--r-- | simpleperf/scripts/test/pprof_proto_generator_test.py | 11 | ||||
-rw-r--r-- | simpleperf/scripts/test/purgatorio_test.py | 44 | ||||
-rw-r--r-- | simpleperf/scripts/test/report_html_test.py | 41 | ||||
-rw-r--r-- | simpleperf/scripts/test/report_lib_test.py | 6 |
11 files changed, 177 insertions, 22 deletions
diff --git a/simpleperf/scripts/inferno/inferno.py b/simpleperf/scripts/inferno/inferno.py index a58758a9..31954943 100755 --- a/simpleperf/scripts/inferno/inferno.py +++ b/simpleperf/scripts/inferno/inferno.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Copyright (C) 2016 The Android Open Source Project # @@ -36,6 +36,7 @@ import os import subprocess import sys +# fmt: off # pylint: disable=wrong-import-position SCRIPTS_PATH = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) sys.path.append(SCRIPTS_PATH) @@ -44,6 +45,7 @@ from simpleperf_utils import log_exit, log_fatal, log_info, AdbHelper, open_repo from data_types import Process from svg_renderer import get_proper_scaled_time_string, render_svg +# fmt: on def collect_data(args): @@ -109,6 +111,8 @@ def parse_samples(process, args, sample_filter_fn): lib.SetKallsymsFile(kallsyms_file) if args.show_art_frames: lib.ShowArtFrames(True) + for file_path in args.proguard_mapping_file or []: + lib.AddProguardMappingFile(file_path) process.cmd = lib.GetRecordCmd() product_props = lib.MetaInfo().get("product_props") if product_props: @@ -184,7 +188,7 @@ def output_report(process, args): event_entry = 'Event count: %s<br/>' % ("{:,}".format(process.num_events)) # TODO: collect capture duration info from perf.data. duration_entry = ("Duration: %s seconds<br/>" % args.capture_duration - ) if args.capture_duration else "" + ) if args.capture_duration else "" f.write("""<div style='display:inline-block;'> <font size='8'> Inferno Flamegraph Report%s</font><br/><br/> @@ -305,6 +309,8 @@ def main(): report_group.add_argument('--title', help='Show a title in the report.') report_group.add_argument('--show_art_frames', action='store_true', help='Show frames of internal methods in the ART Java interpreter.') + report_group.add_argument('--proguard-mapping-file', nargs='+', + help='Add proguard mapping file to de-obfuscate symbols') debug_group = parser.add_argument_group('Debug options') debug_group.add_argument('--disable_adb_root', action='store_true', help="""Force adb to run @@ -361,5 +367,6 @@ def main(): log_info("Flamegraph generated at '%s'." % report_path) + if __name__ == "__main__": main() diff --git a/simpleperf/scripts/pprof_proto_generator.py b/simpleperf/scripts/pprof_proto_generator.py index db2cde52..acac093b 100755 --- a/simpleperf/scripts/pprof_proto_generator.py +++ b/simpleperf/scripts/pprof_proto_generator.py @@ -290,6 +290,8 @@ class PprofProfileGenerator(object): if self.config.get('show_art_frames'): self.lib.ShowArtFrames() + for file_path in self.config['proguard_mapping_file'] or []: + self.lib.AddProguardMappingFile(file_path) # Process all samples in perf.data, aggregate samples. while True: @@ -614,6 +616,9 @@ def main(): parser.add_argument('--ndk_path', type=extant_dir, help='Set the path of a ndk release.') parser.add_argument('--show_art_frames', action='store_true', help='Show frames of internal methods in the ART Java interpreter.') + parser.add_argument( + '--proguard-mapping-file', nargs='+', + help='Add proguard mapping file to de-obfuscate symbols') args = parser.parse_args() if args.show: @@ -632,6 +637,7 @@ def main(): config['ndk_path'] = args.ndk_path config['show_art_frames'] = args.show_art_frames config['max_chain_length'] = args.max_chain_length + config['proguard_mapping_file'] = args.proguard_mapping_file generator = PprofProfileGenerator(config) for record_file in args.record_file: generator.load_record_file(record_file) diff --git a/simpleperf/scripts/purgatorio/purgatorio.py b/simpleperf/scripts/purgatorio/purgatorio.py index 3edc208d..0f072547 100755 --- a/simpleperf/scripts/purgatorio/purgatorio.py +++ b/simpleperf/scripts/purgatorio/purgatorio.py @@ -39,9 +39,11 @@ from bokeh.transform import jitter from bokeh.util.browser import view from functools import cmp_to_key +# fmt: off simpleperf_path = Path(__file__).absolute().parents[1] sys.path.insert(0, str(simpleperf_path)) import simpleperf_report_lib as sp +# fmt: on def create_graph(args, source, data_range): @@ -170,6 +172,9 @@ def generate_datasource(args): if not args.not_art: lib.ShowArtFrames(True) + for file_path in args.proguard_mapping_file or []: + lib.AddProguardMappingFile(file_path) + product = lib.MetaInfo().get('product_props') if product: @@ -264,7 +269,7 @@ def generate_datasource(args): def main(): parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument('input_file', type=str, help='input file') + parser.add_argument('-i', '--input_file', type=str, required=True, help='input file') parser.add_argument('--title', '-t', type=str, help='document title') parser.add_argument('--ksyms', '-k', type=str, help='path to kernel symbols (kallsyms)') parser.add_argument('--usyms', '-u', type=str, help='path to tree with user space symbols') @@ -275,6 +280,9 @@ def main(): help='Include dso names in backtraces') parser.add_argument('--include_symbols_addr', '-s', action='store_true', help='Include addresses of symbols in backtraces') + parser.add_argument( + '--proguard-mapping-file', nargs='+', + help='Add proguard mapping file to de-obfuscate symbols') args = parser.parse_args() # TODO test hierarchical ranges too diff --git a/simpleperf/scripts/report_html.py b/simpleperf/scripts/report_html.py index 17d53e92..582aa0c5 100755 --- a/simpleperf/scripts/report_html.py +++ b/simpleperf/scripts/report_html.py @@ -21,6 +21,7 @@ import datetime import json import os import sys +from typing import List, Optional from simpleperf_report_lib import ReportLib from simpleperf_utils import (Addr2Nearestline, get_script_dir, log_exit, log_info, Objdump, @@ -570,10 +571,12 @@ class RecordData(object): } """ - def __init__(self, binary_cache_path, ndk_path, build_addr_hit_map): + def __init__(self, binary_cache_path, ndk_path, build_addr_hit_map, + proguard_mapping_files: Optional[List[str]] = None): self.binary_cache_path = binary_cache_path self.ndk_path = ndk_path self.build_addr_hit_map = build_addr_hit_map + self.proguard_mapping_files = proguard_mapping_files self.meta_info = None self.cmdline = None self.arch = None @@ -594,6 +597,8 @@ class RecordData(object): lib.ShowArtFrames() if self.binary_cache_path: lib.SetSymfs(self.binary_cache_path) + for file_path in self.proguard_mapping_files or []: + lib.AddProguardMappingFile(file_path) self.meta_info = lib.MetaInfo() self.cmdline = lib.GetRecordCmd() self.arch = lib.GetArch() @@ -929,6 +934,9 @@ def main(): parser.add_argument('--aggregate-by-thread-name', action='store_true', help="""aggregate samples by thread name instead of thread id. This is useful for showing multiple perf.data generated for the same app.""") + parser.add_argument( + '--proguard-mapping-file', nargs='+', + help='Add proguard mapping file to de-obfuscate symbols') args = parser.parse_args() # 1. Process args. @@ -947,7 +955,8 @@ def main(): ndk_path = None if not args.ndk_path else args.ndk_path[0] # 2. Produce record data. - record_data = RecordData(binary_cache_path, ndk_path, build_addr_hit_map) + record_data = RecordData(binary_cache_path, ndk_path, + build_addr_hit_map, args.proguard_mapping_file) for record_file in args.record_file: record_data.load_record_file(record_file, args.show_art_frames) if args.aggregate_by_thread_name: diff --git a/simpleperf/scripts/simpleperf_report_lib.py b/simpleperf/scripts/simpleperf_report_lib.py index 1562b4b0..a3cee28d 100644 --- a/simpleperf/scripts/simpleperf_report_lib.py +++ b/simpleperf/scripts/simpleperf_report_lib.py @@ -22,7 +22,10 @@ import collections import ctypes as ct +from pathlib import Path import struct +from typing import Union + from simpleperf_utils import bytes_to_str, get_host_binary_path, is_windows, str_to_bytes @@ -252,6 +255,8 @@ class ReportLib(object): self._ShowIpForUnknownSymbolFunc = self._lib.ShowIpForUnknownSymbol self._ShowArtFramesFunc = self._lib.ShowArtFrames self._MergeJavaMethodsFunc = self._lib.MergeJavaMethods + self._AddProguardMappingFileFunc = self._lib.AddProguardMappingFile + self._AddProguardMappingFileFunc.restype = ct.c_bool self._GetNextSampleFunc = self._lib.GetNextSample self._GetNextSampleFunc.restype = ct.POINTER(SampleStruct) self._GetEventOfCurrentSampleFunc = self._lib.GetEventOfCurrentSample @@ -317,6 +322,11 @@ class ReportLib(object): """ self._MergeJavaMethodsFunc(self.getInstance(), merge) + def AddProguardMappingFile(self, mapping_file: Union[str, Path]): + """ Add proguard mapping.txt to de-obfuscate method names. """ + if not self._AddProguardMappingFileFunc(self.getInstance(), _char_pt(str(mapping_file))): + raise ValueError(f'failed to add proguard mapping file: {mapping_file}') + def SetKallsymsFile(self, kallsym_file): """ Set the file path to a copy of the /proc/kallsyms file (for off device decoding) """ cond = self._SetKallsymsFileFunc(self.getInstance(), _char_pt(kallsym_file)) diff --git a/simpleperf/scripts/test/do_test.py b/simpleperf/scripts/test/do_test.py index 447a77b9..182f6446 100755 --- a/simpleperf/scripts/test/do_test.py +++ b/simpleperf/scripts/test/do_test.py @@ -47,9 +47,11 @@ from . app_test import * from . binary_cache_builder_test import * from . cpp_app_test import * from . debug_unwind_reporter_test import * +from . inferno_test import * from . java_app_test import * from . kotlin_app_test import * from . pprof_proto_generator_test import * +from . purgatorio_test import * from . report_html_test import * from . report_lib_test import * from . run_simpleperf_on_device_test import * @@ -109,8 +111,9 @@ def get_test_type(test: str) -> Optional[str]: return 'device_test' if testcase_name.startswith('TestExample'): return 'device_test' - if testcase_name in ('TestBinaryCacheBuilder', 'TestDebugUnwindReporter', - 'TestPprofProtoGenerator', 'TestReportHtml', 'TestReportLib', 'TestTools'): + if testcase_name in ('TestBinaryCacheBuilder', 'TestDebugUnwindReporter', 'TestInferno', + 'TestPprofProtoGenerator', 'TestPurgatorio', 'TestReportHtml', + 'TestReportLib', 'TestTools'): return 'host_test' return None diff --git a/simpleperf/scripts/test/inferno_test.py b/simpleperf/scripts/test/inferno_test.py new file mode 100644 index 00000000..2ca4f5d9 --- /dev/null +++ b/simpleperf/scripts/test/inferno_test.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import collections +import json +from typing import Any, Dict, List + +from . test_utils import INFERNO_SCRIPT, TestBase, TestHelper + + +class TestInferno(TestBase): + def get_report(self, options: List[str]) -> str: + self.run_cmd([INFERNO_SCRIPT] + options) + with open('report.html', 'r') as fh: + return fh.read() + + def test_proguard_mapping_file(self): + """ Test --proguard-mapping-file option. """ + testdata_file = TestHelper.testdata_path('perf_need_proguard_mapping.data') + proguard_mapping_file = TestHelper.testdata_path('proguard_mapping.txt') + original_methodname = 'androidx.fragment.app.FragmentActivity.startActivityForResult' + # Can't show original method name without proguard mapping file. + self.assertNotIn(original_methodname, self.get_report( + ['--record_file', testdata_file, '-sc'])) + # Show original method name with proguard mapping file. + self.assertIn(original_methodname, self.get_report( + ['--record_file', testdata_file, '-sc', '--proguard-mapping-file', proguard_mapping_file])) diff --git a/simpleperf/scripts/test/pprof_proto_generator_test.py b/simpleperf/scripts/test/pprof_proto_generator_test.py index 2b194325..3b98a0f4 100644 --- a/simpleperf/scripts/test/pprof_proto_generator_test.py +++ b/simpleperf/scripts/test/pprof_proto_generator_test.py @@ -99,3 +99,14 @@ class TestPprofProtoGenerator(TestBase): # pylint: disable=no-member self.assertGreater(len(profile_both.sample), len(profile1.sample)) self.assertGreater(len(profile_both.sample), len(profile2.sample)) + + def test_proguard_mapping_file(self): + """ Test --proguard-mapping-file option. """ + testdata_file = 'perf_need_proguard_mapping.data' + proguard_mapping_file = TestHelper.testdata_path('proguard_mapping.txt') + original_methodname = 'androidx.fragment.app.FragmentActivity.startActivityForResult' + # Can't show original method name without proguard mapping file. + self.assertNotIn(original_methodname, self.run_generator(testdata_file=testdata_file)) + # Show original method name with proguard mapping file. + self.assertIn(original_methodname, self.run_generator( + ['--proguard-mapping-file', proguard_mapping_file], testdata_file)) diff --git a/simpleperf/scripts/test/purgatorio_test.py b/simpleperf/scripts/test/purgatorio_test.py new file mode 100644 index 00000000..e814c42d --- /dev/null +++ b/simpleperf/scripts/test/purgatorio_test.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2021 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import collections +import json +import os +from typing import Any, Dict, List + +from . test_utils import TestBase, TestHelper + + +class TestPurgatorio(TestBase): + def setUp(self): + super().setUp() + self.script_path = os.path.join('purgatorio', 'purgatorio.py') + + def get_report(self, options: List[str]) -> str: + self.run_cmd([self.script_path, '-d', '-o', 'report.html'] + options) + with open('report.html', 'r') as fh: + return fh.read() + + def test_proguard_mapping_file(self): + """ Test --proguard-mapping-file option. """ + testdata_file = TestHelper.testdata_path('perf_need_proguard_mapping.data') + proguard_mapping_file = TestHelper.testdata_path('proguard_mapping.txt') + original_methodname = 'androidx.fragment.app.FragmentActivity.startActivityForResult' + # Can't show original method name without proguard mapping file. + self.assertNotIn(original_methodname, self.get_report(['-i', testdata_file])) + # Show original method name with proguard mapping file. + self.assertIn(original_methodname, self.get_report( + ['--proguard-mapping-file', proguard_mapping_file, '-i', testdata_file])) diff --git a/simpleperf/scripts/test/report_html_test.py b/simpleperf/scripts/test/report_html_test.py index 824d4ecf..0a9dadc1 100644 --- a/simpleperf/scripts/test/report_html_test.py +++ b/simpleperf/scripts/test/report_html_test.py @@ -16,6 +16,7 @@ import collections import json +from typing import Any, Dict, List from . test_utils import TestBase, TestHelper @@ -29,10 +30,9 @@ class TestReportHtml(TestBase): # Calculate event_count for each thread name before aggregation. event_count_for_thread_name = collections.defaultdict(lambda: 0) # use "--min_func_percent 0" to avoid cutting any thread. - self.run_cmd(['report_html.py', '--min_func_percent', '0', '-i', - TestHelper.testdata_path('aggregatable_perf1.data'), - TestHelper.testdata_path('aggregatable_perf2.data')]) - record_data = self._load_record_data_in_html('report.html') + record_data = self.get_record_data(['--min_func_percent', '0', '-i', + TestHelper.testdata_path('aggregatable_perf1.data'), + TestHelper.testdata_path('aggregatable_perf2.data')]) event = record_data['sampleInfo'][0] for process in event['processes']: for thread in process['threads']: @@ -40,11 +40,10 @@ class TestReportHtml(TestBase): event_count_for_thread_name[thread_name] += thread['eventCount'] # Check event count for each thread after aggregation. - self.run_cmd(['report_html.py', '--aggregate-by-thread-name', - '--min_func_percent', '0', '-i', - TestHelper.testdata_path('aggregatable_perf1.data'), - TestHelper.testdata_path('aggregatable_perf2.data')]) - record_data = self._load_record_data_in_html('report.html') + record_data = self.get_record_data(['--aggregate-by-thread-name', + '--min_func_percent', '0', '-i', + TestHelper.testdata_path('aggregatable_perf1.data'), + TestHelper.testdata_path('aggregatable_perf2.data')]) event = record_data['sampleInfo'][0] hit_count = 0 for process in event['processes']: @@ -58,20 +57,32 @@ class TestReportHtml(TestBase): def test_no_empty_process(self): """ Test not showing a process having no threads. """ perf_data = TestHelper.testdata_path('two_process_perf.data') - self.run_cmd(['report_html.py', '-i', perf_data]) - record_data = self._load_record_data_in_html('report.html') + record_data = self.get_record_data(['-i', perf_data]) processes = record_data['sampleInfo'][0]['processes'] self.assertEqual(len(processes), 2) # One process is removed because all its threads are removed for not # reaching the min_func_percent limit. - self.run_cmd(['report_html.py', '-i', perf_data, '--min_func_percent', '20']) - record_data = self._load_record_data_in_html('report.html') + record_data = self.get_record_data(['-i', perf_data, '--min_func_percent', '20']) processes = record_data['sampleInfo'][0]['processes'] self.assertEqual(len(processes), 1) - def _load_record_data_in_html(self, html_file): - with open(html_file, 'r') as fh: + def test_proguard_mapping_file(self): + """ Test --proguard-mapping-file option. """ + testdata_file = TestHelper.testdata_path('perf_need_proguard_mapping.data') + proguard_mapping_file = TestHelper.testdata_path('proguard_mapping.txt') + original_methodname = 'androidx.fragment.app.FragmentActivity.startActivityForResult' + # Can't show original method name without proguard mapping file. + record_data = self.get_record_data(['-i', testdata_file]) + self.assertNotIn(original_methodname, json.dumps(record_data)) + # Show original method name with proguard mapping file. + record_data = self.get_record_data( + ['-i', testdata_file, '--proguard-mapping-file', proguard_mapping_file]) + self.assertIn(original_methodname, json.dumps(record_data)) + + def get_record_data(self, options: List[str]) -> Dict[str, Any]: + self.run_cmd(['report_html.py'] + options) + with open('report.html', 'r') as fh: data = fh.read() start_str = 'type="application/json"' end_str = '</script>' diff --git a/simpleperf/scripts/test/report_lib_test.py b/simpleperf/scripts/test/report_lib_test.py index 809fc75e..ad09f0e4 100644 --- a/simpleperf/scripts/test/report_lib_test.py +++ b/simpleperf/scripts/test/report_lib_test.py @@ -194,3 +194,9 @@ class TestReportLib(TestBase): else: self.assertIsNone(tracing_data) self.assertTrue(has_dynamic_field) + + def test_add_proguard_mapping_file(self): + with self.assertRaises(ValueError): + self.report_lib.AddProguardMappingFile('non_exist_file') + proguard_mapping_file = TestHelper.testdata_path('proguard_mapping.txt') + self.report_lib.AddProguardMappingFile(proguard_mapping_file) |