OSDN Git Service

simpleperf: make app api available for profileable apps.
authorYabin Cui <yabinc@google.com>
Mon, 25 Feb 2019 23:22:43 +0000 (15:22 -0800)
committerYabin Cui <yabinc@google.com>
Thu, 28 Feb 2019 21:50:41 +0000 (13:50 -0800)
Add api-prepare cmd to prepare recording via app api.
Add api-collect cmd to collect recording data generated by app api. The
recording data is compressed into a zip file.
The two added cmds support both debuggable apps and profileable apps.
Move api_app_profiler.py to api_profiler.py. And use the two added cmds in it.

Also improve app_api code:
1. Fix finding simpleperf.
2. Use time based output filenames.

Bug: 123717243
Test: test manually, will add run python tests later.

Change-Id: I88c20578d01a84bc20ea72276f2cab0f3c4d9109

14 files changed:
simpleperf/Android.bp
simpleperf/app_api/cpp/simpleperf.cpp
simpleperf/app_api/cpp/simpleperf.h
simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java
simpleperf/app_api/java/com/android/simpleperf/RecordOptions.java
simpleperf/cmd_api.cpp [new file with mode: 0644]
simpleperf/command.cpp
simpleperf/demo/CppApi/app/src/main/cpp/native-lib.cpp
simpleperf/demo/JavaApi/app/src/main/java/simpleperf/demo/java_api/MainActivity.java
simpleperf/environment.cpp
simpleperf/environment.h
simpleperf/scripts/api_app_profiler.py [deleted file]
simpleperf/scripts/api_profiler.py [new file with mode: 0755]
simpleperf/simpleperf_app_runner/simpleperf_app_runner.cpp

