OSDN Git Service

simpleperf: fix --app option for multiprocess apps.
authorYabin Cui <yabinc@google.com>
Mon, 28 Aug 2017 21:49:04 +0000 (14:49 -0700)
committerYabin Cui <yabinc@google.com>
Tue, 29 Aug 2017 18:42:35 +0000 (11:42 -0700)
1. Search all processes in an app when using --app option in record command.
2. Fix searching one app process when using -p option in app_profiler.py.
3. Add unittest for profiling multiprocess apps.

Bug: http://b/65025325
Test: run test.py

Change-Id: Iba2e97c2174815d0236636e3cf15b1fc17a5d838

13 files changed:
simpleperf/cmd_record.cpp
simpleperf/cmd_stat.cpp
simpleperf/demo/SimpleperfExamplePureJava/app/build/outputs/apk/app-profiling.apk
simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml
simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessActivity.java [new file with mode: 0644]
simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessService.java [new file with mode: 0644]
simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_main.xml
simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_multi_process.xml [new file with mode: 0644]
simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_sleep.xml
simpleperf/environment.cpp
simpleperf/environment.h
simpleperf/scripts/app_profiler.py
simpleperf/scripts/test.py

index 2bd0506..85e218c 100644 (file)
@@ -325,8 +325,8 @@ bool RecordCommand::Run(const std::vector<std::string>& args) {
     } else if (!app_package_name_.empty()) {
       // If app process is not created, wait for it. This allows simpleperf starts before
       // app process. In this way, we can have a better support of app start-up time profiling.
-      int pid = WaitForAppProcess(app_package_name_);
-      event_selection_set_.AddMonitoredProcesses({pid});
+      std::set<pid_t> pids = WaitForAppProcesses(app_package_name_);
+      event_selection_set_.AddMonitoredProcesses(pids);
     } else {
       LOG(ERROR)
           << "No threads to monitor. Try `simpleperf help record` for help";
index a9d5036..ddd83f7 100644 (file)
@@ -386,8 +386,8 @@ bool StatCommand::Run(const std::vector<std::string>& args) {
       event_selection_set_.AddMonitoredProcesses({workload->GetPid()});
       event_selection_set_.SetEnableOnExec(true);
     } else if (!app_package_name_.empty()) {
-      int pid = WaitForAppProcess(app_package_name_);
-      event_selection_set_.AddMonitoredProcesses({pid});
+      std::set<pid_t> pids = WaitForAppProcesses(app_package_name_);
+      event_selection_set_.AddMonitoredProcesses(pids);
     } else {
       LOG(ERROR)
           << "No threads to monitor. Try `simpleperf help stat` for help\n";
index 0254bc0..f297046 100644 (file)
Binary files a/simpleperf/demo/SimpleperfExamplePureJava/app/build/outputs/apk/app-profiling.apk and b/simpleperf/demo/SimpleperfExamplePureJava/app/build/outputs/apk/app-profiling.apk differ
index c611102..f42ec17 100644 (file)
             </intent-filter>
         </activity>
         <activity android:name=".SleepActivity"
-            android:exported="true">
+            android:exported="true" />
+        <activity android:name=".MultiProcessActivity"
+            android:exported="true" />
 
-        </activity>
+        <service android:name=".MultiProcessService"
+            android:process=":multiprocess_service" />
     </application>
 
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessActivity.java b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessActivity.java
new file mode 100644 (file)
index 0000000..de698ec
--- /dev/null
@@ -0,0 +1,78 @@
+package com.example.simpleperf.simpleperfexamplepurejava;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.util.Log;
+
+public class MultiProcessActivity extends AppCompatActivity {
+    public static final String TAG = "MultiProcessActivity";
+
+    Messenger mService = null;
+    boolean mBound;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_multi_process);
+
+        bindService(new Intent(this, MultiProcessService.class), mConnection,
+                Context.BIND_AUTO_CREATE);
+        createBusyThread();
+    }
+
+    private ServiceConnection mConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
+            mService = new Messenger(iBinder);
+            mBound = true;
+            Message message = new Message();
+            message.what = MultiProcessService.MSG_START_BUSY_THREAD;
+            try {
+                mService.send(message);
+            } catch (RemoteException e) {
+                Log.d(TAG, e.toString());
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName componentName) {
+            mService = null;
+            mBound = false;
+        }
+    };
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        if (mBound) {
+            unbindService(mConnection);
+            mBound = false;
+        }
+    }
+
+    void createBusyThread() {
+        new Thread(new Runnable() {
+            volatile int i = 0;
+
+            @Override
+            public void run() {
+                while (true) {
+                    i = callFunction(i);
+                }
+            }
+
+            private int callFunction(int a) {
+                return a+1;
+            }
+        }, "BusyThread").start();
+    }
+}
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessService.java b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessService.java
new file mode 100644 (file)
index 0000000..2fd4d57
--- /dev/null
@@ -0,0 +1,50 @@
+package com.example.simpleperf.simpleperfexamplepurejava;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+
+public class MultiProcessService extends Service {
+    public static final int MSG_START_BUSY_THREAD = 1;
+
+    public MultiProcessService() {
+    }
+
+    class IncomingHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_START_BUSY_THREAD:
+                    createBusyThread();
+            }
+            super.handleMessage(msg);
+        }
+    }
+
+    final Messenger mMessenger = new Messenger(new IncomingHandler());
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mMessenger.getBinder();
+    }
+
+    void createBusyThread() {
+        new Thread(new Runnable() {
+            volatile int i = 0;
+
+            @Override
+            public void run() {
+                while (true) {
+                    i = callFunction(i);
+                }
+            }
+
+            private int callFunction(int a) {
+                return a+1;
+            }
+        }, "BusyService").start();
+    }
+}
index 1aa4458..4a09b1a 100644 (file)
@@ -9,7 +9,7 @@
     <TextView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:text="Hello World!"
