OSDN Git Service

simpleperf: support --profile_from_launch option in app_profiler.py.
authorYabin Cui <yabinc@google.com>
Fri, 18 Aug 2017 19:44:34 +0000 (12:44 -0700)
committerYabin Cui <yabinc@google.com>
Fri, 18 Aug 2017 22:38:41 +0000 (15:38 -0700)
Bug: http://b/34108866
Test: run test.py.
Change-Id: I6476b02fe31bf3f949e61640a8637582b09f270e

simpleperf/doc/README.md
simpleperf/scripts/app_profiler.py
simpleperf/scripts/test.py

index 2e51639..2e33a1e 100644 (file)
@@ -29,6 +29,7 @@ Bugs and feature requests can be submitted at http://github.com/android-ndk/ndk/
     - [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)
 
@@ -827,11 +828,11 @@ It's content is similar to below:
 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.
@@ -866,6 +867,28 @@ But if we add --trace-offcpu option, the graph is changed as below.
 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
 
index 0de735f..48b0aef 100644 (file)
@@ -53,12 +53,13 @@ class AppProfiler(object):
         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)
@@ -83,6 +84,13 @@ class AppProfiler(object):
             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):
@@ -101,8 +109,9 @@ class AppProfiler(object):
         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):
@@ -169,6 +178,10 @@ class AppProfiler(object):
             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])
@@ -283,28 +296,36 @@ class AppProfiler(object):
 
 
     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):
@@ -370,11 +391,11 @@ don't need to profile java code.""")
 """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.""")
@@ -385,6 +406,12 @@ 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()
@@ -403,6 +430,7 @@ binary_cache directory. It can be used to annotate source code. This option skip
     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)
index 7efd44c..aa9763f 100644 (file)
@@ -121,6 +121,9 @@ class TestExampleBase(TestBase):
             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]
@@ -148,7 +151,7 @@ class TestExampleBase(TestBase):
 
     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:
@@ -159,6 +162,10 @@ class TestExampleBase(TestBase):
             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)
@@ -246,6 +253,7 @@ class TestExampleBase(TestBase):
         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):