- [Visualize profiling data](#visualize-profiling-data)
- [Annotate source code](#annotate-source-code)
- [Trace offcpu time](#trace-offcpu-time)
+ - [Profile from launch of an application](#profile-from-launch-of-an-application)
- [Answers to common issues](#answers-to-common-issues)
- [Why we suggest profiling on android >= N devices](#why-we-suggest-profiling-on-android-n-devices)
Simpleperf is a cpu profiler, it generates samples for a thread only when it is
running on a cpu. However, sometimes we want to find out where time of a thread
is spent, whether it is running on cpu, preempted by other threads, doing I/O
-work, or waiting for some events. To support this, we add a --trace-offcpu
-option in simpleperf record cmd. When --trace-offcpu is used, simpleperf
+work, or waiting for some events. To support this, we added the --trace-offcpu
+option in the simpleperf record command. When --trace-offcpu is used, simpleperf
generates a sample when a running thread is scheduled out, so we know the
callstack of a thread when it is scheduled out. And when reporting a perf.data
-generated with --trace-offcpu option, we use timestamp to the next sample
+generated with --trace-offcpu, we use timestamp to the next sample
(instead of event counts from the previous sample) as the weight of current
sample. As a result, we can get a callgraph based on timestamp, including both
on cpu time and off cpu time.
As shown in the graph, half time is spent in RunFunction(), and half time is
spent in SleepFunction(). It includes both on cpu time and off cpu time.
+### Profile from launch of an application
+
+Sometimes we want to profile the launch-time of an application. To support this,
+we added the --app option in the simpleperf record command. The --app option
+sets the package name of the Android application to profile. If the app is not
+already running, the simpleperf record command will poll for the app process in
+a loop with an interval of 1ms. So to profile from launch of an application,
+we can first start simpleperf record with --app, then start the app.
+Below is an example.
+
+ $ adb shell /data/local/tmp/simpleperf record -g \
+ --app com.example.simpleperf.simpleperfexamplepurejava --duration 1 \
+ -o /data/local/tmp/perf.data
+ # Start the app manually or using the `am` command.
+
+To make it convenient to use, app_profiler.py combines these in the
+--profile_from_launch option. Below is an example.
+
+ $ python app_profiler.py -p com.example.simpleperf.simpleperfexamplepurejava \
+ -a .MainActivity --arch arm64 -r "-g -e cpu-cycles:u --duration 1" \
+ --profile_from_launch
+
## Answers to common issues
self.app_program = self.config['app_package_name'] or self.config['native_program']
self.app_pid = None
self.has_symfs_on_device = False
+ self.record_subproc = None
def check_config(self, config):
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']
+ 'record_options', 'perf_data_path', 'profile_from_launch', 'app_arch']
for name in config_names:
if name not in config:
log_exit('config [%s] is missing' % name)
if not config['launch_activity'] and not config['launch_inst_test']:
# If recompile app, the app needs to be restarted to take effect.
config['launch_activity'] = '.MainActivity'
+ if config['profile_from_launch']:
+ if not config['app_package_name']:
+ log_exit('-p needs to be set to profile from launch.')
+ if not config['launch_activity']:
+ log_exit('-a needs to be set to profile from launch.')
+ if not config['app_arch']:
+ log_exit('--arch needs to be set to profile from launch.')
def profile(self):
self._recompile_app()
self._restart_app()
self._get_app_environment()
- self._download_simpleperf()
- self._download_native_libs()
+ if not self.config['profile_from_launch']:
+ self._download_simpleperf()
+ self._download_native_libs()
def _get_device_environment(self):
self.run_in_app_dir(['kill', '-9', str(pid)])
time.sleep(1)
+ if self.config['profile_from_launch']:
+ self._download_simpleperf()
+ self.start_profiling()
+
if self.config['launch_activity']:
activity = self.config['app_package_name'] + '/' + self.config['launch_activity']
result = self.adb.run(['shell', 'am', 'start', '-n', activity])
def start_and_wait_profiling(self):
- subproc = None
+ if self.record_subproc is None:
+ self.start_profiling()
+ self.wait_profiling()
+
+
+ def wait_profiling(self):
returncode = None
try:
- args = ['/data/local/tmp/simpleperf', 'record', self.config['record_options'],
- '-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
- log_debug('run adb cmd: %s' % adb_args)
- subproc = subprocess.Popen(adb_args)
- returncode = subproc.wait()
+ returncode = self.record_subproc.wait()
except KeyboardInterrupt:
- if subproc:
- self.stop_profiling()
- returncode = 0
- log_debug('run adb cmd: %s [result %s]' % (adb_args, returncode == 0))
+ self.stop_profiling()
+ self.record_subproc = None
+ returncode = 0
+ log_debug('profiling result [%s]' % (returncode == 0))
+
+
+ def start_profiling(self):
+ args = ['/data/local/tmp/simpleperf', 'record', self.config['record_options'],
+ '-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
+ log_debug('run adb cmd: %s' % adb_args)
+ self.record_subproc = subprocess.Popen(adb_args)
def stop_profiling(self):
"""When profiling an Android app, we need the apk file to recompile the app on
Android version <= M.""")
parser.add_argument('-a', '--activity', help=
-"""When profiling an Android app, start an activity before profiling. It can be used to profile
-the startup time of an activity.""")
+"""When profiling an Android app, start an activity before profiling.
+It restarts the app if the app is already running.""")
parser.add_argument('-t', '--test', help=
"""When profiling an Android app, start an instrumentation test before profiling.
-It can be used to profile an instrumentation test.""")
+It restarts the app if the app is already running.""")
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('-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.""")
+ parser.add_argument('--profile_from_launch', action='store_true', help=
+"""Profile an activity from initial launch. It should be used with -p, -a, and --arch options.
+Normally we run in the following order: restart the app, detect the architecture of the app,
+download simpleperf and native libs with debug info on device, and start simpleperf record.
+But with --profile_from_launch option, we change the order as below: kill the app if it is
+already running, download simpleperf on device, start simpleperf record, and start the app.""")
parser.add_argument('--disable_adb_root', action='store_true', help=
"""Force adb to run in non root mode.""")
args = parser.parse_args()
config['record_options'] = args.record_options
config['perf_data_path'] = args.perf_data_path
config['collect_binaries'] = not args.skip_collect_binaries
+ config['profile_from_launch'] = args.profile_from_launch
config['disable_adb_root'] = args.disable_adb_root
profiler = AppProfiler(config)
log_fatal("can't find app-profiling.apk under " + cls.example_path)
cls.package_name = package_name
cls.activity_name = activity_name
+ cls.abi = "arm64"
+ if abi and abi != "arm64" and abi.find("arm") != -1:
+ cls.abi = "arm"
args = ["install", "-r"]
if abi:
args += ["--abi", abi]
def run_app_profiler(self, record_arg = "-g --duration 3 -e cpu-cycles:u",
build_binary_cache=True, skip_compile=False, start_activity=True,
- native_lib_dir=None):
+ native_lib_dir=None, profile_from_launch=False, add_arch=False):
args = ["app_profiler.py", "--app", self.package_name, "--apk", self.apk_path,
"-a", self.activity_name, "-r", record_arg, "-o", "perf.data"]
if not build_binary_cache:
args += ["-a", self.activity_name]
if native_lib_dir:
args += ["-lib", native_lib_dir]
+ if profile_from_launch:
+ args.append("--profile_from_launch")
+ if add_arch:
+ args += ["--arch", self.abi]
if not self.adb_root:
args.append("--disable_adb_root")
self.run_cmd(args)
self.run_app_profiler(build_binary_cache=True)
self.run_app_profiler(skip_compile=True)
self.run_app_profiler(start_activity=False)
+ self.run_app_profiler(profile_from_launch=True, add_arch=True)
def common_test_report(self):