summaryrefslogtreecommitdiff
path: root/perfprofd/perfprofd_perf.cc
diff options
context:
space:
mode:
Diffstat (limited to 'perfprofd/perfprofd_perf.cc')
-rw-r--r--perfprofd/perfprofd_perf.cc332
1 files changed, 332 insertions, 0 deletions
diff --git a/perfprofd/perfprofd_perf.cc b/perfprofd/perfprofd_perf.cc
new file mode 100644
index 00000000..15dde6fb
--- /dev/null
+++ b/perfprofd/perfprofd_perf.cc
@@ -0,0 +1,332 @@
+/*
+**
+** 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