OSDN Git Service

Merge "simpleperf: support "--app" option in record/stat command."
authorTreehugger Robot <treehugger-gerrit@google.com>
Fri, 14 Jul 2017 23:32:35 +0000 (23:32 +0000)
committerGerrit Code Review <noreply-gerritcodereview@google.com>
Fri, 14 Jul 2017 23:32:35 +0000 (23:32 +0000)
simpleperf/cmd_record.cpp
simpleperf/cmd_stat.cpp
simpleperf/environment.cpp
simpleperf/environment.h
simpleperf/workload.cpp
simpleperf/workload.h

index acba6ab..e3f35c3 100644 (file)
@@ -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;
index a0929bc..c1f21f6 100644 (file)
@@ -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") {
index 7fff85f..03fda1a 100644 (file)
@@ -17,6 +17,7 @@
 #include "environment.h"
 
 #include <inttypes.h>
+#include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/utsname.h>
 #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;
+}
index 2f4d58b..a2f0c4b 100644 (file)
@@ -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_
index dcb0e78..b3bd459 100644 (file)
@@ -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";
index 9d9d595..34fe795 100644 (file)
@@ -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.