summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2019-02-21 00:48:17 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2019-02-21 00:48:17 +0000
commit5e64dcd02e4c03f6bcd3d3cc65f4fc105191a31f (patch)
tree7c3db519eaac238925f175ec5345622c190cf500
parentd3eccf50e841b5c9bb543a444d09d15d01563f99 (diff)
parent007eb186e8d1a75e71a8253ff79f851677058ead (diff)
downloadextras-5e64dcd02e4c03f6bcd3d3cc65f4fc105191a31f.tar.gz
Merge "simpleperf: add api_app_profiler.py."
-rwxr-xr-xsimpleperf/scripts/api_app_profiler.py153
1 files changed, 153 insertions, 0 deletions
diff --git a/simpleperf/scripts/api_app_profiler.py b/simpleperf/scripts/api_app_profiler.py
new file mode 100755
index 00000000..2c29a807
--- /dev/null
+++ b/simpleperf/scripts/api_app_profiler.py
@@ -0,0 +1,153 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2019 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.
+#
+
+"""
+ This script is part of controlling simpleperf recording in user code. It is used to prepare
+ profiling environment (upload simpleperf to device and enable profiling) before recording
+ and collect profiling data on host after recording.
+ Controlling simpleperf recording is done in below steps:
+ 1. Add simpleperf Java API/C++ API to the app's source code. And call the API in user code.
+ 2. Run `api_app_profiler.py prepare` to prepare profiling environment.
+ 3. Run the app one or more times to generate profiling data.
+ 4. Run `api_app_profiler.py collect` to collect profiling data on host.
+"""
+
+from __future__ import print_function
+import argparse
+import os
+import os.path
+import shutil
+
+from utils import AdbHelper, get_target_binary_path, log_exit, remove
+
+def prepare_recording(args):
+ adb = AdbHelper()
+ enable_profiling_on_device(adb, args)
+ # TODO: support profileable app in Android >= Q.
+ if not is_debuggable_app(adb, args.app[0]):
+ log_exit("The app isn't debuggable: %s" % args.app[0])
+ upload_simpleperf_to_device(adb, args)
+ prepare_tracepoint_events_file(adb)
+
+def is_debuggable_app(adb, app):
+ result, _ = adb.run_and_return_output(["shell", "run-as", app, "echo"], log_output=False)
+ return result
+
+def enable_profiling_on_device(adb, args):
+ android_version = adb.get_android_version()
+ if android_version >= 10:
+ adb.set_property('debug.perf_event_max_sample_rate', str(args.max_sample_rate[0]))
+ adb.set_property('debug.perf_cpu_time_max_percent', str(args.max_cpu_percent[0]))
+ adb.set_property('debug.perf_event_mlock_kb', str(args.max_memory_in_kb[0]))
+ adb.set_property('security.perf_harden', '0')
+
+def upload_simpleperf_to_device(adb, args):
+ device_arch = adb.get_device_arch()
+ simpleperf_binary = get_target_binary_path(device_arch, 'simpleperf')
+ adb.check_run(['push', simpleperf_binary, '/data/local/tmp'])
+ adb.check_run(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf'])
+ adb.check_run(['shell', 'run-as', args.app[0], 'cp', '/data/local/tmp/simpleperf', '.'])
+
+def prepare_tracepoint_events_file(adb):
+ tracepoint_event_ids = adb.check_run_and_return_output(
+ ['shell', 'ls', '/sys/kernel/debug/tracing/events/*/*/id'], log_output=False)
+ events_file_path = 'tracepoint_events'
+ with open(events_file_path, 'w') as fh:
+ for i, id_path in enumerate(tracepoint_event_ids.split()):
+ # For id_path like "/sys/kernel/debug/tracing/events/sched/sched_switch/id",
+ # get event_name "sched:sched_switch".
+ items = id_path.split('/')
+ event_name = items[-3] + ':' + items[-2]
+ id_value = adb.check_run_and_return_output(['shell', 'cat', id_path], log_output=False)
+ id_value = id_value.strip()
+ if i > 0:
+ fh.write('\n')
+ fh.write('%s %s' % (event_name, id_value))
+ adb.check_run(['push', events_file_path, '/data/local/tmp/tracepoint_events'])
+ remove(events_file_path)
+
+
+def collect_data(args):
+ adb = AdbHelper()
+ if not os.path.isdir(args.out_dir):
+ os.makedirs(args.out_dir)
+ move_profiling_data_to_tmp_dir(adb, args.app[0])
+ adb.check_run(['pull', '/data/local/tmp/simpleperf_data', args.out_dir])
+ adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/simpleperf_data'])
+ simpleperf_data_path = os.path.join(args.out_dir, 'simpleperf_data')
+ for name in os.listdir(simpleperf_data_path):
+ remove(os.path.join(args.out_dir, name))
+ shutil.move(os.path.join(simpleperf_data_path, name), args.out_dir)
+ print('collect profiling data: %s' % os.path.join(args.out_dir, name))
+ remove(simpleperf_data_path)
+
+def move_profiling_data_to_tmp_dir(adb, app):
+ """ move /data/data/<app>/simpleperf_data to /data/local/tmp/simpleperf_data."""
+ # TODO: support profileable app in Android >= Q.
+ if not is_debuggable_app(adb, app):
+ log_exit("The app isn't debuggable: %s" % app)
+ result, output = adb.run_and_return_output(['shell', 'run-as', app, 'ls', 'simpleperf_data'])
+ if not result:
+ log_exit("can't find profiling data for app %s" % app)
+ file_list = output.split()
+ shell_script = 'collect_data_shell_script.sh'
+ with open(shell_script, 'w') as fh:
+ fh.write('set -ex\n')
+ fh.write('rm -rf /data/local/tmp/simpleperf_data\n')
+ fh.write('mkdir /data/local/tmp/simpleperf_data\n')
+ for filename in file_list:
+ fh.write('run-as %s cat simpleperf_data/%s >/data/local/tmp/simpleperf_data/%s\n' % (
+ app, filename, filename))
+ adb.check_run(['push', shell_script, '/data/local/tmp'])
+ adb.check_run(['shell', 'sh', '/data/local/tmp/%s' % shell_script])
+ adb.check_run(['shell', 'run-as', app, 'rm', '-rf', 'simpleperf_data'])
+ adb.check_run(['shell', 'rm', '/data/local/tmp/%s' % shell_script])
+ remove(shell_script)
+
+
+class ArgumentHelpFormatter(argparse.ArgumentDefaultsHelpFormatter,
+ argparse.RawDescriptionHelpFormatter):
+ pass
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__,
+ formatter_class=ArgumentHelpFormatter)
+ subparsers = parser.add_subparsers()
+ prepare_parser = subparsers.add_parser('prepare', help='Prepare recording on device.',
+ formatter_class=ArgumentHelpFormatter)
+ prepare_parser.add_argument('-p', '--app', nargs=1, required=True, help="""
+ The package name of the app to profile.""")
+ prepare_parser.add_argument('--max-sample-rate', nargs=1, type=int, default=[100000], help="""
+ Set max sample rate (only on Android >= Q).""")
+ prepare_parser.add_argument('--max-cpu-percent', nargs=1, type=int, default=[25], help="""
+ Set max cpu percent for recording (only on Android >= Q).""")
+ prepare_parser.add_argument('--max-memory-in-kb', nargs=1, type=int, default=[516], help="""
+ Set max kernel buffer size for recording (only on Android >= Q).
+ """)
+ prepare_parser.set_defaults(func=prepare_recording)
+ collect_parser = subparsers.add_parser('collect', help='Collect profiling data.',
+ formatter_class=ArgumentHelpFormatter)
+ collect_parser.add_argument('-p', '--app', nargs=1, required=True, help="""
+ The app package name of the app profiled.""")
+ collect_parser.add_argument('-o', '--out-dir', default='simpleperf_data', help="""
+ The directory to store profiling data.""")
+ collect_parser.set_defaults(func=collect_data)
+ args = parser.parse_args()
+ args.func(args)
+
+if __name__ == '__main__':
+ main()