/*
|
**
|
** Copyright 2015, 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 "perfprofd_perf.h"
|
|
|
#include <inttypes.h>
|
#include <signal.h>
|
#include <sys/wait.h>
|
#include <unistd.h>
|
|
#include <algorithm>
|
#include <cerrno>
|
#include <cstdio>
|
#include <cstring>
|
#include <memory>
|
#include <vector>
|
|
#include <android-base/file.h>
|
#include <android-base/logging.h>
|
#include <android-base/stringprintf.h>
|
#include <android-base/strings.h>
|
#include <android-base/unique_fd.h>
|
|
#include "config.h"
|
|
namespace android {
|
namespace perfprofd {
|
|
namespace {
|
|
std::unordered_set<std::string>& GetSupportedPerfCountersInternal() {
|
static std::unordered_set<std::string>& vec = *new std::unordered_set<std::string>();
|
return vec;
|
}
|
|
} // namespace
|
|
//
|
// Invoke "perf record". Return value is OK_PROFILE_COLLECTION for
|
// success, or some other error code if something went wrong.
|
//
|
PerfResult InvokePerf(Config& config,
|
const std::string &perf_path,
|
const char *stack_profile_opt,
|
unsigned duration,
|
const std::string &data_file_path,
|
const std::string &perf_stderr_path)
|
{
|
std::vector<std::string> argv_backing;
|
std::vector<const char*> argv_vector;
|
char paranoid_env[] = "PERFPROFD_DISABLE_PERF_EVENT_PARANOID_CHANGE=1";
|
char* envp[2] = {paranoid_env, nullptr};
|
|
{
|
auto add = [&argv_backing](auto arg) {
|
argv_backing.push_back(arg);
|
};
|
|
add(perf_path);
|
add("record");
|
|
// -o perf.data
|
add("-o");
|
add(data_file_path);
|
|
// -c/f N
|
std::string p_str;
|
if (config.sampling_frequency > 0) {
|
add("-f");
|
add(android::base::StringPrintf("%u", config.sampling_frequency));
|
} else if (config.sampling_period > 0) {
|
add("-c");
|
add(android::base::StringPrintf("%u", config.sampling_period));
|
}
|
|
if (!config.event_config.empty()) {
|
const std::unordered_set<std::string>& supported = GetSupportedPerfCountersInternal();
|
for (const auto& event_set : config.event_config) {
|
if (event_set.events.empty()) {
|
LOG(WARNING) << "Unexpected empty event set";
|
continue;
|
}
|
|
std::ostringstream event_str;
|
bool added = false;
|
for (const std::string& event : event_set.events) {
|
if (supported.find(event) == supported.end()) {
|
LOG(WARNING) << "Event " << event << " is unsupported.";
|
if (config.fail_on_unsupported_events) {
|
return PerfResult::kUnsupportedEvent;
|
}
|
continue;
|
}
|
if (added) {
|
event_str << ',';
|
}
|
event_str << event;
|
added = true;
|
}
|
|
if (!added) {
|
continue;
|
}
|
|
if (event_set.sampling_period > 0) {
|
add("-c");
|
add(std::to_string(event_set.sampling_period));
|
}
|
add(event_set.group ? "--group" : "-e");
|
add(event_str.str());
|
}
|
}
|
|
// -g if desired
|
if (stack_profile_opt != nullptr) {
|
add(stack_profile_opt);
|
add("-m");
|
add("8192");
|
}
|
|
if (config.process < 0) {
|
// system wide profiling
|
add("-a");
|
} else {
|
add("-p");
|
add(std::to_string(config.process));
|
}
|
|
// no need for kernel or other symbols
|
add("--no-dump-kernel-symbols");
|
add("--no-dump-symbols");
|
|
// sleep <duration>
|
add("--duration");
|
add(android::base::StringPrintf("%u", duration));
|
|
|
// Now create the char* buffer.
|
argv_vector.resize(argv_backing.size() + 1, nullptr);
|
std::transform(argv_backing.begin(),
|
argv_backing.end(),
|
argv_vector.begin(),
|
[](const std::string& in) { return in.c_str(); });
|
}
|
|
pid_t pid = fork();
|
|
if (pid == -1) {
|
PLOG(ERROR) << "Fork failed";
|
return PerfResult::kForkFailed;
|
}
|
|
if (pid == 0) {
|
// child
|
|
// Open file to receive stderr/stdout from perf
|
FILE *efp = fopen(perf_stderr_path.c_str(), "w");
|
if (efp) {
|
dup2(fileno(efp), STDERR_FILENO);
|
dup2(fileno(efp), STDOUT_FILENO);
|
} else {
|
PLOG(WARNING) << "unable to open " << perf_stderr_path << " for writing";
|
}
|
|
// record the final command line in the error output file for
|
// posterity/debugging purposes
|
fprintf(stderr, "perf invocation (pid=%d):\n", getpid());
|
for (unsigned i = 0; argv_vector[i] != nullptr; ++i) {
|
fprintf(stderr, "%s%s", i ? " " : "", argv_vector[i]);
|
}
|
fprintf(stderr, "\n");
|
|
// exec
|
execvpe(argv_vector[0], const_cast<char* const*>(argv_vector.data()), envp);
|
fprintf(stderr, "exec failed: %s\n", strerror(errno));
|
exit(1);
|
|
} else {
|
// parent
|
|
// Try to sleep.
|
config.Sleep(duration);
|
|
// We may have been woken up to stop profiling.
|
if (config.ShouldStopProfiling()) {
|
// Send SIGHUP to simpleperf to make it stop.
|
kill(pid, SIGHUP);
|
}
|
|
// Wait for the child, so it's reaped correctly.
|
int st = 0;
|
pid_t reaped = TEMP_FAILURE_RETRY(waitpid(pid, &st, 0));
|
|
auto print_perferr = [&perf_stderr_path]() {
|
std::string tmp;
|
if (android::base::ReadFileToString(perf_stderr_path, &tmp)) {
|
LOG(WARNING) << tmp;
|
} else {
|
PLOG(WARNING) << "Could not read " << perf_stderr_path;
|
}
|
};
|
|
if (reaped == -1) {
|
PLOG(WARNING) << "waitpid failed";
|
} else if (WIFSIGNALED(st)) {
|
if (WTERMSIG(st) == SIGHUP && config.ShouldStopProfiling()) {
|
// That was us...
|
return PerfResult::kOK;
|
}
|
LOG(WARNING) << "perf killed by signal " << WTERMSIG(st);
|
print_perferr();
|
} else if (WEXITSTATUS(st) != 0) {
|
LOG(WARNING) << "perf bad exit status " << WEXITSTATUS(st);
|
print_perferr();
|
} else {
|
return PerfResult::kOK;
|
}
|
}
|
|
return PerfResult::kRecordFailed;
|
}
|
|
bool FindSupportedPerfCounters(const std::string& perf_path) {
|
const char* argv[] = { perf_path.c_str(), "list", nullptr };
|
char paranoid_env[] = "PERFPROFD_DISABLE_PERF_EVENT_PARANOID_CHANGE=1";
|
char* envp[2] = {paranoid_env, nullptr};
|
|
base::unique_fd link[2];
|
{
|
int link_fd[2];
|
|
if (pipe(link_fd) == -1) {
|
PLOG(ERROR) << "Pipe failed";
|
return false;
|
}
|
link[0].reset(link_fd[0]);
|
link[1].reset(link_fd[1]);
|
}
|
|
pid_t pid = fork();
|
|
if (pid == -1) {
|
PLOG(ERROR) << "Fork failed";
|
return PerfResult::kForkFailed;
|
}
|
|
if (pid == 0) {
|
// Child
|
|
// Redirect stdout and stderr.
|
dup2(link[1].get(), STDOUT_FILENO);
|
dup2(link[1].get(), STDERR_FILENO);
|
|
link[0].reset();
|
link[1].reset();
|
|
// exec
|
execvpe(argv[0], const_cast<char* const*>(argv), envp);
|
PLOG(WARNING) << "exec failed";
|
exit(1);
|
__builtin_unreachable();
|
}
|
|
link[1].reset();
|
|
std::string result;
|
if (!android::base::ReadFdToString(link[0].get(), &result)) {
|
PLOG(WARNING) << perf_path << " list reading failed.";
|
}
|
|
link[0].reset();
|
|
int status_code;
|
if (waitpid(pid, &status_code, 0) == -1) {
|
LOG(WARNING) << "Failed to wait for " << perf_path << " list";
|
return false;
|
}
|
|
if (!WIFEXITED(status_code) || WEXITSTATUS(status_code) != 0) {
|
LOG(WARNING) << perf_path << " list did not exit normally.";
|
return false;
|
}
|
|
std::unordered_set<std::string>& supported = GetSupportedPerfCountersInternal();
|
supported.clear();
|
|
// Could implement something with less memory requirements. But for now this is good
|
// enough.
|
std::vector<std::string> lines = base::Split(result, "\n");
|
for (const std::string& line : lines) {
|
if (line.length() < 2 || line.compare(0, 2, " ") != 0) {
|
continue;
|
}
|
const size_t comment = line.find('#');
|
const size_t space = line.find(' ', 2);
|
size_t end = std::min(space, comment);
|
if (end != std::string::npos) {
|
// Scan backwards.
|
--end;
|
while (end > 2 && isspace(line[end])) {
|
end--;
|
}
|
}
|
if (end > 2) {
|
supported.insert(line.substr(2, end - 2));
|
}
|
}
|
|
return true;
|
}
|
|
const std::unordered_set<std::string>& GetSupportedPerfCounters() {
|
return GetSupportedPerfCountersInternal();
|
}
|
|
} // namespace perfprofd
|
} // namespace android
|