OSDN Git Service

simpleperf: support "--app" option in record/stat command.
authorYabin Cui <yabinc@google.com>
Wed, 12 Jul 2017 22:50:20 +0000 (15:50 -0700)
committerYabin Cui <yabinc@google.com>
Fri, 14 Jul 2017 21:32:50 +0000 (14:32 -0700)
By using --app [package_name] option, users don't need to
manually run run-as or look for the app's process.
It is also good for profiling app startup time.

For simpleperf, it now can start in the shell's context,
and be able to get tracing events information, which can
support profiling tracepoint events for apps. This is
useful for monitoring thread sleep time.

This CL is tested manually, but I will change the
way of running simpleperf cts test to test this.

Bug: http://b/34108866
Test: test manually.

Change-Id: I8db0390af8e49c56dc4860374153f753bbcf9b11

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.