summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2017-07-14 23:38:33 +0000
committerandroid-build-merger <android-build-merger@google.com>2017-07-14 23:38:33 +0000
commit44139af1f4c1e64fa8903aca3cd5a4f8a4c0dad9 (patch)
tree1393bb90839933aa147d3cfd7a79a7125b35389a
parent4e02dcd7d0e3851b68440b394b923a7b0e9c7ebd (diff)
parent9aa1dc2f3039db9faee9d581360a5d65caddb8f4 (diff)
downloadextras-44139af1f4c1e64fa8903aca3cd5a4f8a4c0dad9.tar.gz
Merge "simpleperf: support "--app" option in record/stat command."
am: 9aa1dc2f30 Change-Id: I4ca9cac0a3400f59d8e66fdeb1b68af2a62e7f98
-rw-r--r--simpleperf/cmd_record.cpp35
-rw-r--r--simpleperf/cmd_stat.cpp31
-rw-r--r--simpleperf/environment.cpp104
-rw-r--r--simpleperf/environment.h5
-rw-r--r--simpleperf/workload.cpp33
-rw-r--r--simpleperf/workload.h6
6 files changed, 206 insertions, 8 deletions
diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp
index acba6abb..e3f35c3d 100644
--- a/simpleperf/cmd_record.cpp
+++ b/simpleperf/cmd_record.cpp
@@ -78,6 +78,11 @@ class RecordCommand : public Command {
" can be used to change target of sampling information.\n"
" The default options are: -e cpu-cycles -f 4000 -o perf.data.\n"
"-a System-wide collection.\n"
+#if defined(__ANDROID__)
+"--app package_name Profile the process of an Android application.\n"
+" On non-rooted devices, the app must be debuggable,\n"
+" because we use run-as to switch to the app's context.\n"
+#endif
"-b Enable take branch stack sampling. Same as '-j any'\n"
"-c count Set event sample period. It means recording one sample when\n"
" [count] events happen. Can't be used with -f/-F option.\n"
@@ -145,6 +150,10 @@ class RecordCommand : public Command {
" This option is used to provide files with symbol table and\n"
" debug information, which are used for unwinding and dumping symbols.\n"
"-t tid1,tid2,... Record events on existing threads. Mutually exclusive with -a.\n"
+#if 0
+// Below options are only used internally and shouldn't be visible to the public.
+"--in-app We are already running in the app's context.\n"
+#endif
// clang-format on
),
use_sample_freq_(false),
@@ -168,7 +177,8 @@ class RecordCommand : public Command {
start_sampling_time_in_ns_(0),
sample_record_count_(0),
lost_record_count_(0),
- start_profiling_fd_(-1) {
+ start_profiling_fd_(-1),
+ in_app_context_(false) {
// Stop profiling if parent exits.
prctl(PR_SET_PDEATHSIG, SIGHUP, 0, 0, 0);
}
@@ -225,6 +235,8 @@ class RecordCommand : public Command {
uint64_t sample_record_count_;
uint64_t lost_record_count_;
int start_profiling_fd_;
+ std::string app_package_name_;
+ bool in_app_context_;
};
bool RecordCommand::Run(const std::vector<std::string>& args) {
@@ -242,6 +254,15 @@ bool RecordCommand::Run(const std::vector<std::string>& args) {
if (!ParseOptions(args, &workload_args)) {
return false;
}
+ if (!app_package_name_.empty() && !in_app_context_) {
+ // Some users want to profile non debuggable apps on rooted devices. If we use run-as,
+ // it will be impossible when using --app. So don't switch to app's context when we are
+ // root.
+ if (!IsRoot()) {
+ return RunInAppContext(app_package_name_, "record", args, workload_args.size(),
+ record_filename_);
+ }
+ }
if (event_selection_set_.empty()) {
if (!event_selection_set_.AddEventType(default_measured_event_type)) {
return false;
@@ -274,6 +295,11 @@ bool RecordCommand::Run(const std::vector<std::string>& args) {
return false;
}
}
+ } 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});
} else {
LOG(ERROR)
<< "No threads to monitor. Try `simpleperf help record` for help";
@@ -380,6 +406,11 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
for (i = 0; i < args.size() && !args[i].empty() && args[i][0] == '-'; ++i) {
if (args[i] == "-a") {
system_wide_collection_ = true;
+ } else if (args[i] == "--app") {
+ if (!NextArgumentOrError(args, &i)) {
+ return false;
+ }
+ app_package_name_ = args[i];
} else if (args[i] == "-b") {
branch_sampling_ = branch_sampling_type_map["any"];
} else if (args[i] == "-c") {
@@ -477,6 +508,8 @@ bool RecordCommand::ParseOptions(const std::vector<std::string>& args,
if (!event_selection_set_.AddEventGroup(event_types)) {
return false;
}
+ } else if (args[i] == "--in-app") {
+ in_app_context_ = true;
} else if (args[i] == "-j") {
if (!NextArgumentOrError(args, &i)) {
return false;
diff --git a/simpleperf/cmd_stat.cpp b/simpleperf/cmd_stat.cpp
index a0929bcf..c1f21f65 100644
--- a/simpleperf/cmd_stat.cpp
+++ b/simpleperf/cmd_stat.cpp
@@ -26,6 +26,7 @@
#include <string>
#include <vector>
+#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parsedouble.h>
#include <android-base/strings.h>
@@ -273,6 +274,11 @@ class StatCommand : public Command {
" Gather performance counter information of running [command].\n"
" And -a/-p/-t option can be used to change target of counter information.\n"
"-a Collect system-wide information.\n"
+#if defined(__ANDROID__)
+"--app package_name Profile the process of an Android application.\n"
+" On non-rooted devices, the app must be debuggable,\n"
+" because we use run-as to switch to the app's context.\n"
+#endif
"--cpu cpu_item1,cpu_item2,...\n"
" Collect information only on the selected cpus. cpu_item can\n"
" be a cpu number like 1, or a cpu range like 0-3.\n"
@@ -298,6 +304,10 @@ class StatCommand : public Command {
"-p pid1,pid2,... Stat events on existing processes. Mutually exclusive with -a.\n"
"-t tid1,tid2,... Stat events on existing threads. Mutually exclusive with -a.\n"
"--verbose Show result in verbose mode.\n"
+#if 0
+// Below options are only used internally and shouldn't be visible to the public.
+"--in-app We are already running in the app's context.\n"
+#endif
// clang-format on
),
verbose_mode_(false),
@@ -306,7 +316,8 @@ class StatCommand : public Command {
duration_in_sec_(0),
interval_in_ms_(0),
event_selection_set_(true),
- csv_(false) {
+ csv_(false),
+ in_app_context_(false) {
// Die if parent exits.
prctl(PR_SET_PDEATHSIG, SIGHUP, 0, 0, 0);
}
@@ -330,6 +341,8 @@ class StatCommand : public Command {
EventSelectionSet event_selection_set_;
std::string output_filename_;
bool csv_;
+ std::string app_package_name_;
+ bool in_app_context_;
};
bool StatCommand::Run(const std::vector<std::string>& args) {
@@ -342,6 +355,12 @@ bool StatCommand::Run(const std::vector<std::string>& args) {
if (!ParseOptions(args, &workload_args)) {
return false;
}
+ if (!app_package_name_.empty() && !in_app_context_) {
+ if (!IsRoot()) {
+ return RunInAppContext(app_package_name_, "stat", args, workload_args.size(),
+ output_filename_);
+ }
+ }
if (event_selection_set_.empty()) {
if (!AddDefaultMeasuredEventTypes()) {
return false;
@@ -364,6 +383,9 @@ bool StatCommand::Run(const std::vector<std::string>& args) {
if (workload != nullptr) {
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});
} else {
LOG(ERROR)
<< "No threads to monitor. Try `simpleperf help stat` for help\n";
@@ -456,6 +478,11 @@ bool StatCommand::ParseOptions(const std::vector<std::string>& args,
for (i = 0; i < args.size() && args[i].size() > 0 && args[i][0] == '-'; ++i) {
if (args[i] == "-a") {
system_wide_collection_ = true;
+ } else if (args[i] == "--app") {
+ if (!NextArgumentOrError(args, &i)) {
+ return false;
+ }
+ app_package_name_ = args[i];
} else if (args[i] == "--cpu") {
if (!NextArgumentOrError(args, &i)) {
return false;
@@ -499,6 +526,8 @@ bool StatCommand::ParseOptions(const std::vector<std::string>& args,
if (!event_selection_set_.AddEventGroup(event_types)) {
return false;
}
+ } else if (args[i] == "--in-app") {
+ in_app_context_ = true;
} else if (args[i] == "--no-inherit") {
child_inherit_ = false;
} else if (args[i] == "-o") {
diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp
index 7fff85f1..03fda1ac 100644
--- a/simpleperf/environment.cpp
+++ b/simpleperf/environment.cpp
@@ -17,6 +17,7 @@
#include "environment.h"
#include <inttypes.h>
+#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/utsname.h>
@@ -37,9 +38,11 @@
#include <sys/system_properties.h>
#endif
+#include "IOEventLoop.h"
#include "read_elf.h"
#include "thread_tree.h"
#include "utils.h"
+#include "workload.h"
class LineReader {
public:
@@ -512,3 +515,104 @@ void PrepareVdsoFile() {
}
Dso::SetVdsoFile(std::move(tmpfile), sizeof(size_t) == sizeof(uint64_t));
}
+
+int WaitForAppProcess(const std::string& package_name) {
+ size_t loop_count = 0;
+ while (true) {
+ std::vector<pid_t> pids = GetAllProcesses();
+ for (pid_t pid : pids) {
+ std::string cmdline;
+ if (!android::base::ReadFileToString("/proc/" + std::to_string(pid) + "/cmdline", &cmdline)) {
+ // Maybe we don't have permission to read it.
+ 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 (++loop_count == 1u) {
+ LOG(INFO) << "Waiting for process of app " << package_name;
+ }
+ usleep(1000);
+ }
+}
+
+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) {
+ // 1. Test if the package exists.
+ if (!Workload::RunCmd({"run-as", app_package_name, "echo", ">/dev/null"}, false)) {
+ LOG(ERROR) << "Package " << app_package_name << "doesn't exist or isn't debuggable.";
+ return false;
+ }
+
+ // 2. Copy simpleperf binary to the package.
+ std::string simpleperf_path;
+ if (!android::base::Readlink("/proc/self/exe", &simpleperf_path)) {
+ PLOG(ERROR) << "ReadLink failed";
+ return false;
+ }
+ if (!Workload::RunCmd({"run-as", app_package_name, "cp", simpleperf_path, "simpleperf"})) {
+ return false;
+ }
+
+ // 3. Prepare to start child process to profile.
+ std::string output_basename = output_filepath.empty() ? "" :
+ android::base::Basename(output_filepath);
+ std::vector<std::string> new_args =
+ {"run-as", app_package_name, "./simpleperf", cmd, "--in-app"};
+ for (size_t i = 0; i < args.size(); ++i) {
+ if (i >= args.size() - workload_args_size || args[i] != "-o") {
+ new_args.push_back(args[i]);
+ } else {
+ new_args.push_back(args[i++]);
+ new_args.push_back(output_basename);
+ }
+ }
+ std::unique_ptr<Workload> workload = Workload::CreateWorkload(new_args);
+ if (!workload) {
+ return false;
+ }
+
+ IOEventLoop loop;
+ bool need_to_kill_child = false;
+ if (!loop.AddSignalEvents({SIGINT, SIGTERM, SIGHUP},
+ [&]() { need_to_kill_child = true; return loop.ExitLoop(); })) {
+ return false;
+ }
+ if (!loop.AddSignalEvent(SIGCHLD, [&]() { return loop.ExitLoop(); })) {
+ return false;
+ }
+
+ // 4. Create child process to run run-as, and wait for the child process.
+ if (!workload->Start()) {
+ return false;
+ }
+ if (!loop.RunLoop()) {
+ return false;
+ }
+ if (need_to_kill_child) {
+ // The child process can exit before we kill it, so don't report kill errors.
+ Workload::RunCmd({"run-as", app_package_name, "pkill", "simpleperf"}, false);
+ }
+ int exit_code;
+ if (!workload->WaitChildProcess(&exit_code) || exit_code != 0) {
+ return false;
+ }
+
+ // 5. If there is any output file, copy it from the app's directory.
+ if (!output_filepath.empty()) {
+ if (!Workload::RunCmd({"run-as", app_package_name, "cat", output_basename,
+ ">" + output_filepath})) {
+ return false;
+ }
+ if (!Workload::RunCmd({"run-as", app_package_name, "rm", output_basename})) {
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/simpleperf/environment.h b/simpleperf/environment.h
index 2f4d58b9..a2f0c4bd 100644
--- a/simpleperf/environment.h
+++ b/simpleperf/environment.h
@@ -91,4 +91,9 @@ static inline int gettid() {
ArchType GetMachineArch();
void PrepareVdsoFile();
+int WaitForAppProcess(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);
+
#endif // SIMPLE_PERF_ENVIRONMENT_H_
diff --git a/simpleperf/workload.cpp b/simpleperf/workload.cpp
index dcb0e78a..b3bd459f 100644
--- a/simpleperf/workload.cpp
+++ b/simpleperf/workload.cpp
@@ -23,6 +23,7 @@
#include <unistd.h>
#include <android-base/logging.h>
+#include <android-base/strings.h>
std::unique_ptr<Workload> Workload::CreateWorkload(const std::vector<std::string>& args) {
std::unique_ptr<Workload> workload(new Workload(args, std::function<void ()>()));
@@ -40,11 +41,21 @@ std::unique_ptr<Workload> Workload::CreateWorkload(const std::function<void ()>&
return nullptr;
}
+bool Workload::RunCmd(const std::vector<std::string>& args, bool report_error) {
+ std::string arg_str = android::base::Join(args, ' ');
+ int ret = system(arg_str.c_str());
+ if (ret != 0 && report_error) {
+ PLOG(ERROR) << "Failed to run cmd " << arg_str;
+ return false;
+ }
+ return ret == 0;
+}
+
Workload::~Workload() {
if (work_pid_ != -1 && work_state_ != NotYetCreateNewProcess) {
- if (!Workload::WaitChildProcess(false, false)) {
+ if (!Workload::WaitChildProcess(false, false, nullptr)) {
kill(work_pid_, SIGKILL);
- Workload::WaitChildProcess(true, true);
+ Workload::WaitChildProcess(true, true, nullptr);
}
}
if (start_signal_fd_ != -1) {
@@ -151,18 +162,30 @@ bool Workload::Start() {
return true;
}
-bool Workload::WaitChildProcess(bool wait_forever, bool is_child_killed) {
+bool Workload::WaitChildProcess(int* exit_code) {
+ return WaitChildProcess(true, false, exit_code);
+}
+
+bool Workload::WaitChildProcess(bool wait_forever, bool is_child_killed, int* exit_code) {
+ if (work_state_ == Finished) {
+ return true;
+ }
bool finished = false;
int status;
pid_t result = TEMP_FAILURE_RETRY(waitpid(work_pid_, &status, (wait_forever ? 0 : WNOHANG)));
if (result == work_pid_) {
finished = true;
+ work_state_ = Finished;
if (WIFSIGNALED(status)) {
if (!(is_child_killed && WTERMSIG(status) == SIGKILL)) {
LOG(WARNING) << "child process was terminated by signal " << strsignal(WTERMSIG(status));
}
- } else if (WIFEXITED(status) && WEXITSTATUS(status) != 0) {
- LOG(WARNING) << "child process exited with exit code " << WEXITSTATUS(status);
+ } else if (WIFEXITED(status)) {
+ if (exit_code != nullptr) {
+ *exit_code = WEXITSTATUS(status);
+ } else if (WEXITSTATUS(status) != 0) {
+ LOG(WARNING) << "child process exited with exit code " << WEXITSTATUS(status);
+ }
}
} else if (result == -1) {
PLOG(ERROR) << "waitpid() failed";
diff --git a/simpleperf/workload.h b/simpleperf/workload.h
index 9d9d5952..34fe7959 100644
--- a/simpleperf/workload.h
+++ b/simpleperf/workload.h
@@ -31,11 +31,13 @@ class Workload {
NotYetCreateNewProcess,
NotYetStartNewProcess,
Started,
+ Finished,
};
public:
static std::unique_ptr<Workload> CreateWorkload(const std::vector<std::string>& args);
static std::unique_ptr<Workload> CreateWorkload(const std::function<void ()>& function);
+ static bool RunCmd(const std::vector<std::string>& args, bool report_error = true);
~Workload();
@@ -47,6 +49,8 @@ class Workload {
return work_pid_;
}
+ bool WaitChildProcess(int* exit_code);
+
private:
explicit Workload(const std::vector<std::string>& args,
const std::function<void ()>& function)
@@ -60,7 +64,7 @@ class Workload {
bool CreateNewProcess();
void ChildProcessFn(int start_signal_fd, int exec_child_fd);
- bool WaitChildProcess(bool wait_forever, bool is_child_killed);
+ bool WaitChildProcess(bool wait_forever, bool is_child_killed, int* exit_code);
WorkState work_state_;
// The child process either executes child_proc_args or run child_proc_function.