+        android:text="MainActivity"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintLeft_toLeftOf="parent"
         app:layout_constraintRight_toRightOf="parent"
diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_multi_process.xml b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_multi_process.xml
new file mode 100644 (file)
index 0000000..f97b72e
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="com.example.simpleperf.simpleperfexamplepurejava.MultiProcessActivity">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="MultiProcessActivity"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+</android.support.constraint.ConstraintLayout>
index e2274ca..f732f77 100644 (file)
@@ -6,4 +6,13 @@
     android:layout_height="match_parent"
     tools:context="com.example.simpleperf.simpleperfexamplepurejava.SleepActivity">
 
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="SleepActivity"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
 </android.support.constraint.ConstraintLayout>
index bf0a061..e238ffb 100644 (file)
@@ -537,7 +537,8 @@ static bool HasOpenedAppApkFile(int pid) {
   return false;
 }
 
-int WaitForAppProcess(const std::string& package_name) {
+std::set<pid_t> WaitForAppProcesses(const std::string& package_name) {
+  std::set<pid_t> result;
   size_t loop_count = 0;
   while (true) {
     std::vector<pid_t> pids = GetAllProcesses();
@@ -547,8 +548,14 @@ int WaitForAppProcess(const std::string& package_name) {
         // Maybe we don't have permission to read it.
         continue;
       }
-      cmdline = android::base::Basename(cmdline);
-      if (cmdline != package_name) {
+      std::string process_name = android::base::Basename(cmdline);
+      // The app may have multiple processes, with process name like
+      // com.google.android.googlequicksearchbox:search.
+      size_t split_pos = process_name.find(':');
+      if (split_pos != std::string::npos) {
+        process_name = process_name.substr(0, split_pos);
+      }
+      if (process_name != package_name) {
         continue;
       }
       // If a debuggable app with wrap.sh runs on Android O, the app will be started with
@@ -567,7 +574,10 @@ int WaitForAppProcess(const std::string& package_name) {
       if (loop_count > 0u) {
         LOG(INFO) << "Got process " << pid << " for package " << package_name;
       }
-      return pid;
+      result.insert(pid);
+    }
+    if (!result.empty()) {
+      return result;
     }
     if (++loop_count == 1u) {
       LOG(INFO) << "Waiting for process of app " << package_name;
index 52d0a7c..0f12146 100644 (file)
@@ -92,7 +92,7 @@ static inline int gettid() {
 ArchType GetMachineArch();
 void PrepareVdsoFile();
 
-int WaitForAppProcess(const std::string& package_name);
+std::set<pid_t> WaitForAppProcesses(const std::string& package_name);
 bool RunInAppContext(const std::string& app_package_name, const std::string& cmd,
                      const std::vector<std::string>& args, size_t workload_args_size,
                      const std::string& output_filepath, bool need_tracepoint_events);
index 9e0f551..c089bbe 100644 (file)
@@ -192,13 +192,23 @@ class AppProfiler(object):
 
 
     def _find_app_process(self):
-        # On Android >= N, pidof is available. Otherwise, we can use ps.
-        if self.android_version >= 7:
+        if not self.config['app_package_name'] and self.android_version >= 7:
             result, output = self.adb.run_and_return_output(['shell', 'pidof', self.app_program])
-            if not result:
-                return None
-            pid = int(output)
-            if self.android_version >= 8 and self.config['app_package_name']:
+            return int(output) if result else None
+        ps_args = ['ps', '-e', '-o', 'PID,NAME'] if self.android_version >= 8 else ['ps']
+        result, output = self.adb.run_and_return_output(['shell'] + ps_args, log_output=False)
+        if not result:
+            return None
+        for line in output.split('\n'):
+            strs = line.split()
+            if len(strs) < 2:
+                continue
+            process_name = strs[-1]
+            if self.config['app_package_name']:
+                # This is to match process names in multiprocess apps.
+                process_name = process_name.split(':')[0]
+            if process_name == self.app_program:
+                pid = int(strs[0] if self.android_version >= 8 else strs[1])
                 # If a debuggable app with wrap.sh runs on Android O, the app will be started with
                 # logwrapper as below:
                 # 1. Zygote forks a child process, rename it to package_name.
@@ -209,16 +219,10 @@ class AppProfiler(object):
                 # The problem here is we want to profile the process started in step 4, but
                 # sometimes we run into the process started in step 1. To solve it, we can check
                 # if the process has opened an apk file in some app dirs.
-                if not self._has_opened_apk_file(pid):
-                    return None
-            return pid
-        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 self.app_program in strs[-1]:
-                return int(strs[1])
+                if self.android_version >= 8 and self.config['app_package_name'] and (
+                    not self._has_opened_apk_file(pid)):
+                    continue
+                return pid
         return None
 
 
index c33bdb0..cbdd27b 100644 (file)
@@ -138,17 +138,20 @@ class TestExampleBase(TestBase):
 
     @classmethod
     def tearDownClass(cls):
+        if hasattr(cls, 'test_result') and cls.test_result and not cls.test_result.wasSuccessful():
+            return
         if hasattr(cls, 'package_name'):
             cls.adb.check_run(["uninstall", cls.package_name])
-
-    @classmethod
-    def cleanupTestFiles(cls):
         remove("binary_cache")
         remove("annotated_files")
         remove("perf.data")
         remove("report.txt")
         remove("pprof.profile")
 
+    def run(self, result=None):
+        self.__class__.test_result = result
+        super(TestBase, self).run(result)
+
     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, profile_from_launch=False, add_arch=False):
@@ -333,6 +336,16 @@ class TestExamplePureJava(TestExampleBase):
             ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()",
              "__start_thread"])
 
+    def test_app_profiler_multiprocesses(self):
+        self.adb.check_run(['shell', 'am', 'force-stop', self.package_name])
+        self.adb.check_run(['shell', 'am', 'start', '-n',
+                            self.package_name + '/.MultiProcessActivity'])
+        # Wait until both MultiProcessActivity and MultiProcessService set up.
+        time.sleep(3)
+        self.run_app_profiler(skip_compile=True, start_activity=False)
+        self.run_cmd(["report.py", "-o", "report.txt"])
+        self.check_strings_in_file("report.txt", ["BusyService", "BusyThread"])
+
     def test_app_profiler_with_ctrl_c(self):
         if is_windows():
             return
@@ -369,7 +382,7 @@ class TestExamplePureJava(TestExampleBase):
             [("MainActivity.java", 80, 80),
              ("run", 80, 0),
              ("callFunction", 0, 0),
-             ("line 24", 80, 0)])
+             ("line 23", 80, 0)])
 
     def test_report_sample(self):
         self.common_test_report_sample(
@@ -830,9 +843,7 @@ def main():
     if AdbHelper().get_android_version() < 7:
         log_info("Skip tests on Android version < N.")
         sys.exit(0)
-    test_program = unittest.main(failfast=True, exit=False)
-    if test_program.result.wasSuccessful():
-        TestExampleBase.cleanupTestFiles()
+    unittest.main(failfast=True)
 
 if __name__ == '__main__':
     main()
\ No newline at end of file