summaryrefslogtreecommitdiff
path: root/simpleperf/app_api
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2019-02-14 16:23:05 -0800
committerYabin Cui <yabinc@google.com>2019-02-20 13:43:09 -0800
commit82c4c23a97dacdf140988dcc1dc46c0cd34ce93e (patch)
tree0ecc3c4287ed2976ef8d497e426116cf54b0852a /simpleperf/app_api
parentd9121cec265ca1ecaea7fcf415ea5a0ddd39530e (diff)
downloadextras-82c4c23a97dacdf140988dcc1dc46c0cd34ce93e.tar.gz
simpleperf: Add Java/C++ API to control simpleperf in app code.
Also add JavaApi app to test the Java API, and CppApi App to test the C++ API. Also expose help msgs for options used in the API. Bug: 123717243 Test: run apps manually and verify the generated profiling data. Change-Id: I64c9de82b3f7eb3d76c6aff0703e12ce33233e49
Diffstat (limited to 'simpleperf/app_api')
-rw-r--r--simpleperf/app_api/cpp/simpleperf.cpp418
-rw-r--r--simpleperf/app_api/cpp/simpleperf.h159
-rw-r--r--simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java279
-rw-r--r--simpleperf/app_api/java/com/android/simpleperf/RecordOptions.java157
4 files changed, 1013 insertions, 0 deletions
diff --git a/simpleperf/app_api/cpp/simpleperf.cpp b/simpleperf/app_api/cpp/simpleperf.cpp
new file mode 100644
index 00000000..11e70e6f
--- /dev/null
+++ b/simpleperf/app_api/cpp/simpleperf.cpp
@@ -0,0 +1,418 @@
+/*
+ * 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 "simpleperf.h"
+
+#include <limits.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <mutex>
+#include <sstream>
+#include <android/log.h>
+
+namespace simpleperf {
+
+enum RecordCmd {
+ CMD_PAUSE_RECORDING = 1,
+ CMD_RESUME_RECORDING,
+};
+
+class RecordOptionsImpl {
+ public:
+ std::string output_filename = "perf.data";
+ std::string event = "cpu-cycles";
+ size_t freq = 4000;
+ double duration_in_second = 0.0;
+ std::vector<pid_t> threads;
+ bool dwarf_callgraph = false;
+ bool fp_callgraph = false;
+ bool trace_offcpu = false;
+};
+
+RecordOptions::RecordOptions() : impl_(new RecordOptionsImpl) {
+}
+
+RecordOptions::~RecordOptions() {
+ delete impl_;
+}
+
+RecordOptions& RecordOptions::SetOutputFilename(const std::string &filename) {
+ impl_->output_filename = filename;
+ return *this;
+}
+
+RecordOptions& RecordOptions::SetEvent(const std::string &event) {
+ impl_->event = event;
+ return *this;
+}
+
+RecordOptions& RecordOptions::SetSampleFrequency(size_t freq) {
+ impl_->freq = freq;
+ return *this;
+}
+
+RecordOptions& RecordOptions::SetDuration(double duration_in_second) {
+ impl_->duration_in_second = duration_in_second;
+ return *this;
+}
+
+RecordOptions& RecordOptions::SetSampleThreads(const std::vector<pid_t> &threads) {
+ impl_->threads = threads;
+ return *this;
+}
+
+RecordOptions& RecordOptions::RecordDwarfCallGraph() {
+ impl_->dwarf_callgraph = true;
+ impl_->fp_callgraph = false;
+ return *this;
+}
+
+RecordOptions& RecordOptions::RecordFramePointerCallGraph() {
+ impl_->fp_callgraph = true;
+ impl_->dwarf_callgraph = false;
+ return *this;
+}
+
+RecordOptions& RecordOptions::TraceOffCpu() {
+ impl_->trace_offcpu = true;
+ return *this;
+}
+
+std::vector<std::string> RecordOptions::ToRecordArgs() const {
+ std::vector<std::string> args;
+ args.insert(args.end(), {"-o", impl_->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) {
+ args.insert(args.end(), {"--duration", std::to_string(impl_->duration_in_second)});
+ }
+ if (impl_->threads.empty()) {
+ args.insert(args.end(), {"-p", std::to_string(getpid())});
+ } else {
+ std::ostringstream os;
+ os << *(impl_->threads.begin());
+ for (auto it = std::next(impl_->threads.begin()); it != impl_->threads.end(); ++it) {
+ os << "," << *it;
+ }
+ args.insert(args.end(), {"-t", os.str()});
+ }
+ if (impl_->dwarf_callgraph) {
+ args.push_back("-g");
+ } else if (impl_->fp_callgraph) {
+ args.insert(args.end(), {"--call-graph", "fp"});
+ }
+ if (impl_->trace_offcpu) {
+ args.push_back("--trace-offcpu");
+ }
+ return args;
+}
+
+static void Abort(const char* fmt, ...) {
+ va_list vl;
+ va_start(vl, fmt);
+ __android_log_vprint(ANDROID_LOG_FATAL, "simpleperf", fmt, vl);
+ va_end(vl);
+ abort();
+}
+
+class ProfileSessionImpl {
+ public:
+ ProfileSessionImpl(const std::string& app_data_dir)
+ : app_data_dir_(app_data_dir),
+ simpleperf_data_dir_(app_data_dir + "/simpleperf_data") {}
+ ~ProfileSessionImpl();
+ void StartRecording(const std::vector<std::string>& args);
+ void PauseRecording();
+ void ResumeRecording();
+ void StopRecording();
+
+ private:
+ std::string FindSimpleperf();
+ void CheckIfPerfEnabled();
+ void CreateSimpleperfDataDir();
+ void CreateSimpleperfProcess(const std::string& simpleperf_path,
+ const std::vector<std::string>& record_args);
+ void SendCmd(const std::string& cmd);
+ std::string ReadReply();
+
+ enum State {
+ NOT_YET_STARTED,
+ STARTED,
+ PAUSED,
+ STOPPED,
+ };
+
+ const std::string app_data_dir_;
+ const std::string simpleperf_data_dir_;
+ std::mutex lock_; // Protect all members below.
+ State state_ = NOT_YET_STARTED;
+ pid_t simpleperf_pid_ = -1;
+ int control_fd_ = -1;
+ int reply_fd_ = -1;
+};
+
+ProfileSessionImpl::~ProfileSessionImpl() {
+ if (control_fd_ != -1) {
+ close(control_fd_);
+ }
+ if (reply_fd_ != -1) {
+ close(reply_fd_);
+ }
+}
+
+void ProfileSessionImpl::StartRecording(const std::vector<std::string> &args) {
+ std::lock_guard<std::mutex> guard(lock_);
+ if (state_ != NOT_YET_STARTED) {
+ Abort("startRecording: session in wrong state %d", state_);
+ }
+ std::string simpleperf_path = FindSimpleperf();
+ CheckIfPerfEnabled();
+ CreateSimpleperfDataDir();
+ CreateSimpleperfProcess(simpleperf_path, args);
+ state_ = STARTED;
+}
+
+void ProfileSessionImpl::PauseRecording() {
+ std::lock_guard<std::mutex> guard(lock_);
+ if (state_ != STARTED) {
+ Abort("pauseRecording: session in wrong state %d", state_);
+ }
+ SendCmd("pause");
+ state_ = PAUSED;
+}
+
+void ProfileSessionImpl::ResumeRecording() {
+ std::lock_guard<std::mutex> guard(lock_);
+ if (state_ != PAUSED) {
+ Abort("resumeRecording: session in wrong state %d", state_);
+ }
+ SendCmd("resume");
+ state_ = STARTED;
+}
+
+void ProfileSessionImpl::StopRecording() {
+ std::lock_guard<std::mutex> guard(lock_);
+ if (state_ != STARTED && state_ != PAUSED) {
+ Abort("stopRecording: session in wrong state %d", state_);
+ }
+ // Send SIGINT to simpleperf to stop recording.
+ if (kill(simpleperf_pid_, SIGINT) == -1) {
+ Abort("failed to stop simpleperf: %s", strerror(errno));
+ }
+ int status;
+ pid_t result = TEMP_FAILURE_RETRY(waitpid(simpleperf_pid_, &status, 0));
+ if (result == -1) {
+ Abort("failed to call waitpid: %s", strerror(errno));
+ }
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+ Abort("simpleperf exited with error, status = 0x%x", status);
+ }
+ state_ = STOPPED;
+}
+
+void ProfileSessionImpl::SendCmd(const std::string& cmd) {
+ std::string data = cmd + "\n";
+ if (TEMP_FAILURE_RETRY(write(control_fd_, &data[0], data.size())) !=
+ static_cast<ssize_t>(data.size())) {
+ Abort("failed to send cmd to simpleperf: %s", strerror(errno));
+ }
+ if (ReadReply() != "ok") {
+ Abort("failed to run cmd in simpleperf: %s", cmd.c_str());
+ }
+}
+
+static bool IsExecutableFile(const std::string& path) {
+ struct stat st;
+ if (stat(path.c_str(), &st) == 0) {
+ if (S_ISREG(st.st_mode) && (st.st_mode & S_IXUSR)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+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;
+ }
+ }
+ Abort("can't find simpleperf on device. Please run api_app_profiler.py.");
+ return "";
+}
+
+static std::string ReadFile(FILE* fp) {
+ std::string s;
+ char buf[200];
+ while (true) {
+ ssize_t n = fread(buf, 1, sizeof(buf), fp);
+ if (n <= 0) {
+ break;
+ }
+ s.insert(s.end(), buf, buf + n);
+ }
+ return s;
+}
+
+void ProfileSessionImpl::CheckIfPerfEnabled() {
+ FILE* fp = popen("/system/bin/getprop security.perf_harden", "re");
+ if (fp == nullptr) {
+ return; // Omit check if getprop doesn't exist.
+ }
+ 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.");
+ }
+}
+
+void ProfileSessionImpl::CreateSimpleperfDataDir() {
+ struct stat st;
+ if (stat(simpleperf_data_dir_.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) {
+ return;
+ }
+ if (mkdir(simpleperf_data_dir_.c_str(), 0700) == -1) {
+ Abort("failed to create simpleperf data dir %s: %s", simpleperf_data_dir_.c_str(),
+ strerror(errno));
+ }
+}
+
+void ProfileSessionImpl::CreateSimpleperfProcess(const std::string &simpleperf_path,
+ const std::vector<std::string> &record_args) {
+ // 1. Create control/reply pips.
+ int control_fd[2];
+ int reply_fd[2];
+ if (pipe(control_fd) != 0 || pipe(reply_fd) != 0) {
+ Abort("failed to call pipe: %s", strerror(errno));
+ }
+
+ // 2. Prepare simpleperf arguments.
+ std::vector<std::string> args;
+ args.emplace_back(simpleperf_path);
+ args.emplace_back("record");
+ args.emplace_back("--log-to-android-buffer");
+ args.insert(args.end(), {"--log", "debug"});
+ args.emplace_back("--stdio-controls-profiling");
+ args.emplace_back("--in-app");
+ args.insert(args.end(), {"--tracepoint-events", "/data/local/tmp/tracepoint_events"});
+ args.insert(args.end(), record_args.begin(), record_args.end());
+ char* argv[args.size() + 1];
+ for (size_t i = 0; i < args.size(); ++i) {
+ argv[i] = &args[i][0];
+ }
+ argv[args.size()] = nullptr;
+
+ // 3. Start simpleperf process.
+ int pid = fork();
+ if (pid == -1) {
+ Abort("failed to fork: %s", strerror(errno));
+ }
+ if (pid == 0) {
+ // child process
+ close(control_fd[1]);
+ dup2(control_fd[0], 0); // simpleperf read control cmd from fd 0.
+ close(control_fd[0]);
+ close(reply_fd[0]);
+ dup2(reply_fd[1], 1); // simpleperf writes reply to fd 1.
+ close(reply_fd[0]);
+ chdir(simpleperf_data_dir_.c_str());
+ execvp(argv[0], argv);
+ Abort("failed to call exec: %s", strerror(errno));
+ }
+ // parent process
+ close(control_fd[0]);
+ control_fd_ = control_fd[1];
+ close(reply_fd[1]);
+ reply_fd_ = reply_fd[0];
+ simpleperf_pid_ = pid;
+
+ // 4. Wait until simpleperf starts recording.
+ std::string start_flag = ReadReply();
+ if (start_flag != "started") {
+ Abort("failed to receive simpleperf start flag");
+ }
+}
+
+std::string ProfileSessionImpl::ReadReply() {
+ std::string s;
+ while (true) {
+ char c;
+ ssize_t result = TEMP_FAILURE_RETRY(read(reply_fd_, &c, 1));
+ if (result <= 0 || c == '\n') {
+ break;
+ }
+ s.push_back(c);
+ }
+ return s;
+}
+
+ProfileSession::ProfileSession() {
+ FILE* fp = fopen("/proc/self/cmdline", "r");
+ if (fp == nullptr) {
+ Abort("failed to open /proc/self/cmdline: %s", strerror(errno));
+ }
+ std::string s = ReadFile(fp);
+ fclose(fp);
+ for (int i = 0; i < s.size(); i++) {
+ if (s[i] == '\0') {
+ s = s.substr(0, i);
+ break;
+ }
+ }
+ std::string app_data_dir = "/data/data/" + s;
+ impl_ = new ProfileSessionImpl(app_data_dir);
+}
+
+ProfileSession::ProfileSession(const std::string& app_data_dir)
+ : impl_(new ProfileSessionImpl(app_data_dir)) {}
+
+ProfileSession::~ProfileSession() {
+ delete impl_;
+}
+
+void ProfileSession::StartRecording(const RecordOptions &options) {
+ StartRecording(options.ToRecordArgs());
+}
+
+void ProfileSession::StartRecording(const std::vector<std::string> &record_args) {
+ impl_->StartRecording(record_args);
+}
+
+void ProfileSession::PauseRecording() {
+ impl_->PauseRecording();
+}
+
+void ProfileSession::ResumeRecording() {
+ impl_->ResumeRecording();
+}
+
+void ProfileSession::StopRecording() {
+ impl_->StopRecording();
+}
+
+} // namespace simpleperf
diff --git a/simpleperf/app_api/cpp/simpleperf.h b/simpleperf/app_api/cpp/simpleperf.h
new file mode 100644
index 00000000..d6704fc8
--- /dev/null
+++ b/simpleperf/app_api/cpp/simpleperf.h
@@ -0,0 +1,159 @@
+/*
+ * 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.
+ */
+
+#pragma once
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+// A C++ API used to control simpleperf recording.
+namespace simpleperf {
+
+/**
+ * RecordOptions sets record options used by ProfileSession. The options are
+ * converted to a string list in toRecordArgs(), which is then passed to
+ * `simpleperf record` cmd. Run `simpleperf record -h` or
+ * `run_simpleperf_on_device.py record -h` for help messages.
+ *
+ * Example:
+ * RecordOptions options;
+ * options.setDuration(3).recordDwarfCallGraph().setOutputFilename("perf.data");
+ * ProfileSession session;
+ * session.startRecording(options);
+ */
+class RecordOptionsImpl;
+class RecordOptions {
+ public:
+ RecordOptions();
+ ~RecordOptions();
+ /**
+ * Set output filename. Default is perf.data. The file will be generated under simpleperf_data/.
+ */
+ RecordOptions& SetOutputFilename(const std::string& filename);
+
+ /**
+ * Set event to record. Default is cpu-cycles. See `simpleperf list` for all available events.
+ */
+ RecordOptions& SetEvent(const std::string& event);
+
+ /**
+ * Set how many samples to generate each second running. Default is 4000.
+ */
+ RecordOptions& SetSampleFrequency(size_t freq);
+
+ /**
+ * Set record duration. The record stops after `durationInSecond` seconds. By default,
+ * record stops only when stopRecording() is called.
+ */
+ RecordOptions& SetDuration(double duration_in_second);
+
+ /**
+ * Record some threads in the app process. By default, record all threads in the process.
+ */
+ RecordOptions& SetSampleThreads(const std::vector<pid_t>& threads);
+
+ /**
+ * Record dwarf based call graph. It is needed to get Java callstacks.
+ */
+ RecordOptions& RecordDwarfCallGraph();
+
+ /**
+ * Record frame pointer based call graph. It is suitable to get C++ callstacks on 64bit devices.
+ */
+ RecordOptions& RecordFramePointerCallGraph();
+
+ /**
+ * Trace context switch info to show where threads spend time off cpu.
+ */
+ RecordOptions& TraceOffCpu();
+
+ /**
+ * Translate record options into arguments for `simpleperf record` cmd.
+ */
+ std::vector<std::string> ToRecordArgs() const;
+
+ private:
+ RecordOptionsImpl* impl_;
+};
+
+/**
+ * ProfileSession uses `simpleperf record` cmd to generate a recording file.
+ * It allows users to start recording with some options, pause/resume recording
+ * to only profile interested code, and stop recording.
+ *
+ * Example:
+ * RecordOptions options;
+ * options.setDwarfCallGraph();
+ * ProfileSession session;
+ * session.StartRecording(options);
+ * sleep(1);
+ * session.PauseRecording();
+ * sleep(1);
+ * session.ResumeRecording();
+ * sleep(1);
+ * session.StopRecording();
+ *
+ * It aborts when error happens. To read error messages of simpleperf record
+ * process, filter logcat with `simpleperf`.
+ */
+class ProfileSessionImpl;
+class ProfileSession {
+ public:
+ /**
+ * @param appDataDir the same as android.content.Context.getDataDir().
+ * ProfileSession stores profiling data in appDataDir/simpleperf_data/.
+ */
+ ProfileSession(const std::string& app_data_dir);
+
+ /**
+ * ProfileSession assumes appDataDir as /data/data/app_package_name.
+ */
+ ProfileSession();
+ ~ProfileSession();
+
+ /**
+ * Start recording.
+ * @param options RecordOptions
+ */
+ void StartRecording(const RecordOptions& options);
+
+ /**
+ * Start recording.
+ * @param args arguments for `simpleperf record` cmd.
+ */
+ void StartRecording(const std::vector<std::string>& record_args);
+
+ /**
+ * Pause recording. No samples are generated in paused state.
+ */
+ void PauseRecording();
+
+ /**
+ * Resume a paused session.
+ */
+ void ResumeRecording();
+
+ /**
+ * Stop recording and generate a recording file under appDataDir/simpleperf_data/.
+ */
+ void StopRecording();
+ private:
+ ProfileSessionImpl* impl_;
+};
+
+} // namespace simpleperf
diff --git a/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java b/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java
new file mode 100644
index 00000000..10cbb518
--- /dev/null
+++ b/simpleperf/app_api/java/com/android/simpleperf/ProfileSession.java
@@ -0,0 +1,279 @@
+/*
+ * 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.
+ */
+
+package com.android.simpleperf;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * This class uses `simpleperf record` cmd to generate a recording file.
+ * It allows users to start recording with some options, pause/resume recording
+ * to only profile interested code, and stop recording.
+ * </p>
+ *
+ * <p>
+ * Example:
+ * RecordOptions options = new RecordOptions();
+ * options.setDwarfCallGraph();
+ * ProfileSession session = new ProfileSession();
+ * session.StartRecording(options);
+ * Thread.sleep(1000);
+ * session.PauseRecording();
+ * Thread.sleep(1000);
+ * session.ResumeRecording();
+ * Thread.sleep(1000);
+ * session.StopRecording();
+ * </p>
+ *
+ * <p>
+ * It throws an Error when error happens. To read error messages of simpleperf record
+ * process, filter logcat with `simpleperf`.
+ * </p>
+ */
+public class ProfileSession {
+
+ enum State {
+ NOT_YET_STARTED,
+ STARTED,
+ PAUSED,
+ STOPPED,
+ }
+
+ private State state = State.NOT_YET_STARTED;
+ private String appDataDir;
+ private String simpleperfDataDir;
+ private Process simpleperfProcess;
+
+ /**
+ * @param appDataDir the same as android.content.Context.getDataDir().
+ * ProfileSession stores profiling data in appDataDir/simpleperf_data/.
+ */
+ public ProfileSession(String appDataDir) {
+ this.appDataDir = appDataDir;
+ simpleperfDataDir = appDataDir + "/simpleperf_data";
+ }
+
+ /**
+ * ProfileSession assumes appDataDir as /data/data/app_package_name.
+ */
+ public ProfileSession() {
+ String packageName = "";
+ try {
+ String s = readInputStream(new FileInputStream("/proc/self/cmdline"));
+ for (int i = 0; i < s.length(); i++) {
+ if (s.charAt(i) == '\0') {
+ s = s.substring(0, i);
+ break;
+ }
+ }
+ packageName = s;
+ } catch (IOException e) {
+ throw new Error("failed to find packageName: " + e.getMessage());
+ }
+ if (packageName.isEmpty()) {
+ throw new Error("failed to find packageName");
+ }
+ appDataDir = "/data/data/" + packageName;
+ simpleperfDataDir = appDataDir + "/simpleperf_data";
+ }
+
+ /**
+ * Start recording.
+ * @param options RecordOptions
+ */
+ public void startRecording(RecordOptions options) {
+ startRecording(options.toRecordArgs());
+ }
+
+ /**
+ * Start recording.
+ * @param args arguments for `simpleperf record` cmd.
+ */
+ public synchronized void startRecording(List<String> args) {
+ if (state != State.NOT_YET_STARTED) {
+ throw new AssertionError("startRecording: session in wrong state " + state);
+ }
+ String simpleperfPath = findSimpleperf();
+ checkIfPerfEnabled();
+ createSimpleperfDataDir();
+ createSimpleperfProcess(simpleperfPath, args);
+ state = State.STARTED;
+ }
+
+ /**
+ * Pause recording. No samples are generated in paused state.
+ */
+ public synchronized void pauseRecording() {
+ if (state != State.STARTED) {
+ throw new AssertionError("pauseRecording: session in wrong state " + state);
+ }
+ sendCmd("pause");
+ state = State.PAUSED;
+ }
+
+ /**
+ * Resume a paused session.
+ */
+ public synchronized void resumeRecording() {
+ if (state != State.PAUSED) {
+ throw new AssertionError("resumeRecording: session in wrong state " + state);
+ }
+ sendCmd("resume");
+ state = State.STARTED;
+ }
+
+ /**
+ * Stop recording and generate a recording file under appDataDir/simpleperf_data/.
+ */
+ public synchronized void stopRecording() {
+ if (state != State.STARTED && state != State.PAUSED) {
+ throw new AssertionError("stopRecording: session in wrong state " + state);
+ }
+ simpleperfProcess.destroy();
+ try {
+ int exitCode = simpleperfProcess.waitFor();
+ if (exitCode != 0) {
+ throw new AssertionError("simpleperf exited with error: " + exitCode);
+ }
+ } catch (InterruptedException e) {
+ }
+ simpleperfProcess = null;
+ state = State.STOPPED;
+ }
+
+ private String readInputStream(InputStream in) {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+ String result = reader.lines().collect(Collectors.joining("\n"));
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ return result;
+ }
+
+ 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;
+ }
+ }
+ throw new Error("can't find simpleperf on device. Please run api_app_profiler.py.");
+ }
+
+ private void checkIfPerfEnabled() {
+ String value = "";
+ Process process;
+ try {
+ process = new ProcessBuilder()
+ .command("/system/bin/getprop", "security.perf_harden").start();
+ } catch (IOException e) {
+ // Omit check if getprop doesn't exist.
+ return;
+ }
+ try {
+ process.waitFor();
+ } catch (InterruptedException e) {
+ }
+ 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.");
+ }
+ }
+
+ private void createSimpleperfDataDir() {
+ File file = new File(simpleperfDataDir);
+ if (!file.isDirectory()) {
+ file.mkdir();
+ }
+ }
+
+ private void createSimpleperfProcess(String simpleperfPath, List<String> recordArgs) {
+ // 1. Prepare simpleperf arguments.
+ ArrayList<String> args = new ArrayList<>();
+ args.add(simpleperfPath);
+ args.add("record");
+ args.add("--log-to-android-buffer");
+ args.add("--log");
+ args.add("debug");
+ args.add("--stdio-controls-profiling");
+ args.add("--in-app");
+ args.add("--tracepoint-events");
+ args.add("/data/local/tmp/tracepoint_events");
+ args.addAll(recordArgs);
+
+ // 2. Create the simpleperf process.
+ ProcessBuilder pb = new ProcessBuilder(args).directory(new File(simpleperfDataDir));
+ try {
+ simpleperfProcess = pb.start();
+ } catch (IOException e) {
+ throw new Error("failed to create simpleperf process: " + e.getMessage());
+ }
+
+ // 3. Wait until simpleperf starts recording.
+ String startFlag = readReply();
+ if (!startFlag.equals("started")) {
+ throw new Error("failed to receive simpleperf start flag");
+ }
+ }
+
+ private void sendCmd(String cmd) {
+ cmd += "\n";
+ try {
+ simpleperfProcess.getOutputStream().write(cmd.getBytes());
+ simpleperfProcess.getOutputStream().flush();
+ } catch (IOException e) {
+ throw new Error("failed to send cmd to simpleperf: " + e.getMessage());
+ }
+ if (!readReply().equals("ok")) {
+ throw new Error("failed to run cmd in simpleperf: " + cmd);
+ }
+ }
+
+ private String readReply() {
+ // Read one byte at a time to stop at line break or EOF. BufferedReader will try to read
+ // more than available and make us blocking, so don't use it.
+ String s = "";
+ while (true) {
+ int c = -1;
+ try {
+ c = simpleperfProcess.getInputStream().read();
+ } catch (IOException e) {
+ }
+ if (c == -1 || c == '\n') {
+ break;
+ }
+ s += (char)c;
+ }
+ return s;
+ }
+}
diff --git a/simpleperf/app_api/java/com/android/simpleperf/RecordOptions.java b/simpleperf/app_api/java/com/android/simpleperf/RecordOptions.java
new file mode 100644
index 00000000..de5a715c
--- /dev/null
+++ b/simpleperf/app_api/java/com/android/simpleperf/RecordOptions.java
@@ -0,0 +1,157 @@
+/*
+ * 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.
+ */
+
+package com.android.simpleperf;
+
+import android.system.Os;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>
+ * This class sets record options used by ProfileSession. The options are
+ * converted to a string list in toRecordArgs(), which is then passed to
+ * `simpleperf record` cmd. Run `simpleperf record -h` or
+ * `run_simpleperf_on_device.py record -h` for help messages.
+ * </p>
+ *
+ * <p>
+ * Example:
+ * RecordOptions options = new RecordOptions();
+ * options.setDuration(3).recordDwarfCallGraph().setOutputFilename("perf.data");
+ * ProfileSession session = new ProfileSession();
+ * session.startRecording(options);
+ * </p>
+ */
+public class RecordOptions {
+
+ /**
+ * Set output filename. Default is perf.data. The file will be generated under simpleperf_data/.
+ */
+ public RecordOptions setOutputFilename(String filename) {
+ outputFilename = filename;
+ return this;
+ }
+
+ /**
+ * Set event to record. Default is cpu-cycles. See `simpleperf list` for all available events.
+ */
+ public RecordOptions setEvent(String event) {
+ this.event = event;
+ return this;
+ }
+
+ /**
+ * Set how many samples to generate each second running. Default is 4000.
+ */
+ public RecordOptions setSampleFrequency(int freq) {
+ this.freq = freq;
+ return this;
+ }
+
+ /**
+ * Set record duration. The record stops after `durationInSecond` seconds. By default,
+ * record stops only when stopRecording() is called.
+ */
+ public RecordOptions setDuration(double durationInSecond) {
+ this.durationInSecond = durationInSecond;
+ return this;
+ }
+
+ /**
+ * Record some threads in the app process. By default, record all threads in the process.
+ */
+ public RecordOptions setSampleThreads(List<Integer> threads) {
+ this.threads.addAll(threads);
+ return this;
+ }
+
+ /**
+ * Record dwarf based call graph. It is needed to get Java callstacks.
+ */
+ public RecordOptions recordDwarfCallGraph() {
+ this.dwarfCallGraph = true;
+ this.fpCallGraph = false;
+ return this;
+ }
+
+ /**
+ * Record frame pointer based call graph. It is suitable to get C++ callstacks on 64bit devices.
+ */
+ public RecordOptions recordFramePointerCallGraph() {
+ this.fpCallGraph = true;
+ this.dwarfCallGraph = false;
+ return this;
+ }
+
+ /**
+ * Trace context switch info to show where threads spend time off cpu.
+ */
+ public RecordOptions traceOffCpu() {
+ this.traceOffCpu = true;
+ return this;
+ }
+
+ /**
+ * Translate record options into arguments for `simpleperf record` cmd.
+ */
+ public List<String> toRecordArgs() {
+ ArrayList<String> args = new ArrayList<>();
+ args.add("-o");
+ args.add(outputFilename);
+ args.add("-e");
+ args.add(event);
+ args.add("-f");
+ args.add(String.valueOf(freq));
+ if (durationInSecond != 0.0) {
+ args.add("--duration");
+ args.add(String.valueOf(durationInSecond));
+ }
+ if (threads.isEmpty()) {
+ args.add("-p");
+ args.add(String.valueOf(Os.getpid()));
+ } else {
+ String s = "";
+ for (int i = 0; i < threads.size(); i++) {
+ if (i > 0) {
+ s += ",";
+ }
+ s += threads.get(i).toString();
+ }
+ args.add("-t");
+ args.add(s);
+ }
+ if (dwarfCallGraph) {
+ args.add("-g");
+ } else if (fpCallGraph) {
+ args.add("--call-graph");
+ args.add("fp");
+ }
+ if (traceOffCpu) {
+ args.add("--trace-offcpu");
+ }
+ return args;
+ }
+
+ private String outputFilename = "perf.data";
+ private String event = "cpu-cycles";
+ private int freq = 4000;
+ private double durationInSecond = 0.0;
+ private ArrayList<Integer> threads = new ArrayList<>();
+ private boolean dwarfCallGraph = false;
+ private boolean fpCallGraph = false;
+ private boolean traceOffCpu = false;
+}