summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2017-08-28 15:49:33 -0700
committerYabin Cui <yabinc@google.com>2017-08-28 18:03:51 -0700
commit88387691082a3b9afeacd92c7c28d537190ea8a5 (patch)
treedebacafe372bf4e823b171f8bacb60a453504a3e
parent14f2ee3581b867e70567693b4f3520a80ad2d9a3 (diff)
downloadextras-88387691082a3b9afeacd92c7c28d537190ea8a5.tar.gz
simpleperf: fix finding app's process.
Also check the return value of recording, fix some tiny errors in tests. Bug: None. Test: run test.py. Change-Id: I42b33c796a302b71ca1c87888b4a2e9ad53306af
-rw-r--r--simpleperf/cmd_record.cpp2
-rw-r--r--simpleperf/environment.cpp39
-rw-r--r--simpleperf/scripts/app_profiler.py31
-rw-r--r--simpleperf/scripts/test.py27
4 files changed, 87 insertions, 12 deletions
diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp
index c22129a3..2bd0506c 100644
--- a/simpleperf/cmd_record.cpp
+++ b/simpleperf/cmd_record.cpp
@@ -111,7 +111,7 @@ class RecordCommand : public Command {
"-f freq Set event sample frequency. It means recording at most [freq]\n"
" samples every second. For non-tracepoint events, the default\n"
" option is -f 4000. A -f/-c option affects all event types\n"
-" following it until meeting another -f/-c option. For example,"
+" following it until meeting another -f/-c option. For example,\n"
" for \"-f 1000 cpu-cycles -c 1 -e sched:sched_switch\", cpu-cycles\n"
" has sample freq 1000, sched:sched_switch event has sample period 1.\n"
"-c count Set event sample period. It means recording one sample when\n"
diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp
index c936ac80..bf0a0614 100644
--- a/simpleperf/environment.cpp
+++ b/simpleperf/environment.cpp
@@ -522,6 +522,21 @@ void PrepareVdsoFile() {
Dso::SetVdsoFile(std::move(tmpfile), sizeof(size_t) == sizeof(uint64_t));
}
+static bool HasOpenedAppApkFile(int pid) {
+ std::string fd_path = "/proc/" + std::to_string(pid) + "/fd/";
+ std::vector<std::string> files = GetEntriesInDir(fd_path);
+ for (const auto& file : files) {
+ std::string real_path;
+ if (!android::base::Readlink(fd_path + file, &real_path)) {
+ continue;
+ }
+ if (real_path.find("app") != std::string::npos && real_path.find(".apk") != std::string::npos) {
+ return true;
+ }
+ }
+ return false;
+}
+
int WaitForAppProcess(const std::string& package_name) {
size_t loop_count = 0;
while (true) {
@@ -533,12 +548,26 @@ int WaitForAppProcess(const std::string& package_name) {
continue;
}
cmdline = android::base::Basename(cmdline);
- if (cmdline == package_name) {
- if (loop_count > 0u) {
- LOG(INFO) << "Got process " << pid << " for package " << package_name;
- }
- return pid;
+ if (cmdline != package_name) {
+ continue;
+ }
+ // 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.
+ // 2. The child process execute sh, which starts a child process running
+ // /system/bin/logwrapper.
+ // 3. logwrapper starts a child process running sh, which interprets wrap.sh.
+ // 4. wrap.sh starts a child process running the app.
+ // 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 (!HasOpenedAppApkFile(pid)) {
+ continue;
+ }
+ if (loop_count > 0u) {
+ LOG(INFO) << "Got process " << pid << " for package " << package_name;
}
+ return pid;
}
if (++loop_count == 1u) {
LOG(INFO) << "Waiting for process of app " << package_name;
diff --git a/simpleperf/scripts/app_profiler.py b/simpleperf/scripts/app_profiler.py
index a724e2da..9e0f5517 100644
--- a/simpleperf/scripts/app_profiler.py
+++ b/simpleperf/scripts/app_profiler.py
@@ -25,6 +25,7 @@ import argparse
import copy
import os
import os.path
+import re
import shutil
import subprocess
import sys
@@ -194,7 +195,23 @@ class AppProfiler(object):
# On Android >= N, pidof is available. Otherwise, we can use ps.
if self.android_version >= 7:
result, output = self.adb.run_and_return_output(['shell', 'pidof', self.app_program])
- return int(output) if result else None
+ if not result:
+ return None
+ pid = int(output)
+ if self.android_version >= 8 and self.config['app_package_name']:
+ # 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.
+ # 2. The child process execute sh, which starts a child process running
+ # /system/bin/logwrapper.
+ # 3. logwrapper starts a child process running sh, which interprets wrap.sh.
+ # 4. wrap.sh starts a child process running the app.
+ # 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
@@ -205,6 +222,12 @@ class AppProfiler(object):
return None
+ def _has_opened_apk_file(self, pid):
+ result, output = self.run_in_app_dir(['ls -l /proc/%d/fd' % pid],
+ check_result=False, log_output=False)
+ return result and re.search(r'app.*\.apk', output)
+
+
def _get_app_environment(self):
if not self.config['cmd']:
if self.app_pid is None:
@@ -252,7 +275,7 @@ class AppProfiler(object):
searched_lib[item] = True
# Use '/' as path separator as item comes from android environment.
filename = item[item.rfind('/') + 1:]
- dirname = '/data/local/tmp' + item[:item.rfind('/')]
+ dirname = '/data/local/tmp/native_libs' + item[:item.rfind('/')]
path = filename_dict.get(filename)
if path is None:
continue
@@ -295,8 +318,12 @@ class AppProfiler(object):
except KeyboardInterrupt:
self.stop_profiling()
self.record_subproc = None
+ # Don't check return value of record_subproc. Because record_subproc also
+ # receives Ctrl-C, and always returns non-zero.
returncode = 0
log_debug('profiling result [%s]' % (returncode == 0))
+ if returncode != 0:
+ log_exit('Failed to record profiling data.')
def start_profiling(self):
diff --git a/simpleperf/scripts/test.py b/simpleperf/scripts/test.py
index bbee65a4..c33bdb06 100644
--- a/simpleperf/scripts/test.py
+++ b/simpleperf/scripts/test.py
@@ -153,7 +153,7 @@ class TestExampleBase(TestBase):
build_binary_cache=True, skip_compile=False, start_activity=True,
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"]
+ "-r", record_arg, "-o", "perf.data"]
if not build_binary_cache:
args.append("-nb")
if skip_compile or self.__class__.compiled:
@@ -253,8 +253,6 @@ 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):
self.run_cmd(["report.py", "-h"])
@@ -278,7 +276,7 @@ class TestExampleBase(TestBase):
self.run_cmd(["report_sample.py"])
output = self.run_cmd(["report_sample.py", "perf.data"], return_output=True)
self.check_strings_in_content(output, check_strings)
- self.run_app_profiler(record_arg="-g --duration 3 -e cpu-cycles:u, --no-dump-symbols")
+ self.run_app_profiler(record_arg="-g --duration 3 -e cpu-cycles:u --no-dump-symbols")
output = self.run_cmd(["report_sample.py", "--symfs", "binary_cache"], return_output=True)
self.check_strings_in_content(output, check_strings)
@@ -328,6 +326,13 @@ class TestExamplePureJava(TestExampleBase):
def test_app_profiler(self):
self.common_test_app_profiler()
+ def test_app_profiler_profile_from_launch(self):
+ self.run_app_profiler(profile_from_launch=True, add_arch=True, build_binary_cache=False)
+ self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+ self.check_strings_in_file("report.txt",
+ ["com.example.simpleperf.simpleperfexamplepurejava.MainActivity$1.run()",
+ "__start_thread"])
+
def test_app_profiler_with_ctrl_c(self):
if is_windows():
return
@@ -447,6 +452,13 @@ class TestExampleWithNative(TestExampleBase):
remove("binary_cache")
self.run_app_profiler(native_lib_dir=self.example_path)
+ def test_app_profiler_profile_from_launch(self):
+ self.run_app_profiler(profile_from_launch=True, add_arch=True, build_binary_cache=False)
+ self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+ self.check_strings_in_file("report.txt",
+ ["BusyLoopThread",
+ "__start_thread"])
+
def test_report(self):
self.common_test_report()
self.run_cmd(["report.py", "-g", "-o", "report.txt"])
@@ -596,6 +608,13 @@ class TestExampleOfKotlin(TestExampleBase):
def test_app_profiler(self):
self.common_test_app_profiler()
+ def test_app_profiler_profile_from_launch(self):
+ self.run_app_profiler(profile_from_launch=True, add_arch=True, build_binary_cache=False)
+ self.run_cmd(["report.py", "-g", "-o", "report.txt"])
+ self.check_strings_in_file("report.txt",
+ ["com.example.simpleperf.simpleperfexampleofkotlin.MainActivity$createBusyThread$1.run()",
+ "__start_thread"])
+
def test_report(self):
self.common_test_report()
self.run_cmd(["report.py", "-g", "-o", "report.txt"])