diff options
Diffstat (limited to 'perfprofd/perfprofdcore.cc')
-rw-r--r-- | perfprofd/perfprofdcore.cc | 732 |
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); +} |