Merge changes I7dc2edd4,I419b2ec0
* changes:
iotop: add total read/write stats
iotop: fix bytes per second, and add accumulation
diff --git a/boot_control_copy/Android.mk b/boot_control_copy/Android.mk
new file mode 100644
index 0000000..0027c10
--- /dev/null
+++ b/boot_control_copy/Android.mk
@@ -0,0 +1,16 @@
+# Copyright 2015 The Android Open Source Project
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := boot_control_copy.c bootinfo.h bootinfo.c
+LOCAL_CFLAGS := -Wall -Wno-missing-field-initializers
+LOCAL_C_INCLUDES := system/core/mkbootimg bootable/recovery
+LOCAL_SHARED_LIBRARIES := libcutils
+LOCAL_STATIC_LIBRARIES := libfs_mgr
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
+LOCAL_MODULE:= bootctrl.default
+LOCAL_MODULE_TAGS := optional
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/boot_control_copy/boot_control_copy.c b/boot_control_copy/boot_control_copy.c
new file mode 100644
index 0000000..a5deb5a
--- /dev/null
+++ b/boot_control_copy/boot_control_copy.c
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 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 <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <fs_mgr.h>
+#include <hardware/hardware.h>
+#include <hardware/boot_control.h>
+
+#include "bootinfo.h"
+
+void module_init(boot_control_module_t *module)
+{
+}
+
+unsigned module_getNumberSlots(boot_control_module_t *module)
+{
+ return 2;
+}
+
+unsigned module_getCurrentSlot(boot_control_module_t *module)
+{
+ BrilloBootInfo info;
+
+ if (!boot_info_load(&info)) {
+ fprintf(stderr, "WARNING: Error loading boot-info. Resetting.\n");
+ boot_info_reset(&info);
+ } else {
+ if (!boot_info_validate(&info)) {
+ fprintf(stderr, "WARNING: boot-info is invalid. Resetting.\n");
+ boot_info_reset(&info);
+ }
+ }
+
+ return info.active_slot;
+}
+
+int module_markBootSuccessful(boot_control_module_t *module)
+{
+ return 0;
+}
+
+#define COPY_BUF_SIZE 1024*1024
+
+static bool copy_data(int src_fd, int dst_fd, size_t num_bytes)
+{
+ char copy_buf[COPY_BUF_SIZE];
+ size_t remaining;
+
+ remaining = num_bytes;
+ while (remaining > 0) {
+ size_t num_to_read = remaining > COPY_BUF_SIZE ? COPY_BUF_SIZE : remaining;
+ ssize_t num_read;
+ do {
+ num_read = read(src_fd, copy_buf, num_to_read);
+ } while (num_read == -1 && errno == EINTR);
+ if (num_read <= 0) {
+ fprintf(stderr, "Error reading %zd bytes from source: %s\n",
+ num_to_read, strerror(errno));
+ return false;
+ }
+ size_t num_to_write = num_read;
+ while (num_to_write > 0) {
+ size_t offset = num_read - num_to_write;
+ ssize_t num_written;
+ do {
+ num_written = write(dst_fd, copy_buf + offset, num_to_write);
+ } while (num_written == -1 && errno == EINTR);
+ if (num_written <= 0) {
+ fprintf(stderr, "Error writing %zd bytes to destination: %s\n",
+ num_to_write, strerror(errno));
+ return false;
+ }
+ num_to_write -= num_written;
+ }
+ remaining -= num_read;
+ }
+
+ return true;
+}
+
+int module_setActiveBootSlot(boot_control_module_t *module, unsigned slot)
+{
+ BrilloBootInfo info;
+ int src_fd, dst_fd;
+ uint64_t src_size, dst_size;
+ char src_name[32];
+
+ if (slot >= 2)
+ return -EINVAL;
+
+ if (!boot_info_load(&info)) {
+ fprintf(stderr, "WARNING: Error loading boot-info. Resetting.\n");
+ boot_info_reset(&info);
+ } else {
+ if (!boot_info_validate(&info)) {
+ fprintf(stderr, "WARNING: boot-info is invalid. Resetting.\n");
+ boot_info_reset(&info);
+ }
+ }
+
+ info.active_slot = slot;
+ info.slot_info[slot].bootable = true;
+ snprintf(info.bootctrl_suffix,
+ sizeof(info.bootctrl_suffix),
+ "_%c", slot + 'a');
+
+ if (!boot_info_save(&info)) {
+ fprintf(stderr, "Error saving boot-info.\n");
+ return -errno;
+ }
+
+ // Finally copy the contents of boot_X into boot.
+ snprintf(src_name, sizeof(src_name), "boot_%c", slot + 'a');
+ src_fd = boot_info_open_partition(src_name, &src_size, O_RDONLY);
+ if (src_fd == -1) {
+ fprintf(stderr, "Error opening \"%s\" partition.\n", src_name);
+ return -errno;
+ }
+
+ dst_fd = boot_info_open_partition("boot", &dst_size, O_RDWR);
+ if (dst_fd == -1) {
+ fprintf(stderr, "Error opening \"boot\" partition.\n");
+ close(src_fd);
+ return -errno;
+ }
+
+ if (src_size != dst_size) {
+ fprintf(stderr,
+ "src (%" PRIu64 " bytes) and dst (%" PRIu64 " bytes) "
+ "have different sizes.\n",
+ src_size, dst_size);
+ close(src_fd);
+ close(dst_fd);
+ return -EINVAL;
+ }
+
+ if (!copy_data(src_fd, dst_fd, src_size)) {
+ close(src_fd);
+ close(dst_fd);
+ return -errno;
+ }
+
+ if (fsync(dst_fd) != 0) {
+ fprintf(stderr, "Error calling fsync on destination: %s\n",
+ strerror(errno));
+ return -errno;
+ }
+
+ close(src_fd);
+ close(dst_fd);
+ return 0;
+}
+
+int module_setSlotAsUnbootable(struct boot_control_module *module, unsigned slot)
+{
+ BrilloBootInfo info;
+
+ if (slot >= 2)
+ return -EINVAL;
+
+ if (!boot_info_load(&info)) {
+ fprintf(stderr, "WARNING: Error loading boot-info. Resetting.\n");
+ boot_info_reset(&info);
+ } else {
+ if (!boot_info_validate(&info)) {
+ fprintf(stderr, "WARNING: boot-info is invalid. Resetting.\n");
+ boot_info_reset(&info);
+ }
+ }
+
+ info.slot_info[slot].bootable = false;
+
+ if (!boot_info_save(&info)) {
+ fprintf(stderr, "Error saving boot-info.\n");
+ return -errno;
+ }
+
+ return 0;
+}
+
+int module_isSlotBootable(struct boot_control_module *module, unsigned slot)
+{
+ BrilloBootInfo info;
+
+ if (slot >= 2)
+ return -EINVAL;
+
+ if (!boot_info_load(&info)) {
+ fprintf(stderr, "WARNING: Error loading boot-info. Resetting.\n");
+ boot_info_reset(&info);
+ } else {
+ if (!boot_info_validate(&info)) {
+ fprintf(stderr, "WARNING: boot-info is invalid. Resetting.\n");
+ boot_info_reset(&info);
+ }
+ }
+
+ return info.slot_info[slot].bootable;
+}
+
+const char* module_getSuffix(boot_control_module_t *module, unsigned slot)
+{
+ static const char* suffix[2] = {"_a", "_b"};
+ if (slot >= 2)
+ return NULL;
+ return suffix[slot];
+}
+
+static struct hw_module_methods_t module_methods = {
+ .open = NULL,
+};
+
+
+/* This boot_control HAL implementation emulates A/B by copying the
+ * contents of the boot partition of the requested slot to the boot
+ * partition. It hence works with bootloaders that are not yet aware
+ * of A/B. This code is only intended to be used for development.
+ */
+
+boot_control_module_t HAL_MODULE_INFO_SYM = {
+ .common = {
+ .tag = HARDWARE_MODULE_TAG,
+ .module_api_version = BOOT_CONTROL_MODULE_API_VERSION_0_1,
+ .hal_api_version = HARDWARE_HAL_API_VERSION,
+ .id = BOOT_CONTROL_HARDWARE_MODULE_ID,
+ .name = "Copy Implementation of boot_control HAL",
+ .author = "The Android Open Source Project",
+ .methods = &module_methods,
+ },
+ .init = module_init,
+ .getNumberSlots = module_getNumberSlots,
+ .getCurrentSlot = module_getCurrentSlot,
+ .markBootSuccessful = module_markBootSuccessful,
+ .setActiveBootSlot = module_setActiveBootSlot,
+ .setSlotAsUnbootable = module_setSlotAsUnbootable,
+ .isSlotBootable = module_isSlotBootable,
+ .getSuffix = module_getSuffix,
+};
diff --git a/boot_control_copy/bootinfo.c b/boot_control_copy/bootinfo.c
new file mode 100644
index 0000000..e3c2412
--- /dev/null
+++ b/boot_control_copy/bootinfo.c
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 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 <errno.h>
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <cutils/properties.h>
+
+#include <bootloader.h>
+#include <fs_mgr.h>
+
+#include "bootinfo.h"
+
+// Open the appropriate fstab file and fallback to /fstab.device if
+// that's what's being used.
+static struct fstab *open_fstab(void)
+{
+ char propbuf[PROPERTY_VALUE_MAX];
+ char fstab_name[PROPERTY_VALUE_MAX + 32];
+ struct fstab *fstab;
+
+ property_get("ro.hardware", propbuf, "");
+ snprintf(fstab_name, sizeof(fstab_name), "/fstab.%s", propbuf);
+ fstab = fs_mgr_read_fstab(fstab_name);
+ if (fstab != NULL)
+ return fstab;
+
+ fstab = fs_mgr_read_fstab("/fstab.device");
+ return fstab;
+}
+
+int boot_info_open_partition(const char *name, uint64_t *out_size, int flags)
+{
+ char *path;
+ int fd;
+ struct fstab *fstab;
+ struct fstab_rec *record;
+
+ // We can't use fs_mgr to look up |name| because fstab doesn't list
+ // every slot partition (it uses the slotselect option to mask the
+ // suffix) and |slot| is expected to be of that form, e.g. boot_a.
+ //
+ // We can however assume that there's an entry for the /misc mount
+ // point and use that to get the device file for the misc
+ // partition. From there we'll assume that a by-name scheme is used
+ // so we can just replace the trailing "misc" by the given |name|,
+ // e.g.
+ //
+ // /dev/block/platform/soc.0/7824900.sdhci/by-name/misc ->
+ // /dev/block/platform/soc.0/7824900.sdhci/by-name/boot_a
+ //
+ // If needed, it's possible to relax this assumption in the future
+ // by trawling /sys/block looking for the appropriate sibling of
+ // misc and then finding an entry in /dev matching the sysfs entry.
+
+ fstab = open_fstab();
+ if (fstab == NULL)
+ return -1;
+ record = fs_mgr_get_entry_for_mount_point(fstab, "/misc");
+ if (record == NULL) {
+ fs_mgr_free_fstab(fstab);
+ return -1;
+ }
+ if (strcmp(name, "misc") == 0) {
+ path = strdup(record->blk_device);
+ } else {
+ size_t trimmed_len, name_len;
+ const char *end_slash = strrchr(record->blk_device, '/');
+ if (end_slash == NULL) {
+ fs_mgr_free_fstab(fstab);
+ return -1;
+ }
+ trimmed_len = end_slash - record->blk_device + 1;
+ name_len = strlen(name);
+ path = calloc(trimmed_len + name_len + 1, 1);
+ strncpy(path, record->blk_device, trimmed_len);
+ strncpy(path + trimmed_len, name, name_len);
+ }
+ fs_mgr_free_fstab(fstab);
+
+ fd = open(path, flags);
+ free(path);
+
+ // If we successfully opened the device, get size if requested.
+ if (fd != -1 && out_size != NULL) {
+ if (ioctl(fd, BLKGETSIZE64, out_size) != 0) {
+ close(fd);
+ return -1;
+ }
+ }
+
+ return fd;
+}
+
+// As per struct bootloader_message which is defined in
+// bootable/recovery/bootloader.h we can use the 32 bytes in the
+// bootctrl_suffix field provided that they start with the active slot
+// suffix terminated by NUL. It just so happens that BrilloBootInfo is
+// laid out this way.
+#define BOOTINFO_OFFSET offsetof(struct bootloader_message, slot_suffix)
+
+bool boot_info_load(BrilloBootInfo *out_info)
+{
+ int fd;
+
+ memset(out_info, '\0', sizeof(BrilloBootInfo));
+
+ fd = boot_info_open_partition("misc", NULL, O_RDONLY);
+ if (fd == -1)
+ return false;
+ if (lseek(fd, BOOTINFO_OFFSET, SEEK_SET) != BOOTINFO_OFFSET) {
+ close(fd);
+ return false;
+ }
+ ssize_t num_read;
+ do {
+ num_read = read(fd, (void*) out_info, sizeof(BrilloBootInfo));
+ } while (num_read == -1 && errno == EINTR);
+ close(fd);
+ if (num_read != sizeof(BrilloBootInfo))
+ return false;
+ return true;
+}
+
+bool boot_info_save(BrilloBootInfo *info)
+{
+ int fd;
+
+ fd = boot_info_open_partition("misc", NULL, O_RDWR);
+ if (fd == -1)
+ return false;
+ if (lseek(fd, BOOTINFO_OFFSET, SEEK_SET) != BOOTINFO_OFFSET) {
+ close(fd);
+ return false;
+ }
+ ssize_t num_written;
+ do {
+ num_written = write(fd, (void*) info, sizeof(BrilloBootInfo));
+ } while (num_written == -1 && errno == EINTR);
+ close(fd);
+ if (num_written != sizeof(BrilloBootInfo))
+ return false;
+ return true;
+}
+
+bool boot_info_validate(BrilloBootInfo* info)
+{
+ if (info->magic[0] != 'B' ||
+ info->magic[1] != 'C' ||
+ info->magic[2] != 'c')
+ return false;
+ if (info->active_slot >= 2)
+ return false;
+ return true;
+}
+
+void boot_info_reset(BrilloBootInfo* info)
+{
+ size_t n;
+ memset(info, '\0', sizeof(BrilloBootInfo));
+ info->magic[0] = 'B';
+ info->magic[1] = 'C';
+ info->magic[2] = 'c';
+}
diff --git a/boot_control_copy/bootinfo.h b/boot_control_copy/bootinfo.h
new file mode 100644
index 0000000..4b36b2c
--- /dev/null
+++ b/boot_control_copy/bootinfo.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#ifndef BOOTINFO_H_
+#define BOOTINFO_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+
+typedef struct BrilloSlotInfo {
+ uint8_t bootable : 1;
+ uint8_t reserved[3];
+} BrilloSlotInfo;
+
+typedef struct BrilloBootInfo {
+ // Used by fs_mgr. Must be NUL terminated.
+ char bootctrl_suffix[4];
+
+ // Magic for identification - must be 'B', 'C', 'c' (short for
+ // "boot_control copy" implementation).
+ uint8_t magic[3];
+
+ // Version of BrilloBootInfo struct, must be 0 or larger.
+ uint8_t version;
+
+ // Currently active slot.
+ uint8_t active_slot;
+
+ // Information about each slot.
+ BrilloSlotInfo slot_info[2];
+
+ uint8_t reserved[15];
+} BrilloBootInfo;
+
+// Loading and saving BrillBootInfo instances.
+bool boot_info_load(BrilloBootInfo *out_info);
+bool boot_info_save(BrilloBootInfo *info);
+
+// Returns non-zero if valid.
+bool boot_info_validate(BrilloBootInfo* info);
+void boot_info_reset(BrilloBootInfo* info);
+
+// Opens partition by |name|, e.g. "misc" or "boot_a" with |flags|
+// (e.g. O_RDONLY or O_RDWR) passed directly to open(2). Returns fd on
+// success and -1 on error.
+int boot_info_open_partition(const char *name, uint64_t *out_size, int flags);
+
+#if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 6
+_Static_assert(sizeof(BrilloBootInfo) == 32, "BrilloBootInfo has wrong size");
+#endif
+
+#endif // BOOTINFO_H
diff --git a/perfprofd/Android.mk b/perfprofd/Android.mk
index 3a6872e..99d6c66 100644
--- a/perfprofd/Android.mk
+++ b/perfprofd/Android.mk
@@ -29,6 +29,7 @@
quipper/perf_reader.cc \
quipper/perf_parser.cc \
perf_data_converter.cc \
+ configreader.cc \
cpuconfig.cc \
perfprofdcore.cc \
diff --git a/perfprofd/configreader.cc b/perfprofd/configreader.cc
new file mode 100644
index 0000000..9c8d5ac
--- /dev/null
+++ b/perfprofd/configreader.cc
@@ -0,0 +1,287 @@
+/*
+**
+** 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 <stdio.h>
+#include <stdlib.h>
+#include <sstream>
+
+#include <base/file.h>
+
+#include "configreader.h"
+#include "perfprofdutils.h"
+
+//
+// Config file path
+//
+static const char *config_file_path =
+ "/data/data/com.google.android.gms/files/perfprofd.conf";
+
+ConfigReader::ConfigReader()
+ : trace_config_read(false)
+{
+ addDefaultEntries();
+}
+
+ConfigReader::~ConfigReader()
+{
+}
+
+const char *ConfigReader::getConfigFilePath()
+{
+ return config_file_path;
+}
+
+void ConfigReader::setConfigFilePath(const char *path)
+{
+ config_file_path = strdup(path);
+ W_ALOGI("config file path set to %s", config_file_path);
+}
+
+//
+// Populate the reader with the set of allowable entries
+//
+void ConfigReader::addDefaultEntries()
+{
+ // Average number of seconds between perf profile collections (if
+ // 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", 14400, 100, UINT32_MAX);
+
+ // Use the specified fixed seed for random number generation (unit
+ // testing)
+ addUnsignedEntry("use_fixed_seed", 0, 0, UINT32_MAX);
+
+ // For testing purposes, number of times to iterate through main
+ // loop. Value of zero indicates that we should loop forever.
+ addUnsignedEntry("main_loop_iterations", 0, 0, UINT32_MAX);
+
+ // Destination directory (where to write profiles). This location
+ // chosen since it is accessible to the uploader service.
+ 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");
+
+ // Desired sampling period (passed to perf -c option). Small
+ // sampling periods can perturb the collected profiles, so enforce
+ // min/max.
+ addUnsignedEntry("sampling_period", 500000, 5000, UINT32_MAX);
+
+ // Length of time to collect samples (number of seconds for 'perf
+ // record -a' run).
+ addUnsignedEntry("sample_duration", 3, 2, 600);
+
+ // If this parameter is non-zero it will cause perfprofd to
+ // exit immediately if the build type is not userdebug or eng.
+ // Currently defaults to 1 (true).
+ addUnsignedEntry("only_debug_build", 1, 0, 1);
+
+ // If the "mpdecision" service is running at the point we are ready
+ // to kick off a profiling run, then temporarily disable the service
+ // and hard-wire all cores on prior to the collection run, provided
+ // that the duration of the recording is less than or equal to the value of
+ // 'hardwire_cpus_max_duration'.
+ 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);
+
+ // For unit testing only: if set to 1, emit info messages on config
+ // file parsing.
+ addUnsignedEntry("trace_config_read", 0, 0, 1);
+
+ // Control collection of various additional profile tags
+ addUnsignedEntry("collect_cpu_utilization", 1, 0, 1);
+ addUnsignedEntry("collect_charging_state", 1, 0, 1);
+ addUnsignedEntry("collect_booting", 1, 0, 1);
+ addUnsignedEntry("collect_camera_active", 0, 0, 1);
+}
+
+void ConfigReader::addUnsignedEntry(const char *key,
+ unsigned default_value,
+ unsigned min_value,
+ unsigned max_value)
+{
+ std::string ks(key);
+ if (u_entries.find(ks) != u_entries.end() ||
+ s_entries.find(ks) != s_entries.end()) {
+ W_ALOGE("internal error -- duplicate entry for key %s", key);
+ exit(9);
+ }
+ values vals;
+ vals.minv = min_value;
+ vals.maxv = max_value;
+ u_info[ks] = vals;
+ u_entries[ks] = default_value;
+}
+
+void ConfigReader::addStringEntry(const char *key, const char *default_value)
+{
+ std::string ks(key);
+ if (u_entries.find(ks) != u_entries.end() ||
+ s_entries.find(ks) != s_entries.end()) {
+ W_ALOGE("internal error -- duplicate entry for key %s", key);
+ exit(9);
+ }
+ if (default_value == nullptr) {
+ W_ALOGE("internal error -- bad default value for key %s", key);
+ exit(9);
+ }
+ s_entries[ks] = std::string(default_value);
+}
+
+unsigned ConfigReader::getUnsignedValue(const char *key) const
+{
+ std::string ks(key);
+ auto it = u_entries.find(ks);
+ assert(it != u_entries.end());
+ return it->second;
+}
+
+std::string ConfigReader::getStringValue(const char *key) const
+{
+ std::string ks(key);
+ auto it = s_entries.find(ks);
+ assert(it != s_entries.end());
+ return it->second;
+}
+
+void ConfigReader::overrideUnsignedEntry(const char *key, unsigned new_value)
+{
+ std::string ks(key);
+ auto it = u_entries.find(ks);
+ assert(it != u_entries.end());
+ values vals;
+ auto iit = u_info.find(key);
+ assert(iit != u_info.end());
+ vals = iit->second;
+ assert(new_value >= vals.minv && new_value <= vals.maxv);
+ it->second = new_value;
+ W_ALOGI("option %s overridden to %u", key, new_value);
+}
+
+
+//
+// Parse a key=value pair read from the config file. This will issue
+// warnings or errors to the system logs if the line can't be
+// interpreted properly.
+//
+void ConfigReader::parseLine(const char *key,
+ const char *value,
+ unsigned linecount)
+{
+ assert(key);
+ assert(value);
+
+ auto uit = u_entries.find(key);
+ if (uit != u_entries.end()) {
+ unsigned uvalue = 0;
+ if (isdigit(value[0]) == 0 || sscanf(value, "%u", &uvalue) != 1) {
+ W_ALOGW("line %d: malformed unsigned value (ignored)", linecount);
+ } else {
+ values vals;
+ auto iit = u_info.find(key);
+ assert(iit != u_info.end());
+ vals = iit->second;
+ if (uvalue < vals.minv || uvalue > vals.maxv) {
+ W_ALOGW("line %d: specified value %u for '%s' "
+ "outside permitted range [%u %u] (ignored)",
+ linecount, uvalue, key, vals.minv, vals.maxv);
+ } else {
+ if (trace_config_read) {
+ W_ALOGI("option %s set to %u", key, uvalue);
+ }
+ uit->second = uvalue;
+ }
+ }
+ trace_config_read = (getUnsignedValue("trace_config_read") != 0);
+ return;
+ }
+
+ auto sit = s_entries.find(key);
+ if (sit != s_entries.end()) {
+ if (trace_config_read) {
+ W_ALOGI("option %s set to %s", key, value);
+ }
+ sit->second = std::string(value);
+ return;
+ }
+
+ W_ALOGW("line %d: unknown option '%s' ignored", linecount, key);
+}
+
+static bool isblank(const std::string &line)
+{
+ for (std::string::const_iterator it = line.begin(); it != line.end(); ++it)
+ {
+ if (isspace(*it) == 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool ConfigReader::readFile()
+{
+ std::string contents;
+ if (! android::base::ReadFileToString(config_file_path, &contents)) {
+ return false;
+ }
+
+ std::stringstream ss(contents);
+ std::string line;
+ for (unsigned linecount = 1;
+ std::getline(ss,line,'\n');
+ linecount += 1)
+ {
+
+ // comment line?
+ if (line[0] == '#') {
+ continue;
+ }
+
+ // blank line?
+ if (isblank(line.c_str())) {
+ continue;
+ }
+
+ // look for X=Y assignment
+ auto efound = line.find('=');
+ if (efound == std::string::npos) {
+ W_ALOGW("line %d: line malformed (no '=' found)", linecount);
+ continue;
+ }
+
+ std::string key(line.substr(0, efound));
+ std::string value(line.substr(efound+1, std::string::npos));
+
+ parseLine(key.c_str(), value.c_str(), linecount);
+ }
+
+ return true;
+}
diff --git a/perfprofd/configreader.h b/perfprofd/configreader.h
new file mode 100644
index 0000000..2e29601
--- /dev/null
+++ b/perfprofd/configreader.h
@@ -0,0 +1,67 @@
+/*
+**
+** 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.
+*/
+
+#ifndef SYSTEM_EXTRAS_PERFPROFD_CONFIGREADER_H_
+#define SYSTEM_EXTRAS_PERFPROFD_CONFIGREADER_H_
+
+#include <string>
+#include <map>
+
+//
+// This table describes the perfprofd config file syntax in terms of
+// key/value pairs. Values come in two flavors: strings, or unsigned
+// integers. In the latter case the reader sets allowable
+// minimum/maximum for the setting.
+//
+class ConfigReader {
+
+ public:
+ ConfigReader();
+ ~ConfigReader();
+
+ // Ask for the current setting of a config item
+ unsigned getUnsignedValue(const char *key) const;
+ std::string getStringValue(const char *key) const;
+
+ // read the specified config file, applying any settings it contains
+ // returns true for successful read, false if conf file cannot be opened.
+ bool readFile();
+
+ // set/get path to config file
+ static void setConfigFilePath(const char *path);
+ static const char *getConfigFilePath();
+
+ // override a config item (for unit testing purposes)
+ void overrideUnsignedEntry(const char *key, unsigned new_value);
+
+ private:
+ void addUnsignedEntry(const char *key,
+ unsigned default_value,
+ unsigned min_value,
+ unsigned max_value);
+ void addStringEntry(const char *key, const char *default_value);
+ void addDefaultEntries();
+ void parseLine(const char *key, const char *value, unsigned linecount);
+
+ typedef struct { unsigned minv, maxv; } values;
+ std::map<std::string, values> u_info;
+ std::map<std::string, unsigned> u_entries;
+ std::map<std::string, std::string> s_entries;
+ bool trace_config_read;
+};
+
+#endif
diff --git a/perfprofd/perf_profile.proto b/perfprofd/perf_profile.proto
index 3932a16..2f60e12 100644
--- a/perfprofd/perf_profile.proto
+++ b/perfprofd/perf_profile.proto
@@ -99,4 +99,19 @@
// to first value from /proc/loadavg multiplied by 100 then
// converted to int32
optional int32 sys_load_average = 6;
+
+ // At the point when the profile was collected, was a camera active?
+ optional bool camera_active = 7;
+
+ // At the point when the profile was collected, was the device still booting?
+ optional bool booting = 8;
+
+ // At the point when the profile was collected, was the device plugged into
+ // a charger?
+ optional bool on_charger = 9;
+
+ // CPU utilization measured prior to profile collection (expressed as
+ // 100 minus the idle percentage).
+ optional int32 cpu_utilization = 10;
+
}
diff --git a/perfprofd/perfprofdcore.cc b/perfprofd/perfprofdcore.cc
index 8f5b013..3fb532c 100644
--- a/perfprofd/perfprofdcore.cc
+++ b/perfprofd/perfprofdcore.cc
@@ -42,6 +42,7 @@
#include "perfprofdutils.h"
#include "perf_data_converter.h"
#include "cpuconfig.h"
+#include "configreader.h"
//
// Perf profiling daemon -- collects system-wide profiles using
@@ -101,274 +102,12 @@
static unsigned short random_seed[3];
//
-// Config file path. May be overridden with -c command line option
+// 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 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.
-// Values come in two flavors: strings, or unsigned integers. In the latter
-// case the reader sets allowable minimum/maximum for the setting.
-//
-class ConfigReader {
-
- public:
- ConfigReader();
- ~ConfigReader();
-
- // Ask for the current setting of a config item
- unsigned getUnsignedValue(const char *key) const;
- std::string getStringValue(const char *key) const;
-
- // read the specified config file, applying any settings it contains
- void readFile(bool initial);
-
- private:
- void addUnsignedEntry(const char *key,
- unsigned default_value,
- unsigned min_value,
- unsigned max_value);
- void addStringEntry(const char *key, const char *default_value);
- void addDefaultEntries();
- void parseLine(const char *key, const char *value, unsigned linecount);
-
- typedef struct { unsigned minv, maxv; } values;
- std::map<std::string, values> u_info;
- std::map<std::string, unsigned> u_entries;
- std::map<std::string, std::string> s_entries;
- bool trace_config_read;
-};
-
-ConfigReader::ConfigReader()
- : trace_config_read(false)
+static void sig_hup(int /* signum */)
{
- addDefaultEntries();
-}
-
-ConfigReader::~ConfigReader()
-{
-}
-
-//
-// Populate the reader with the set of allowable entries
-//
-void ConfigReader::addDefaultEntries()
-{
- // Average number of seconds between perf profile collections (if
- // 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", 14400, 100, UINT32_MAX);
-
- // Use the specified fixed seed for random number generation (unit
- // testing)
- addUnsignedEntry("use_fixed_seed", 0, 0, UINT32_MAX);
-
- // For testing purposes, number of times to iterate through main
- // loop. Value of zero indicates that we should loop forever.
- addUnsignedEntry("main_loop_iterations", 0, 0, UINT32_MAX);
-
- // Destination directory (where to write profiles). This location
- // chosen since it is accessible to the uploader service.
- 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");
-
- // Desired sampling period (passed to perf -c option). Small
- // sampling periods can perturb the collected profiles, so enforce
- // min/max.
- addUnsignedEntry("sampling_period", 500000, 5000, UINT32_MAX);
-
- // Length of time to collect samples (number of seconds for 'perf
- // record -a' run).
- addUnsignedEntry("sample_duration", 3, 2, 600);
-
- // If this parameter is non-zero it will cause perfprofd to
- // exit immediately if the build type is not userdebug or eng.
- // Currently defaults to 1 (true).
- addUnsignedEntry("only_debug_build", 1, 0, 1);
-
- // If the "mpdecision" service is running at the point we are ready
- // to kick off a profiling run, then temporarily disable the service
- // and hard-wire all cores on prior to the collection run, provided
- // that the duration of the recording is less than or equal to the value of
- // 'hardwire_cpus_max_duration'.
- 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);
-
- // For unit testing only: if set to 1, emit info messages on config
- // file parsing.
- addUnsignedEntry("trace_config_read", 0, 0, 1);
-}
-
-void ConfigReader::addUnsignedEntry(const char *key,
- unsigned default_value,
- unsigned min_value,
- unsigned max_value)
-{
- std::string ks(key);
- if (u_entries.find(ks) != u_entries.end() ||
- s_entries.find(ks) != s_entries.end()) {
- W_ALOGE("internal error -- duplicate entry for key %s", key);
- exit(9);
- }
- values vals;
- vals.minv = min_value;
- vals.maxv = max_value;
- u_info[ks] = vals;
- u_entries[ks] = default_value;
-}
-
-void ConfigReader::addStringEntry(const char *key, const char *default_value)
-{
- std::string ks(key);
- if (u_entries.find(ks) != u_entries.end() ||
- s_entries.find(ks) != s_entries.end()) {
- W_ALOGE("internal error -- duplicate entry for key %s", key);
- exit(9);
- }
- if (default_value == nullptr) {
- W_ALOGE("internal error -- bad default value for key %s", key);
- exit(9);
- }
- s_entries[ks] = std::string(default_value);
-}
-
-unsigned ConfigReader::getUnsignedValue(const char *key) const
-{
- std::string ks(key);
- auto it = u_entries.find(ks);
- assert(it != u_entries.end());
- return it->second;
-}
-
-std::string ConfigReader::getStringValue(const char *key) const
-{
- std::string ks(key);
- auto it = s_entries.find(ks);
- assert(it != s_entries.end());
- return it->second;
-}
-
-//
-// Parse a key=value pair read from the config file. This will issue
-// warnings or errors to the system logs if the line can't be
-// interpreted properly.
-//
-void ConfigReader::parseLine(const char *key,
- const char *value,
- unsigned linecount)
-{
- assert(key);
- assert(value);
-
- auto uit = u_entries.find(key);
- if (uit != u_entries.end()) {
- unsigned uvalue = 0;
- if (isdigit(value[0]) == 0 || sscanf(value, "%u", &uvalue) != 1) {
- W_ALOGW("line %d: malformed unsigned value (ignored)", linecount);
- } else {
- values vals;
- auto iit = u_info.find(key);
- assert(iit != u_info.end());
- vals = iit->second;
- if (uvalue < vals.minv || uvalue > vals.maxv) {
- W_ALOGW("line %d: specified value %u for '%s' "
- "outside permitted range [%u %u] (ignored)",
- linecount, uvalue, key, vals.minv, vals.maxv);
- } else {
- if (trace_config_read) {
- W_ALOGI("option %s set to %u", key, uvalue);
- }
- uit->second = uvalue;
- }
- }
- trace_config_read = (getUnsignedValue("trace_config_read") != 0);
- return;
- }
-
- auto sit = s_entries.find(key);
- if (sit != s_entries.end()) {
- if (trace_config_read) {
- W_ALOGI("option %s set to %s", key, value);
- }
- sit->second = std::string(value);
- return;
- }
-
- W_ALOGW("line %d: unknown option '%s' ignored", linecount, key);
-}
-
-static bool isblank(const std::string &line)
-{
- for (std::string::const_iterator it = line.begin(); it != line.end(); ++it)
- {
- if (isspace(*it) == 0) {
- return false;
- }
- }
- return true;
-}
-
-void ConfigReader::readFile(bool initial)
-{
- FILE *fp = fopen(config_file_path, "r");
- if (!fp) {
- if (initial) {
- W_ALOGE("unable to open configuration file %s", config_file_path);
- }
- return;
- }
-
- char *linebuf = NULL;
- size_t line_length = 0;
- for (unsigned linecount = 1;
- getline(&linebuf, &line_length, fp) != -1;
- ++linecount) {
- char *eq = 0;
- char *key, *value;
-
- // comment line?
- if (linebuf[0] == '#') {
- continue;
- }
-
- // blank line?
- if (isblank(linebuf)) {
- continue;
- }
-
- // look for X=Y assignment
- eq = strchr(linebuf, '=');
- if (!eq) {
- W_ALOGW("line %d: line malformed (no '=' found)", linecount);
- continue;
- }
-
- *eq = '\0';
- key = linebuf;
- value = eq+1;
- char *ln = strrchr(value, '\n');
- if (ln) { *ln = '\0'; }
-
- parseLine(key, value, linecount);
- }
- free(linebuf);
- fclose(fp);
+ W_ALOGW("SIGHUP received");
}
//
@@ -385,8 +124,7 @@
W_ALOGE("malformed command line: -c option requires argument)");
continue;
}
- config_file_path = strdup(argv[ac+1]);
- W_ALOGI("config file path set to %s", config_file_path);
+ ConfigReader::setConfigFilePath(argv[ac+1]);
++ac;
} else {
W_ALOGE("malformed command line: unknown option or arg %s)", argv[ac]);
@@ -482,8 +220,207 @@
return DO_COLLECT_PROFILE;
}
-static void annotate_encoded_perf_profile(wireless_android_play_playlog::AndroidPerfProfile *profile)
+bool get_booting()
{
+ char propBuf[PROPERTY_VALUE_MAX];
+ propBuf[0] = '\0';
+ property_get("sys.boot_completed", propBuf, "");
+ return (propBuf[0] != '1');
+}
+
+//
+// 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 *)
+{
+ W_ALOGW("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) {
+ W_ALOGE("pipe2() failed (%s)", strerror(errno));
+ return false;
+ }
+ pid_t pid = fork();
+ if (pid == -1) {
+ W_ALOGE("fork() failed (%s)", strerror(errno));
+ 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);
+ W_ALOGE("execvp() failed (%s)", strerror(errno));
+ 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()
+{
+ 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));
+ return false;
+ }
+ struct dirent* e;
+ bool result = false;
+ while ((e = readdir(dir)) != 0) {
+ if (e->d_name[0] != '.') {
+ std::string online_path = psdir + "/" + e->d_name + "/online";
+ std::string contents;
+ int value = 0;
+ if (android::base::ReadFileToString(online_path.c_str(), &contents) &&
+ sscanf(contents.c_str(), "%d", &value) == 1) {
+ if (value) {
+ result = true;
+ break;
+ }
+ }
+ }
+ }
+ closedir(dir);
+ return result;
+}
+
+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(wireless_android_play_playlog::AndroidPerfProfile *profile,
+ const ConfigReader &config,
+ unsigned cpu_utilization)
+{
+ //
+ // Incorporate cpu utilization (collected prior to perf run)
+ //
+ if (config.getUnsignedValue("collect_cpu_utilization")) {
+ profile->set_cpu_utilization(cpu_utilization);
+ }
+
//
// Load average as reported by the kernel
//
@@ -498,6 +435,20 @@
}
//
+ // Device still booting? Camera in use? Plugged into charger?
+ //
+ bool is_booting = get_booting();
+ if (config.getUnsignedValue("collect_booting")) {
+ profile->set_booting(is_booting);
+ }
+ if (config.getUnsignedValue("collect_camera_active")) {
+ profile->set_camera_active(is_booting ? false : get_camera_active());
+ }
+ if (config.getUnsignedValue("collect_charging_state")) {
+ profile->set_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?
@@ -516,7 +467,9 @@
}
PROFILE_RESULT encode_to_proto(const std::string &data_file_path,
- const char *encoded_file_path)
+ const char *encoded_file_path,
+ const ConfigReader &config,
+ unsigned cpu_utilization)
{
//
// Open and read perf.data file
@@ -532,11 +485,11 @@
}
// All of the info in 'encodedProfile' is derived from the perf.data file;
- // here we tack display status and system load.
+ // 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);
+ annotate_encoded_perf_profile(&prof, config, cpu_utilization);
//
// Serialize protobuf to array
@@ -751,6 +704,14 @@
static PROFILE_RESULT collect_profile(const ConfigReader &config, int seq)
{
//
+ // Collect cpu utilization if enabled
+ //
+ unsigned cpu_utilization = 0;
+ if (config.getUnsignedValue("collect_cpu_utilization")) {
+ cpu_utilization = collect_cpu_utilization();
+ }
+
+ //
// Form perf.data file name, perf error output file name
//
std::string destdir = config.getStringValue("destination_directory");
@@ -813,15 +774,7 @@
//
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. 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 */)
-{
+ return encode_to_proto(data_file_path, path.c_str(), config, cpu_utilization);
}
//
@@ -870,7 +823,11 @@
//
static void init(ConfigReader &config)
{
- config.readFile(true);
+ if (!config.readFile()) {
+ W_ALOGE("unable to open configuration file %s",
+ config.getConfigFilePath());
+ }
+
set_seed(config);
cleanup_destination_dir(config);
@@ -924,7 +881,7 @@
// Reread config file -- the uploader may have rewritten it as a result
// of a gservices change
- config.readFile(false);
+ config.readFile();
// Check for profiling enabled...
CKPROFILE_RESULT ckresult = check_profiling_enabled(config);
diff --git a/perfprofd/perfprofdcore.h b/perfprofd/perfprofdcore.h
index 53695e2..2607c48 100644
--- a/perfprofd/perfprofdcore.h
+++ b/perfprofd/perfprofdcore.h
@@ -15,6 +15,11 @@
** limitations under the License.
*/
+#ifndef SYSTEM_EXTRAS_PERFPROFD_PERFPROFDCORE_H_
+#define SYSTEM_EXTRAS_PERFPROFD_PERFPROFDCORE_H_
+
+class ConfigReader;
+
// Semaphore file that indicates that the user is opting in
#define SEMAPHORE_FILENAME "perf_profile_collection_enabled.txt"
@@ -63,4 +68,16 @@
// was successful (either OK_PROFILE_COLLECTION or an error of some sort).
//
PROFILE_RESULT encode_to_proto(const std::string &data_file_path,
- const char *encoded_file_path);
+ const char *encoded_file_path,
+ const ConfigReader &config,
+ unsigned cpu_utilization);
+
+//
+// Exposed for unit testing
+//
+extern unsigned collect_cpu_utilization();
+extern bool get_booting();
+extern bool get_charging();
+extern bool get_camera_active();
+
+#endif
diff --git a/perfprofd/tests/Android.mk b/perfprofd/tests/Android.mk
index d8ea10a..7d372c4 100644
--- a/perfprofd/tests/Android.mk
+++ b/perfprofd/tests/Android.mk
@@ -34,7 +34,7 @@
LOCAL_CLANG := true
LOCAL_CPP_EXTENSION := cc
LOCAL_CXX_STL := libc++
-LOCAL_STATIC_LIBRARIES := libperfprofdcore libperfprofdmockutils libbase
+LOCAL_STATIC_LIBRARIES := libperfprofdcore libperfprofdmockutils libgtest libbase
LOCAL_SHARED_LIBRARIES := libprotobuf-cpp-lite
LOCAL_C_INCLUDES += system/extras/perfprofd external/protobuf/src
LOCAL_SRC_FILES := perfprofd_test.cc
diff --git a/perfprofd/tests/perfprofd_test.cc b/perfprofd/tests/perfprofd_test.cc
index d13e21e..666d987 100644
--- a/perfprofd/tests/perfprofd_test.cc
+++ b/perfprofd/tests/perfprofd_test.cc
@@ -27,6 +27,7 @@
#include <base/stringprintf.h>
#include "perfprofdcore.h"
+#include "configreader.h"
#include "perfprofdutils.h"
#include "perfprofdmockutils.h"
@@ -480,6 +481,20 @@
expected, "ConfigFileParsing");
}
+TEST_F(PerfProfdTest, ProfileCollectionAnnotations)
+{
+ unsigned util1 = collect_cpu_utilization();
+ EXPECT_LE(util1, 100);
+ EXPECT_GE(util1, 0);
+
+ // NB: expectation is that when we run this test, the device will be
+ // completed booted, will be on charger, and will not have the camera
+ // active.
+ EXPECT_FALSE(get_booting());
+ EXPECT_TRUE(get_charging());
+ EXPECT_FALSE(get_camera_active());
+}
+
TEST_F(PerfProfdTest, BasicRunWithCannedPerf)
{
//
@@ -491,9 +506,15 @@
std::string input_perf_data(test_dir);
input_perf_data += "/canned.perf.data";
+ // Set up config to avoid these annotations (they are tested elsewhere)
+ ConfigReader config;
+ config.overrideUnsignedEntry("collect_cpu_utilization", 0);
+ config.overrideUnsignedEntry("collect_charging_state", 0);
+ config.overrideUnsignedEntry("collect_camera_active", 0);
+
// Kick off encoder and check return code
PROFILE_RESULT result =
- encode_to_proto(input_perf_data, encoded_file_path(0).c_str());
+ encode_to_proto(input_perf_data, encoded_file_path(0).c_str(), config, 0);
EXPECT_EQ(OK_PROFILE_COLLECTION, result);
// Read and decode the resulting perf.data.encoded file