} 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";
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";
</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>
--- /dev/null
+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();
+ }
+}
--- /dev/null
+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();
+ }
+}
<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"
--- /dev/null
+<?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>
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>
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();
// 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
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;
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);
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.
# 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
@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):
["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
[("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(
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