#include <string>
#include <sstream>
#include <map>
+#include <set>
#include <cctype>
+#include <base/stringprintf.h>
#include <cutils/properties.h>
#include "perfprofdcore.h"
// All systems go for profile collection.
DO_COLLECT_PROFILE,
- // The destination directory selected in the conf file doesn't exist. Most
- // likely this is due to a missing or out-of-date version of the uploading
- // service in GMS core.
- DONT_PROFILE_MISSING_DESTINATION_DIR,
+ // 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
//
// Config file path. May be overridden with -c command line option
//
-static const char *config_file_path = NULL;
-
-//
-// Set by SIGHUP signal handler
-//
-volatile unsigned please_reread_config_file = 0;
+static const char *config_file_path =
+ "/data/data/com.google.android.gms/files/perfprofd.conf";
//
// This table describes the config file syntax in terms of key/value pairs.
std::string getStringValue(const char *key) const;
// read the specified config file, applying any settings it contains
- void readFile(const char *configFilePath);
+ void readFile(bool initial);
private:
void addUnsignedEntry(const char *key,
// set to 100, then over time we want to see a perf profile
// collected every 100 seconds). The actual time within the interval
// for the collection is chosen randomly.
- addUnsignedEntry("collection_interval", 901, 100, UINT32_MAX);
+ addUnsignedEntry("collection_interval", 14400, 100, UINT32_MAX);
// Use the specified fixed seed for random number generation (unit
// testing)
// Destination directory (where to write profiles). This location
// chosen since it is accessible to the uploader service.
- addStringEntry("destination_directory",
- "/data/data/com.google.android.gms/files");
+ addStringEntry("destination_directory", "/data/misc/perfprofd");
+
+ // Config directory (where to read configs).
+ addStringEntry("config_directory", "/data/data/com.google.android.gms/files");
// Full path to 'perf' executable.
addStringEntry("perf_path", "/system/xbin/simpleperf");
addUnsignedEntry("hardwire_cpus", 1, 0, 1);
addUnsignedEntry("hardwire_cpus_max_duration", 5, 1, UINT32_MAX);
+ // Maximum number of unprocessed profiles we can accumulate in the
+ // destination directory. Once we reach this limit, we continue
+ // to collect, but we just overwrite the most recent profile.
+ addUnsignedEntry("max_unprocessed_profiles", 10, 1, UINT32_MAX);
+
// If set to 1, pass the -g option when invoking 'perf' (requests
// stack traces as opposed to flat profile).
addUnsignedEntry("stack_profile", 0, 0, 1);
return true;
}
-void ConfigReader::readFile(const char *configFilePath)
+void ConfigReader::readFile(bool initial)
{
- FILE *fp = fopen(configFilePath, "r");
+ FILE *fp = fopen(config_file_path, "r");
if (!fp) {
- W_ALOGE("unable to open configuration file %s", config_file_path);
+ if (initial) {
+ W_ALOGE("unable to open configuration file %s", config_file_path);
+ }
return;
}
switch (result) {
case DO_COLLECT_PROFILE:
return "DO_COLLECT_PROFILE";
- case DONT_PROFILE_MISSING_DESTINATION_DIR:
- return "missing destination directory";
+ 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:
}
//
-// The daemon does a read of the main config file on startup, however
-// if the destination directory also contains a config file, then we
-// read parameters from that as well. This provides a mechanism for
-// changing/controlling the behavior of the daemon via the settings
-// established in the uploader service (which may be easier to update
-// than the daemon).
-//
-static void read_aux_config(ConfigReader &config)
-{
- std::string destConfig(config.getStringValue("destination_directory"));
- destConfig += "/perfprofd.conf";
- FILE *fp = fopen(destConfig.c_str(), "r");
- if (fp) {
- fclose(fp);
- bool trace_config_read =
- (config.getUnsignedValue("trace_config_read") != 0);
- if (trace_config_read) {
- W_ALOGI("reading auxiliary config file %s", destConfig.c_str());
- }
- config.readFile(destConfig.c_str());
- }
-}
-
-//
// Check to see whether we should perform a profile collection
//
static CKPROFILE_RESULT check_profiling_enabled(ConfigReader &config)
}
//
- // Check for the existence of the destination directory
+ // Check for existence of semaphore file in config directory
//
- std::string destdir = config.getStringValue("destination_directory");
- DIR* dir = opendir(destdir.c_str());
- if (!dir) {
- W_ALOGW("unable to open destination directory %s: (%s)",
- destdir.c_str(), strerror(errno));
- return DONT_PROFILE_MISSING_DESTINATION_DIR;
+ if (access(config.getStringValue("config_directory").c_str(), F_OK) == -1) {
+ W_ALOGW("unable to open config directory %s: (%s)",
+ config.getStringValue("config_directory").c_str(), strerror(errno));
+ return DONT_PROFILE_MISSING_CONFIG_DIR;
}
- // Reread aux config file -- it may have changed
- read_aux_config(config);
+
+ // Check for existence of semaphore file
+ std::string semaphore_filepath = config.getStringValue("config_directory")
+ + "/" + SEMAPHORE_FILENAME;
+ if (access(semaphore_filepath.c_str(), F_OK) == -1) {
+ return DONT_PROFILE_MISSING_SEMAPHORE;
+ }
// Check for existence of simpleperf/perf executable
std::string pp = config.getStringValue("perf_path");
if (access(pp.c_str(), R_OK|X_OK) == -1) {
W_ALOGW("unable to access/execute %s", pp.c_str());
- closedir(dir);
return DONT_PROFILE_MISSING_PERF_EXECUTABLE;
}
- // Check for existence of semaphore file
- unsigned found = 0;
- struct dirent* e;
- while ((e = readdir(dir)) != 0) {
- if (!strcmp(e->d_name, SEMAPHORE_FILENAME)) {
- found = 1;
- break;
- }
- }
- closedir(dir);
- if (!found) {
- return DONT_PROFILE_MISSING_SEMAPHORE;
- }
-
//
// We are good to go
//
}
PROFILE_RESULT encode_to_proto(const std::string &data_file_path,
- const std::string &encoded_file_path)
+ const char *encoded_file_path)
{
//
// Open and read perf.data file
//
// Open file and write encoded data to it
//
- FILE *fp = fopen(encoded_file_path.c_str(), "w");
+ FILE *fp = fopen(encoded_file_path, "w");
if (!fp) {
return ERR_OPEN_ENCODED_FILE_FAILED;
}
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;
}
// -c N
argv[slot++] = "-c";
- char pbuf[64]; snprintf(pbuf, 64, "%u", sampling_period);
- argv[slot++] = pbuf;
+ std::string p_str = android::base::StringPrintf("%u", sampling_period);
+ argv[slot++] = p_str.c_str();
// -g if desired
if (stack_profile_opt)
// sleep <duration>
argv[slot++] = "/system/bin/sleep";
- char dbuf[64]; snprintf(dbuf, 64, "%u", duration);
- argv[slot++] = dbuf;
+ std::string d_str = android::base::StringPrintf("%u", duration);
+ argv[slot++] = d_str.c_str();
// terminator
argv[slot++] = nullptr;
}
//
+// Remove all files in the destination directory during initialization
+//
+static void cleanup_destination_dir(const ConfigReader &config)
+{
+ std::string dest_dir = config.getStringValue("destination_directory");
+ 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 {
+ W_ALOGW("unable to open destination dir %s for cleanup",
+ dest_dir.c_str());
+ }
+}
+
+//
+// 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 ConfigReader &config, int current_seq)
+{
+ std::string dest_dir = config.getStringValue("destination_directory");
+ std::string processed_file_path =
+ config.getStringValue("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);
+ }
+
+ unsigned maxLive = config.getUnsignedValue("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(ConfigReader &config)
+static PROFILE_RESULT collect_profile(const ConfigReader &config, int seq)
{
//
// Form perf.data file name, perf error output file name
// Read the resulting perf.data file, encode into protocol buffer, then write
// the result to the file perf.data.encoded
//
- std::string encoded_file_path(data_file_path);
- encoded_file_path += ".encoded";
- return encode_to_proto(data_file_path, encoded_file_path);
+ 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());
}
//
-// SIGHUP handler. Sets a flag to indicate that we should reread the
-// config file
+// 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 */)
{
- please_reread_config_file = 1;
}
//
//
static void init(ConfigReader &config)
{
- if (config_file_path != NULL) {
- config.readFile(config_file_path);
- }
+ config.readFile(true);
set_seed(config);
+ cleanup_destination_dir(config);
char propBuf[PROPERTY_VALUE_MAX];
propBuf[0] = '\0';
parse_args(argc, argv);
init(config);
- read_aux_config(config);
// Early exit if we're not supposed to run on this build flavor
if (is_debug_build != 1 &&
}
unsigned iterations = 0;
+ int seq = 0;
while(config.getUnsignedValue("main_loop_iterations") == 0 ||
iterations < config.getUnsignedValue("main_loop_iterations")) {
config.getUnsignedValue("collection_interval"));
perfprofd_sleep(sleep_before_collect);
- // Reread config file if someone sent a SIGHUP
- if (please_reread_config_file) {
- if (config_file_path) {
- config.readFile(config_file_path);
- } else {
- read_aux_config(config);
- }
- please_reread_config_file = 0;
- }
+ // Reread config file -- the uploader may have rewritten it as a result
+ // of a gservices change
+ config.readFile(false);
// Check for profiling enabled...
CKPROFILE_RESULT ckresult = check_profiling_enabled(config);
} else {
// Kick off the profiling run...
W_ALOGI("initiating profile collection");
- PROFILE_RESULT result = collect_profile(config);
+ 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");
}
}