summaryrefslogtreecommitdiff
path: root/simpleperf
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2021-04-19 20:58:14 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2021-04-19 20:58:14 +0000
commit416226e55eb999283ad2ba5218c61788aeecfbec (patch)
tree9ea8e7e0504b25525e0a994684bde02c62a62b1c /simpleperf
parente34d9f9f480de89e0993820dbbb6286ef02e510a (diff)
parent36e87b914232fd83c96d18a09e4d065dc0087d43 (diff)
downloadextras-416226e55eb999283ad2ba5218c61788aeecfbec.tar.gz
Merge "simpleperf: support proguard mapping file in scripts."
Diffstat (limited to 'simpleperf')
-rwxr-xr-xsimpleperf/scripts/inferno/inferno.py11
-rwxr-xr-xsimpleperf/scripts/pprof_proto_generator.py6
-rwxr-xr-xsimpleperf/scripts/purgatorio/purgatorio.py10
-rwxr-xr-xsimpleperf/scripts/report_html.py13
-rw-r--r--simpleperf/scripts/simpleperf_report_lib.py10
-rwxr-xr-xsimpleperf/scripts/test/do_test.py7
-rw-r--r--simpleperf/scripts/test/inferno_test.py40
-rw-r--r--simpleperf/scripts/test/pprof_proto_generator_test.py11
-rw-r--r--simpleperf/scripts/test/purgatorio_test.py44
-rw-r--r--simpleperf/scripts/test/report_html_test.py41
-rw-r--r--simpleperf/scripts/test/report_lib_test.py6
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)