summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2017-08-28 14:49:04 -0700
committerYabin Cui <yabinc@google.com>2017-08-29 11:42:35 -0700
commit7cb6f297ae5d527b7e83472bf2e58b821be5b706 (patch)
tree014f3af80d3223128cd465413dba337b9c186678
parent5c4dcdb68dccf48c701d8daa4e8b88a566f6eb1e (diff)
downloadextras-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
-rw-r--r--simpleperf/cmd_record.cpp4
-rw-r--r--simpleperf/cmd_stat.cpp4
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/build/outputs/apk/app-profiling.apkbin1576599 -> 1578709 bytes
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/src/main/AndroidManifest.xml9
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessActivity.java78
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/src/main/java/com/example/simpleperf/simpleperfexamplepurejava/MultiProcessService.java50
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_main.xml2
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_multi_process.xml18
-rw-r--r--simpleperf/demo/SimpleperfExamplePureJava/app/src/main/res/layout/activity_sleep.xml9
-rw-r--r--simpleperf/environment.cpp18
-rw-r--r--simpleperf/environment.h2
-rw-r--r--simpleperf/scripts/app_profiler.py36
-rw-r--r--simpleperf/scripts/test.py25
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
index 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
Binary files differ
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