diff options
author | Yabin Cui <yabinc@google.com> | 2019-05-02 00:01:38 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2019-05-02 00:01:38 +0000 |
commit | e85543e73fbdb5f96aaaacc3890a58058582667f (patch) | |
tree | db128cb5d24ccc228821a911e17dfcadbf2b1822 | |
parent | f225e8c9342f9916b3be6b6c9eaabe46031c7c4c (diff) | |
parent | e2f99e6102781356aa00ef38efa0802d6ec63fe2 (diff) | |
download | extras-e85543e73fbdb5f96aaaacc3890a58058582667f.tar.gz |
Merge "simpleperf: add test for start/stop api."
14 files changed, 193 insertions, 50 deletions
diff --git a/simpleperf/Android.mk b/simpleperf/Android.mk index 2e3a3c84..6ba707a9 100644 --- a/simpleperf/Android.mk +++ b/simpleperf/Android.mk @@ -34,7 +34,8 @@ SIMPLEPERF_SCRIPT_LIST := \ testdata/perf_with_symbols.data \ testdata/perf_with_trace_offcpu.data \ testdata/perf_with_tracepoint_event.data \ - testdata/perf_with_interpreter_frames.data + testdata/perf_with_interpreter_frames.data \ + $(call all-named-files-under,*,app_api) SIMPLEPERF_SCRIPT_LIST := $(addprefix -f $(LOCAL_PATH)/,$(SIMPLEPERF_SCRIPT_LIST)) diff --git a/simpleperf/app_api/cpp/simpleperf.cpp b/simpleperf/app_api/cpp/simpleperf.cpp index 23bb4fb5..54b331a3 100644 --- a/simpleperf/app_api/cpp/simpleperf.cpp +++ b/simpleperf/app_api/cpp/simpleperf.cpp @@ -188,6 +188,7 @@ class ProfileSessionImpl { pid_t simpleperf_pid_ = -1; int control_fd_ = -1; int reply_fd_ = -1; + bool trace_offcpu_ = false; }; ProfileSessionImpl::~ProfileSessionImpl() { @@ -204,6 +205,11 @@ void ProfileSessionImpl::StartRecording(const std::vector<std::string> &args) { if (state_ != NOT_YET_STARTED) { Abort("startRecording: session in wrong state %d", state_); } + for (const auto& arg : args) { + if (arg == "--trace-offcpu") { + trace_offcpu_ = true; + } + } std::string simpleperf_path = FindSimpleperf(); CheckIfPerfEnabled(); CreateSimpleperfDataDir(); @@ -216,6 +222,9 @@ void ProfileSessionImpl::PauseRecording() { if (state_ != STARTED) { Abort("pauseRecording: session in wrong state %d", state_); } + if (trace_offcpu_) { + Abort("--trace-offcpu doesn't work well with pause/resume recording"); + } SendCmd("pause"); state_ = PAUSED; } @@ -270,6 +279,61 @@ static bool IsExecutableFile(const std::string& path) { return false; } +static std::string ReadFile(FILE* fp) { + std::string s; + if (fp == nullptr) { + return s; + } + char buf[200]; + while (true) { + ssize_t n = fread(buf, 1, sizeof(buf), fp); + if (n <= 0) { + break; + } + s.insert(s.end(), buf, buf + n); + } + fclose(fp); + return s; +} + +static bool RunCmd(std::vector<const char*> args, std::string* stdout) { + int stdout_fd[2]; + if (pipe(stdout_fd) != 0) { + return false; + } + args.push_back(nullptr); + // Fork handlers (like gsl_library_close) may hang in a multi-thread environment. + // So we use vfork instead of fork to avoid calling them. + int pid = vfork(); + if (pid == -1) { + return false; + } + if (pid == 0) { + // child process + close(stdout_fd[0]); + dup2(stdout_fd[1], 1); + close(stdout_fd[1]); + execvp(const_cast<char*>(args[0]), const_cast<char**>(args.data())); + _exit(1); + } + // parent process + close(stdout_fd[1]); + int status; + pid_t result = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0)); + if (result == -1) { + Abort("failed to call waitpid: %s", strerror(errno)); + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + return false; + } + if (stdout == nullptr) { + close(stdout_fd[0]); + } else { + *stdout = ReadFile(fdopen(stdout_fd[0], "r")); + } + return true; +} + std::string ProfileSessionImpl::FindSimpleperf() { // 1. Try /data/local/tmp/simpleperf first. Probably it's newer than /system/bin/simpleperf. std::string simpleperf_path = FindSimpleperfInTempDir(); @@ -292,38 +356,21 @@ std::string ProfileSessionImpl::FindSimpleperfInTempDir() { } // Copy it to app_dir to execute it. const std::string to_path = app_data_dir_ + "/simpleperf"; - const std::string copy_cmd = "cp " + path + " " + to_path; - if (system(copy_cmd.c_str()) != 0) { + if (!RunCmd({"/system/bin/cp", path.c_str(), to_path.c_str()}, nullptr)) { return ""; } - const std::string test_cmd = to_path; // For apps with target sdk >= 29, executing app data file isn't allowed. So test executing it. - if (system(test_cmd.c_str()) != 0) { + if (!RunCmd({to_path.c_str()}, nullptr)) { return ""; } return to_path; } -static std::string ReadFile(FILE* fp) { - std::string s; - char buf[200]; - while (true) { - ssize_t n = fread(buf, 1, sizeof(buf), fp); - if (n <= 0) { - break; - } - s.insert(s.end(), buf, buf + n); - } - return s; -} - void ProfileSessionImpl::CheckIfPerfEnabled() { - FILE* fp = popen("/system/bin/getprop security.perf_harden", "re"); - if (fp == nullptr) { + std::string s; + if (!RunCmd({"/system/bin/getprop", "security.perf_harden"}, &s)) { return; // Omit check if getprop doesn't exist. } - std::string s = ReadFile(fp); - pclose(fp); if (!s.empty() && s[0] == '1') { Abort("linux perf events aren't enabled on the device. Please run api_profiler.py."); } @@ -366,7 +413,9 @@ void ProfileSessionImpl::CreateSimpleperfProcess(const std::string &simpleperf_p argv[args.size()] = nullptr; // 3. Start simpleperf process. - int pid = fork(); + // Fork handlers (like gsl_library_close) may hang in a multi-thread environment. + // So we use vfork instead of fork to avoid calling them. + int pid = vfork(); if (pid == -1) { Abort("failed to fork: %s", strerror(errno)); } @@ -415,7 +464,6 @@ ProfileSession::ProfileSession() { Abort("failed to open /proc/self/cmdline: %s", strerror(errno)); } std::string s = ReadFile(fp); - fclose(fp); for (int i = 0; i < s.size(); i++) { if (s[i] == '\0') { s = s.substr(0, i); diff --git a/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java b/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java index f1e2b202..ce043c41 100644 --- a/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java +++ b/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java @@ -65,6 +65,7 @@ public class ProfileSession { private String appDataDir; private String simpleperfDataDir; private Process simpleperfProcess; + private boolean traceOffcpu = false; /** * @param appDataDir the same as android.content.Context.getDataDir(). @@ -115,6 +116,11 @@ public class ProfileSession { if (state != State.NOT_YET_STARTED) { throw new AssertionError("startRecording: session in wrong state " + state); } + for (String arg : args) { + if (arg.equals("--trace-offcpu")) { + traceOffcpu = true; + } + } String simpleperfPath = findSimpleperf(); checkIfPerfEnabled(); createSimpleperfDataDir(); @@ -129,6 +135,10 @@ public class ProfileSession { if (state != State.STARTED) { throw new AssertionError("pauseRecording: session in wrong state " + state); } + if (traceOffcpu) { + throw new AssertionError( + "--trace-offcpu option doesn't work well with pause/resume recording"); + } sendCmd("pause"); state = State.PAUSED; } diff --git a/simpleperf/demo/CppApi/app/src/main/cpp/native-lib.cpp b/simpleperf/demo/CppApi/app/src/main/cpp/native-lib.cpp index a47e73b6..bdf5f468 100644 --- a/simpleperf/demo/CppApi/app/src/main/cpp/native-lib.cpp +++ b/simpleperf/demo/CppApi/app/src/main/cpp/native-lib.cpp @@ -79,14 +79,18 @@ extern "C" JNIEXPORT void JNICALL Java_simpleperf_demo_cpp_1api_MainActivity_runNativeCode( JNIEnv *env, jobject jobj) { - pthread_t profile_thread; - if (pthread_create(&profile_thread, nullptr, ProfileThreadFunc, nullptr) != 0) { - log("failed to create profile thread"); - return; - } - pthread_t busy_thread; - if (pthread_create(&busy_thread, nullptr, BusyThreadFunc, nullptr) != 0) { - log("failed to create busy thread"); + static bool threadsStarted = false; + if (!threadsStarted) { + pthread_t profile_thread; + if (pthread_create(&profile_thread, nullptr, ProfileThreadFunc, nullptr) != 0) { + log("failed to create profile thread"); + return; + } + pthread_t busy_thread; + if (pthread_create(&busy_thread, nullptr, BusyThreadFunc, nullptr) != 0) { + log("failed to create busy thread"); + } + threadsStarted = true; } } diff --git a/simpleperf/demo/JavaApi/app/src/main/java/simpleperf/demo/java_api/MainActivity.java b/simpleperf/demo/JavaApi/app/src/main/java/simpleperf/demo/java_api/MainActivity.java index 2cb96f03..9cb0c4ad 100644 --- a/simpleperf/demo/JavaApi/app/src/main/java/simpleperf/demo/java_api/MainActivity.java +++ b/simpleperf/demo/JavaApi/app/src/main/java/simpleperf/demo/java_api/MainActivity.java @@ -28,6 +28,8 @@ public class MainActivity extends AppCompatActivity { TextView textView; + static boolean threadsStarted = false; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -35,8 +37,11 @@ public class MainActivity extends AppCompatActivity { textView = (TextView) findViewById(R.id.textView); - Thread profileThread = createProfileThread(); - createBusyThread(profileThread); + if (!threadsStarted) { + threadsStarted = true; + Thread profileThread = createProfileThread(); + createBusyThread(profileThread); + } } Thread createProfileThread() { diff --git a/simpleperf/scripts/script_testdata/cpp_api-debug_Q.apk b/simpleperf/scripts/script_testdata/cpp_api-debug_Q.apk Binary files differnew file mode 100644 index 00000000..62591ad5 --- /dev/null +++ b/simpleperf/scripts/script_testdata/cpp_api-debug_Q.apk diff --git a/simpleperf/scripts/script_testdata/cpp_api-debug_prev_Q.apk b/simpleperf/scripts/script_testdata/cpp_api-debug_prev_Q.apk Binary files differnew file mode 100644 index 00000000..2ebf32b0 --- /dev/null +++ b/simpleperf/scripts/script_testdata/cpp_api-debug_prev_Q.apk diff --git a/simpleperf/scripts/script_testdata/cpp_api-profile_Q.apk b/simpleperf/scripts/script_testdata/cpp_api-profile_Q.apk Binary files differnew file mode 100644 index 00000000..2d1d4e7f --- /dev/null +++ b/simpleperf/scripts/script_testdata/cpp_api-profile_Q.apk diff --git a/simpleperf/scripts/script_testdata/cpp_api-profile_prev_Q.apk b/simpleperf/scripts/script_testdata/cpp_api-profile_prev_Q.apk Binary files differnew file mode 100644 index 00000000..33d78806 --- /dev/null +++ b/simpleperf/scripts/script_testdata/cpp_api-profile_prev_Q.apk diff --git a/simpleperf/scripts/script_testdata/java_api-debug_Q.apk b/simpleperf/scripts/script_testdata/java_api-debug_Q.apk Binary files differnew file mode 100644 index 00000000..b569f1df --- /dev/null +++ b/simpleperf/scripts/script_testdata/java_api-debug_Q.apk diff --git a/simpleperf/scripts/script_testdata/java_api-debug_prev_Q.apk b/simpleperf/scripts/script_testdata/java_api-debug_prev_Q.apk Binary files differnew file mode 100644 index 00000000..cd551aef --- /dev/null +++ b/simpleperf/scripts/script_testdata/java_api-debug_prev_Q.apk diff --git a/simpleperf/scripts/script_testdata/java_api-profile_Q.apk b/simpleperf/scripts/script_testdata/java_api-profile_Q.apk Binary files differnew file mode 100644 index 00000000..b3d0ebca --- /dev/null +++ b/simpleperf/scripts/script_testdata/java_api-profile_Q.apk diff --git a/simpleperf/scripts/script_testdata/java_api-profile_prev_Q.apk b/simpleperf/scripts/script_testdata/java_api-profile_prev_Q.apk Binary files differnew file mode 100644 index 00000000..96327d2a --- /dev/null +++ b/simpleperf/scripts/script_testdata/java_api-profile_prev_Q.apk diff --git a/simpleperf/scripts/test.py b/simpleperf/scripts/test.py index a3ef42fc..7c39e528 100755 --- a/simpleperf/scripts/test.py +++ b/simpleperf/scripts/test.py @@ -123,6 +123,21 @@ class TestBase(unittest.TestCase): return output_data return '' + def check_strings_in_file(self, filename, strings): + self.check_exist(filename=filename) + with open(filename, 'r') as fh: + self.check_strings_in_content(fh.read(), strings) + + def check_exist(self, filename=None, dirname=None): + if filename: + self.assertTrue(os.path.isfile(filename), filename) + if dirname: + self.assertTrue(os.path.isdir(dirname), dirname) + + def check_strings_in_content(self, content, strings): + for s in strings: + self.assertNotEqual(content.find(s), -1, "s: %s, content: %s" % (s, content)) + class TestExampleBase(TestBase): @classmethod @@ -208,12 +223,6 @@ class TestExampleBase(TestBase): if build_binary_cache: self.check_exist(dirname="binary_cache") - def check_exist(self, filename=None, dirname=None): - if filename: - self.assertTrue(os.path.isfile(filename), filename) - if dirname: - self.assertTrue(os.path.isdir(dirname), dirname) - def check_file_under_dir(self, dirname, filename): self.check_exist(dirname=dirname) for _, _, files in os.walk(dirname): @@ -222,16 +231,6 @@ class TestExampleBase(TestBase): return self.fail("Failed to call check_file_under_dir(dir=%s, file=%s)" % (dirname, filename)) - - def check_strings_in_file(self, filename, strings): - self.check_exist(filename=filename) - with open(filename, 'r') as fh: - self.check_strings_in_content(fh.read(), strings) - - def check_strings_in_content(self, content, strings): - for s in strings: - self.assertNotEqual(content.find(s), -1, "s: %s, content: %s" % (s, content)) - def check_annotation_summary(self, summary_file, check_entries): """ check_entries is a list of (name, accumulated_period, period). This function checks for each entry, if the line containing [name] @@ -1273,6 +1272,82 @@ class TestBinaryCacheBuilder(TestBase): self.assertTrue(filecmp.cmp(target_file, source_file)) +class TestApiProfiler(TestBase): + def run_api_test(self, package_name, apk_name, expected_reports, min_android_version): + adb = AdbHelper() + if adb.get_android_version() < ord(min_android_version) - ord('L') + 5: + log_info('skip this test on Android < %s.' % min_android_version) + return + # step 1: Prepare profiling. + self.run_cmd(['api_profiler.py', 'prepare']) + # step 2: Install and run the app. + apk_path = os.path.join('testdata', apk_name) + adb.run(['uninstall', package_name]) + adb.check_run(['install', '-t', apk_path]) + adb.check_run(['shell', 'am', 'start', '-n', package_name + '/.MainActivity']) + # step 3: Wait until the app exits. + time.sleep(4) + while True: + result = adb.run(['shell', 'pidof', package_name]) + if not result: + break + time.sleep(1) + # step 4: Collect recording data. + remove('simpleperf_data') + self.run_cmd(['api_profiler.py', 'collect', '-p', package_name, '-o', 'simpleperf_data']) + # step 5: Check recording data. + names = os.listdir('simpleperf_data') + self.assertGreater(len(names), 0) + for name in names: + path = os.path.join('simpleperf_data', name) + remove('report.txt') + self.run_cmd(['report.py', '-g', '-o', 'report.txt', '-i', path]) + self.check_strings_in_file('report.txt', expected_reports) + # step 6: Clean up. + remove('report.txt') + remove('simpleperf_data') + adb.check_run(['uninstall', package_name]) + + def run_cpp_api_test(self, apk_name, min_android_version): + self.run_api_test('simpleperf.demo.cpp_api', apk_name, ['BusyThreadFunc'], + min_android_version) + + def test_cpp_api_on_a_debuggable_app_targeting_prev_q(self): + # The source code of the apk is in simpleperf/demo/CppApi (with a small change to exit + # after recording). + self.run_cpp_api_test('cpp_api-debug_prev_Q.apk', 'N') + + def test_cpp_api_on_a_debuggable_app_targeting_q(self): + self.run_cpp_api_test('cpp_api-debug_Q.apk', 'N') + + def test_cpp_api_on_a_profileable_app_targeting_prev_q(self): + # a release apk with <profileable android:shell="true" /> + self.run_cpp_api_test('cpp_api-profile_prev_Q.apk', 'Q') + + def test_cpp_api_on_a_profileable_app_targeting_q(self): + self.run_cpp_api_test('cpp_api-profile_Q.apk', 'Q') + + def run_java_api_test(self, apk_name, min_android_version): + self.run_api_test('simpleperf.demo.java_api', apk_name, + ['simpleperf.demo.java_api.MainActivity', 'java.lang.Thread.run'], + min_android_version) + + def test_java_api_on_a_debuggable_app_targeting_prev_q(self): + # The source code of the apk is in simpleperf/demo/JavaApi (with a small change to exit + # after recording). + self.run_java_api_test('java_api-debug_prev_Q.apk', 'P') + + def test_java_api_on_a_debuggable_app_targeting_q(self): + self.run_java_api_test('java_api-debug_Q.apk', 'P') + + def test_java_api_on_a_profileable_app_targeting_prev_q(self): + # a release apk with <profileable android:shell="true" /> + self.run_java_api_test('java_api-profile_prev_Q.apk', 'Q') + + def test_java_api_on_a_profileable_app_targeting_q(self): + self.run_java_api_test('java_api-profile_Q.apk', 'Q') + + def get_all_tests(): tests = [] for name, value in globals().items(): |