diff options
author | Yabin Cui <yabinc@google.com> | 2017-08-28 15:49:33 -0700 |
---|---|---|
committer | Yabin Cui <yabinc@google.com> | 2017-08-28 18:03:51 -0700 |
commit | 88387691082a3b9afeacd92c7c28d537190ea8a5 (patch) | |
tree | debacafe372bf4e823b171f8bacb60a453504a3e | |
parent | 14f2ee3581b867e70567693b4f3520a80ad2d9a3 (diff) | |
download | extras-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.cpp | 2 | ||||
-rw-r--r-- | simpleperf/environment.cpp | 39 | ||||
-rw-r--r-- | simpleperf/scripts/app_profiler.py | 31 | ||||
-rw-r--r-- | simpleperf/scripts/test.py | 27 |
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"]) |