summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2017-07-17 18:27:29 +0000
committerandroid-build-merger <android-build-merger@google.com>2017-07-17 18:27:29 +0000
commit813e6cbea9062525586daca79cbeb2b09cd538f0 (patch)
tree83ed9eb15e969b034ed50625ce67e186e1062d5d
parent525b125bed44833e9899f5fdcd38b29c409dfaa1 (diff)
parent1688f3939797b09fef7f87468192ce6850aec89c (diff)
downloadextras-813e6cbea9062525586daca79cbeb2b09cd538f0.tar.gz
Merge "simpleperf: replace config file with cmdline options." am: e5ad887a62 am: 271a040687 am: ac882615b5
am: 1688f39397 Change-Id: I5c923e680e63db67e022a7ae447ba66fed9b6f63
-rw-r--r--simpleperf/README.md22
-rw-r--r--simpleperf/demo/README.md25
-rw-r--r--simpleperf/scripts/annotate.config40
-rw-r--r--simpleperf/scripts/annotate.py74
-rw-r--r--simpleperf/scripts/app_profiler.config22
-rw-r--r--simpleperf/scripts/app_profiler.py114
-rw-r--r--simpleperf/scripts/binary_cache_builder.config27
-rw-r--r--simpleperf/scripts/binary_cache_builder.py41
-rw-r--r--simpleperf/scripts/pprof_proto_generator.config39
-rw-r--r--simpleperf/scripts/pprof_proto_generator.py61
-rw-r--r--simpleperf/scripts/utils.py90
11 files changed, 315 insertions, 240 deletions
diff --git a/simpleperf/README.md b/simpleperf/README.md
index 126fbc65..5311f03d 100644
--- a/simpleperf/README.md
+++ b/simpleperf/README.md
@@ -31,7 +31,6 @@ Bugs and feature requests can be submitted at http://github.com/android-ndk/ndk/
- [Answers to common issues](#answers-to-common-issues)
- [The correct way to pull perf.data on host](#the-correct-way-to-pull-perfdata-on-host)
-
## Simpleperf introduction
### Why simpleperf
@@ -777,23 +776,16 @@ generate profiling data in a format acceptable by pprof.
`annotate.py` reads perf.data, binaries in `binary-cache` (collected by `app-profiler.py`)
and source code, and generates annoated source code in `annotated_files/`.
-It is configured by `annotate.config`.
-
-**1. Fill `annotate.config`**
-
- Change `source_dirs` line to source_dirs = ["../SimpleperfExamplePureJava"]
- Change `addr2line_path` line to addr2line_path = "addr2line"
-
-`addr2line` is need to annotate source code. It can be found in Android ndk release.
-Please set `addr2line_path` to the location of `addr2line` if it can't be found
-in PATH environment variable.
-
-**2. Run `annotate.py`**
+**1. Run annotate.py**
- $ python annotate.py
+ $ python annotate.py -s ../SimpleperfExamplePureJava
+`addr2line` is need to annotate source code. It can be found in Android ndk
+release, in paths like toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-addr2line.
+Please use `--addr2line` option to set the path of `addr2line` if annotate.py
+can't find it.
-**3. Read annotated code**
+**2. Read annotated code**
The annotated source code is located at `annotated_files/`.
`annotated_files/summary` shows how each source file is annotated.
diff --git a/simpleperf/demo/README.md b/simpleperf/demo/README.md
index 1ecd9dd5..2d2c377a 100644
--- a/simpleperf/demo/README.md
+++ b/simpleperf/demo/README.md
@@ -46,10 +46,7 @@ $ adb install -r app/build/outputs/apk/app-profiling.apk
2. Record profiling data:
```
$ cd ../../scripts/
-$ gvim app_profiler.config
- change app_package_name line to: app_package_name = "com.example.simpleperf.simpleperfexamplepurejava"
-$ python app_profiler.py
- It runs the application and collects profiling data in perf.data, binaries on device in binary_cache/.
+$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplepurejava
```
3. Show profiling data:
@@ -61,9 +58,7 @@ a. show call graph in txt mode
b. show call graph in gui mode
$ python report.py -g
c. show samples in source code
- $ gvim annotate.config
- change source_dirs line to: source_dirs = ["../demo/SimpleperfExamplePureJava"]
- $ python annotate.py
+ $ python annotate.py -s ../demo/SimpleperfExamplePureJava
$ gvim annotated_files/java/com/example/simpleperf/simpleperfexamplepurejava/MainActivity.java
check the annoated source file MainActivity.java.
```
@@ -89,9 +84,7 @@ $ adb install -r app/build/outputs/apk/app-profiling.apk
2. Record profiling data:
```
$ cd ../../scripts/
-$ gvim app_profiler.config
- change app_package_name line to: app_package_name = "com.example.simpleperf.simpleperfexamplewithnative"
-$ python app_profiler.py
+$ python app_profiler.py -p com.example.simpleperf.simpleperfexamplewithnative
It runs the application and collects profiling data in perf.data, binaries on device in binary_cache/.
```
@@ -104,9 +97,7 @@ a. show call graph in txt mode
b. show call graph in gui mode
$ python report.py -g
c. show samples in source code
- $ gvim annotate.config
- change source_dirs line to: source_dirs = ["../demo/SimpleperfExampleWithNative"]
- $ python annotate.py
+ $ python annotate.py -s ../demo/SimpleperfExampleWithNative
$ find . -name "native-lib.cpp" | xargs gvim
check the annoated source file native-lib.cpp.
```
@@ -132,9 +123,7 @@ $ adb install -r app/build/outputs/apk/profiling/app-profiling.apk
2. Record profiling data:
```
$ cd ../../scripts/
-$ gvim app_profiler.config
- change app_package_name line to: app_package_name = "com.example.simpleperf.simpleperfexampleofkotlin"
-$ python app_profiler.py
+$ python app_profiler.py -p com.example.simpleperf.simpleperfexampleofkotlin
It runs the application and collects profiling data in perf.data, binaries on device in binary_cache/.
```
@@ -147,9 +136,7 @@ a. show call graph in txt mode
b. show call graph in gui mode
$ python report.py -g
c. show samples in source code
- $ gvim annotate.config
- change source_dirs line to: source_dirs = ["../demo/SimpleperfExampleOfKotlin"]
- $ python annotate.py
+ $ python annotate.py -s ../demo/SimpleperfExampleOfKotlin
$ find . -name "MainActivity.kt" | xargs gvim
check the annoated source file MainActivity.kt.
```
diff --git a/simpleperf/scripts/annotate.config b/simpleperf/scripts/annotate.config
deleted file mode 100644
index 2e5db555..00000000
--- a/simpleperf/scripts/annotate.config
+++ /dev/null
@@ -1,40 +0,0 @@
-# This configuration is written in python and used by annotate.py.
-
-import os
-
-# A list of profiling record files. By default it only contains perf.data.
-perf_data_list = ["perf.data"]
-
-
-# Directory used to read binaries with debug info. Ideally, it should be
-# set to the path of binary_cache_dir collected by app_profiler.py.
-# Set to "" if not available.
-symfs_dir = "binary_cache"
-
-
-# File path used to find kernel symbols. Set to "" if not available.
-kallsyms = ""
-
-
-# A list of directories used to find source files.
-source_dirs = []
-
-
-# Directory used to output annotated source files.
-annotate_dest_dir = "annotated_files"
-
-
-# Sample Filters
-# Use samples only in threads with selected names.
-comm_filters = []
-# Use samples only in processes with selected process ids.
-pid_filters = []
-# Use samples only in threads with selected thread ids.
-tid_filters = []
-# Use samples only in selected binaries.
-dso_filters = []
-
-
-# We use addr2line to map virtual address to source file and source line.
-# So set the path to addr2line here.
-addr2line_path = "addr2line" \ No newline at end of file
diff --git a/simpleperf/scripts/annotate.py b/simpleperf/scripts/annotate.py
index 2b253bd7..27414ac6 100644
--- a/simpleperf/scripts/annotate.py
+++ b/simpleperf/scripts/annotate.py
@@ -57,7 +57,12 @@ class Addr2Line(object):
"""
def __init__(self, addr2line_path, symfs_dir=None):
self.dso_dict = dict()
- self.addr2line_path = addr2line_path
+ if addr2line_path and is_executable_available(addr2line_path):
+ self.addr2line_path = addr2line_path
+ else:
+ self.addr2line_path = find_tool_path('addr2line')
+ if not self.addr2line_path:
+ log_exit("Can't find addr2line.")
self.symfs_dir = symfs_dir
@@ -267,27 +272,28 @@ class SourceFileAnnotator(object):
"""group code for annotating source files"""
def __init__(self, config):
# check config variables
- config_names = ['perf_data_list', 'symfs_dir', 'source_dirs',
- 'annotate_dest_dir', 'comm_filters', 'pid_filters',
- 'tid_filters', 'dso_filters', 'addr2line_path']
+ config_names = ['perf_data_list', 'source_dirs', 'comm_filters',
+ 'pid_filters', 'tid_filters', 'dso_filters', 'addr2line_path']
for name in config_names:
if name not in config:
- log_fatal('config [%s] is missing' % name)
- symfs_dir = config['symfs_dir']
- if symfs_dir and not os.path.isdir(symfs_dir):
- log_fatal('[symfs_dir] "%s" is not a dir' % symfs_dir)
- kallsyms = config['kallsyms']
- if kallsyms and not os.path.isfile(kallsyms):
- log_fatal('[kallsyms] "%s" is not a file' % kallsyms)
+ log_exit('config [%s] is missing' % name)
+ symfs_dir = 'binary_cache'
+ if not os.path.isdir(symfs_dir):
+ symfs_dir = None
+ kallsyms = 'binary_cache/kallsyms'
+ if not os.path.isfile(kallsyms):
+ kallsyms = None
source_dirs = config['source_dirs']
for dir in source_dirs:
if not os.path.isdir(dir):
- log_fatal('[source_dirs] "%s" is not a dir' % dir)
+ log_exit('[source_dirs] "%s" is not a dir' % dir)
+ if not config['source_dirs']:
+ log_exit('Please set source directories.')
# init member variables
self.config = config
- self.symfs_dir = config.get('symfs_dir')
- self.kallsyms = config.get('kallsyms')
+ self.symfs_dir = symfs_dir
+ self.kallsyms = kallsyms
self.comm_filter = set(config['comm_filters']) if config.get('comm_filters') else None
if config.get('pid_filters'):
self.pid_filter = {int(x) for x in config['pid_filters']}
@@ -299,6 +305,7 @@ class SourceFileAnnotator(object):
self.tid_filter = None
self.dso_filter = set(config['dso_filters']) if config.get('dso_filters') else None
+ config['annotate_dest_dir'] = 'annotated_files'
output_dir = config['annotate_dest_dir']
if os.path.isdir(output_dir):
shutil.rmtree(output_dir)
@@ -623,14 +630,41 @@ class SourceFileAnnotator(object):
wf.write(annotate)
wf.write(lines[line-1])
+def main():
+ parser = argparse.ArgumentParser(description=
+"""Annotate source files based on profiling data. It reads line information from
+binary_cache generated by app_profiler.py or binary_cache_builder.py, and
+generate annotated source files in annotated_files directory.""")
+ parser.add_argument('-i', '--perf_data_list', nargs='+', action='append', help=
+"""The paths of profiling data. Default is perf.data.""")
+ parser.add_argument('-s', '--source_dirs', nargs='+', action='append', help=
+"""Directories to find source files.""")
+ parser.add_argument('--comm', nargs='+', action='append', help=
+"""Use samples only in threads with selected names.""")
+ parser.add_argument('--pid', nargs='+', action='append', help=
+"""Use samples only in processes with selected process ids.""")
+ parser.add_argument('--tid', nargs='+', action='append', help=
+"""Use samples only in threads with selected thread ids.""")
+ parser.add_argument('--dso', nargs='+', action='append', help=
+"""Use samples only in selected binaries.""")
+ parser.add_argument('--addr2line', help=
+"""Set the path of addr2line.""")
-if __name__ == '__main__':
- parser = argparse.ArgumentParser(
- description='Annotate based on perf.data. See configurations in annotate.config.')
- parser.add_argument('--config', default='annotate.config',
- help='Set configuration file. Default is annotate.config.')
args = parser.parse_args()
- config = load_config(args.config)
+ config = {}
+ config['perf_data_list'] = flatten_arg_list(args.perf_data_list)
+ if not config['perf_data_list']:
+ config['perf_data_list'].append('perf.data')
+ config['source_dirs'] = flatten_arg_list(args.source_dirs)
+ config['comm_filters'] = flatten_arg_list(args.comm)
+ config['pid_filters'] = flatten_arg_list(args.pid)
+ config['tid_filters'] = flatten_arg_list(args.tid)
+ config['dso_filters'] = flatten_arg_list(args.dso)
+ config['addr2line_path'] = args.addr2line
+
annotator = SourceFileAnnotator(config)
annotator.annotate()
log_info('annotate finish successfully, please check result in annotated_files/.')
+
+if __name__ == '__main__':
+ main()
diff --git a/simpleperf/scripts/app_profiler.config b/simpleperf/scripts/app_profiler.config
index 752b85f7..9102cf0c 100644
--- a/simpleperf/scripts/app_profiler.config
+++ b/simpleperf/scripts/app_profiler.config
@@ -4,7 +4,7 @@ import os
import os.path
# The name of the android package, like com.example.android.
-app_package_name = "com.example.android"
+app_package_name = ""
# Path of android studio project. It is used to find debug version of native shared libraries.
@@ -43,11 +43,6 @@ launch_activity = '.MainActivity'
launch_inst_test = ''
-if recompile_app and not launch_activity and not launch_inst_test:
- raise Exception('one of [launch_activity or launch_inst_test] is'
- + 'needed for [recompile_app] to take effect.')
-
-
# Profiling record options that will be passed directly to `simpleperf record` command on device.
# You can set how long to profile using "--duration" option, or use Ctrl-C to stop profiling.
record_options = "-e cpu-cycles:u -f 4000 -g --duration 10"
@@ -57,15 +52,6 @@ record_options = "-e cpu-cycles:u -f 4000 -g --duration 10"
perf_data_path = "perf.data"
-# The path of adb.
-adb_path = "adb"
-
-
-# The path of readelf, used to read build id of files in binary cache.
-# Set to "" if not available.
-readelf_path = "readelf"
-
-
-# binary_cache_dir is used to cache binaries pulled from device. To report precisely, we pull each
-# binary hit by perf.data on host.
-binary_cache_dir = "binary_cache"
+# Collect binaries used in profiling data from device to binary_cache directory.
+# It can be used to annotate source code.
+collect_binaries = True
diff --git a/simpleperf/scripts/app_profiler.py b/simpleperf/scripts/app_profiler.py
index e2a137f6..683d6b42 100644
--- a/simpleperf/scripts/app_profiler.py
+++ b/simpleperf/scripts/app_profiler.py
@@ -43,27 +43,34 @@ class AppProfiler(object):
3. Collect profiling data.
"""
def __init__(self, config):
- # check config variables
+ self.check_config(config)
+ self.config = config
+ self.adb = AdbHelper()
+ self.is_root_device = False
+ self.android_version = 0
+ self.device_arch = None
+ self.app_arch = None
+ self.app_pid = None
+
+
+ def check_config(self, config):
config_names = ['app_package_name', 'native_lib_dir', 'apk_file_path',
'recompile_app', 'launch_activity', 'launch_inst_test',
- 'record_options', 'perf_data_path', 'adb_path', 'readelf_path',
- 'binary_cache_dir']
+ 'record_options', 'perf_data_path']
for name in config_names:
if name not in config:
- log_fatal('config [%s] is missing' % name)
+ log_exit('config [%s] is missing' % name)
+ if not config['app_package_name']:
+ log_exit("The package name of the application hasn't been set")
native_lib_dir = config.get('native_lib_dir')
if native_lib_dir and not os.path.isdir(native_lib_dir):
- log_fatal('[native_lib_dir] "%s" is not a dir' % native_lib_dir)
+ log_exit('[native_lib_dir] "%s" is not a dir' % native_lib_dir)
apk_file_path = config.get('apk_file_path')
if apk_file_path and not os.path.isfile(apk_file_path):
- log_fatal('[apk_file_path] "%s" is not a file' % apk_file_path)
- self.config = config
- self.adb = AdbHelper(self.config['adb_path'])
- self.is_root_device = False
- self.android_version = 0
- self.device_arch = None
- self.app_arch = None
- self.app_pid = None
+ log_exit('[apk_file_path] "%s" is not a file' % apk_file_path)
+ if config['recompile_app']:
+ if not config['launch_activity'] and not config['launch_inst_test']:
+ log_exit('one of launch_activity and launch_inst_test is needed for recompile app')
def profile(self):
@@ -159,13 +166,13 @@ class AppProfiler(object):
activity = self.config['app_package_name'] + '/' + self.config['launch_activity']
result = self.adb.run(['shell', 'am', 'start', '-n', activity])
if not result:
- log_fatal("Can't start activity %s" % activity)
+ log_exit("Can't start activity %s" % activity)
else:
runner = self.config['app_package_name'] + '/android.support.test.runner.AndroidJUnitRunner'
result = self.adb.run(['shell', 'am', 'instrument', '-e', 'class',
self.config['launch_inst_test'], runner])
if not result:
- log_fatal("Can't start instrumentation test %s" % self.config['launch_inst_test'])
+ log_exit("Can't start instrumentation test %s" % self.config['launch_inst_test'])
for i in range(10):
pid = self._find_app_process()
@@ -173,7 +180,7 @@ class AppProfiler(object):
return
time.sleep(1)
log_info('Wait for the app process for %d seconds' % (i + 1))
- log_fatal("Can't find the app process")
+ log_exit("Can't find the app process")
def _find_app_process(self):
@@ -192,7 +199,7 @@ class AppProfiler(object):
def _get_app_environment(self):
self.app_pid = self._find_app_process()
if self.app_pid is None:
- log_fatal("can't find process for app [%s]" % self.config['app_package_name'])
+ log_exit("can't find process for app [%s]" % self.config['app_package_name'])
if self.device_arch in ['aarch64', 'x86_64']:
output = self.run_in_app_dir(['cat', '/proc/%d/maps' % self.app_pid])
if output.find('linker64') != -1:
@@ -300,12 +307,14 @@ class AppProfiler(object):
self.run_in_app_dir(['cat perf.data | tee /data/local/tmp/perf.data >/dev/null'])
self.adb.check_run_and_return_output(['pull', '/data/local/tmp/perf.data',
self.config['perf_data_path']])
- config = copy.copy(self.config)
- config['symfs_dirs'] = []
- if self.config['native_lib_dir']:
- config['symfs_dirs'].append(self.config['native_lib_dir'])
- binary_cache_builder = BinaryCacheBuilder(config)
- binary_cache_builder.build_binary_cache()
+ if self.config['collect_binaries']:
+ config = copy.copy(self.config)
+ config['binary_cache_dir'] = 'binary_cache'
+ config['symfs_dirs'] = []
+ if self.config['native_lib_dir']:
+ config['symfs_dirs'].append(self.config['native_lib_dir'])
+ binary_cache_builder = BinaryCacheBuilder(config)
+ binary_cache_builder.build_binary_cache()
def run_in_app_dir(self, args, stdout_file=None, check_result=True):
@@ -323,13 +332,62 @@ class AppProfiler(object):
else:
return ['shell', 'run-as', self.config['app_package_name']] + args
-
-if __name__ == '__main__':
+def main():
parser = argparse.ArgumentParser(
- description='Profile an android app. See configurations in app_profiler.config.')
- parser.add_argument('--config', default='app_profiler.config',
- help='Set configuration file. Default is app_profiler.config.')
+ description=
+"""Profile an android app. See configurations in app_profiler.config.""")
+ parser.add_argument('--config', default='app_profiler.config', help=
+"""Set configuration file. Default is app_profiler.config. The configurations
+can be overridden by options in cmdline.""")
+ parser.add_argument('-p', '--package_name', help=
+"""The package name of the profiled Android app.""")
+ parser.add_argument('-lib', '--native_lib_dir', help=
+"""Path to find debug version of native shared libraries used in the app.""")
+ parser.add_argument('-nc', '--skip_recompile', action='store_true', help=
+"""By default we recompile java bytecode to native instructions to profile java
+code. It takes some time. You can skip it if the code has been compiled or you
+don't need to profile java code.""")
+ parser.add_argument('--apk', help=
+"""Apk file of the profiled app, used on Android version <= M, which needs to
+reinstall the app to recompile it.""")
+ parser.add_argument('-a', '--activity', help=
+"""Start an activity before profiling. It can be used to profile the startup
+time of an activity. Default is .MainActivity.""")
+ parser.add_argument('-t', '--test', help=
+"""Start an instrumentation test before profiling. It can be used to profile
+an instrumentation test.""")
+ parser.add_argument('-r', '--record_options', help=
+"""Set options for `simpleperf record` command. Default is "-e cpu-cycles:u -f 4000 -g --duration 10".""")
+ parser.add_argument('-o', '--perf_data_path', help=
+"""The path to store profiling data. Default is perf.data.""")
+ parser.add_argument('-nb', '--skip_collect_binaries', action='store_true', help=
+"""By default we collect binaries used in profiling data from device to
+binary_cache directory. It can be used to annotate source code. This option skips it.""")
args = parser.parse_args()
config = load_config(args.config)
+ if args.package_name:
+ config['app_package_name'] = args.package_name
+ if args.native_lib_dir:
+ config['native_lib_dir'] = args.native_lib_dir
+ if args.skip_recompile:
+ config['recompile_app'] = False
+ if args.apk:
+ config['apk'] = args.apk
+ if args.activity:
+ config['launch_activity'] = args.activity
+ config['launch_inst_test'] = None
+ if args.test:
+ config['launch_inst_test'] = args.test
+ config['launch_activity'] = None
+ if args.record_options:
+ config['record_options'] = args.record_options
+ if args.perf_data_path:
+ config['perf_data_path'] = args.perf_data_path
+ if args.skip_collect_binaries:
+ config['collect_binaries'] = False
+
profiler = AppProfiler(config)
profiler.profile()
+
+if __name__ == '__main__':
+ main() \ No newline at end of file
diff --git a/simpleperf/scripts/binary_cache_builder.config b/simpleperf/scripts/binary_cache_builder.config
deleted file mode 100644
index 49cc5ae7..00000000
--- a/simpleperf/scripts/binary_cache_builder.config
+++ /dev/null
@@ -1,27 +0,0 @@
-# This configuration is written in python and used by binary_cache_builder.py.
-
-import os
-import os.path
-
-# path of profiling record data.
-perf_data_path = "perf.data"
-
-
-# directories to find binaries with symbols and debug information.
-# If binaries are found in any of these directories, having the same build_id
-# as the one recorded in perf.data, then we copy the binary in the directory
-# instead of pulling the binary from device.
-symfs_dirs = []
-
-
-# directory to cache binaries. To report precisely, we pull needed binaries
-# to host. However, We don't need to pull a binary if there is already a binary
-# in binary_cache_dir having the same build_id as the one on device.
-binary_cache_dir = "binary_cache"
-
-
-# path of adb.
-adb_path = "adb"
-
-# path of readelf, set to "" if not available.
-readelf_path = "readelf" \ No newline at end of file
diff --git a/simpleperf/scripts/binary_cache_builder.py b/simpleperf/scripts/binary_cache_builder.py
index 950e2fb7..4ce4a320 100644
--- a/simpleperf/scripts/binary_cache_builder.py
+++ b/simpleperf/scripts/binary_cache_builder.py
@@ -36,22 +36,23 @@ from utils import *
class BinaryCacheBuilder(object):
"""Collect all binaries needed by perf.data in binary_cache."""
def __init__(self, config):
- config_names = ['perf_data_path', 'symfs_dirs', 'adb_path',
- 'readelf_path', 'binary_cache_dir']
+ config_names = ['perf_data_path', 'symfs_dirs']
for name in config_names:
if name not in config:
- log_fatal('config for "%s" is missing' % name)
+ log_exit('config for "%s" is missing' % name)
self.perf_data_path = config.get('perf_data_path')
if not os.path.isfile(self.perf_data_path):
- log_fatal("can't find file %s" % self.perf_data_path)
+ log_exit("can't find file %s" % self.perf_data_path)
self.symfs_dirs = config.get('symfs_dirs')
for symfs_dir in self.symfs_dirs:
if not os.path.isdir(symfs_dir):
- log_fatal("symfs_dir '%s' is not a directory" % symfs_dir)
- self.adb = AdbHelper(config['adb_path'])
- self.readelf_path = config['readelf_path']
- self.binary_cache_dir = config['binary_cache_dir']
+ log_exit("symfs_dir '%s' is not a directory" % symfs_dir)
+ self.adb = AdbHelper()
+ self.readelf_path = find_tool_path('readelf')
+ if not self.readelf_path and self.symfs_dirs:
+ log_warning("Debug shared libraries on host are not used because can't find readelf.")
+ self.binary_cache_dir = 'binary_cache'
if not os.path.isdir(self.binary_cache_dir):
os.makedirs(self.binary_cache_dir)
@@ -227,12 +228,22 @@ class BinaryCacheBuilder(object):
self.adb.run(['pull', '/proc/kallsyms', file])
-if __name__ == '__main__':
- parser = argparse.ArgumentParser(
- description="Pull binaries needed by perf.data from device to binary_cache.")
- parser.add_argument('--config', default='binary_cache_builder.config',
- help='Set configuration file. Default is binary_cache_builder.config.')
+def main():
+ parser = argparse.ArgumentParser(description=
+"""Pull binaries needed by perf.data from device to binary_cache directory.""")
+ parser.add_argument('-i', '--perf_data_path', default='perf.data', help=
+"""The path of profiling data.""")
+ parser.add_argument('-lib', '--native_lib_dir', nargs='+', help=
+"""Path to find debug version of native shared libraries used in the app.""",
+ action='append')
args = parser.parse_args()
- config = load_config(args.config)
+ config = {}
+ config['perf_data_path'] = args.perf_data_path
+ config['symfs_dirs'] = flatten_arg_list(args.native_lib_dir)
+
builder = BinaryCacheBuilder(config)
- builder.build_binary_cache() \ No newline at end of file
+ builder.build_binary_cache()
+
+
+if __name__ == '__main__':
+ main() \ No newline at end of file
diff --git a/simpleperf/scripts/pprof_proto_generator.config b/simpleperf/scripts/pprof_proto_generator.config
deleted file mode 100644
index aa82b92c..00000000
--- a/simpleperf/scripts/pprof_proto_generator.config
+++ /dev/null
@@ -1,39 +0,0 @@
-# This configuration is written in python and used by binary_cache_builder.py.
-
-import os
-import os.path
-
-# path of profiling record data.
-perf_data_path = "perf.data"
-
-# output path.
-output_file = "pprof.profile"
-
-
-# directory to cache binaries with symbols and debug information.
-# Can be generated by binary_cache_builder.py.
-binary_cache_dir = "binary_cache"
-
-
-# path to find kernel symbols.
-kallsyms = ""
-
-
-if binary_cache_dir:
- path = os.path.join(binary_cache_dir, 'kallsyms')
- if os.path.isfile(path):
- kallsyms = path
-
-# Sample Filters
-# Use samples only in threads with selected names.
-comm_filters = []
-# Use samples only in processes with selected process ids.
-pid_filters = []
-# Use samples only in threads with selected thread ids.
-tid_filters = []
-# Use samples only in selected binaries.
-dso_filters = []
-
-# We use addr2line to map virtual address to source file and source line.
-# So set the path to addr2line here.
-addr2line_path = "addr2line" \ No newline at end of file
diff --git a/simpleperf/scripts/pprof_proto_generator.py b/simpleperf/scripts/pprof_proto_generator.py
index fa2fdb11..ad519110 100644
--- a/simpleperf/scripts/pprof_proto_generator.py
+++ b/simpleperf/scripts/pprof_proto_generator.py
@@ -255,15 +255,16 @@ class PprofProfileGenerator(object):
self.config = config
self.lib = ReportLib()
- if config.get('binary_cache_dir'):
- if not os.path.isdir(config.get('binary_cache_dir')):
- config['binary_cache_dir'] = ''
- else:
- self.lib.SetSymfs(config['binary_cache_dir'])
+ config['binary_cache_dir'] = 'binary_cache'
+ if not os.path.isdir(config['binary_cache_dir']):
+ config['binary_cache_dir'] = None
+ else:
+ self.lib.SetSymfs(config['binary_cache_dir'])
if config.get('record_file'):
self.lib.SetRecordFile(config['record_file'])
- if config.get('kallsyms'):
- self.lib.SetKallsymsFile(config['kallsyms'])
+ kallsyms = 'binary_cache/kallsyms'
+ if os.path.isfile(kallsyms):
+ self.lib.SetKallsymsFile(kallsyms)
self.comm_filter = set(config['comm_filters']) if config.get('comm_filters') else None
if config.get('pid_filters'):
self.pid_filter = {int(x) for x in config['pid_filters']}
@@ -318,8 +319,7 @@ class PprofProfileGenerator(object):
self.add_sample(sample)
# 2. Generate line info for locations and functions.
- if self.config.get('binary_cache_dir'):
- self.gen_source_lines()
+ self.gen_source_lines()
# 3. Produce samples/locations/functions in profile
for sample in self.sample_list:
@@ -446,6 +446,15 @@ class PprofProfileGenerator(object):
def gen_source_lines(self):
# 1. Create Addr2line instance
+ if not self.config.get('binary_cache_dir'):
+ log_info("Can't generate line information because binary_cache is missing.")
+ return
+ if not self.config['addr2line_path'] or not is_executable_available(
+ self.config['addr2line_path']):
+ if not find_tool_path('addr2line'):
+ log_info("Can't generate line information because can't find addr2line.")
+ return
+
addr2line = Addr2Line(self.config['addr2line_path'], self.config['binary_cache_dir'])
# 2. Put all needed addresses to it.
@@ -542,16 +551,38 @@ class PprofProfileGenerator(object):
def main():
parser = argparse.ArgumentParser(description='Generate pprof profile data in pprof.profile.')
- parser.add_argument('--show', nargs=1, help='print existing profile.pprof')
- parser.add_argument('--config', nargs=1, default='pprof_proto_generator.config',
- help='Set config file, default is gen_pprof_proto.config.')
- args = parser.parse_args(sys.argv[1:])
+ parser.add_argument('--show', nargs='?', action='append', help='print existing pprof.profile.')
+ parser.add_argument('-i', '--perf_data_path', default='perf.data', help=
+"""The path of profiling data.""")
+ parser.add_argument('-o', '--output_file', default='pprof.profile', help=
+"""The path of generated pprof profile data.""")
+ parser.add_argument('--comm', nargs='+', action='append', help=
+"""Use samples only in threads with selected names.""")
+ parser.add_argument('--pid', nargs='+', action='append', help=
+"""Use samples only in processes with selected process ids.""")
+ parser.add_argument('--tid', nargs='+', action='append', help=
+"""Use samples only in threads with selected thread ids.""")
+ parser.add_argument('--dso', nargs='+', action='append', help=
+"""Use samples only in selected binaries.""")
+ parser.add_argument('--addr2line', help=
+"""Set the path of addr2line.""")
+
+ args = parser.parse_args()
if args.show:
- profile = load_pprof_profile(args.show[0])
+ show_file = args.show[0] if args.show[0] else 'pprof.profile'
+ profile = load_pprof_profile(show_file)
printer = PprofProfilePrinter(profile)
printer.show()
return
- config = load_config(args.config)
+
+ config = {}
+ config['perf_data_path'] = args.perf_data_path
+ config['output_file'] = args.output_file
+ config['comm_filters'] = flatten_arg_list(args.comm)
+ config['pid_filters'] = flatten_arg_list(args.pid)
+ config['tid_filters'] = flatten_arg_list(args.tid)
+ config['dso_filters'] = flatten_arg_list(args.dso)
+ config['addr2line_path'] = args.addr2line
generator = PprofProfileGenerator(config)
profile = generator.gen()
store_pprof_profile(config['output_file'], profile)
diff --git a/simpleperf/scripts/utils.py b/simpleperf/scripts/utils.py
index a0b3b3ba..2e2c69ad 100644
--- a/simpleperf/scripts/utils.py
+++ b/simpleperf/scripts/utils.py
@@ -20,6 +20,7 @@
from __future__ import print_function
import logging
+import os
import os.path
import subprocess
import sys
@@ -31,6 +32,9 @@ def get_script_dir():
def is_windows():
return sys.platform == 'win32' or sys.platform == 'cygwin'
+def is_darwin():
+ return sys.platform == 'darwin'
+
def is_python3():
return sys.version_info >= (3, 0)
@@ -50,6 +54,9 @@ def log_warning(msg):
def log_fatal(msg):
raise Exception(msg)
+def log_exit(msg):
+ sys.exit(msg)
+
def str_to_bytes(str):
if not is_python3():
return str
@@ -95,8 +102,75 @@ def get_host_binary_path(binary_name):
return binary_path
+def is_executable_available(executable, option='--help'):
+ """ Run an executable to see if it exists. """
+ try:
+ subproc = subprocess.Popen([executable, option], stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ subproc.communicate()
+ return subproc.returncode == 0
+ except:
+ return False
+
+expected_tool_paths = {
+ 'adb': {
+ 'test_option': 'version',
+ 'darwin': [(True, 'Library/Android/sdk/platform-tools/adb'),
+ (False, '../../platform-tools/adb')],
+ 'linux': [(True, 'Android/Sdk/platform-tools/adb'),
+ (False, '../../platform-tools/adb')],
+ 'windows': [(True, 'AppData/Local/Android/sdk/platform-tools/adb'),
+ (False, '../../platform-tools/adb')],
+ },
+ 'readelf': {
+ 'test_option': '--help',
+ 'darwin': [(True, 'Library/Android/sdk/ndk-bundle/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-readelf'),
+ (False, '../toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-readelf')],
+ 'linux': [(True, 'Android/Sdk/ndk-bundle/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-readelf'),
+ (False, '../toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-readelf')],
+ 'windows': [(True, 'AppData/Local/Android/sdk/ndk-bundle/toolchains/aarch64-linux-android-4.9/prebuilt/windows-x86_64/bin/aarch64-linux-android-readelf'),
+ (False, '../toolchains/aarch64-linux-android-4.9/prebuilt/windows-x86_64/bin/aarch64-linux-android-readelf')],
+ },
+ 'addr2line': {
+ 'test_option': '--help',
+ 'darwin': [(True, 'Library/Android/sdk/ndk-bundle/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-addr2line'),
+ (False, '../toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-addr2line')],
+ 'linux': [(True, 'Android/Sdk/ndk-bundle/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-addr2line'),
+ (False, '../toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-addr2line')],
+ 'windows': [(True, 'AppData/Local/Android/sdk/ndk-bundle/toolchains/aarch64-linux-android-4.9/prebuilt/windows-x86_64/bin/aarch64-linux-android-addr2line'),
+ (False, '../toolchains/aarch64-linux-android-4.9/prebuilt/windows-x86_64/bin/aarch64-linux-android-addr2line')],
+ },
+}
+
+def find_tool_path(toolname):
+ if toolname not in expected_tool_paths:
+ return None
+ test_option = expected_tool_paths[toolname]['test_option']
+ if is_executable_available(toolname, test_option):
+ return toolname
+ platform = 'linux'
+ if is_windows():
+ platform = 'windows'
+ elif is_darwin():
+ platform = 'darwin'
+ paths = expected_tool_paths[toolname][platform]
+ home = os.environ.get('HOMEPATH') if is_windows() else os.environ.get('HOME')
+ for (relative_to_home, path) in paths:
+ path = path.replace('/', os.sep)
+ if relative_to_home:
+ path = os.path.join(home, path)
+ else:
+ path = os.path.join(get_script_dir(), path)
+ if is_executable_available(path, test_option):
+ return path
+ return None
+
+
class AdbHelper(object):
- def __init__(self, adb_path):
+ def __init__(self):
+ adb_path = find_tool_path('adb')
+ if not adb_path:
+ log_exit("Can't find adb in PATH environment.")
self.adb_path = adb_path
@@ -116,7 +190,7 @@ class AdbHelper(object):
(stdoutdata, _) = subproc.communicate()
returncode = subproc.returncode
result = (returncode == 0)
- if stdoutdata:
+ if stdoutdata and adb_args[1] != 'push' and adb_args[1] != 'pull':
stdoutdata = bytes_to_str(stdoutdata)
log_debug(stdoutdata)
log_debug('run adb cmd: %s [result %s]' % (adb_args, result))
@@ -129,7 +203,7 @@ class AdbHelper(object):
def check_run_and_return_output(self, adb_args, stdout_file=None):
result, stdoutdata = self.run_and_return_output(adb_args, stdout_file)
if not result:
- log_fatal('run "adb %s" failed' % adb_args)
+ log_exit('run "adb %s" failed' % adb_args)
return stdoutdata
@@ -161,7 +235,7 @@ class AdbHelper(object):
def load_config(config_file):
if not os.path.exists(config_file):
- log_fatal("can't find config_file: %s" % config_file)
+ log_exit("can't find config_file: %s" % config_file)
config = {}
if is_python3():
with open(config_file, 'r') as fh:
@@ -172,4 +246,12 @@ def load_config(config_file):
return config
+def flatten_arg_list(arg_list):
+ res = []
+ if arg_list:
+ for items in arg_list:
+ res += items
+ return res
+
+
logging.getLogger().setLevel(logging.DEBUG)