summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2019-05-02 00:01:38 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2019-05-02 00:01:38 +0000
commite85543e73fbdb5f96aaaacc3890a58058582667f (patch)
treedb128cb5d24ccc228821a911e17dfcadbf2b1822
parentf225e8c9342f9916b3be6b6c9eaabe46031c7c4c (diff)
parente2f99e6102781356aa00ef38efa0802d6ec63fe2 (diff)
downloadextras-e85543e73fbdb5f96aaaacc3890a58058582667f.tar.gz
Merge "simpleperf: add test for start/stop api."
-rw-r--r--simpleperf/Android.mk3
-rw-r--r--simpleperf/app_api/cpp/simpleperf.cpp94
-rw-r--r--simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java10
-rw-r--r--simpleperf/demo/CppApi/app/src/main/cpp/native-lib.cpp20
-rw-r--r--simpleperf/demo/JavaApi/app/src/main/java/simpleperf/demo/java_api/MainActivity.java9
-rw-r--r--simpleperf/scripts/script_testdata/cpp_api-debug_Q.apkbin0 -> 2791612 bytes
-rw-r--r--simpleperf/scripts/script_testdata/cpp_api-debug_prev_Q.apkbin0 -> 2791656 bytes
-rw-r--r--simpleperf/scripts/script_testdata/cpp_api-profile_Q.apkbin0 -> 1329603 bytes
-rw-r--r--simpleperf/scripts/script_testdata/cpp_api-profile_prev_Q.apkbin0 -> 1329603 bytes
-rw-r--r--simpleperf/scripts/script_testdata/java_api-debug_Q.apkbin0 -> 1521458 bytes
-rw-r--r--simpleperf/scripts/script_testdata/java_api-debug_prev_Q.apkbin0 -> 1521349 bytes
-rw-r--r--simpleperf/scripts/script_testdata/java_api-profile_Q.apkbin0 -> 20887 bytes
-rw-r--r--simpleperf/scripts/script_testdata/java_api-profile_prev_Q.apkbin0 -> 20887 bytes
-rwxr-xr-xsimpleperf/scripts/test.py107
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
new file mode 100644
index 00000000..62591ad5
--- /dev/null
+++ b/simpleperf/scripts/script_testdata/cpp_api-debug_Q.apk
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/cpp_api-debug_prev_Q.apk b/simpleperf/scripts/script_testdata/cpp_api-debug_prev_Q.apk
new file mode 100644
index 00000000..2ebf32b0
--- /dev/null
+++ b/simpleperf/scripts/script_testdata/cpp_api-debug_prev_Q.apk
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/cpp_api-profile_Q.apk b/simpleperf/scripts/script_testdata/cpp_api-profile_Q.apk
new file mode 100644
index 00000000..2d1d4e7f
--- /dev/null
+++ b/simpleperf/scripts/script_testdata/cpp_api-profile_Q.apk
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/cpp_api-profile_prev_Q.apk b/simpleperf/scripts/script_testdata/cpp_api-profile_prev_Q.apk
new file mode 100644
index 00000000..33d78806
--- /dev/null
+++ b/simpleperf/scripts/script_testdata/cpp_api-profile_prev_Q.apk
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/java_api-debug_Q.apk b/simpleperf/scripts/script_testdata/java_api-debug_Q.apk
new file mode 100644
index 00000000..b569f1df
--- /dev/null
+++ b/simpleperf/scripts/script_testdata/java_api-debug_Q.apk
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/java_api-debug_prev_Q.apk b/simpleperf/scripts/script_testdata/java_api-debug_prev_Q.apk
new file mode 100644
index 00000000..cd551aef
--- /dev/null
+++ b/simpleperf/scripts/script_testdata/java_api-debug_prev_Q.apk
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/java_api-profile_Q.apk b/simpleperf/scripts/script_testdata/java_api-profile_Q.apk
new file mode 100644
index 00000000..b3d0ebca
--- /dev/null
+++ b/simpleperf/scripts/script_testdata/java_api-profile_Q.apk
Binary files differ
diff --git a/simpleperf/scripts/script_testdata/java_api-profile_prev_Q.apk b/simpleperf/scripts/script_testdata/java_api-profile_prev_Q.apk
new file mode 100644
index 00000000..96327d2a
--- /dev/null
+++ b/simpleperf/scripts/script_testdata/java_api-profile_prev_Q.apk
Binary files differ
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():