diff options
Diffstat (limited to 'perfprofd/perfprofd_perf.cc')
-rw-r--r-- | perfprofd/perfprofd_perf.cc | 332 |
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 |