index 096f7bf..d45cc6a 100644 (file)
@@ -265,6 +265,7 @@ cc_defaults {
         linux: {
             srcs: [
                 "CallChainJoiner.cpp",
+                "cmd_api.cpp",
                 "cmd_debug_unwind.cpp",
                 "cmd_list.cpp",
                 "cmd_record.cpp",
index 11e70e6..23bb4fb 100644 (file)
@@ -23,6 +23,7 @@
 #include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
+#include <time.h>
 #include <unistd.h>
 
 #include <mutex>
@@ -38,7 +39,7 @@ enum RecordCmd {
 
 class RecordOptionsImpl {
  public:
-  std::string output_filename = "perf.data";
+  std::string output_filename;
   std::string event = "cpu-cycles";
   size_t freq = 4000;
   double duration_in_second = 0.0;
@@ -97,9 +98,27 @@ RecordOptions& RecordOptions::TraceOffCpu() {
   return *this;
 }
 
+static std::string GetDefaultOutputFilename() {
+  time_t t = time(nullptr);
+  struct tm tm;
+  if (localtime_r(&t, &tm) != &tm) {
+    return "perf.data";
+  }
+  char* buf = nullptr;
+  asprintf(&buf, "perf-%02d-%02d-%02d-%02d-%02d.data", tm.tm_mon + 1, tm.tm_mday, tm.tm_hour,
+           tm.tm_min, tm.tm_sec);
+  std::string result = buf;
+  free(buf);
+  return result;
+}
+
 std::vector<std::string> RecordOptions::ToRecordArgs() const {
   std::vector<std::string> args;
-  args.insert(args.end(), {"-o", impl_->output_filename});
+  std::string output_filename = impl_->output_filename;
+  if (output_filename.empty()) {
+    output_filename = GetDefaultOutputFilename();
+  }
+  args.insert(args.end(), {"-o", output_filename});
   args.insert(args.end(), {"-e", impl_->event});
   args.insert(args.end(), {"-f", std::to_string(impl_->freq)});
   if (impl_->duration_in_second != 0.0) {
@@ -147,6 +166,7 @@ class ProfileSessionImpl {
 
  private:
   std::string FindSimpleperf();
+  std::string FindSimpleperfInTempDir();
   void CheckIfPerfEnabled();
   void CreateSimpleperfDataDir();
   void CreateSimpleperfProcess(const std::string& simpleperf_path,
@@ -251,21 +271,39 @@ static bool IsExecutableFile(const std::string& path) {
 }
 
 std::string ProfileSessionImpl::FindSimpleperf() {
-  std::vector<std::string> candidates = {
-      // For debuggable apps, simpleperf is put to the appDir by api_app_profiler.py.
-      app_data_dir_ + "/simpleperf",
-      // For profileable apps on Android >= Q, use simpleperf in system image.
-      "/system/bin/simpleperf"
-  };
-  for (const auto& path : candidates) {
-    if (IsExecutableFile(path)) {
-      return path;
-    }
+  // 1. Try /data/local/tmp/simpleperf first. Probably it's newer than /system/bin/simpleperf.
+  std::string simpleperf_path = FindSimpleperfInTempDir();
+  if (!simpleperf_path.empty()) {
+    return simpleperf_path;
   }
-  Abort("can't find simpleperf on device. Please run api_app_profiler.py.");
+  // 2. Try /system/bin/simpleperf, which is available on Android >= Q.
+  simpleperf_path = "/system/bin/simpleperf";
+  if (IsExecutableFile(simpleperf_path)) {
+    return simpleperf_path;
+  }
+  Abort("can't find simpleperf on device. Please run api_profiler.py.");
   return "";
 }
 
+std::string ProfileSessionImpl::FindSimpleperfInTempDir() {
+  const std::string path = "/data/local/tmp/simpleperf";
+  if (!IsExecutableFile(path)) {
+    return "";
+  }
+  // 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) {
+    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) {
+    return "";
+  }
+  return to_path;
+}
+
 static std::string ReadFile(FILE* fp) {
   std::string s;
   char buf[200];
@@ -287,7 +325,7 @@ void ProfileSessionImpl::CheckIfPerfEnabled() {
   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_app_profiler.py.");
+    Abort("linux perf events aren't enabled on the device. Please run api_profiler.py.");
   }
 }
 
index d6704fc..309b37b 100644 (file)
@@ -42,7 +42,8 @@ class RecordOptions {
   RecordOptions();
   ~RecordOptions();
   /**
-   * Set output filename. Default is perf.data. The file will be generated under simpleperf_data/.
+   * Set output filename. Default is perf-<month>-<day>-<hour>-<minute>-<second>.data.
+   * The file will be generated under simpleperf_data/.
    */
   RecordOptions& SetOutputFilename(const std::string& filename);
 
index 10cbb51..f1e2b20 100644 (file)
@@ -174,19 +174,52 @@ public class ProfileSession {
     }
 
     private String findSimpleperf() {
-        String[] candidates = new String[]{
-                // For debuggable apps, simpleperf is put to the appDir by api_app_profiler.py.
-                appDataDir + "/simpleperf",
-                // For profileable apps on Android >= Q, use simpleperf in system image.
-                "/system/bin/simpleperf"
-        };
-        for (String path : candidates) {
-            File file = new File(path);
-            if (file.isFile()) {
-                return path;
-            }
+        // 1. Try /data/local/tmp/simpleperf. Probably it's newer than /system/bin/simpleperf.
+        String simpleperfPath = findSimpleperfInTempDir();
+        if (simpleperfPath != null) {
+            return simpleperfPath;
+        }
+        // 2. Try /system/bin/simpleperf, which is available on Android >= Q.
+        simpleperfPath = "/system/bin/simpleperf";
+        if (isExecutableFile(simpleperfPath)) {
+            return simpleperfPath;
+        }
+        throw new Error("can't find simpleperf on device. Please run api_profiler.py.");
+    }
+
+    private boolean isExecutableFile(String path) {
+        File file = new File(path);
+        return file.canExecute();
+    }
+
+    private String findSimpleperfInTempDir() {
+        String path = "/data/local/tmp/simpleperf";
+        File file = new File(path);
+        if (!file.isFile()){
+            return null;
+        }
+        // Copy it to app dir to execute it.
+        String toPath = appDataDir + "/simpleperf";
+        try {
+            Process process = new ProcessBuilder()
+                    .command("cp", path, toPath).start();
+            process.waitFor();
+        } catch (Exception e) {
+            return null;
+        }
+        if (!isExecutableFile(toPath)) {
+            return null;
+        }
+        // For apps with target sdk >= 29, executing app data file isn't allowed. So test executing
+        // it.
+        try {
+            Process process = new ProcessBuilder()
+                    .command(toPath).start();
+            process.waitFor();
+        } catch (Exception e) {
+            return null;
         }
-        throw new Error("can't find simpleperf on device. Please run api_app_profiler.py.");
+        return toPath;
     }
 
     private void checkIfPerfEnabled() {
@@ -206,7 +239,7 @@ public class ProfileSession {
         value = readInputStream(process.getInputStream());
         if (value.startsWith("1")) {
             throw new Error("linux perf events aren't enabled on the device." +
-                            " Please run api_app_profiler.py.");
+                            " Please run api_profiler.py.");
         }
     }
 
index de5a715..3ed39fb 100644 (file)
@@ -17,6 +17,9 @@
 package com.android.simpleperf;
 
 import android.system.Os;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -39,7 +42,8 @@ import java.util.List;
 public class RecordOptions {
 
     /**
-     * Set output filename. Default is perf.data. The file will be generated under simpleperf_data/.
+     * Set output filename. Default is perf-<month>-<day>-<hour>-<minute>-<second>.data.
+     * The file will be generated under simpleperf_data/.
      */
     public RecordOptions setOutputFilename(String filename) {
         outputFilename = filename;
@@ -110,8 +114,13 @@ public class RecordOptions {
      */
     public List<String> toRecordArgs() {
         ArrayList<String> args = new ArrayList<>();
+
+        String filename = outputFilename;
+        if (filename == null) {
+            filename = getDefaultOutputFilename();
+        }
         args.add("-o");
-        args.add(outputFilename);
+        args.add(filename);
         args.add("-e");
         args.add(event);
         args.add("-f");
@@ -146,7 +155,13 @@ public class RecordOptions {
         return args;
     }
 
-    private String outputFilename = "perf.data";
+    private String getDefaultOutputFilename() {
+        LocalDateTime time = LocalDateTime.now();
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("'perf'-MM-dd-HH-mm-ss'.data'");
+        return time.format(formatter);
+    }
+
+    private String outputFilename;
     private String event = "cpu-cycles";
     private int freq = 4000;
     private double durationInSecond = 0.0;
diff --git a/simpleperf/cmd_api.cpp b/simpleperf/cmd_api.cpp
new file mode 100644 (file)
index 0000000..7f93373
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+
+#include <memory>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <ziparchive/zip_writer.h>
+
+#include "command.h"
+#include "event_type.h"
+#include "environment.h"
+#include "utils.h"
+#include "workload.h"
+
+namespace {
+const std::string SIMPLEPERF_DATA_DIR = "simpleperf_data";
+
+class PrepareCommand : public Command {
+ public:
+  PrepareCommand()
+      : Command("api-prepare", "Prepare recording via app api",
+                "Usage: simpleperf api-prepare\n"
+                ) {}
+  bool Run(const std::vector<std::string>& args);
+};
+
+bool PrepareCommand::Run(const std::vector<std::string>&) {
+  // Enable profiling.
+  if (!CheckPerfEventLimit()) {
+    return false;
+  }
+  // Create tracepoint_events file.
+  if (!android::base::WriteStringToFile(GetTracepointEvents(),
+                                        "/data/local/tmp/tracepoint_events")) {
+    PLOG(ERROR) << "failed to write tracepoint_events file";
+    return false;
+  }
+  return true;
+}
+
+class CollectCommand : public Command {
+ public:
+  CollectCommand()
+      : Command("api-collect", "Collect recording data generated by app api",
+                // clang-format off
+"Usage: simpleperf api-collect [options]\n"
+"--app <package_name>    the android application having recording data\n"
+"-o record_zipfile_path  the path to store recording data\n"
+"                        Default is simpleperf_data.zip.\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"
+"--out-fd <fd>          Write output to a file descriptor.\n"
+"--stop-signal-fd <fd>  Stop recording when fd is readable.\n"
+#endif
+                // clang-format on
+                ) {}
+  bool Run(const std::vector<std::string>& args);
+
+ private:
+  bool ParseOptions(const std::vector<std::string>& args);
+  void HandleStopSignal();
+  bool CollectRecordingData();
+  bool RemoveRecordingData();
+
+  std::string app_name_;
+  std::string output_filepath_ = "simpleperf_data.zip";
+  bool in_app_context_ = false;
+  android::base::unique_fd out_fd_;
+  android::base::unique_fd stop_signal_fd_;
+};
+
+bool CollectCommand::Run(const std::vector<std::string>& args) {
+  if (!ParseOptions(args)) {
+    return false;
+  }
+  if (in_app_context_) {
+    HandleStopSignal();
+    return CollectRecordingData() && RemoveRecordingData();
+  }
+  return RunInAppContext(app_name_, Name(), args, 0, output_filepath_, false);
+}
+
+bool CollectCommand::ParseOptions(const std::vector<std::string>& args) {
+  for (size_t i = 0; i < args.size(); ++i) {
+    if (args[i] == "--app") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      app_name_ = args[i];
+    } else if (args[i] == "--in-app") {
+      in_app_context_ = true;
+    } else if (args[i] == "-o") {
+      if (!NextArgumentOrError(args, &i)) {
+        return false;
+      }
+      output_filepath_ = args[i];
+    } else if (args[i] == "--out-fd") {
+      int fd;
+      if (!GetUintOption(args, &i, &fd)) {
+        return false;
+      }
+      out_fd_.reset(fd);
+    } else if (args[i] == "--stop-signal-fd") {
+      int fd;
+      if (!GetUintOption(args, &i, &fd)) {
+        return false;
+      }
+      stop_signal_fd_.reset(fd);
+    } else {
+      ReportUnknownOption(args, i);
+      return false;
+    }
+  }
+  if (!in_app_context_) {
+    if (app_name_.empty()) {
+      LOG(ERROR) << "--app is missing";
+      return false;
+    }
+  }
+  return true;
+}
+
+void CollectCommand::HandleStopSignal() {
+  int fd = stop_signal_fd_.release();
+  std::thread thread([fd]() {
+    char c;
+    static_cast<void>(read(fd, &c, 1));
+    exit(1);
+  });
+  thread.detach();
+}
+
+bool CollectCommand::CollectRecordingData() {
+  std::unique_ptr<FILE, decltype(&fclose)>  fp(android::base::Fdopen(std::move(out_fd_), "w"),
+                                               fclose);
+  if (fp == nullptr) {
+    PLOG(ERROR) << "failed to call fdopen";
+    return false;
+  }
+  std::vector<char> buffer(64 * 1024);
+  ZipWriter zip_writer(fp.get());
+  for (const auto& name : GetEntriesInDir(SIMPLEPERF_DATA_DIR)) {
+    // No need to collect temporary files.
+    const std::string path = SIMPLEPERF_DATA_DIR + "/" + name;
+    if (android::base::StartsWith(name, "TemporaryFile-") || !IsRegularFile(path)) {
+      continue;
+    }
+    int result = zip_writer.StartEntry(name.c_str(), ZipWriter::kCompress);
+    if (result != 0) {
+      LOG(ERROR) << "failed to start zip entry " << name << ": "
+                 << zip_writer.ErrorCodeString(result);
+      return false;
+    }
+    android::base::unique_fd in_fd(FileHelper::OpenReadOnly(path));
+    if (in_fd == -1) {
+      PLOG(ERROR) << "failed to open " << path;
+      return false;
+    }
+    while (true) {
+      ssize_t nread = TEMP_FAILURE_RETRY(read(in_fd, buffer.data(), buffer.size()));
+      if (nread < 0) {
+        PLOG(ERROR) << "failed to read " << path;
+        return false;
+      }
+      if (nread == 0) {
+        break;
+      }
+      result = zip_writer.WriteBytes(buffer.data(), nread);
+      if (result != 0) {
+        LOG(ERROR) << "failed to write zip entry " << name << ": "
+                   << zip_writer.ErrorCodeString(result);
+        return false;
+      }
+    }
+    result = zip_writer.FinishEntry();
+    if (result != 0) {
+      LOG(ERROR) << "failed to finish zip entry " << name << ": "
+                 << zip_writer.ErrorCodeString(result);
+      return false;
+    }
+  }
+  int result = zip_writer.Finish();
+  if (result != 0) {
+    LOG(ERROR) << "failed to finish zip writer: " << zip_writer.ErrorCodeString(result);
+    return false;
+  }
+  return true;
+}
+
+bool CollectCommand::RemoveRecordingData() {
+  return Workload::RunCmd({"rm", "-rf", SIMPLEPERF_DATA_DIR});
+}
+}  // namespace
+
+void RegisterAPICommands() {
+  RegisterCommand("api-prepare",
+                  []{ return std::unique_ptr<Command>(new PrepareCommand()); });
+  RegisterCommand("api-collect",
+                  []{ return std::unique_ptr<Command>(new CollectCommand()); });
+}
index bfb6b16..4c83540 100644 (file)
@@ -96,6 +96,7 @@ extern void RegisterReportSampleCommand();
 extern void RegisterStatCommand();
 extern void RegisterDebugUnwindCommand();
 extern void RegisterTraceSchedCommand();
+extern void RegisterAPICommands();
 
 class CommandRegister {
  public:
@@ -111,6 +112,9 @@ class CommandRegister {
     RegisterStatCommand();
     RegisterDebugUnwindCommand();
     RegisterTraceSchedCommand();
+#if defined(__ANDROID__)
+    RegisterAPICommands();
+#endif
 #endif
   }
 };
index 73fac03..a47e73b 100644 (file)
@@ -47,6 +47,7 @@ void* ProfileThreadFunc(void*) {
   sleep(1);
   log("stop recording");
   session.StopRecording();
+  log("stop recording successfully");
   profile_thread_exited = true;
   return nullptr;
 };
index bf3b0c2..2cb96f0 100644 (file)
@@ -60,7 +60,7 @@ public class MainActivity extends AppCompatActivity {
                     Thread.sleep(1000);
                     Log.e("simpleperf", "stopRecording");
                     profileSession.stopRecording();
-                    Log.e("simpleperf", "stopRecording success");
+                    Log.e("simpleperf", "stopRecording successfully");
                 } catch (Exception e) {
                     Log.e("simpleperf", "exception: " + e.getMessage());
                 }
index e82162b..3591626 100644 (file)
@@ -623,6 +623,10 @@ std::set<pid_t> WaitForAppProcesses(const std::string& package_name) {
   }
 }
 
+bool IsAppDebuggable(const std::string& package_name) {
+  return Workload::RunCmd({"run-as", package_name, "echo", ">/dev/null", "2>/dev/null"}, false);
+}
+
 namespace {
 
 class InAppRunner {
@@ -767,7 +771,7 @@ class RunAs : public InAppRunner {
 
 bool RunAs::Prepare() {
   // Test if run-as can access the package.
-  if (!Workload::RunCmd({"run-as", package_name_, "echo", ">/dev/null", "2>/dev/null"}, false)) {
+  if (!IsAppDebuggable(package_name_)) {
     return false;
   }
   // run-as can't run /data/local/tmp/simpleperf directly. So copy simpleperf binary if needed.
index e72dcec..173cdcc 100644 (file)
@@ -103,6 +103,7 @@ ArchType GetMachineArch();
 void PrepareVdsoFile();
 
 std::set<pid_t> WaitForAppProcesses(const std::string& package_name);
+bool IsAppDebuggable(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, bool need_tracepoint_events);
diff --git a/simpleperf/scripts/api_app_profiler.py b/simpleperf/scripts/api_app_profiler.py
deleted file mode 100755 (executable)
index 2c29a80..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-"""
-    This script is part of controlling simpleperf recording in user code. It is used to prepare
-    profiling environment (upload simpleperf to device and enable profiling) before recording
-    and collect profiling data on host after recording.
-    Controlling simpleperf recording is done in below steps:
-    1. Add simpleperf Java API/C++ API to the app's source code. And call the API in user code.
-    2. Run `api_app_profiler.py prepare` to prepare profiling environment.
-    3. Run the app one or more times to generate profiling data.
-    4. Run `api_app_profiler.py collect` to collect profiling data on host.
-"""
-
-from __future__ import print_function
-import argparse
-import os
-import os.path
-import shutil
-
-from utils import AdbHelper, get_target_binary_path, log_exit, remove
-
-def prepare_recording(args):
-    adb = AdbHelper()
-    enable_profiling_on_device(adb, args)
-    # TODO: support profileable app in Android >= Q.
-    if not is_debuggable_app(adb, args.app[0]):
-        log_exit("The app isn't debuggable: %s" % args.app[0])
-    upload_simpleperf_to_device(adb, args)
-    prepare_tracepoint_events_file(adb)
-
-def is_debuggable_app(adb, app):
-    result, _ = adb.run_and_return_output(["shell", "run-as", app, "echo"], log_output=False)
-    return result
-
-def enable_profiling_on_device(adb, args):
-    android_version = adb.get_android_version()
-    if android_version >= 10:
-        adb.set_property('debug.perf_event_max_sample_rate', str(args.max_sample_rate[0]))
-        adb.set_property('debug.perf_cpu_time_max_percent', str(args.max_cpu_percent[0]))
-        adb.set_property('debug.perf_event_mlock_kb', str(args.max_memory_in_kb[0]))
-    adb.set_property('security.perf_harden', '0')
-
-def upload_simpleperf_to_device(adb, args):
-    device_arch = adb.get_device_arch()
-    simpleperf_binary = get_target_binary_path(device_arch, 'simpleperf')
-    adb.check_run(['push', simpleperf_binary, '/data/local/tmp'])
-    adb.check_run(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf'])
-    adb.check_run(['shell', 'run-as', args.app[0], 'cp', '/data/local/tmp/simpleperf', '.'])
-
-def prepare_tracepoint_events_file(adb):
-    tracepoint_event_ids = adb.check_run_and_return_output(
-        ['shell', 'ls', '/sys/kernel/debug/tracing/events/*/*/id'], log_output=False)
-    events_file_path = 'tracepoint_events'
-    with open(events_file_path, 'w') as fh:
-        for i, id_path in enumerate(tracepoint_event_ids.split()):
-            # For id_path like "/sys/kernel/debug/tracing/events/sched/sched_switch/id",
-            # get event_name "sched:sched_switch".
-            items = id_path.split('/')
-            event_name = items[-3] + ':' + items[-2]
-            id_value = adb.check_run_and_return_output(['shell', 'cat', id_path], log_output=False)
-            id_value = id_value.strip()
-            if i > 0:
-                fh.write('\n')
-            fh.write('%s %s' % (event_name, id_value))
-    adb.check_run(['push', events_file_path, '/data/local/tmp/tracepoint_events'])
-    remove(events_file_path)
-
-
-def collect_data(args):
-    adb = AdbHelper()
-    if not os.path.isdir(args.out_dir):
-        os.makedirs(args.out_dir)
-    move_profiling_data_to_tmp_dir(adb, args.app[0])
-    adb.check_run(['pull', '/data/local/tmp/simpleperf_data', args.out_dir])
-    adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/simpleperf_data'])
-    simpleperf_data_path = os.path.join(args.out_dir, 'simpleperf_data')
-    for name in os.listdir(simpleperf_data_path):
-        remove(os.path.join(args.out_dir, name))
-        shutil.move(os.path.join(simpleperf_data_path, name), args.out_dir)
-        print('collect profiling data: %s' % os.path.join(args.out_dir, name))
-    remove(simpleperf_data_path)
-
-def move_profiling_data_to_tmp_dir(adb, app):
-    """ move /data/data/<app>/simpleperf_data to /data/local/tmp/simpleperf_data."""
-    # TODO: support profileable app in Android >= Q.
-    if not is_debuggable_app(adb, app):
-        log_exit("The app isn't debuggable: %s" % app)
-    result, output = adb.run_and_return_output(['shell', 'run-as', app, 'ls', 'simpleperf_data'])
-    if not result:
-        log_exit("can't find profiling data for app %s" % app)
-    file_list = output.split()
-    shell_script = 'collect_data_shell_script.sh'
-    with open(shell_script, 'w') as fh:
-        fh.write('set -ex\n')
-        fh.write('rm -rf /data/local/tmp/simpleperf_data\n')
-        fh.write('mkdir /data/local/tmp/simpleperf_data\n')
-        for filename in file_list:
-            fh.write('run-as %s cat simpleperf_data/%s >/data/local/tmp/simpleperf_data/%s\n' % (
-                app, filename, filename))
-    adb.check_run(['push', shell_script, '/data/local/tmp'])
-    adb.check_run(['shell', 'sh', '/data/local/tmp/%s' % shell_script])
-    adb.check_run(['shell', 'run-as', app, 'rm', '-rf', 'simpleperf_data'])
-    adb.check_run(['shell', 'rm', '/data/local/tmp/%s' % shell_script])
-    remove(shell_script)
-
-
-class ArgumentHelpFormatter(argparse.ArgumentDefaultsHelpFormatter,
-                            argparse.RawDescriptionHelpFormatter):
-    pass
-
-def main():
-    parser = argparse.ArgumentParser(description=__doc__,
-                                     formatter_class=ArgumentHelpFormatter)
-    subparsers = parser.add_subparsers()
-    prepare_parser = subparsers.add_parser('prepare', help='Prepare recording on device.',
-                                           formatter_class=ArgumentHelpFormatter)
-    prepare_parser.add_argument('-p', '--app', nargs=1, required=True, help="""
-                                The package name of the app to profile.""")
-    prepare_parser.add_argument('--max-sample-rate', nargs=1, type=int, default=[100000], help="""
-                                Set max sample rate (only on Android >= Q).""")
-    prepare_parser.add_argument('--max-cpu-percent', nargs=1, type=int, default=[25], help="""
-                                Set max cpu percent for recording (only on Android >= Q).""")
-    prepare_parser.add_argument('--max-memory-in-kb', nargs=1, type=int, default=[516], help="""
-                                Set max kernel buffer size for recording (only on Android >= Q).
-                                """)
-    prepare_parser.set_defaults(func=prepare_recording)
-    collect_parser = subparsers.add_parser('collect', help='Collect profiling data.',
-                                           formatter_class=ArgumentHelpFormatter)
-    collect_parser.add_argument('-p', '--app', nargs=1, required=True, help="""
-                                The app package name of the app profiled.""")
-    collect_parser.add_argument('-o', '--out-dir', default='simpleperf_data', help="""
-                                The directory to store profiling data.""")
-    collect_parser.set_defaults(func=collect_data)
-    args = parser.parse_args()
-    args.func(args)
-
-if __name__ == '__main__':
-    main()
diff --git a/simpleperf/scripts/api_profiler.py b/simpleperf/scripts/api_profiler.py
new file mode 100755 (executable)
index 0000000..424de69
--- /dev/null
@@ -0,0 +1,117 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+"""
+    This script is part of controlling simpleperf recording in user code. It is used to prepare
+    profiling environment (upload simpleperf to device and enable profiling) before recording
+    and collect recording data on host after recording.
+    Controlling simpleperf recording is done in below steps:
+    1. Add simpleperf Java API/C++ API to the app's source code. And call the API in user code.
+    2. Run `api_profiler.py prepare` to prepare profiling environment.
+    3. Run the app one or more times to generate recording data.
+    4. Run `api_profiler.py collect` to collect recording data on host.
+"""
+
+from __future__ import print_function
+import argparse
+import os
+import os.path
+import shutil
+import zipfile
+
+from utils import AdbHelper, get_target_binary_path, log_exit, log_info, remove
+
+def prepare_recording(args):
+    adb = AdbHelper()
+    enable_profiling_on_device(adb, args)
+    upload_simpleperf_to_device(adb)
+    run_simpleperf_prepare_cmd(adb)
+
+def enable_profiling_on_device(adb, args):
+    android_version = adb.get_android_version()
+    if android_version >= 10:
+        adb.set_property('debug.perf_event_max_sample_rate', str(args.max_sample_rate[0]))
+        adb.set_property('debug.perf_cpu_time_max_percent', str(args.max_cpu_percent[0]))
+        adb.set_property('debug.perf_event_mlock_kb', str(args.max_memory_in_kb[0]))
+    adb.set_property('security.perf_harden', '0')
+
+def upload_simpleperf_to_device(adb):
+    device_arch = adb.get_device_arch()
+    simpleperf_binary = get_target_binary_path(device_arch, 'simpleperf')
+    adb.check_run(['push', simpleperf_binary, '/data/local/tmp'])
+    adb.check_run(['shell', 'chmod', 'a+x', '/data/local/tmp/simpleperf'])
+
+def run_simpleperf_prepare_cmd(adb):
+    adb.check_run(['shell', '/data/local/tmp/simpleperf', 'api-prepare'])
+
+
+def collect_data(args):
+    adb = AdbHelper()
+    if not os.path.isdir(args.out_dir):
+        os.makedirs(args.out_dir)
+    download_recording_data(adb, args)
+    unzip_recording_data(args)
+
+def download_recording_data(adb, args):
+    """ download recording data to simpleperf_data.zip."""
+    upload_simpleperf_to_device(adb)
+    adb.check_run(['shell', '/data/local/tmp/simpleperf', 'api-collect', '--app', args.app[0],
+                   '-o', '/data/local/tmp/simpleperf_data.zip'])
+    adb.check_run(['pull', '/data/local/tmp/simpleperf_data.zip', args.out_dir])
+    adb.check_run(['shell', 'rm', '-rf', '/data/local/tmp/simpleperf_data'])
+
+def unzip_recording_data(args):
+    zip_file_path = os.path.join(args.out_dir, 'simpleperf_data.zip')
+    with zipfile.ZipFile(zip_file_path, 'r') as zip_fh:
+        names = zip_fh.namelist()
+        log_info('There are %d recording data files.' % len(names))
+        for name in names:
+            log_info('recording file: %s' % os.path.join(args.out_dir, name))
+            zip_fh.extract(name, args.out_dir)
+    remove(zip_file_path)
+
+class ArgumentHelpFormatter(argparse.ArgumentDefaultsHelpFormatter,
+                            argparse.RawDescriptionHelpFormatter):
+    pass
+
+def main():
+    parser = argparse.ArgumentParser(description=__doc__,
+                                     formatter_class=ArgumentHelpFormatter)
+    subparsers = parser.add_subparsers()
+    prepare_parser = subparsers.add_parser('prepare', help='Prepare recording on device.',
+                                           formatter_class=ArgumentHelpFormatter)
+    prepare_parser.add_argument('--max-sample-rate', nargs=1, type=int, default=[100000], help="""
+                                Set max sample rate (only on Android >= Q).""")
+    prepare_parser.add_argument('--max-cpu-percent', nargs=1, type=int, default=[25], help="""
+                                Set max cpu percent for recording (only on Android >= Q).""")
+    prepare_parser.add_argument('--max-memory-in-kb', nargs=1, type=int,
+                                default=[(1024 + 1) * 4 * 8], help="""
+                                Set max kernel buffer size for recording (only on Android >= Q).
+                                """)
+    prepare_parser.set_defaults(func=prepare_recording)
+    collect_parser = subparsers.add_parser('collect', help='Collect recording data.',
+                                           formatter_class=ArgumentHelpFormatter)
+    collect_parser.add_argument('-p', '--app', nargs=1, required=True, help="""
+                                The app package name of the app profiled.""")
+    collect_parser.add_argument('-o', '--out-dir', default='simpleperf_data', help="""
+                                The directory to store recording data.""")
+    collect_parser.set_defaults(func=collect_data)
+    args = parser.parse_args()
+    args.func(args)
+
+if __name__ == '__main__':
+    main()
index a5b1656..aeddee9 100644 (file)
@@ -98,8 +98,9 @@ std::vector<gid_t> GetSupplementaryGids(uid_t userAppId) {
 }
 
 static void CheckSimpleperfArguments(const char* cmdname, char** args) {
-  if (strcmp(cmdname, "stat") != 0 && strcmp(cmdname, "record") != 0) {
-    error(1, 0, "only stat/record commands are allowed");
+  if (strcmp(cmdname, "stat") != 0 && strcmp(cmdname, "record") != 0 &&
+      strcmp(cmdname, "api-collect") != 0) {
+    error(1, 0, "cmd isn't allowed: %s", cmdname);
   }
   std::set<std::string> zero_arg_options = {
       "-b", "--csv", "--exit-with-parent", "-g", "--in-app", "--interval-only-values",