summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--simpleperf/Android.bp1
-rw-r--r--simpleperf/app_api/cpp/simpleperf.cpp66
-rw-r--r--simpleperf/app_api/cpp/simpleperf.h3
-rw-r--r--simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java59
-rw-r--r--simpleperf/app_api/java/com/android/simpleperf/RecordOptions.java21
-rw-r--r--simpleperf/cmd_api.cpp223
-rw-r--r--simpleperf/command.cpp4
-rw-r--r--simpleperf/demo/CppApi/app/src/main/cpp/native-lib.cpp1
-rw-r--r--simpleperf/demo/JavaApi/app/src/main/java/simpleperf/demo/java_api/MainActivity.java2
-rw-r--r--simpleperf/environment.cpp6
-rw-r--r--simpleperf/environment.h1
-rwxr-xr-xsimpleperf/scripts/api_app_profiler.py153
-rwxr-xr-xsimpleperf/scripts/api_profiler.py117
-rw-r--r--simpleperf/simpleperf_app_runner/simpleperf_app_runner.cpp5
14 files changed, 474 insertions, 188 deletions
diff --git a/simpleperf/Android.bp b/simpleperf/Android.bp
index 096f7bf0..d45cc6a8 100644
--- a/simpleperf/Android.bp
+++ b/simpleperf/Android.bp
@@ -265,6 +265,7 @@ cc_defaults {
linux: {
srcs: [
"CallChainJoiner.cpp",
+ "cmd_api.cpp",
"cmd_debug_unwind.cpp",
"cmd_list.cpp",
"cmd_record.cpp",
diff --git a/simpleperf/app_api/cpp/simpleperf.cpp b/simpleperf/app_api/cpp/simpleperf.cpp
index 11e70e6f..23bb4fb5 100644
--- a/simpleperf/app_api/cpp/simpleperf.cpp
+++ b/simpleperf/app_api/cpp/simpleperf.cpp
@@ -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.");
}
}
diff --git a/simpleperf/app_api/cpp/simpleperf.h b/simpleperf/app_api/cpp/simpleperf.h
index d6704fc8..309b37b7 100644
--- a/simpleperf/app_api/cpp/simpleperf.h
+++ b/simpleperf/app_api/cpp/simpleperf.h
@@ -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);
diff --git a/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java b/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java
index 10cbb518..f1e2b202 100644
--- a/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java
+++ b/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java
@@ -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.");
}
}
diff --git a/simpleperf/app_api/java/com/android/simpleperf/RecordOptions.java b/simpleperf/app_api/java/com/android/simpleperf/RecordOptions.java
index de5a715c..3ed39fb7 100644
--- a/simpleperf/app_api/java/com/android/simpleperf/RecordOptions.java
+++ b/simpleperf/app_api/java/com/android/simpleperf/RecordOptions.java
@@ -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
index 00000000..7f933736
--- /dev/null
+++ b/simpleperf/cmd_api.cpp
@@ -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()); });
+}
diff --git a/simpleperf/command.cpp b/simpleperf/command.cpp
index bfb6b16f..4c83540c 100644
--- a/simpleperf/command.cpp
+++ b/simpleperf/command.cpp
@@ -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
}
};
diff --git a/simpleperf/demo/CppApi/app/src/main/cpp/native-lib.cpp b/simpleperf/demo/CppApi/app/src/main/cpp/native-lib.cpp
index 73fac03e..a47e73b6 100644
--- a/simpleperf/demo/CppApi/app/src/main/cpp/native-lib.cpp
+++ b/simpleperf/demo/CppApi/app/src/main/cpp/native-lib.cpp
@@ -47,6 +47,7 @@ void* ProfileThreadFunc(void*) {
sleep(1);
log("stop recording");
session.StopRecording();
+ log("stop recording successfully");
profile_thread_exited = true;
return nullptr;
};
diff --git a/simpleperf/demo/JavaApi/app/src/main/java/simpleperf/demo/java_api/MainActivity.java b/simpleperf/demo/JavaApi/app/src/main/java/simpleperf/demo/java_api/MainActivity.java
index bf3b0c2e..2cb96f03 100644
--- a/simpleperf/demo/JavaApi/app/src/main/java/simpleperf/demo/java_api/MainActivity.java
+++ b/simpleperf/demo/JavaApi/app/src/main/java/simpleperf/demo/java_api/MainActivity.java
@@ -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());
}
diff --git a/simpleperf/environment.cpp b/simpleperf/environment.cpp
index e82162b5..3591626e 100644
--- a/simpleperf/environment.cpp
+++ b/simpleperf/environment.cpp
@@ -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.
diff --git a/simpleperf/environment.h b/simpleperf/environment.h
index e72dcecf..173cdcc6 100644
--- a/simpleperf/environment.h
+++ b/simpleperf/environment.h
@@ -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
index 2c29a807..00000000
--- a/simpleperf/scripts/api_app_profiler.py
+++ /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
index 00000000..424de698
--- /dev/null
+++ b/simpleperf/scripts/api_profiler.py
@@ -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()
diff --git a/simpleperf/simpleperf_app_runner/simpleperf_app_runner.cpp b/simpleperf/simpleperf_app_runner/simpleperf_app_runner.cpp
index a5b1656e..aeddee9e 100644
--- a/simpleperf/simpleperf_app_runner/simpleperf_app_runner.cpp
+++ b/simpleperf/simpleperf_app_runner/simpleperf_app_runner.cpp
@@ -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",