#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
-#include <string>
+
+#include <memory>
#include <sstream>
-#include <map>
-#include <set>
-#include <cctype>
+#include <string>
#include <android-base/file.h>
+#include <android-base/logging.h>
#include <android-base/macros.h>
-#include <android-base/properties.h>
#include <android-base/stringprintf.h>
-#include "perfprofdcore.h"
-#include "perfprofdutils.h"
-#include "perf_data_converter.h"
+#ifdef __BIONIC__
+#include <android-base/properties.h>
+#endif
+
+#include "perfprofd_record.pb.h"
+
+#include "config.h"
#include "cpuconfig.h"
-#include "configreader.h"
+#include "perf_data_converter.h"
+#include "perfprofdcore.h"
+#include "perfprofd_io.h"
+#include "symbolizer.h"
//
// Perf profiling daemon -- collects system-wide profiles using
//......................................................................
+using ProtoUniquePtr = std::unique_ptr<android::perfprofd::PerfprofdRecord>;
+
//
// Output file from 'perf record'.
//
} 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.
//
// Is this a debug build ('userdebug' or 'eng')?
-// Starts as uninitialized (-1), then set to 1 or 0 at init time.
//
-static int is_debug_build = -1;
-
-//
-// Path to the perf file to convert and exit? Empty value is the default, daemon mode.
-//
-static std::string perf_file_to_convert = "";
+static bool is_debug_build = false;
//
// Random number generator seed (set at startup time).
static unsigned short random_seed[3];
//
-// SIGHUP handler. Sending SIGHUP to the daemon can be used to break it
-// out of a sleep() call so as to trigger a new collection (debugging)
-//
-static void sig_hup(int /* signum */)
-{
- W_ALOGW("SIGHUP received");
-}
-
-//
-// Parse command line args. Currently supported flags:
-// * "-c PATH" sets the path of the config file to PATH.
-// * "-x PATH" reads PATH as a perf data file and saves it as a file in
-// perf_profile.proto format. ".encoded" suffix is appended to PATH to form
-// the output file path.
-//
-static void parse_args(int argc, char** argv)
-{
- int ac;
-
- for (ac = 1; ac < argc; ++ac) {
- if (!strcmp(argv[ac], "-c")) {
- if (ac >= argc-1) {
- W_ALOGE("malformed command line: -c option requires argument)");
- continue;
- }
- ConfigReader::setConfigFilePath(argv[ac+1]);
- ++ac;
- } else if (!strcmp(argv[ac], "-x")) {
- if (ac >= argc-1) {
- W_ALOGE("malformed command line: -x option requires argument)");
- continue;
- }
- perf_file_to_convert = argv[ac+1];
- ++ac;
- } else {
- W_ALOGE("malformed command line: unknown option or arg %s)", argv[ac]);
- continue;
- }
- }
-}
-
-//
// Convert a CKPROFILE_RESULT to a string
//
-const char *ckprofile_result_to_string(CKPROFILE_RESULT result)
+static const char *ckprofile_result_to_string(CKPROFILE_RESULT result)
{
switch (result) {
case DO_COLLECT_PROFILE:
return "missing 'perf' executable";
case DONT_PROFILE_RUNNING_IN_EMULATOR:
return "running in emulator";
- default: return "unknown";
+ default:
+ return "unknown";
}
- return "notreached";
-}
-
-//
-// Convert a PROFILE_RESULT to a string
-//
-const char *profile_result_to_string(PROFILE_RESULT result)
-{
- switch(result) {
- case OK_PROFILE_COLLECTION:
- return "profile collection succeeded";
- case ERR_FORK_FAILED:
- return "fork() system call failed";
- case ERR_PERF_RECORD_FAILED:
- return "perf record returned bad exit status";
- case ERR_PERF_ENCODE_FAILED:
- return "failure encoding perf.data to protobuf";
- case ERR_OPEN_ENCODED_FILE_FAILED:
- return "failed to open encoded perf file";
- case ERR_WRITE_ENCODED_FILE_FAILED:
- return "write to encoded perf file failed";
- default: return "unknown";
- }
- return "notreached";
}
//
// Check for existence of simpleperf/perf executable
std::string pp = config.perf_path;
if (access(pp.c_str(), R_OK|X_OK) == -1) {
- W_ALOGW("unable to access/execute %s", pp.c_str());
+ LOG(WARNING) << "unable to access/execute " << pp;
return DONT_PROFILE_MISSING_PERF_EXECUTABLE;
}
bool get_booting()
{
+#ifdef __BIONIC__
return android::base::GetBoolProperty("sys.boot_completed", false) != true;
+#else
+ return false;
+#endif
}
//
void AlarmHelper::handler(int, siginfo_t *, void *)
{
- W_ALOGW("SIGALRM timeout");
+ LOG(WARNING) << "SIGALRM timeout";
kill(child_, SIGKILL);
}
{
int pipefds[2];
if (pipe2(pipefds, O_CLOEXEC) != 0) {
- W_ALOGE("pipe2() failed (%s)", strerror(errno));
+ PLOG(ERROR) << "pipe2() failed";
return false;
}
pid_t pid = fork();
if (pid == -1) {
- W_ALOGE("fork() failed (%s)", strerror(errno));
+ PLOG(ERROR) << "fork() failed";
close(pipefds[0]);
close(pipefds[1]);
return false;
argv[slot++] = "media.camera";
argv[slot++] = nullptr;
execvp(argv[0], (char * const *)argv);
- W_ALOGE("execvp() failed (%s)", strerror(errno));
+ PLOG(ERROR) << "execvp() failed";
return false;
}
// parent
std::string psdir("/sys/class/power_supply");
DIR* dir = opendir(psdir.c_str());
if (dir == NULL) {
- W_ALOGE("Failed to open dir %s (%s)", psdir.c_str(), strerror(errno));
+ PLOG(ERROR) << "Failed to open dir " << psdir;
return false;
}
struct dirent* e;
return result;
}
-bool postprocess_proc_stat_contents(const std::string &pscontents,
- long unsigned *idleticks,
- long unsigned *remainingticks)
+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;
return busy_delta * 100 / total_delta;
}
-static void annotate_encoded_perf_profile(wireless_android_play_playlog::AndroidPerfProfile *profile,
+static void annotate_encoded_perf_profile(android::perfprofd::PerfprofdRecord* profile,
const Config& config,
unsigned cpu_utilization)
{
int iload = static_cast<int>(fload * 100.0);
profile->set_sys_load_average(iload);
} else {
- W_ALOGE("Failed to read or scan /proc/loadavg (%s)", strerror(errno));
+ PLOG(ERROR) << "Failed to read or scan /proc/loadavg";
}
//
bool ison = (strstr(disp.c_str(), "PowerManagerService.Display") == 0);
profile->set_display_on(ison);
} else {
- W_ALOGE("Failed to read /sys/power/wake_unlock (%s)", strerror(errno));
+ PLOG(ERROR) << "Failed to read /sys/power/wake_unlock";
}
}
-inline char* string_as_array(std::string* str) {
- return str->empty() ? NULL : &*str->begin();
+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));
+ 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,
unsigned cpu_utilization,
perfprofd::Symbolizer* symbolizer)
{
- //
- // Open and read perf.data file
- //
- const wireless_android_play_playlog::AndroidPerfProfile &encodedProfile =
- wireless_android_logging_awp::RawPerfDataToAndroidPerfProfile(data_file_path, symbolizer);
+ ProtoUniquePtr encodedProfile = encode_to_proto(data_file_path,
+ config,
+ cpu_utilization,
+ symbolizer);
//
// Issue error if no samples
//
- if (encodedProfile.programs().size() == 0) {
+ if (encodedProfile == nullptr || encodedProfile->perf_data().events_size() == 0) {
return ERR_PERF_ENCODE_FAILED;
}
- // All of the info in 'encodedProfile' is derived from the perf.data file;
- // here we tack display status, cpu utilization, system load, etc.
- wireless_android_play_playlog::AndroidPerfProfile &prof =
- const_cast<wireless_android_play_playlog::AndroidPerfProfile&>
- (encodedProfile);
- annotate_encoded_perf_profile(&prof, config, cpu_utilization);
-
- //
- // Serialize protobuf to array
- //
- int size = encodedProfile.ByteSize();
- std::string data;
- data.resize(size);
- ::google::protobuf::uint8* dtarget =
- reinterpret_cast<::google::protobuf::uint8*>(string_as_array(&data));
- encodedProfile.SerializeWithCachedSizesToArray(dtarget);
-
- //
- // Open file and write encoded data to it
- //
- FILE *fp = fopen(encoded_file_path, "w");
- if (!fp) {
- return ERR_OPEN_ENCODED_FILE_FAILED;
- }
- size_t fsiz = size;
- if (fwrite(dtarget, fsiz, 1, fp) != 1) {
- fclose(fp);
- return ERR_WRITE_ENCODED_FILE_FAILED;
- }
- fclose(fp);
- chmod(encoded_file_path, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
-
- return OK_PROFILE_COLLECTION;
+ return android::perfprofd::SerializeProtobuf(encodedProfile.get(),
+ encoded_file_path,
+ config.compress)
+ ? OK_PROFILE_COLLECTION
+ : ERR_WRITE_ENCODED_FILE_FAILED;
}
//
dup2(fileno(efp), STDERR_FILENO);
dup2(fileno(efp), STDOUT_FILENO);
} else {
- W_ALOGW("unable to open %s for writing", perf_stderr_path.c_str());
+ PLOG(WARNING) << "unable to open " << perf_stderr_path << " for writing";
}
// marshall arguments
- constexpr unsigned max_args = 14;
+ constexpr unsigned max_args = 15;
const char *argv[max_args];
unsigned slot = 0;
argv[slot++] = perf_path.c_str();
argv[slot++] = pid_str.c_str();
}
- // no need for kernel symbols
+ // no need for kernel or other symbols
argv[slot++] = "--no-dump-kernel-symbols";
+ argv[slot++] = "--no-dump-symbols";
// sleep <duration>
- argv[slot++] = "/system/bin/sleep";
+ argv[slot++] = "--duration";
std::string d_str = android::base::StringPrintf("%u", duration);
argv[slot++] = d_str.c_str();
pid_t reaped = TEMP_FAILURE_RETRY(waitpid(pid, &st, 0));
if (reaped == -1) {
- W_ALOGW("waitpid failed: %s", strerror(errno));
+ PLOG(WARNING) << "waitpid failed";
} else if (WIFSIGNALED(st)) {
if (WTERMSIG(st) == SIGHUP && config.ShouldStopProfiling()) {
// That was us...
return OK_PROFILE_COLLECTION;
}
- W_ALOGW("perf killed by signal %d", WTERMSIG(st));
+ LOG(WARNING) << "perf killed by signal " << WTERMSIG(st);
} else if (WEXITSTATUS(st) != 0) {
- W_ALOGW("perf bad exit status %d", WEXITSTATUS(st));
+ LOG(WARNING) << "perf bad exit status " << WEXITSTATUS(st);
} else {
return OK_PROFILE_COLLECTION;
}
}
closedir(dir);
} else {
- W_ALOGW("unable to open destination dir %s for cleanup",
- dest_dir.c_str());
+ PLOG(WARNING) << "unable to open destination dir " << dest_dir << " for cleanup";
}
}
//
-// Post-processes after profile is collected and converted to protobuf.
-// * GMS core stores processed file sequence numbers in
-// /data/data/com.google.android.gms/files/perfprofd_processed.txt
-// * Update /data/misc/perfprofd/perfprofd_produced.txt to remove the sequence
-// numbers that have been processed and append the current seq number
-// Returns true if the current_seq should increment.
-//
-static bool post_process(const Config& config, int current_seq)
-{
- const std::string& dest_dir = config.destination_directory;
- std::string processed_file_path =
- config.config_directory + "/" + PROCESSED_FILENAME;
- std::string produced_file_path = dest_dir + "/" + PRODUCED_FILENAME;
-
-
- std::set<int> processed;
- FILE *fp = fopen(processed_file_path.c_str(), "r");
- if (fp != NULL) {
- int seq;
- while(fscanf(fp, "%d\n", &seq) > 0) {
- if (remove(android::base::StringPrintf(
- "%s/perf.data.encoded.%d", dest_dir.c_str(),seq).c_str()) == 0) {
- processed.insert(seq);
- }
- }
- fclose(fp);
- }
-
- std::set<int> produced;
- fp = fopen(produced_file_path.c_str(), "r");
- if (fp != NULL) {
- int seq;
- while(fscanf(fp, "%d\n", &seq) > 0) {
- if (processed.find(seq) == processed.end()) {
- produced.insert(seq);
- }
- }
- fclose(fp);
- }
-
- uint32_t maxLive = config.max_unprocessed_profiles;
- if (produced.size() >= maxLive) {
- return false;
- }
-
- produced.insert(current_seq);
- fp = fopen(produced_file_path.c_str(), "w");
- if (fp == NULL) {
- W_ALOGW("Cannot write %s", produced_file_path.c_str());
- return false;
- }
- for (std::set<int>::const_iterator iter = produced.begin();
- iter != produced.end(); ++iter) {
- fprintf(fp, "%d\n", *iter);
- }
- fclose(fp);
- chmod(produced_file_path.c_str(),
- S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
- return true;
-}
-
-//
// Collect a perf profile. Steps for this operation are:
// - kick off 'perf record'
// - read perf.data, convert to protocol buf
//
-static PROFILE_RESULT collect_profile(Config& config, int seq)
+static ProtoUniquePtr collect_profile(Config& config)
{
//
// Collect cpu utilization if enabled
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
- W_ALOGW("unable to unlink previous perf.data file");
+ PLOG(WARNING) << "unable to unlink previous perf.data file";
}
}
data_file_path,
perf_stderr_path);
if (ret != OK_PROFILE_COLLECTION) {
- return ret;
+ return nullptr;
}
//
// Read the resulting perf.data file, encode into protocol buffer, then write
// the result to the file perf.data.encoded
//
- std::string path = android::base::StringPrintf(
- "%s.encoded.%d", data_file_path.c_str(), seq);
- return encode_to_proto(data_file_path, path.c_str(), config, cpu_utilization, nullptr);
+ 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());
}
//
//
// Randomized seed
//
+#ifdef __BIONIC__
seed = arc4random();
+#else
+ seed = 12345678u;
+#endif
}
- W_ALOGI("random seed set to %u", seed);
+ 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[2] = (random_seed[0] ^ random_seed[1]);
}
-static void CommonInit(uint32_t use_fixed_seed, const char* dest_dir) {
+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())) {
- W_ALOGE("unable to write to %s", oomscore_path.str().c_str());
+ LOG(ERROR) << "unable to write to " << oomscore_path.str();
}
set_seed(use_fixed_seed);
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
-//
-// Initialization
-//
-static void init(const Config& config)
-{
- // TODO: Consider whether we want to clean things or just overwrite.
- CommonInit(config.use_fixed_seed, nullptr);
+ common_initialized = true;
}
-static void init(ConfigReader &config)
-{
- if (!config.readFile()) {
- W_ALOGE("unable to open configuration file %s",
- config.getConfigFilePath());
- }
-
- CommonInit(static_cast<uint32_t>(config.getUnsignedValue("use_fixed_seed")),
- config.getStringValue("destination_directory").c_str());
-
- signal(SIGHUP, sig_hup);
+bool IsDebugBuild() {
+ CHECK(common_initialized);
+ return is_debug_build;
}
template <typename ConfigFn, typename UpdateFn>
-static void ProfilingLoopImpl(ConfigFn config, UpdateFn update) {
+static void ProfilingLoopImpl(ConfigFn config, UpdateFn update, HandlerFn handler) {
unsigned iterations = 0;
- int seq = 0;
while(config()->main_loop_iterations == 0 ||
iterations < config()->main_loop_iterations) {
if (config()->ShouldStopProfiling()) {
// run perf
unsigned sleep_before_collect = 0;
unsigned sleep_after_collect = 0;
- determine_before_after(sleep_before_collect, sleep_after_collect,
+ determine_before_after(sleep_before_collect,
+ sleep_after_collect,
config()->collection_interval_in_s);
- config()->Sleep(sleep_before_collect);
+ if (sleep_before_collect > 0) {
+ config()->Sleep(sleep_before_collect);
+ }
if (config()->ShouldStopProfiling()) {
return;
// Check for profiling enabled...
CKPROFILE_RESULT ckresult = check_profiling_enabled(*config());
if (ckresult != DO_COLLECT_PROFILE) {
- W_ALOGI("profile collection skipped (%s)",
- ckprofile_result_to_string(ckresult));
+ LOG(INFO) << "profile collection skipped (" << ckprofile_result_to_string(ckresult) << ")";
} else {
// Kick off the profiling run...
- W_ALOGI("initiating profile collection");
- PROFILE_RESULT result = collect_profile(*config(), seq);
- if (result != OK_PROFILE_COLLECTION) {
- W_ALOGI("profile collection failed (%s)",
- profile_result_to_string(result));
- } else {
- if (post_process(*config(), seq)) {
- seq++;
- }
- W_ALOGI("profile collection complete");
+ 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";
}
}
return;
}
- config()->Sleep(sleep_after_collect);
+ if (sleep_after_collect > 0) {
+ config()->Sleep(sleep_after_collect);
+ }
iterations += 1;
}
}
-void ProfilingLoop(Config& config) {
- init(config);
+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);
+ ProfilingLoopImpl(config_fn, do_nothing, handler);
}
-//
-// Main routine:
-// 1. parse cmd line args
-// 2. read config file
-// 3. loop: {
-// sleep for a while
-// perform a profile collection
-// }
-//
-int perfprofd_main(int argc, char** argv, Config* config)
-{
- ConfigReader config_reader;
-
- W_ALOGI("starting Android Wide Profiling daemon");
-
- parse_args(argc, argv);
- init(config_reader);
- config_reader.FillConfig(config);
-
- if (!perf_file_to_convert.empty()) {
- std::string encoded_path = perf_file_to_convert + ".encoded";
- encode_to_proto(perf_file_to_convert, encoded_path.c_str(), *config, 0, nullptr);
- return 0;
- }
-
- // Early exit if we're not supposed to run on this build flavor
- if (is_debug_build != 1 && config->only_debug_build) {
- W_ALOGI("early exit due to inappropriate build type");
- return 0;
- }
-
- auto config_fn = [config]() {
- return config;
- };
- auto reread_config = [&config_reader, config]() {
- // Reread config file -- the uploader may have rewritten it as a result
- // of a gservices change
- config_reader.readFile();
- config_reader.FillConfig(config);
- };
- ProfilingLoopImpl(config_fn, reread_config);
-
- W_ALOGI("finishing Android Wide Profiling daemon");
- return 0;
+void ProfilingLoop(std::function<Config*()> config_fn,
+ std::function<void()> update_fn,
+ HandlerFn handler) {
+ ProfilingLoopImpl(config_fn, update_fn, handler);
}