diff options
author | Yabin Cui <yabinc@google.com> | 2017-08-28 14:49:04 -0700 |
---|---|---|
committer | Yabin Cui <yabinc@google.com> | 2017-08-29 11:42:35 -0700 |
commit | 7cb6f297ae5d527b7e83472bf2e58b821be5b706 (patch) | |
tree | 014f3af80d3223128cd465413dba337b9c186678 | |
parent | 5c4dcdb68dccf48c701d8daa4e8b88a566f6eb1e (diff) | |
download | extras-7cb6f297ae5d527b7e83472bf2e58b821be5b706.tar.gz |
simpleperf: fix --app option for multiprocess apps.
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, 219 insertions, 36 deletions
diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp index 2bd0506c..85e218ca 100644 --- a/simpleperf/cmd_record.cpp +++ b/simpleperf/cmd_record.cpp @@ -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"; diff --git a/simpleperf/cmd_stat.cpp b/simpleperf/cmd_stat.cpp index a9d5036b..ddd83f7b 100644 --- a/simpleperf/cmd_stat.cpp +++ b/simpleperf/cmd_stat.cpp @@ -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"; diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/build/outputs/apk/app-profiling.apk b/simpleperf/demo/SimpleperfExamplePureJava/app/build/outputs/apk/app-profiling.apk Binary files differindex 0254bc06..f2970460 100644 --- a/simpleperf/demo/SimpleperfExamplePureJava/app/build/outputs/apk/app-profiling.apk +++ b/simpleperf/demo/SimpleperfExamplePureJava/app/build/outputs/apk/app-profiling.apk diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml index c6111027..f42ec17d 100644 --- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml +++ b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml @@ -17,9 +17,12 @@ </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 index 00000000..de698ec6 --- /dev/null +++ b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessActivity.java @@ -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 index 00000000..2fd4d571 --- /dev/null +++ b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessService.java @@ -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(); + } +} diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_main.xml b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_main.xml index 1aa44587..4a09b1ae 100644 --- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_main.xml +++ b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_main.xml @@ -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 index 00000000..f97b72e0 --- /dev/null +++ b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_multi_process.xml @@ -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> diff --git a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_sleep.xml b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_sleep.xml index e2274ca8..f732f777 100644 --- a/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_sleep.xml +++ b/simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_sleep.xml @@ -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> diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp index bf0a0614..e238ffbd 100644 --- a/simpleperf/environment.cpp +++ b/simpleperf/environment.cpp @@ -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; diff --git a/simpleperf/environment.h b/simpleperf/environment.h index 52d0a7c9..0f121465 100644 --- a/simpleperf/environment.h +++ b/simpleperf/environment.h @@ -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); diff --git a/simpleperf/scripts/app_profiler.py b/simpleperf/scripts/app_profiler.py index 9e0f5517..c089bbe8 100644 --- a/simpleperf/scripts/app_profiler.py +++ b/simpleperf/scripts/app_profiler.py @@ -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 diff --git a/simpleperf/scripts/test.py b/simpleperf/scripts/test.py index c33bdb06..cbdd27b3 100644 --- a/simpleperf/scripts/test.py +++ b/simpleperf/scripts/test.py @@ -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 |