(file, line) = items
line = line.split()[0] # Remove comments after line number
out_pos += 1
- if file.find('?') != -1:
+ if '?' in file:
file = 0
else:
file = self._get_file_id(file)
- if line.find('?') != -1:
+ if '?' in line:
line = 0
else:
line = int(line)
path = key
from_path = path
to_path = os.path.join(dest_dir, path[1:])
- elif is_windows() and key.find(':\\') != -1 and os.path.isfile(key):
+ elif is_windows() and ':\\' in key and os.path.isfile(key):
from_path = key
to_path = os.path.join(dest_dir, key.replace(':\\', '\\'))
else:
self.is_root_device = False
self.android_version = 0
self.device_arch = None
- self.app_arch = None
+ self.app_arch = self.config['app_arch']
+ self.app_program = self.config['app_package_name'] or self.config['native_program']
self.app_pid = None
self.has_symfs_on_device = False
def check_config(self, config):
- config_names = ['app_package_name', 'native_lib_dir', 'apk_file_path',
- 'recompile_app', 'launch_activity', 'launch_inst_test',
+ config_names = ['app_package_name', 'native_program', 'cmd', 'native_lib_dir',
+ 'apk_file_path', 'recompile_app', 'launch_activity', 'launch_inst_test',
'record_options', 'perf_data_path']
for name in config_names:
if name not in config:
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")
+ if config['app_package_name'] and config['native_program']:
+ log_exit("We can't profile an Android app and a native program at the same time.")
+ elif config['app_package_name'] and config['cmd']:
+ log_exit("We can't profile an Android app and a cmd at the same time.")
+ elif config['native_program'] and config['cmd']:
+ log_exit("We can't profile a native program and a cmd at the same time.")
+ elif not config['app_package_name'] and not config['native_program'] and not config["cmd"]:
+ log_exit("Please set a profiling target: an Android app, a native program or a cmd.")
+ if config['app_package_name']:
+ if config['launch_activity'] and config['launch_inst_test']:
+ log_exit("We can't launch an activity and a test at the same time.")
native_lib_dir = config.get('native_lib_dir')
if native_lib_dir and not os.path.isdir(native_lib_dir):
log_exit('[native_lib_dir] "%s" is not a dir' % native_lib_dir)
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')
+ # If recompile app, the app needs to be restarted to take effect.
+ config['launch_activity'] = '.MainActivity'
def profile(self):
if strs:
self.android_version = int(strs[0])
- # Get device architecture.
- output = self.adb.check_run_and_return_output(['shell', 'uname', '-m'])
- if output.find('aarch64') != -1:
- self.device_arch = 'aarch64'
- elif output.find('arm') != -1:
- self.device_arch = 'arm'
- elif output.find('x86_64') != -1:
- self.device_arch = 'x86_64'
- elif output.find('86') != -1:
- self.device_arch = 'x86'
- else:
- log_fatal('unsupported architecture: %s' % output.strip())
+ self.device_arch = self.adb.get_device_arch()
def _enable_profiling(self):
def _restart_app(self):
- if not self.config['launch_activity'] and not self.config['launch_inst_test']:
+ if not self.config['app_package_name']:
return
+ if not self.config['launch_activity'] and not self.config['launch_inst_test']:
+ self.app_pid = self._find_app_process()
+ if self.app_pid is not None:
+ return
+ else:
+ self.config['launch_activity'] = '.MainActivity'
pid = self._find_app_process()
if pid is not None:
def _find_app_process(self):
# On Android >= N, pidof is available. Otherwise, we can use ps.
if self.android_version >= 7:
- result, output = self.adb.run_and_return_output(['shell', 'pidof',
- self.config['app_package_name']])
+ result, output = self.adb.run_and_return_output(['shell', 'pidof', self.app_program])
return int(output) if result else None
result, output = self.adb.run_and_return_output(['shell', 'ps'], log_output=False)
if not result:
return None
for line in output.split('\n'):
strs = line.split()
- if len(strs) > 2 and strs[-1].find(self.config['app_package_name']) != -1:
+ if len(strs) > 2 and self.app_program in strs[-1]:
return int(strs[1])
return None
def _get_app_environment(self):
- self.app_pid = self._find_app_process()
- if self.app_pid is None:
- 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], log_output=False)
- if output.find('linker64') != -1:
- self.app_arch = self.device_arch
+ if not self.config['cmd']:
+ if self.app_pid is None:
+ self.app_pid = self._find_app_process()
+ if self.app_pid is None:
+ log_exit("can't find process for app [%s]" % self.app_program)
+ if not self.app_arch:
+ if not self.config['cmd'] and self.device_arch in ['arm64', 'x86_64']:
+ output = self.run_in_app_dir(['cat', '/proc/%d/maps' % self.app_pid], log_output=False)
+ if 'linker64' in output:
+ self.app_arch = self.device_arch
+ else:
+ self.app_arch = 'arm' if self.device_arch == 'arm64' else 'x86'
else:
- self.app_arch = 'arm' if self.device_arch == 'aarch64' else 'x86'
- else:
- self.app_arch = self.device_arch
+ self.app_arch = self.device_arch
log_info('app_arch: %s' % self.app_arch)
if old_path is None:
return True
if self.app_arch == 'arm':
- result1 = new_path.find('armeabi-v7a/') != -1
- result2 = old_path.find('armeabi-v7a') != -1
+ result1 = 'armeabi-v7a/' in new_path
+ result2 = 'armeabi-v7a' in old_path
if result1 != result2:
return result1
- arch_dir = 'arm64' if self.app_arch == 'aarch64' else self.app_arch + '/'
- result1 = new_path.find(arch_dir) != -1
- result2 = old_path.find(arch_dir) != -1
+ arch_dir = self.app_arch + '/'
+ result1 = arch_dir in new_path
+ result2 = arch_dir in old_path
if result1 != result2:
return result1
- result1 = new_path.find('obj/') != -1
- result2 = old_path.find('obj/') != -1
+ result1 = 'obj/' in new_path
+ result2 = 'obj/' in old_path
if result1 != result2:
return result1
return False
returncode = None
try:
args = ['/data/local/tmp/simpleperf', 'record', self.config['record_options'],
- '--app', self.config['app_package_name'], '-o', '/data/local/tmp/perf.data']
+ '-o', '/data/local/tmp/perf.data']
+ if self.config['app_package_name']:
+ args += ['--app', self.config['app_package_name']]
+ elif self.config['native_program']:
+ args += ['-p', str(self.app_pid)]
+ elif self.config['cmd']:
+ args.append(self.config['cmd'])
if self.has_symfs_on_device:
args += ['--symfs', '/data/local/tmp/native_libs']
adb_args = [self.adb.adb_path, 'shell'] + args
args = self.get_run_in_app_dir_args(args)
if check_result:
return self.adb.check_run_and_return_output(args, stdout_file, log_output=log_output)
- else:
- return self.adb.run_and_return_output(args, stdout_file, log_output=log_output)
+ return self.adb.run_and_return_output(args, stdout_file, log_output=log_output)
def get_run_in_app_dir_args(self, args):
+ if not self.config['app_package_name']:
+ return ['shell'] + args
if self.is_root_device:
return ['shell', 'cd /data/data/' + self.config['app_package_name'] + ' && ' +
(' '.join(args))]
- else:
- return ['shell', 'run-as', self.config['app_package_name']] + args
+ return ['shell', 'run-as', self.config['app_package_name']] + args
def main():
parser = argparse.ArgumentParser(
description=
-"""Profile an android app.""")
+"""Profile an Android app or native program.""")
parser.add_argument('-p', '--app', help=
-"""The package name of the profiled Android app.""")
+"""Profile an Android app, given the package name. Like -p com.example.android.myapp.""")
+ parser.add_argument('-np', '--native_program', help=
+"""Profile a native program. The program should be running on the device.
+Like -np surfaceflinger.""")
+ parser.add_argument('-cmd', help=
+"""Run a cmd and profile it. Like -cmd "pm -l".""")
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
+"""When profiling an Android app, 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.""")
+"""When profiling an Android app, we need the apk file to recompile the app on
+Android version <= M.""")
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.""")
+"""When profiling an Android app, start an activity before profiling. It can be used to profile
+the startup time of an activity.""")
parser.add_argument('-t', '--test', help=
-"""Start an instrumentation test before profiling. It can be used to profile
-an instrumentation test.""")
+"""When profiling an Android app, start an instrumentation test before profiling.
+It can be used to profile an instrumentation test.""")
+ parser.add_argument('--arch', help=
+"""Select which arch the app is running on, possible values are:
+arm, arm64, x86, x86_64. If not set, the script will try to detect it.""")
parser.add_argument('-r', '--record_options', default="-e cpu-cycles:u -g --duration 10", help=
"""Set options for `simpleperf record` command.""")
parser.add_argument('-o', '--perf_data_path', default="perf.data", help=
args = parser.parse_args()
config = {}
config['app_package_name'] = args.app
+ config['native_program'] = args.native_program
+ config['cmd'] = args.cmd
config['native_lib_dir'] = args.native_lib_dir
- config['recompile_app'] = not args.skip_recompile
+ config['recompile_app'] = args.app and not args.skip_recompile
config['apk_file_path'] = args.apk
- if args.activity and args.test:
- log_exit("-a and -t can't be used at the same time.")
- if not args.activity and not args.test:
- args.activity = '.MainActivity'
config['launch_activity'] = args.activity
config['launch_inst_test'] = args.test
+ config['app_arch'] = args.arch
config['record_options'] = args.record_options
config['perf_data_path'] = args.perf_data_path
config['collect_binaries'] = not args.skip_collect_binaries
return False
output = subprocess.check_output([self.readelf_path, '-S', file])
output = bytes_to_str(output)
- if output.find('.symtab') != -1:
- return True
- return False
+ return '.symtab' in output
def _pull_file_from_device(self, device_path, host_path):
if not line.strip('| \t'):
continue
- if line.find('skipped in brief callgraph mode') != -1:
+ if 'skipped in brief callgraph mode' in line:
has_skipped_callgraph = True
continue
except:
has_google_protobuf = False
+support_trace_offcpu = None
+
+def is_trace_offcpu_supported():
+ global support_trace_offcpu
+ if support_trace_offcpu is None:
+ adb = AdbHelper()
+ adb.check_run_and_return_output(['push',
+ 'bin/android/%s/simpleperf' % adb.get_device_arch(),
+ "/data/local/tmp"])
+ adb.check_run_and_return_output(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf'])
+ output = adb.check_run_and_return_output(['shell', '/data/local/tmp/simpleperf', 'list',
+ '--show-features'])
+ support_trace_offcpu = 'trace-offcpu' in output
+ return support_trace_offcpu
-adb = AdbHelper()
-# TODO: Change to check ro.build.version.sdk >= 26 when OMR1 is prevalent.
-device_version = adb.check_run_and_return_output(['shell', 'getprop', 'ro.build.version.release'])
-support_trace_offcpu = device_version.find('OMR1') != -1
def build_testdata():
""" Collect testdata from ../testdata and ../demo. """
'SimpleperfExampleOfKotlin']
testdata_path = "testdata"
- if os.path.isdir(testdata_path):
- shutil.rmtree(testdata_path)
- os.mkdir(testdata_path)
+ remove(testdata_path)
+ os.mkdir(testdata_path)
for testdata in copy_testdata_list:
shutil.copy(os.path.join(from_testdata_path, testdata), testdata_path)
for demo in copy_demo_list:
shutil.copytree(os.path.join(from_demo_path, demo), os.path.join(testdata_path, demo))
-
class TestExampleBase(unittest.TestCase):
@classmethod
def prepare(cls, example_name, package_name, activity_name, abi=None, adb_root=False):
log_fatal("can't find app-profiling.apk under " + cls.example_path)
cls.package_name = package_name
cls.activity_name = activity_name
- cls.python_path = sys.executable
args = ["install", "-r"]
if abi:
args += ["--abi", abi]
cls.compiled = False
def setUp(self):
- if self.id().find('TraceOffCpu') != -1 and not support_trace_offcpu:
+ if self.id().find('TraceOffCpu') != -1 and not is_trace_offcpu_supported():
self.skipTest('trace-offcpu is not supported on device')
@classmethod
def tearDownClass(cls):
- cls.adb.check_run(["uninstall", cls.package_name])
+ if hasattr(cls, 'package_name'):
+ cls.adb.check_run(["uninstall", cls.package_name])
@classmethod
def cleanupTestFiles(cls):
- cls.remove("binary_cache")
- cls.remove("annotated_files")
- cls.remove("perf.data")
- cls.remove("report.txt")
- cls.remove("pprof.profile")
+ remove("binary_cache")
+ remove("annotated_files")
+ remove("perf.data")
+ remove("report.txt")
+ remove("pprof.profile")
def run_cmd(self, args, return_output=False):
- args = [self.python_path] + args
+ args = [sys.executable] + args
try:
if not return_output:
returncode = subprocess.call(args)
if not skip_compile:
self.__class__.compiled = True
- @classmethod
- def remove(cls, dir):
- shutil.rmtree(dir, ignore_errors=True)
-
def check_exist(self, file=None, dir=None):
if file:
self.assertTrue(os.path.isfile(file), file)
for line in summary.split('\n'):
for i in range(len(check_entries)):
(name, need_acc_period, need_period) = check_entries[i]
- if not fulfilled[i] and line.find(name) != -1:
+ if not fulfilled[i] and name in line:
m = self.summary_check_re.search(line)
if m:
acc_period = float(m.group(1))
def common_test_app_profiler(self):
self.run_cmd(["app_profiler.py", "-h"])
- self.remove("binary_cache")
+ remove("binary_cache")
self.run_app_profiler(build_binary_cache=False)
self.assertFalse(os.path.isdir("binary_cache"))
args = ["binary_cache_builder.py"]
args.append("--disable_adb_root")
self.run_cmd(args)
self.check_exist(dir="binary_cache")
- self.remove("binary_cache")
+ remove("binary_cache")
self.run_app_profiler(build_binary_cache=True)
self.run_app_profiler(skip_compile=True)
self.run_app_profiler(start_activity=False)
def common_test_annotate(self):
self.run_cmd(["annotate.py", "-h"])
self.run_app_profiler()
- self.remove("annotated_files")
+ remove("annotated_files")
self.run_cmd(["annotate.py", "-s", self.example_path])
self.check_exist(dir="annotated_files")
def common_test_report_sample(self, check_strings):
self.run_cmd(["report_sample.py", "-h"])
- self.remove("binary_cache")
+ remove("binary_cache")
self.run_app_profiler(build_binary_cache=False)
self.run_cmd(["report_sample.py"])
output = self.run_cmd(["report_sample.py", "perf.data"], return_output=True)
self.run_cmd(["pprof_proto_generator.py", "-h"])
self.run_app_profiler()
self.run_cmd(["pprof_proto_generator.py"])
- self.remove("pprof.profile")
+ remove("pprof.profile")
self.run_cmd(["pprof_proto_generator.py", "-i", "perf.data", "-o", "pprof.profile"])
self.check_exist(file="pprof.profile")
self.run_cmd(["pprof_proto_generator.py", "--show"])
return_output=True)
self.check_strings_in_content(output, check_strings_with_lines +
["has_line_numbers: True"])
- self.remove("binary_cache")
+ remove("binary_cache")
self.run_cmd(["pprof_proto_generator.py"])
output = self.run_cmd(["pprof_proto_generator.py", "--show", "pprof.profile"],
return_output=True)
"long com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.RunFunction()",
"long com.example.simpleperf.simpleperfexamplepurejava.SleepActivity$1.SleepFunction(long)"
])
- self.remove("annotated_files")
+ remove("annotated_files")
self.run_cmd(["annotate.py", "-s", self.example_path])
self.check_exist(dir="annotated_files")
self.check_file_under_dir("annotated_files", "SleepActivity.java")
def test_app_profiler(self):
self.common_test_app_profiler()
- self.remove("binary_cache")
+ remove("binary_cache")
self.run_app_profiler(native_lib_dir=self.example_path)
def test_report(self):
def test_app_profiler(self):
self.common_test_app_profiler()
- self.remove("binary_cache")
+ remove("binary_cache")
self.run_app_profiler(native_lib_dir=self.example_path)
["SleepThread(void*)",
"RunFunction()",
"SleepFunction(unsigned long long)"])
- self.remove("annotated_files")
+ remove("annotated_files")
self.run_cmd(["annotate.py", "-s", self.example_path, "--comm", "SleepThread"])
self.check_exist(dir="annotated_files")
self.check_file_under_dir("annotated_files", "native-lib.cpp")
["void com.example.simpleperf.simpleperfexamplewithnative.MixActivity$1.run()",
"int com.example.simpleperf.simpleperfexamplewithnative.MixActivity.callFunction(int)",
"Java_com_example_simpleperf_simpleperfexamplewithnative_MixActivity_callFunction"])
- self.remove("annotated_files")
+ remove("annotated_files")
self.run_cmd(["annotate.py", "-s", self.example_path, "--comm", "BusyThread"])
self.check_exist(dir="annotated_files")
self.check_file_under_dir("annotated_files", "native-lib.cpp")
"long com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.RunFunction()",
"long com.example.simpleperf.simpleperfexampleofkotlin.SleepActivity$createRunSleepThread$1.SleepFunction(long)"
])
- self.remove("annotated_files")
+ remove("annotated_files")
self.run_cmd(["annotate.py", "-s", self.example_path])
self.check_exist(dir="annotated_files")
self.check_file_under_dir("annotated_files", "SleepActivity.kt")
("line 32", 20, 0)])
+class TestProfilingNativeProgram(TestExampleBase):
+ def test_smoke(self):
+ if not AdbHelper().switch_to_root():
+ log_info('skip TestProfilingNativeProgram on non-rooted devices.')
+ return
+ remove("perf.data")
+ self.run_cmd(["app_profiler.py", "-np", "surfaceflinger",
+ "-r", "-g --duration 3 -e cpu-cycles:u"])
+ self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+
+
+class TestProfilingCmd(TestExampleBase):
+ def test_smoke(self):
+ remove("perf.data")
+ self.run_cmd(["app_profiler.py", "-cmd", "pm -l", "--disable_adb_root"])
+ self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+
+ def test_set_arch(self):
+ arch = AdbHelper().get_device_arch()
+ remove("perf.data")
+ self.run_cmd(["app_profiler.py", "-cmd", "pm -l", "--arch", arch])
+ self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+
+
class TestReportLib(unittest.TestCase):
def setUp(self):
self.report_lib = ReportLib()
import logging
import os
import os.path
+import shutil
import subprocess
import sys
import time
if is_windows():
if binary_name.endswith('.so'):
binary_name = binary_name[0:-3] + '.dll'
- elif binary_name.find('.') == -1:
+ elif '.' not in binary_name:
binary_name += '.exe'
dir = os.path.join(dir, 'windows')
elif sys.platform == 'darwin': # OSX
result, stdoutdata = self.run_and_return_output(['shell', 'whoami'])
if not result:
return
- if stdoutdata.find('root') == -1:
+ if 'root' not in stdoutdata:
return
log_info('unroot adb')
self.run(['unroot'])
result, stdoutdata = self.run_and_return_output(['shell', 'whoami'])
if not result:
return False
- if stdoutdata.find('root') != -1:
+ if 'root' in stdoutdata:
return True
build_type = self.get_property('ro.build.type')
if build_type == 'user':
time.sleep(1)
self.run(['wait-for-device'])
result, stdoutdata = self.run_and_return_output(['shell', 'whoami'])
- if result and stdoutdata.find('root') != -1:
- return True
- return False
+ return result and 'root' in stdoutdata
def get_property(self, name):
result, stdoutdata = self.run_and_return_output(['shell', 'getprop', name])
- if not result:
- return None
- return stdoutdata
-
+ return stdoutdata if result else None
def set_property(self, name, value):
return self.run(['shell', 'setprop', name, value])
+ def get_device_arch(self):
+ output = self.check_run_and_return_output(['shell', 'uname', '-m'])
+ if 'aarch64' in output:
+ return 'arm64'
+ if 'arm' in output:
+ return 'arm'
+ if 'x86_64' in output:
+ return 'x86_64'
+ if '86' in output:
+ return 'x86'
+ log_fatal('unsupported architecture: %s' % output.strip())
+
+
def flatten_arg_list(arg_list):
res = []
if arg_list:
return res
+def remove(dir_or_file):
+ if os.path.isfile(dir_or_file):
+ os.remove(dir_or_file)
+ elif os.path.isdir(dir_or_file):
+ shutil.rmtree(dir_or_file, ignore_errors=True)
+
logging.getLogger().setLevel(logging.DEBUG)