summaryrefslogtreecommitdiff
path: root/perfprofd/perfprofdcore.cc
diff options
context:
space:
mode:
Diffstat (limited to 'perfprofd/perfprofdcore.cc')
-rw-r--r--perfprofd/perfprofdcore.cc732
1 files changed, 732 insertions, 0 deletions
diff --git a/perfprofd/perfprofdcore.cc b/perfprofd/perfprofdcore.cc
new file mode 100644
index 00000000..00ec8a70
--- /dev/null
+++ b/perfprofd/perfprofdcore.cc
@@ -0,0 +1,732 @@
+/*
+**
+** 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 <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <memory>
+#include <sstream>
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/macros.h>
+#include <android-base/scopeguard.h>
+#include <android-base/stringprintf.h>
+
+#ifdef __BIONIC__
+#include <android-base/properties.h>
+#endif
+
+#ifdef __ANDROID__
+#include <healthhalutils/HealthHalUtils.h>
+#endif
+
+#include "perfprofd_record.pb.h"
+
+#include "config.h"
+#include "cpuconfig.h"
+#include "perf_data_converter.h"
+#include "perfprofdcore.h"
+#include "perfprofd_io.h"
+#include "perfprofd_perf.h"
+#include "symbolizer.h"
+
+//
+// Perf profiling daemon -- collects system-wide profiles using
+//
+// simpleperf record -a
+//
+// and encodes them so that they can be uploaded by a separate service.
+//
+
+//......................................................................
+
+using ProtoUniquePtr = std::unique_ptr<android::perfprofd::PerfprofdRecord>;
+
+//
+// Output file from 'perf record'.
+//
+#define PERF_OUTPUT "perf.data"
+
+//
+// This enum holds the results of the "should we profile" configuration check.
+//
+typedef enum {
+
+ // All systems go for profile collection.
+ DO_COLLECT_PROFILE,
+
+ // The selected configuration directory doesn't exist.
+ DONT_PROFILE_MISSING_CONFIG_DIR,
+
+ // Destination directory does not contain the semaphore file that
+ // the perf profile uploading service creates when it determines
+ // that the user has opted "in" for usage data collection. No
+ // semaphore -> no user approval -> no profiling.
+ DONT_PROFILE_MISSING_SEMAPHORE,
+
+ // No perf executable present
+ DONT_PROFILE_MISSING_PERF_EXECUTABLE,
+
+ // We're running in the emulator, perf won't be able to do much
+ DONT_PROFILE_RUNNING_IN_EMULATOR
+
+} CKPROFILE_RESULT;
+
+static bool common_initialized = false;
+
+//
+// Are we running in the emulator? If so, stub out profile collection
+// Starts as uninitialized (-1), then set to 1 or 0 at init time.
+//
+static int running_in_emulator = -1;
+
+//
+// Is this a debug build ('userdebug' or 'eng')?
+//
+static bool is_debug_build = false;
+
+//
+// Random number generator seed (set at startup time).
+//
+static unsigned short random_seed[3];
+
+//
+// Convert a CKPROFILE_RESULT to a string
+//
+static const char *ckprofile_result_to_string(CKPROFILE_RESULT result)
+{
+ switch (result) {
+ case DO_COLLECT_PROFILE:
+ return "DO_COLLECT_PROFILE";
+ case DONT_PROFILE_MISSING_CONFIG_DIR:
+ return "missing config directory";
+ case DONT_PROFILE_MISSING_SEMAPHORE:
+ return "missing semaphore file";
+ case DONT_PROFILE_MISSING_PERF_EXECUTABLE:
+ return "missing 'perf' executable";
+ case DONT_PROFILE_RUNNING_IN_EMULATOR:
+ return "running in emulator";
+ default:
+ return "unknown";
+ }
+}
+
+//
+// Check to see whether we should perform a profile collection
+//
+static CKPROFILE_RESULT check_profiling_enabled(const Config& config)
+{
+ //
+ // Profile collection in the emulator doesn't make sense
+ //
+ assert(running_in_emulator != -1);
+ if (running_in_emulator) {
+ return DONT_PROFILE_RUNNING_IN_EMULATOR;
+ }
+
+ if (!config.IsProfilingEnabled()) {
+ return DONT_PROFILE_MISSING_CONFIG_DIR;
+ }
+
+ // Check for existence of simpleperf/perf executable
+ std::string pp = config.perf_path;
+ if (access(pp.c_str(), R_OK|X_OK) == -1) {
+ LOG(WARNING) << "unable to access/execute " << pp;
+ return DONT_PROFILE_MISSING_PERF_EXECUTABLE;
+ }
+
+ //
+ // We are good to go
+ //
+ return DO_COLLECT_PROFILE;
+}
+
+bool get_booting()
+{
+#ifdef __BIONIC__
+ return android::base::GetBoolProperty("sys.boot_completed", false) != true;
+#else
+ return false;
+#endif
+}
+
+//
+// Constructor takes a timeout (in seconds) and a child pid; If an
+// alarm set for the specified number of seconds triggers, then a
+// SIGKILL is sent to the child. Destructor resets alarm. Example:
+//
+// pid_t child_pid = ...;
+// { AlarmHelper h(10, child_pid);
+// ... = read_from_child(child_pid, ...);
+// }
+//
+// NB: this helper is not re-entrant-- avoid nested use or
+// use by multiple threads
+//
+class AlarmHelper {
+ public:
+ AlarmHelper(unsigned num_seconds, pid_t child)
+ {
+ struct sigaction sigact;
+ assert(child);
+ assert(child_ == 0);
+ memset(&sigact, 0, sizeof(sigact));
+ sigact.sa_sigaction = handler;
+ sigaction(SIGALRM, &sigact, &oldsigact_);
+ child_ = child;
+ alarm(num_seconds);
+ }
+ ~AlarmHelper()
+ {
+ alarm(0);
+ child_ = 0;
+ sigaction(SIGALRM, &oldsigact_, NULL);
+ }
+ static void handler(int, siginfo_t *, void *);
+
+ private:
+ struct sigaction oldsigact_;
+ static pid_t child_;
+};
+
+pid_t AlarmHelper::child_;
+
+void AlarmHelper::handler(int, siginfo_t *, void *)
+{
+ LOG(WARNING) << "SIGALRM timeout";
+ kill(child_, SIGKILL);
+}
+
+//
+// This implementation invokes "dumpsys media.camera" and inspects the
+// output to determine if any camera clients are active. NB: this is
+// currently disable (via config option) until the selinux issues can
+// be sorted out. Another possible implementation (not yet attempted)
+// would be to use the binder to call into the native camera service
+// via "ICameraService".
+//
+bool get_camera_active()
+{
+ int pipefds[2];
+ if (pipe2(pipefds, O_CLOEXEC) != 0) {
+ PLOG(ERROR) << "pipe2() failed";
+ return false;
+ }
+ pid_t pid = fork();
+ if (pid == -1) {
+ PLOG(ERROR) << "fork() failed";
+ close(pipefds[0]);
+ close(pipefds[1]);
+ return false;
+ } else if (pid == 0) {
+ // child
+ close(pipefds[0]);
+ dup2(pipefds[1], fileno(stderr));
+ dup2(pipefds[1], fileno(stdout));
+ const char *argv[10];
+ unsigned slot = 0;
+ argv[slot++] = "/system/bin/dumpsys";
+ argv[slot++] = "media.camera";
+ argv[slot++] = nullptr;
+ execvp(argv[0], (char * const *)argv);
+ PLOG(ERROR) << "execvp() failed";
+ return false;
+ }
+ // parent
+ AlarmHelper helper(10, pid);
+ close(pipefds[1]);
+
+ // read output
+ bool have_cam = false;
+ bool have_clients = true;
+ std::string dump_output;
+ bool result = android::base::ReadFdToString(pipefds[0], &dump_output);
+ close(pipefds[0]);
+ if (result) {
+ std::stringstream ss(dump_output);
+ std::string line;
+ while (std::getline(ss,line,'\n')) {
+ if (line.find("Camera module API version:") !=
+ std::string::npos) {
+ have_cam = true;
+ }
+ if (line.find("No camera module available") !=
+ std::string::npos ||
+ line.find("No active camera clients yet") !=
+ std::string::npos) {
+ have_clients = false;
+ }
+ }
+ }
+
+ // reap child (no zombies please)
+ int st = 0;
+ TEMP_FAILURE_RETRY(waitpid(pid, &st, 0));
+ return have_cam && have_clients;
+}
+
+bool get_charging()
+{
+#ifdef __ANDROID__
+ using android::sp;
+ using android::hardware::Return;
+ using android::hardware::health::V2_0::get_health_service;
+ using android::hardware::health::V2_0::HealthInfo;
+ using android::hardware::health::V2_0::IHealth;
+ using android::hardware::health::V2_0::Result;
+
+ sp<IHealth> service = get_health_service();
+ if (service == nullptr) {
+ LOG(ERROR) << "Failed to get health HAL";
+ return false;
+ }
+ Result res = Result::UNKNOWN;
+ HealthInfo val;
+ Return<void> ret =
+ service->getHealthInfo([&](Result out_res, HealthInfo out_val) {
+ res = out_res;
+ val = out_val;
+ });
+ if (!ret.isOk()) {
+ LOG(ERROR) << "Failed to call getChargeStatus on health HAL: " << ret.description();
+ return false;
+ }
+ if (res != Result::SUCCESS) {
+ LOG(ERROR) << "Failed to retrieve charge status from health HAL: result = "
+ << toString(res);
+ return false;
+ }
+ return val.legacy.chargerAcOnline || val.legacy.chargerUsbOnline ||
+ val.legacy.chargerWirelessOnline;
+#else
+ return false;
+#endif
+}
+
+static bool postprocess_proc_stat_contents(const std::string &pscontents,
+ long unsigned *idleticks,
+ long unsigned *remainingticks)
+{
+ long unsigned usertime, nicetime, systime, idletime, iowaittime;
+ long unsigned irqtime, softirqtime;
+
+ int rc = sscanf(pscontents.c_str(), "cpu %lu %lu %lu %lu %lu %lu %lu",
+ &usertime, &nicetime, &systime, &idletime,
+ &iowaittime, &irqtime, &softirqtime);
+ if (rc != 7) {
+ return false;
+ }
+ *idleticks = idletime;
+ *remainingticks = usertime + nicetime + systime + iowaittime + irqtime + softirqtime;
+ return true;
+}
+
+unsigned collect_cpu_utilization()
+{
+ std::string contents;
+ long unsigned idle[2];
+ long unsigned busy[2];
+ for (unsigned iter = 0; iter < 2; ++iter) {
+ if (!android::base::ReadFileToString("/proc/stat", &contents)) {
+ return 0;
+ }
+ if (!postprocess_proc_stat_contents(contents, &idle[iter], &busy[iter])) {
+ return 0;
+ }
+ if (iter == 0) {
+ sleep(1);
+ }
+ }
+ long unsigned total_delta = (idle[1] + busy[1]) - (idle[0] + busy[0]);
+ long unsigned busy_delta = busy[1] - busy[0];
+ return busy_delta * 100 / total_delta;
+}
+
+static void annotate_encoded_perf_profile(android::perfprofd::PerfprofdRecord* profile,
+ const Config& config,
+ unsigned cpu_utilization)
+{
+ //
+ // Incorporate cpu utilization (collected prior to perf run)
+ //
+ if (config.collect_cpu_utilization) {
+ profile->SetExtension(quipper::cpu_utilization, cpu_utilization);
+ }
+
+ //
+ // Load average as reported by the kernel
+ //
+ std::string load;
+ double fload = 0.0;
+ if (android::base::ReadFileToString("/proc/loadavg", &load) &&
+ sscanf(load.c_str(), "%lf", &fload) == 1) {
+ int iload = static_cast<int>(fload * 100.0);
+ profile->SetExtension(quipper::sys_load_average, iload);
+ } else {
+ PLOG(ERROR) << "Failed to read or scan /proc/loadavg";
+ }
+
+ //
+ // Device still booting? Camera in use? Plugged into charger?
+ //
+ bool is_booting = get_booting();
+ if (config.collect_booting) {
+ profile->SetExtension(quipper::booting, is_booting);
+ }
+ if (config.collect_camera_active) {
+ profile->SetExtension(quipper::camera_active, is_booting ? false : get_camera_active());
+ }
+ if (config.collect_charging_state) {
+ profile->SetExtension(quipper::on_charger, get_charging());
+ }
+
+ //
+ // Examine the contents of wake_unlock to determine whether the
+ // device display is on or off. NB: is this really the only way to
+ // determine this info?
+ //
+ std::string disp;
+ if (android::base::ReadFileToString("/sys/power/wake_unlock", &disp)) {
+ bool ison = (strstr(disp.c_str(), "PowerManagerService.Display") == 0);
+ profile->SetExtension(quipper::display_on, ison);
+ } else {
+ PLOG(ERROR) << "Failed to read /sys/power/wake_unlock";
+ }
+}
+
+static ProtoUniquePtr encode_to_proto(const std::string &data_file_path,
+ const Config& config,
+ unsigned cpu_utilization,
+ perfprofd::Symbolizer* symbolizer) {
+ //
+ // Open and read perf.data file
+ //
+ ProtoUniquePtr encodedProfile(
+ android::perfprofd::RawPerfDataToAndroidPerfProfile(data_file_path,
+ symbolizer,
+ config.symbolize_everything));
+ if (encodedProfile == nullptr) {
+ return nullptr;
+ }
+
+ // All of the info in 'encodedProfile' is derived from the perf.data file;
+ // here we tack display status, cpu utilization, system load, etc.
+ annotate_encoded_perf_profile(encodedProfile.get(), config, cpu_utilization);
+
+ return encodedProfile;
+}
+
+PROFILE_RESULT encode_to_proto(const std::string &data_file_path,
+ const char *encoded_file_path,
+ const Config& config,
+ unsigned cpu_utilization,
+ perfprofd::Symbolizer* symbolizer)
+{
+ ProtoUniquePtr encodedProfile = encode_to_proto(data_file_path,
+ config,
+ cpu_utilization,
+ symbolizer);
+
+ //
+ // Issue error if no samples
+ //
+ if (encodedProfile == nullptr || encodedProfile->events_size() == 0) {
+ return ERR_PERF_ENCODE_FAILED;
+ }
+
+ return android::perfprofd::SerializeProtobuf(encodedProfile.get(),
+ encoded_file_path,
+ config.compress)
+ ? OK_PROFILE_COLLECTION
+ : ERR_WRITE_ENCODED_FILE_FAILED;
+}
+
+//
+// Remove all files in the destination directory during initialization
+//
+static void cleanup_destination_dir(const std::string& dest_dir)
+{
+ DIR* dir = opendir(dest_dir.c_str());
+ if (dir != NULL) {
+ struct dirent* e;
+ while ((e = readdir(dir)) != 0) {
+ if (e->d_name[0] != '.') {
+ std::string file_path = dest_dir + "/" + e->d_name;
+ remove(file_path.c_str());
+ }
+ }
+ closedir(dir);
+ } else {
+ PLOG(WARNING) << "unable to open destination dir " << dest_dir << " for cleanup";
+ }
+}
+
+//
+// Collect a perf profile. Steps for this operation are:
+// - kick off 'perf record'
+// - read perf.data, convert to protocol buf
+//
+static ProtoUniquePtr collect_profile(Config& config)
+{
+ //
+ // Collect cpu utilization if enabled
+ //
+ unsigned cpu_utilization = 0;
+ if (config.collect_cpu_utilization) {
+ cpu_utilization = collect_cpu_utilization();
+ }
+
+ //
+ // Form perf.data file name, perf error output file name
+ //
+ const std::string& destdir = config.destination_directory;
+ std::string data_file_path(destdir);
+ data_file_path += "/";
+ data_file_path += PERF_OUTPUT;
+ std::string perf_stderr_path(destdir);
+ perf_stderr_path += "/perferr.txt";
+
+ //
+ // Remove any existing perf.data file -- if we don't do this, perf
+ // will rename the old file and we'll have extra cruft lying around.
+ //
+ struct stat statb;
+ if (stat(data_file_path.c_str(), &statb) == 0) { // if file exists...
+ if (unlink(data_file_path.c_str())) { // then try to remove
+ PLOG(WARNING) << "unable to unlink previous perf.data file";
+ }
+ }
+
+ //
+ // The "mpdecision" daemon can cause problems for profile
+ // collection: if it decides to online a CPU partway through the
+ // 'perf record' run, the activity on that CPU will be invisible to
+ // perf, and if it offlines a CPU during the recording this can
+ // sometimes leave the PMU in an unusable state (dmesg errors of the
+ // form "perfevents: unable to request IRQXXX for ..."). To avoid
+ // these issues, if "mpdecision" is running the helper below will
+ // stop the service and then online all available CPUs. The object
+ // destructor (invoked when this routine terminates) will then
+ // restart the service again when needed.
+ //
+ uint32_t duration = config.sample_duration_in_s;
+ bool hardwire = config.hardwire_cpus;
+ uint32_t max_duration = config.hardwire_cpus_max_duration_in_s;
+ bool take_action = (hardwire && duration <= max_duration);
+ HardwireCpuHelper helper(take_action);
+
+ auto scope_guard = android::base::make_scope_guard(
+ [&data_file_path]() { unlink(data_file_path.c_str()); });
+
+ //
+ // Invoke perf
+ //
+ const char *stack_profile_opt =
+ (config.stack_profile ? "-g" : nullptr);
+ const std::string& perf_path = config.perf_path;
+
+ android::perfprofd::PerfResult invoke_res =
+ android::perfprofd::InvokePerf(config,
+ perf_path,
+ stack_profile_opt,
+ duration,
+ data_file_path,
+ perf_stderr_path);
+ if (invoke_res != android::perfprofd::PerfResult::kOK) {
+ return nullptr;
+ }
+
+ //
+ // Read the resulting perf.data file, encode into protocol buffer, then write
+ // the result to the file perf.data.encoded
+ //
+ std::unique_ptr<perfprofd::Symbolizer> symbolizer;
+ if (config.use_elf_symbolizer) {
+ symbolizer = perfprofd::CreateELFSymbolizer();
+ }
+ return encode_to_proto(data_file_path, config, cpu_utilization, symbolizer.get());
+}
+
+//
+// Assuming that we want to collect a profile every N seconds,
+// randomly partition N into two sub-intervals.
+//
+static void determine_before_after(unsigned &sleep_before_collect,
+ unsigned &sleep_after_collect,
+ unsigned collection_interval)
+{
+ double frac = erand48(random_seed);
+ sleep_before_collect = (unsigned) (((double)collection_interval) * frac);
+ assert(sleep_before_collect <= collection_interval);
+ sleep_after_collect = collection_interval - sleep_before_collect;
+}
+
+//
+// Set random number generator seed
+//
+static void set_seed(uint32_t use_fixed_seed)
+{
+ unsigned seed = 0;
+ if (use_fixed_seed) {
+ //
+ // Use fixed user-specified seed
+ //
+ seed = use_fixed_seed;
+ } else {
+ //
+ // Randomized seed
+ //
+#ifdef __BIONIC__
+ seed = arc4random();
+#else
+ seed = 12345678u;
+#endif
+ }
+ LOG(INFO) << "random seed set to " << seed;
+ // Distribute the 32-bit seed into the three 16-bit array
+ // elements. The specific values being written do not especially
+ // matter as long as we are setting them to something based on the seed.
+ random_seed[0] = seed & 0xffff;
+ random_seed[1] = (seed >> 16);
+ random_seed[2] = (random_seed[0] ^ random_seed[1]);
+}
+
+void CommonInit(uint32_t use_fixed_seed, const char* dest_dir) {
+ // Children of init inherit an artificially low OOM score -- this is not
+ // desirable for perfprofd (its OOM score should be on par with
+ // other user processes).
+ std::stringstream oomscore_path;
+ oomscore_path << "/proc/" << getpid() << "/oom_score_adj";
+ if (!android::base::WriteStringToFile("0", oomscore_path.str())) {
+ LOG(ERROR) << "unable to write to " << oomscore_path.str();
+ }
+
+ set_seed(use_fixed_seed);
+ if (dest_dir != nullptr) {
+ cleanup_destination_dir(dest_dir);
+ }
+
+#ifdef __BIONIC__
+ running_in_emulator = android::base::GetBoolProperty("ro.kernel.qemu", false);
+ is_debug_build = android::base::GetBoolProperty("ro.debuggable", false);
+#else
+ running_in_emulator = false;
+ is_debug_build = true;
+#endif
+
+ common_initialized = true;
+}
+
+void GlobalInit(const std::string& perf_path) {
+ if (!android::perfprofd::FindSupportedPerfCounters(perf_path)) {
+ LOG(WARNING) << "Could not read supported perf counters.";
+ }
+}
+
+bool IsDebugBuild() {
+ CHECK(common_initialized);
+ return is_debug_build;
+}
+
+template <typename ConfigFn, typename UpdateFn>
+static void ProfilingLoopImpl(ConfigFn config, UpdateFn update, HandlerFn handler) {
+ unsigned iterations = 0;
+ while(config()->main_loop_iterations == 0 ||
+ iterations < config()->main_loop_iterations) {
+ if (config()->ShouldStopProfiling()) {
+ return;
+ }
+
+ // Figure out where in the collection interval we're going to actually
+ // run perf
+ unsigned sleep_before_collect = 0;
+ unsigned sleep_after_collect = 0;
+ determine_before_after(sleep_before_collect,
+ sleep_after_collect,
+ config()->collection_interval_in_s);
+ if (sleep_before_collect > 0) {
+ config()->Sleep(sleep_before_collect);
+ }
+
+ if (config()->ShouldStopProfiling()) {
+ return;
+ }
+
+ // Run any necessary updates.
+ update();
+
+ // Check for profiling enabled...
+ CKPROFILE_RESULT ckresult = check_profiling_enabled(*config());
+ if (ckresult != DO_COLLECT_PROFILE) {
+ LOG(INFO) << "profile collection skipped (" << ckprofile_result_to_string(ckresult) << ")";
+ } else {
+ // Kick off the profiling run...
+ LOG(INFO) << "initiating profile collection";
+ ProtoUniquePtr proto = collect_profile(*config());
+ if (proto == nullptr) {
+ LOG(WARNING) << "profile collection failed";
+ }
+
+ // Always report, even a null result.
+ bool handle_result = handler(proto.get(), config());
+ if (handle_result) {
+ LOG(INFO) << "profile collection complete";
+ } else if (proto != nullptr) {
+ LOG(WARNING) << "profile handling failed";
+ }
+ }
+
+ if (config()->ShouldStopProfiling()) {
+ return;
+ }
+
+ if (sleep_after_collect > 0) {
+ config()->Sleep(sleep_after_collect);
+ }
+ iterations += 1;
+ }
+}
+
+void ProfilingLoop(Config& config, HandlerFn handler) {
+ CommonInit(config.use_fixed_seed, nullptr);
+
+ auto config_fn = [&config]() {
+ return &config;;
+ };
+ auto do_nothing = []() {
+ };
+ ProfilingLoopImpl(config_fn, do_nothing, handler);
+}
+
+void ProfilingLoop(std::function<Config*()> config_fn,
+ std::function<void()> update_fn,
+ HandlerFn handler) {
+ ProfilingLoopImpl(config_fn, update_fn, handler);
+}