diff options
author | Yabin Cui <yabinc@google.com> | 2017-07-14 23:38:33 +0000 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2017-07-14 23:38:33 +0000 |
commit | 44139af1f4c1e64fa8903aca3cd5a4f8a4c0dad9 (patch) | |
tree | 1393bb90839933aa147d3cfd7a79a7125b35389a | |
parent | 4e02dcd7d0e3851b68440b394b923a7b0e9c7ebd (diff) | |
parent | 9aa1dc2f3039db9faee9d581360a5d65caddb8f4 (diff) | |
download | extras-44139af1f4c1e64fa8903aca3cd5a4f8a4c0dad9.tar.gz |
Merge "simpleperf: support "--app" option in record/stat command."
am: 9aa1dc2f30
Change-Id: I4ca9cac0a3400f59d8e66fdeb1b68af2a62e7f98
-rw-r--r-- | simpleperf/cmd_record.cpp | 35 | ||||
-rw-r--r-- | simpleperf/cmd_stat.cpp | 31 | ||||
-rw-r--r-- | simpleperf/environment.cpp | 104 | ||||
-rw-r--r-- | simpleperf/environment.h | 5 | ||||
-rw-r--r-- | simpleperf/workload.cpp | 33 | ||||
-rw-r--r-- | simpleperf/workload.h | 6 |
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. |