Android Build Tools
This commit is contained in:
530
Android/android-ndk-r27d/simpleperf/app_api/cpp/simpleperf.cpp
Normal file
530
Android/android-ndk-r27d/simpleperf/app_api/cpp/simpleperf.cpp
Normal file
@ -0,0 +1,530 @@
|
||||
/*
|
||||
* 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 <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <android/log.h>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
|
||||
namespace simpleperf {
|
||||
|
||||
constexpr int AID_USER_OFFSET = 100000;
|
||||
|
||||
enum RecordCmd {
|
||||
CMD_PAUSE_RECORDING = 1,
|
||||
CMD_RESUME_RECORDING,
|
||||
};
|
||||
|
||||
class RecordOptionsImpl {
|
||||
public:
|
||||
std::string output_filename;
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
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) {
|
||||
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();
|
||||
std::string FindSimpleperfInTempDir();
|
||||
void CheckIfPerfEnabled();
|
||||
std::string GetProperty(const std::string& name);
|
||||
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;
|
||||
bool trace_offcpu_ = false;
|
||||
};
|
||||
|
||||
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_);
|
||||
}
|
||||
for (const auto& arg : args) {
|
||||
if (arg == "--trace-offcpu") {
|
||||
trace_offcpu_ = true;
|
||||
}
|
||||
}
|
||||
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_);
|
||||
}
|
||||
if (trace_offcpu_) {
|
||||
Abort("--trace-offcpu doesn't work well with pause/resume recording");
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
static std::string ReadFile(FILE* fp) {
|
||||
std::string s;
|
||||
if (fp == nullptr) {
|
||||
return 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);
|
||||
}
|
||||
fclose(fp);
|
||||
return s;
|
||||
}
|
||||
|
||||
static bool RunCmd(std::vector<const char*> args, std::string* stdout) {
|
||||
int stdout_fd[2];
|
||||
if (pipe(stdout_fd) != 0) {
|
||||
return false;
|
||||
}
|
||||
args.push_back(nullptr);
|
||||
// Fork handlers (like gsl_library_close) may hang in a multi-thread environment.
|
||||
// So we use vfork instead of fork to avoid calling them.
|
||||
int pid = vfork();
|
||||
if (pid == -1) {
|
||||
return false;
|
||||
}
|
||||
if (pid == 0) {
|
||||
// child process
|
||||
close(stdout_fd[0]);
|
||||
dup2(stdout_fd[1], 1);
|
||||
close(stdout_fd[1]);
|
||||
execvp(const_cast<char*>(args[0]), const_cast<char**>(args.data()));
|
||||
_exit(1);
|
||||
}
|
||||
// parent process
|
||||
close(stdout_fd[1]);
|
||||
int status;
|
||||
pid_t result = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0));
|
||||
if (result == -1) {
|
||||
Abort("failed to call waitpid: %s", strerror(errno));
|
||||
}
|
||||
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (stdout == nullptr) {
|
||||
close(stdout_fd[0]);
|
||||
} else {
|
||||
*stdout = ReadFile(fdopen(stdout_fd[0], "r"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string ProfileSessionImpl::FindSimpleperf() {
|
||||
// 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;
|
||||
}
|
||||
// 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";
|
||||
if (!RunCmd({"/system/bin/cp", path.c_str(), to_path.c_str()}, nullptr)) {
|
||||
return "";
|
||||
}
|
||||
// For apps with target sdk >= 29, executing app data file isn't allowed.
|
||||
// For android R, app context isn't allowed to use perf_event_open.
|
||||
// So test executing downloaded simpleperf.
|
||||
std::string s;
|
||||
if (!RunCmd({to_path.c_str(), "list", "sw"}, &s)) {
|
||||
return "";
|
||||
}
|
||||
if (s.find("cpu-clock") == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
return to_path;
|
||||
}
|
||||
|
||||
void ProfileSessionImpl::CheckIfPerfEnabled() {
|
||||
if (GetProperty("persist.simpleperf.profile_app_uid") == std::to_string(getuid())) {
|
||||
std::string time_str = GetProperty("persist.simpleperf.profile_app_expiration_time");
|
||||
if (!time_str.empty()) {
|
||||
errno = 0;
|
||||
uint64_t expiration_time = strtoull(time_str.data(), nullptr, 10);
|
||||
if (errno == 0 && expiration_time > time(nullptr)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (GetProperty("security.perf_harden") == "1") {
|
||||
Abort("Recording app isn't enabled on the device. Please run api_profiler.py.");
|
||||
}
|
||||
}
|
||||
|
||||
std::string ProfileSessionImpl::GetProperty(const std::string& name) {
|
||||
std::string s;
|
||||
if (!RunCmd({"/system/bin/getprop", name.c_str()}, &s)) {
|
||||
return "";
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
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.
|
||||
// Fork handlers (like gsl_library_close) may hang in a multi-thread environment.
|
||||
// So we use vfork instead of fork to avoid calling them.
|
||||
int pid = vfork();
|
||||
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);
|
||||
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;
|
||||
int uid = getuid();
|
||||
if (uid >= AID_USER_OFFSET) {
|
||||
int user_id = uid / AID_USER_OFFSET;
|
||||
app_data_dir = "/data/user/" + std::to_string(user_id) + "/" + 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
|
||||
161
Android/android-ndk-r27d/simpleperf/app_api/cpp/simpleperf.h
Normal file
161
Android/android-ndk-r27d/simpleperf/app_api/cpp/simpleperf.h
Normal file
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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-<month>-<day>-<hour>-<minute>-<second>.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
|
||||
@ -0,0 +1,383 @@
|
||||
/*
|
||||
* 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.os.Build;
|
||||
import android.system.Os;
|
||||
import android.system.OsConstants;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
|
||||
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>
|
||||
*/
|
||||
@RequiresApi(28)
|
||||
public class ProfileSession {
|
||||
private static final String SIMPLEPERF_PATH_IN_IMAGE = "/system/bin/simpleperf";
|
||||
|
||||
enum State {
|
||||
NOT_YET_STARTED,
|
||||
STARTED,
|
||||
PAUSED,
|
||||
STOPPED,
|
||||
}
|
||||
|
||||
private State mState = State.NOT_YET_STARTED;
|
||||
private final String mAppDataDir;
|
||||
private String mSimpleperfPath;
|
||||
private final String mSimpleperfDataDir;
|
||||
private Process mSimpleperfProcess;
|
||||
private boolean mTraceOffCpu = false;
|
||||
|
||||
/**
|
||||
* @param appDataDir the same as android.content.Context.getDataDir().
|
||||
* ProfileSession stores profiling data in appDataDir/simpleperf_data/.
|
||||
*/
|
||||
public ProfileSession(@NonNull String appDataDir) {
|
||||
mAppDataDir = appDataDir;
|
||||
mSimpleperfDataDir = 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");
|
||||
}
|
||||
final int AID_USER_OFFSET = 100000;
|
||||
int uid = Os.getuid();
|
||||
if (uid >= AID_USER_OFFSET) {
|
||||
int user_id = uid / AID_USER_OFFSET;
|
||||
mAppDataDir = "/data/user/" + user_id + "/" + packageName;
|
||||
} else {
|
||||
mAppDataDir = "/data/data/" + packageName;
|
||||
}
|
||||
mSimpleperfDataDir = mAppDataDir + "/simpleperf_data";
|
||||
}
|
||||
|
||||
/**
|
||||
* Start recording.
|
||||
* @param options RecordOptions
|
||||
*/
|
||||
public void startRecording(@NonNull RecordOptions options) {
|
||||
startRecording(options.toRecordArgs());
|
||||
}
|
||||
|
||||
/**
|
||||
* Start recording.
|
||||
* @param args arguments for `simpleperf record` cmd.
|
||||
*/
|
||||
public synchronized void startRecording(@NonNull List<String> args) {
|
||||
if (mState != State.NOT_YET_STARTED) {
|
||||
throw new IllegalStateException("startRecording: session in wrong state " + mState);
|
||||
}
|
||||
for (String arg : args) {
|
||||
if (arg.equals("--trace-offcpu")) {
|
||||
mTraceOffCpu = true;
|
||||
}
|
||||
}
|
||||
mSimpleperfPath = findSimpleperf();
|
||||
checkIfPerfEnabled();
|
||||
createSimpleperfDataDir();
|
||||
createSimpleperfProcess(mSimpleperfPath, args);
|
||||
mState = State.STARTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause recording. No samples are generated in paused state.
|
||||
*/
|
||||
public synchronized void pauseRecording() {
|
||||
if (mState != State.STARTED) {
|
||||
throw new IllegalStateException("pauseRecording: session in wrong state " + mState);
|
||||
}
|
||||
if (mTraceOffCpu) {
|
||||
throw new AssertionError(
|
||||
"--trace-offcpu option doesn't work well with pause/resume recording");
|
||||
}
|
||||
sendCmd("pause");
|
||||
mState = State.PAUSED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume a paused session.
|
||||
*/
|
||||
public synchronized void resumeRecording() {
|
||||
if (mState != State.PAUSED) {
|
||||
throw new IllegalStateException("resumeRecording: session in wrong state " + mState);
|
||||
}
|
||||
sendCmd("resume");
|
||||
mState = State.STARTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop recording and generate a recording file under appDataDir/simpleperf_data/.
|
||||
*/
|
||||
public synchronized void stopRecording() {
|
||||
if (mState != State.STARTED && mState != State.PAUSED) {
|
||||
throw new IllegalStateException("stopRecording: session in wrong state " + mState);
|
||||
}
|
||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P + 1
|
||||
&& mSimpleperfPath.equals(SIMPLEPERF_PATH_IN_IMAGE)) {
|
||||
// The simpleperf shipped on Android Q contains a bug, which may make it abort if
|
||||
// calling simpleperfProcess.destroy().
|
||||
destroySimpleperfProcessWithoutClosingStdin();
|
||||
} else {
|
||||
mSimpleperfProcess.destroy();
|
||||
}
|
||||
try {
|
||||
int exitCode = mSimpleperfProcess.waitFor();
|
||||
if (exitCode != 0) {
|
||||
throw new AssertionError("simpleperf exited with error: " + exitCode);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
mSimpleperfProcess = null;
|
||||
mState = State.STOPPED;
|
||||
}
|
||||
|
||||
private void destroySimpleperfProcessWithoutClosingStdin() {
|
||||
// In format "Process[pid=? ..."
|
||||
String s = mSimpleperfProcess.toString();
|
||||
final String prefix = "Process[pid=";
|
||||
if (s.startsWith(prefix)) {
|
||||
int startIndex = prefix.length();
|
||||
int endIndex = s.indexOf(',');
|
||||
if (endIndex > startIndex) {
|
||||
int pid = Integer.parseInt(s.substring(startIndex, endIndex).trim());
|
||||
android.os.Process.sendSignal(pid, OsConstants.SIGTERM);
|
||||
return;
|
||||
}
|
||||
}
|
||||
mSimpleperfProcess.destroy();
|
||||
}
|
||||
|
||||
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() {
|
||||
// 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 = SIMPLEPERF_PATH_IN_IMAGE;
|
||||
if (isExecutableFile(simpleperfPath)) {
|
||||
return simpleperfPath;
|
||||
}
|
||||
throw new Error("can't find simpleperf on device. Please run api_profiler.py.");
|
||||
}
|
||||
|
||||
private boolean isExecutableFile(@NonNull String path) {
|
||||
File file = new File(path);
|
||||
return file.canExecute();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
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 = mAppDataDir + "/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.
|
||||
// For android R, app context isn't allowed to use perf_event_open.
|
||||
// So test executing downloaded simpleperf.
|
||||
try {
|
||||
Process process = new ProcessBuilder().command(toPath, "list", "sw").start();
|
||||
process.waitFor();
|
||||
String data = readInputStream(process.getInputStream());
|
||||
if (!data.contains("cpu-clock")) {
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
return toPath;
|
||||
}
|
||||
|
||||
private void checkIfPerfEnabled() {
|
||||
if (getProperty("persist.simpleperf.profile_app_uid").equals("" + Os.getuid())) {
|
||||
String timeStr = getProperty("persist.simpleperf.profile_app_expiration_time");
|
||||
if (!timeStr.isEmpty()) {
|
||||
try {
|
||||
long expirationTime = Long.parseLong(timeStr);
|
||||
if (expirationTime > System.currentTimeMillis() / 1000) {
|
||||
return;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (getProperty("security.perf_harden") == "1") {
|
||||
throw new Error("Recording app isn't enabled on the device."
|
||||
+ " Please run api_profiler.py.");
|
||||
}
|
||||
}
|
||||
|
||||
private String getProperty(String name) {
|
||||
String value;
|
||||
Process process;
|
||||
try {
|
||||
process = new ProcessBuilder()
|
||||
.command("/system/bin/getprop", name).start();
|
||||
} catch (IOException e) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
process.waitFor();
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
return readInputStream(process.getInputStream());
|
||||
}
|
||||
|
||||
private void createSimpleperfDataDir() {
|
||||
File file = new File(mSimpleperfDataDir);
|
||||
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(mSimpleperfDataDir));
|
||||
try {
|
||||
mSimpleperfProcess = 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(@NonNull String cmd) {
|
||||
cmd += "\n";
|
||||
try {
|
||||
mSimpleperfProcess.getOutputStream().write(cmd.getBytes());
|
||||
mSimpleperfProcess.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);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
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 = mSimpleperfProcess.getInputStream().read();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
if (c == -1 || c == '\n') {
|
||||
break;
|
||||
}
|
||||
s += (char) c;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,196 @@
|
||||
/*
|
||||
* 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 android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
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>
|
||||
*/
|
||||
@RequiresApi(28)
|
||||
public class RecordOptions {
|
||||
|
||||
/**
|
||||
* Set output filename. Default is perf-<month>-<day>-<hour>-<minute>-<second>.data.
|
||||
* The file will be generated under simpleperf_data/.
|
||||
*/
|
||||
@NonNull
|
||||
public RecordOptions setOutputFilename(@NonNull String filename) {
|
||||
mOutputFilename = filename;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set event to record. Default is cpu-cycles. See `simpleperf list` for all available events.
|
||||
*/
|
||||
@NonNull
|
||||
public RecordOptions setEvent(@NonNull String event) {
|
||||
mEvent = event;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set how many samples to generate each second running. Default is 4000.
|
||||
*/
|
||||
@NonNull
|
||||
public RecordOptions setSampleFrequency(int freq) {
|
||||
mFreq = freq;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set record duration. The record stops after `durationInSecond` seconds. By default,
|
||||
* record stops only when stopRecording() is called.
|
||||
*/
|
||||
@NonNull
|
||||
public RecordOptions setDuration(double durationInSecond) {
|
||||
mDurationInSeconds = durationInSecond;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record some threads in the app process. By default, record all threads in the process.
|
||||
*/
|
||||
@NonNull
|
||||
public RecordOptions setSampleThreads(@NonNull List<Integer> threads) {
|
||||
mThreads.addAll(threads);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record dwarf based call graph. It is needed to get Java callstacks.
|
||||
*/
|
||||
@NonNull
|
||||
public RecordOptions recordDwarfCallGraph() {
|
||||
mDwarfCallGraph = true;
|
||||
mFpCallGraph = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record frame pointer based call graph. It is suitable to get C++ callstacks on 64bit devices.
|
||||
*/
|
||||
@NonNull
|
||||
public RecordOptions recordFramePointerCallGraph() {
|
||||
mFpCallGraph = true;
|
||||
mDwarfCallGraph = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trace context switch info to show where threads spend time off cpu.
|
||||
*/
|
||||
@NonNull
|
||||
public RecordOptions traceOffCpu() {
|
||||
mTraceOffCpu = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate record options into arguments for `simpleperf record` cmd.
|
||||
*/
|
||||
@NonNull
|
||||
public List<String> toRecordArgs() {
|
||||
ArrayList<String> args = new ArrayList<>();
|
||||
|
||||
String filename = mOutputFilename;
|
||||
if (filename == null) {
|
||||
filename = getDefaultOutputFilename();
|
||||
}
|
||||
args.add("-o");
|
||||
args.add(filename);
|
||||
args.add("-e");
|
||||
args.add(mEvent);
|
||||
args.add("-f");
|
||||
args.add(String.valueOf(mFreq));
|
||||
if (mDurationInSeconds != 0.0) {
|
||||
args.add("--duration");
|
||||
args.add(String.valueOf(mDurationInSeconds));
|
||||
}
|
||||
if (mThreads.isEmpty()) {
|
||||
args.add("-p");
|
||||
args.add(String.valueOf(Os.getpid()));
|
||||
} else {
|
||||
String s = "";
|
||||
for (int i = 0; i < mThreads.size(); i++) {
|
||||
if (i > 0) {
|
||||
s += ",";
|
||||
}
|
||||
s += mThreads.get(i).toString();
|
||||
}
|
||||
args.add("-t");
|
||||
args.add(s);
|
||||
}
|
||||
if (mDwarfCallGraph) {
|
||||
args.add("-g");
|
||||
} else if (mFpCallGraph) {
|
||||
args.add("--call-graph");
|
||||
args.add("fp");
|
||||
}
|
||||
if (mTraceOffCpu) {
|
||||
args.add("--trace-offcpu");
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
private String getDefaultOutputFilename() {
|
||||
LocalDateTime time = LocalDateTime.now();
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("'perf'-MM-dd-HH-mm-ss'.data'");
|
||||
return time.format(formatter);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String mOutputFilename;
|
||||
|
||||
@NonNull
|
||||
private String mEvent = "cpu-cycles";
|
||||
|
||||
private int mFreq = 4000;
|
||||
|
||||
private double mDurationInSeconds = 0.0;
|
||||
|
||||
@NonNull
|
||||
private ArrayList<Integer> mThreads = new ArrayList<>();
|
||||
|
||||
private boolean mDwarfCallGraph = false;
|
||||
|
||||
private boolean mFpCallGraph = false;
|
||||
|
||||
private boolean mTraceOffCpu = false;
|
||||
}
|
||||
Reference in New Issue
Block a user