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