Merge "system/core: Add initial implementation of the bootstat command."
am: d0009911de

* commit 'd0009911de67f0625df8175bd298fa3863c1879d':
  system/core: Add initial implementation of the bootstat command.
diff --git a/bootstat/Android.mk b/bootstat/Android.mk
new file mode 100644
index 0000000..348db88
--- /dev/null
+++ b/bootstat/Android.mk
@@ -0,0 +1,137 @@
+#
+# Copyright (C) 2016 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+bootstat_c_includes := external/gtest/include
+
+bootstat_lib_src_files := \
+        boot_event_record_store.cpp \
+        event_log_list_builder.cpp
+
+bootstat_src_files := \
+        bootstat.cpp
+
+bootstat_test_src_files := \
+        boot_event_record_store_test.cpp \
+        event_log_list_builder_test.cpp \
+        testrunner.cpp
+
+bootstat_shared_libs := \
+        libbase \
+        liblog
+
+bootstat_cflags := \
+        -Wall \
+        -Wextra \
+        -Werror
+
+bootstat_cppflags := \
+        -Wno-non-virtual-dtor
+
+bootstat_debug_cflags := \
+        $(bootstat_cflags) \
+        -UNDEBUG
+
+# 524291 corresponds to sysui_histogram, from
+# frameworks/base/core/java/com/android/internal/logging/EventLogTags.logtags
+bootstat_cflags += -DHISTOGRAM_LOG_TAG=524291
+
+
+# bootstat static library
+# -----------------------------------------------------------------------------
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libbootstat
+LOCAL_CFLAGS := $(bootstat_cflags)
+LOCAL_CPPFLAGS := $(bootstat_cppflags)
+LOCAL_C_INCLUDES := $(bootstat_c_includes)
+LOCAL_SHARED_LIBRARIES := $(bootstat_shared_libs)
+LOCAL_SRC_FILES := $(bootstat_lib_src_files)
+
+include $(BUILD_STATIC_LIBRARY)
+
+# bootstat static library, debug
+# -----------------------------------------------------------------------------
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libbootstat_debug
+LOCAL_CFLAGS := $(bootstat_cflags)
+LOCAL_CPPFLAGS := $(bootstat_debug_cppflags)
+LOCAL_C_INCLUDES := $(bootstat_c_includes)
+LOCAL_SHARED_LIBRARIES := $(bootstat_shared_libs)
+LOCAL_SRC_FILES := $(bootstat_lib_src_files)
+
+include $(BUILD_STATIC_LIBRARY)
+
+# bootstat host static library, debug
+# -----------------------------------------------------------------------------
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libbootstat_host_debug
+LOCAL_CFLAGS := $(bootstat_debug_cflags)
+LOCAL_CPPFLAGS := $(bootstat_cppflags)
+LOCAL_C_INCLUDES := $(bootstat_c_includes)
+LOCAL_SHARED_LIBRARIES := $(bootstat_shared_libs)
+LOCAL_SRC_FILES := $(bootstat_lib_src_files)
+
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+# bootstat binary
+# -----------------------------------------------------------------------------
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := bootstat
+LOCAL_CFLAGS := $(bootstat_cflags)
+LOCAL_CPPFLAGS := $(bootstat_cppflags)
+LOCAL_C_INCLUDES := $(bootstat_c_includes)
+LOCAL_SHARED_LIBRARIES := $(bootstat_shared_libs)
+LOCAL_STATIC_LIBRARIES := libbootstat
+LOCAL_SRC_FILES := $(bootstat_src_files)
+
+include $(BUILD_EXECUTABLE)
+
+# Native tests
+# -----------------------------------------------------------------------------
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := bootstat_tests
+LOCAL_CFLAGS := $(bootstat_tests_cflags)
+LOCAL_CPPFLAGS := $(bootstat_cppflags)
+LOCAL_SHARED_LIBRARIES := $(bootstat_shared_libs)
+LOCAL_STATIC_LIBRARIES := libbootstat_debug libgmock
+LOCAL_SRC_FILES := $(bootstat_test_src_files)
+
+include $(BUILD_NATIVE_TEST)
+
+# Host native tests
+# -----------------------------------------------------------------------------
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := bootstat_tests
+LOCAL_CFLAGS := $(bootstat_tests_cflags)
+LOCAL_CPPFLAGS := $(bootstat_cppflags)
+LOCAL_SHARED_LIBRARIES := $(bootstat_shared_libs)
+LOCAL_STATIC_LIBRARIES := libbootstat_host_debug libgmock_host
+LOCAL_SRC_FILES := $(bootstat_test_src_files)
+
+include $(BUILD_HOST_NATIVE_TEST)
diff --git a/bootstat/README.md b/bootstat/README.md
new file mode 100644
index 0000000..b494951
--- /dev/null
+++ b/bootstat/README.md
@@ -0,0 +1,47 @@
+# bootstat #
+
+The bootstat command records boot events (e.g., `firmware_loaded`,
+`boot_complete`) and the relative time at which these events occurred. The
+command also aggregates boot event metrics locally and logs the metrics for
+analysis.
+
+    Usage: bootstat [options]
+    options include:
+      -d              Dump the boot event records to the console.
+      -h              Show this help.
+      -l              Log all metrics to logstorage.
+      -r              Record the relative time of a named boot event.
+
+## Relative time ##
+
+The timestamp recorded by bootstat is the uptime of the system, i.e., the
+number of seconds since the system booted.
+
+## Recording boot events ##
+
+To record the relative time of an event during the boot phase, call `bootstat`
+with the `-r` option and the name of the boot event.
+
+    $ bootstat -r boot_complete
+
+The relative time at which the command runs is recorded along with the name of
+the boot event to be persisted.
+
+## Logging boot events ##
+
+To log the persisted boot events, call `bootstat` with the `-l` option.
+
+    $ bootstat -l
+
+bootstat logs all boot events recorded using the `-r` option to the EventLog
+using the Tron histogram. On GMS devices these logs are uploaded via Clearcut
+for aggregation and analysis.
+
+## Printing boot events ##
+
+To print the set of persisted boot events, call `bootstat` with the `-p` option.
+
+    $ bootstat -p
+    Boot events:
+    ------------
+    boot_complete   71
\ No newline at end of file
diff --git a/bootstat/boot_event_record_store.cpp b/bootstat/boot_event_record_store.cpp
new file mode 100644
index 0000000..0133f5b
--- /dev/null
+++ b/bootstat/boot_event_record_store.cpp
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2016 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 "boot_event_record_store.h"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <utime.h>
+#include <cstdlib>
+#include <base/file.h>
+#include <base/logging.h>
+
+namespace {
+
+const char BOOTSTAT_DATA_DIR[] = "/data/misc/bootstat/";
+
+// Given a boot even record file at |path|, extracts the event's relative time
+// from the record into |uptime|.
+bool ParseRecordEventTime(const std::string& path, int32_t* uptime) {
+  DCHECK_NE(static_cast<int32_t*>(nullptr), uptime);
+
+  struct stat file_stat;
+  if (stat(path.c_str(), &file_stat) == -1) {
+    PLOG(ERROR) << "Failed to read " << path;
+    return false;
+  }
+
+  *uptime = file_stat.st_mtime;
+  return true;
+}
+
+}  // namespace
+
+BootEventRecordStore::BootEventRecordStore() {
+  SetStorePath(BOOTSTAT_DATA_DIR);
+}
+
+void BootEventRecordStore::AddBootEvent(const std::string& name) {
+  std::string uptime_str;
+  if (!android::base::ReadFileToString("/proc/uptime", &uptime_str)) {
+    LOG(ERROR) << "Failed to read /proc/uptime";
+  }
+
+  std::string record_path = GetBootEventPath(name);
+  if (creat(record_path.c_str(), S_IRUSR | S_IWUSR) == -1) {
+    PLOG(ERROR) << "Failed to create " << record_path;
+  }
+
+  struct stat file_stat;
+  if (stat(record_path.c_str(), &file_stat) == -1) {
+    PLOG(ERROR) << "Failed to read " << record_path;
+  }
+
+  // Cast intentionally rounds down.
+  time_t uptime = static_cast<time_t>(strtod(uptime_str.c_str(), NULL));
+  struct utimbuf times = {file_stat.st_atime, uptime};
+  if (utime(record_path.c_str(), &times) == -1) {
+    PLOG(ERROR) << "Failed to set mtime for " << record_path;
+  }
+}
+
+std::vector<BootEventRecordStore::BootEventRecord> BootEventRecordStore::
+    GetAllBootEvents() const {
+  std::vector<BootEventRecord> events;
+
+  std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(store_path_.c_str()), closedir);
+
+  // This case could happen due to external manipulation of the filesystem,
+  // so crash out if the record store doesn't exist.
+  CHECK_NE(static_cast<DIR*>(nullptr), dir.get());
+
+  struct dirent* entry;
+  while ((entry = readdir(dir.get())) != NULL) {
+    // Only parse regular files.
+    if (entry->d_type != DT_REG) {
+      continue;
+    }
+
+    const std::string event = entry->d_name;
+    const std::string record_path = GetBootEventPath(event);
+    int32_t uptime;
+    if (!ParseRecordEventTime(record_path, &uptime)) {
+      LOG(ERROR) << "Failed to parse boot time record: " << record_path;
+      continue;
+    }
+
+    events.push_back(std::make_pair(event, uptime));
+  }
+
+  return events;
+}
+
+void BootEventRecordStore::SetStorePath(const std::string& path) {
+  DCHECK_EQ('/', path.back());
+  store_path_ = path;
+}
+
+std::string BootEventRecordStore::GetBootEventPath(
+    const std::string& event) const {
+  DCHECK_EQ('/', store_path_.back());
+  return store_path_ + event;
+}
diff --git a/bootstat/boot_event_record_store.h b/bootstat/boot_event_record_store.h
new file mode 100644
index 0000000..efe1e43
--- /dev/null
+++ b/bootstat/boot_event_record_store.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 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 BOOT_EVENT_RECORD_STORE_H_
+#define BOOT_EVENT_RECORD_STORE_H_
+
+#include <cstdint>
+#include <string>
+#include <utility>
+#include <vector>
+#include <base/macros.h>
+#include <gtest/gtest_prod.h>
+
+// BootEventRecordStore manages the persistence of boot events to the record
+// store and the retrieval of all boot event records from the store.
+class BootEventRecordStore {
+ public:
+  // A BootEventRecord consists of the event name and the timestamp the event
+  // occurred.
+  typedef std::pair<std::string, int32_t> BootEventRecord;
+
+  BootEventRecordStore();
+
+  // Persists the boot event named |name| in the record store.
+  void AddBootEvent(const std::string& name);
+
+  // Returns a list of all of the boot events persisted in the record store.
+  std::vector<BootEventRecord> GetAllBootEvents() const;
+
+ private:
+  // The tests call SetStorePath to override the default store location with a
+  // more test-friendly path.
+  FRIEND_TEST(BootEventRecordStoreTest, AddSingleBootEvent);
+  FRIEND_TEST(BootEventRecordStoreTest, AddMultipleBootEvents);
+
+  // Sets the filesystem path of the record store.
+  void SetStorePath(const std::string& path);
+
+  // Constructs the full path of the given boot |event|.
+  std::string GetBootEventPath(const std::string& event) const;
+
+  // The filesystem path of the record store.
+  std::string store_path_;
+
+  DISALLOW_COPY_AND_ASSIGN(BootEventRecordStore);
+};
+
+#endif  // BOOT_EVENT_RECORD_STORE_H_
\ No newline at end of file
diff --git a/bootstat/boot_event_record_store_test.cpp b/bootstat/boot_event_record_store_test.cpp
new file mode 100644
index 0000000..56af0a6
--- /dev/null
+++ b/bootstat/boot_event_record_store_test.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2016 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 "boot_event_record_store.h"
+
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <cstdint>
+#include <cstdlib>
+#include <base/file.h>
+#include <base/test_utils.h>
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+using testing::UnorderedElementsAreArray;
+
+namespace {
+
+// Returns true if the time difference between |a| and |b| is no larger
+// than 10 seconds.  This allow for a relatively large fuzz when comparing
+// two timestamps taken back-to-back.
+bool FuzzUptimeEquals(int32_t a, int32_t b) {
+  const int32_t FUZZ_SECONDS = 10;
+  return (abs(a - b) <= FUZZ_SECONDS);
+}
+
+// Returns the uptime as read from /proc/uptime, rounded down to an integer.
+int32_t ReadUptime() {
+  std::string uptime_str;
+  if (!android::base::ReadFileToString("/proc/uptime", &uptime_str)) {
+    return -1;
+  }
+
+  // Cast to int to round down.
+  return static_cast<int32_t>(strtod(uptime_str.c_str(), NULL));
+}
+
+// Recursively deletes the directory at |path|.
+void DeleteDirectory(const std::string& path) {
+  typedef std::unique_ptr<DIR, decltype(&closedir)> ScopedDIR;
+  ScopedDIR dir(opendir(path.c_str()), closedir);
+  ASSERT_NE(nullptr, dir.get());
+
+  struct dirent* entry;
+  while ((entry = readdir(dir.get())) != NULL) {
+    const std::string entry_name(entry->d_name);
+    if (entry_name == "." || entry_name == "..") {
+      continue;
+    }
+
+    const std::string entry_path = path + "/" + entry_name;
+    if (entry->d_type == DT_DIR) {
+      DeleteDirectory(entry_path);
+    }  else {
+      unlink(entry_path.c_str());
+    }
+  }
+
+  rmdir(path.c_str());
+}
+
+class BootEventRecordStoreTest : public ::testing::Test {
+ public:
+  BootEventRecordStoreTest() {
+    store_path_ = std::string(store_dir_.path) + "/";
+  }
+
+  const std::string& GetStorePathForTesting() const {
+    return store_path_;
+  }
+
+ private:
+  void TearDown() {
+    // This removes the record store temporary directory even though
+    // TemporaryDir should already take care of it, but this method cleans up
+    // the test files added to the directory which prevent TemporaryDir from
+    // being able to remove the directory.
+    DeleteDirectory(store_path_);
+  }
+
+  // A scoped temporary directory. Using this abstraction provides creation of
+  // the directory and the path to the directory, which is stored in
+  // |store_path_|.
+  TemporaryDir store_dir_;
+
+  // The path to the temporary directory used by the BootEventRecordStore to
+  // persist records.  The directory is created and destroyed for each test.
+  std::string store_path_;
+
+  DISALLOW_COPY_AND_ASSIGN(BootEventRecordStoreTest);
+};
+
+}  // namespace
+
+TEST_F(BootEventRecordStoreTest, AddSingleBootEvent) {
+  BootEventRecordStore store;
+  store.SetStorePath(GetStorePathForTesting());
+
+  int32_t uptime = ReadUptime();
+  ASSERT_NE(-1, uptime);
+
+  store.AddBootEvent("cenozoic");
+
+  auto events = store.GetAllBootEvents();
+  ASSERT_EQ(1U, events.size());
+  EXPECT_EQ("cenozoic", events[0].first);
+  EXPECT_TRUE(FuzzUptimeEquals(uptime, events[0].second));
+}
+
+TEST_F(BootEventRecordStoreTest, AddMultipleBootEvents) {
+  BootEventRecordStore store;
+  store.SetStorePath(GetStorePathForTesting());
+
+  int32_t uptime = ReadUptime();
+  ASSERT_NE(-1, uptime);
+
+  store.AddBootEvent("cretaceous");
+  store.AddBootEvent("jurassic");
+  store.AddBootEvent("triassic");
+
+  const std::string EXPECTED_NAMES[] = {
+    "cretaceous",
+    "jurassic",
+    "triassic",
+  };
+
+  auto events = store.GetAllBootEvents();
+  ASSERT_EQ(3U, events.size());
+
+  std::vector<std::string> names;
+  std::vector<int32_t> timestamps;
+  for (auto i = events.begin(); i != events.end(); ++i) {
+    names.push_back(i->first);
+    timestamps.push_back(i->second);
+  }
+
+  EXPECT_THAT(names, UnorderedElementsAreArray(EXPECTED_NAMES));
+
+  for (auto i = timestamps.cbegin(); i != timestamps.cend(); ++i) {
+    EXPECT_TRUE(FuzzUptimeEquals(uptime, *i));
+  }
+}
diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp
new file mode 100644
index 0000000..a4cbf4e
--- /dev/null
+++ b/bootstat/bootstat.cpp
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+// The bootstat command provides options to persist boot events with the current
+// timestamp, dump the persisted events, and log all events to EventLog to be
+// uploaded to Android log storage via Tron.
+
+//#define LOG_TAG "bootstat"
+
+#include <unistd.h>
+#include <cstddef>
+#include <cstdio>
+#include <memory>
+#include <string>
+#include <base/logging.h>
+#include <log/log.h>
+#include "boot_event_record_store.h"
+#include "event_log_list_builder.h"
+
+namespace {
+
+// Builds an EventLog buffer named |event| containing |data| and writes
+// the log into the Tron histogram logs.
+void LogBootEvent(const std::string& event, int32_t data) {
+  LOG(INFO) << "Logging boot time: " << event << " " << data;
+
+  EventLogListBuilder log_builder;
+  log_builder.Append(event);
+  log_builder.Append(data);
+
+  std::unique_ptr<uint8_t[]> log;
+  size_t size;
+  log_builder.Release(&log, &size);
+
+  android_bWriteLog(HISTOGRAM_LOG_TAG, log.get(), size);
+}
+
+// Scans the boot event record store for record files and logs each boot event
+// via EventLog.
+void LogBootEvents() {
+  BootEventRecordStore boot_event_store;
+
+  auto events = boot_event_store.GetAllBootEvents();
+  for (auto i = events.cbegin(); i != events.cend(); ++i) {
+    LogBootEvent(i->first, i->second);
+  }
+}
+
+void PrintBootEvents() {
+  printf("Boot events:\n");
+  printf("------------\n");
+
+  BootEventRecordStore boot_event_store;
+  auto events = boot_event_store.GetAllBootEvents();
+  for (auto i = events.cbegin(); i != events.cend(); ++i) {
+    printf("%s\t%d\n", i->first.c_str(), i->second);
+  }
+}
+
+void ShowHelp(const char *cmd) {
+  fprintf(stderr, "Usage: %s [options]\n", cmd);
+  fprintf(stderr,
+          "options include:\n"
+          "  -d              Dump the boot event records to the console.\n"
+          "  -h              Show this help.\n"
+          "  -l              Log all metrics to logstorage.\n"
+          "  -r              Record the timestamp of a named boot event.\n");
+}
+
+// Constructs a readable, printable string from the givencommand line
+// arguments.
+std::string GetCommandLine(int argc, char **argv) {
+  std::string cmd;
+  for (int i = 0; i < argc; ++i) {
+    cmd += argv[i];
+    cmd += " ";
+  }
+
+  return cmd;
+}
+
+}  // namespace
+
+int main(int argc, char **argv) {
+  android::base::InitLogging(argv);
+
+  const std::string cmd_line = GetCommandLine(argc, argv);
+  LOG(INFO) << "Service started: " << cmd_line;
+
+  int opt = 0;
+  while ((opt = getopt(argc, argv, "hlpr:")) != -1) {
+    switch (opt) {
+      case 'h': {
+        ShowHelp(argv[0]);
+        break;
+      }
+
+      case 'l': {
+        LogBootEvents();
+        break;
+      }
+
+      case 'p': {
+        PrintBootEvents();
+        break;
+      }
+
+      case 'r': {
+        // |optarg| is an external variable set by getopt representing
+        // the option argument.
+        const char* event = optarg;
+
+        BootEventRecordStore boot_event_store;
+        boot_event_store.AddBootEvent(event);
+        break;
+      }
+
+      default: {
+        DCHECK_EQ(opt, '?');
+
+        // |optopt| is an external variable set by getopt representing
+        // the value of the invalid option.
+        LOG(ERROR) << "Invalid option: " << optopt;
+        ShowHelp(argv[0]);
+        return EXIT_FAILURE;
+      }
+    }
+  }
+
+  return 0;
+}
diff --git a/bootstat/event_log_list_builder.cpp b/bootstat/event_log_list_builder.cpp
new file mode 100644
index 0000000..017a7c5
--- /dev/null
+++ b/bootstat/event_log_list_builder.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2016 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 "event_log_list_builder.h"
+
+#include <cinttypes>
+#include <string>
+#include <base/logging.h>
+#include <log/log.h>
+
+namespace {
+
+const size_t MAX_EVENT_PAYLOAD_SIZE = 512 - 1;  // Leave room for final '\n'.
+const size_t EVENT_TYPE_SIZE = 1;  // Size in bytes of the event type marker.
+
+}  // namespace
+
+EventLogListBuilder::EventLogListBuilder()
+    : payload_count_(0),
+      payload_size_(0),
+      payload_(std::make_unique<uint8_t[]>(MAX_EVENT_PAYLOAD_SIZE)) {
+  memset(payload_.get(), 0, MAX_EVENT_PAYLOAD_SIZE);
+
+  // Set up the top-level EventLog data type.
+  AppendByte(EVENT_TYPE_LIST);
+
+  // Skip over the byte prepresenting the number of items in the list. This
+  // value is set in Release().
+  payload_size_++;
+}
+
+bool EventLogListBuilder::Append(int value) {
+  DCHECK_NE(static_cast<uint8_t*>(nullptr), payload_.get());
+
+  if (!IsSpaceAvailable(sizeof(value) + EVENT_TYPE_SIZE)) {
+    return false;
+  }
+
+  AppendByte(EVENT_TYPE_INT);
+  AppendData(&value, sizeof(value));
+
+  payload_count_++;
+  return true;
+}
+
+bool EventLogListBuilder::Append(const std::string& value) {
+  DCHECK_NE(static_cast<uint8_t*>(nullptr), payload_.get());
+
+  int len = value.length();
+  if (!IsSpaceAvailable(sizeof(len) + len)) {
+    return false;
+  }
+
+  AppendByte(EVENT_TYPE_STRING);
+  AppendData(&len, sizeof(len));
+  AppendData(value.c_str(), len);
+
+  payload_count_++;
+  return true;
+}
+
+void EventLogListBuilder::Release(std::unique_ptr<uint8_t[]>* log,
+                                  size_t* size) {
+  // Finalize the log payload.
+  payload_[1] = payload_count_;
+
+  // Return the log payload.
+  *size = payload_size_;
+  *log = std::move(payload_);
+}
+
+void EventLogListBuilder::AppendData(const void* data, size_t size) {
+  DCHECK_LT(payload_size_ + size, MAX_EVENT_PAYLOAD_SIZE);
+  memcpy(&payload_[payload_size_], data, size);
+  payload_size_ += size;
+}
+
+void EventLogListBuilder::AppendByte(uint8_t byte) {
+  DCHECK_LT(payload_size_ + sizeof(byte), MAX_EVENT_PAYLOAD_SIZE);
+  payload_[payload_size_++] = byte;
+}
+
+bool EventLogListBuilder::IsSpaceAvailable(size_t value_size) {
+  size_t space_needed = value_size + EVENT_TYPE_SIZE;
+  if (payload_size_ + space_needed > MAX_EVENT_PAYLOAD_SIZE) {
+    size_t remaining = MAX_EVENT_PAYLOAD_SIZE - payload_size_;
+    LOG(WARNING) << "Not enough space for value. remain=" <<
+        remaining << "; needed=" << space_needed;
+    return false;
+  }
+
+  return true;
+}
diff --git a/bootstat/event_log_list_builder.h b/bootstat/event_log_list_builder.h
new file mode 100644
index 0000000..52d623f
--- /dev/null
+++ b/bootstat/event_log_list_builder.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 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 EVENT_LOG_LIST_BUILDER_H_
+#define EVENT_LOG_LIST_BUILDER_H_
+
+#include <cstdint>
+#include <memory>
+
+#include <base/macros.h>
+
+// EventLogListBuilder provides a mechanism to build an EventLog list
+// consisting of int and string EventLog values.
+//
+// NOTE: This class does not provide the ability to append an embedded list,
+// i.e., a list containing a list.
+class EventLogListBuilder {
+ public:
+  EventLogListBuilder();
+
+  // Append a single value of a specified type.
+  bool Append(int value);
+  bool Append(const std::string& value);
+
+  // Finalizes construction of the EventLog list and releases the data
+  // to the caller. Caller takes ownership of the payload. No further calls
+  // to append* may be made once the payload is acquired by the caller.
+  void Release(std::unique_ptr<uint8_t[]>* log, size_t* size);
+
+ private:
+  // Appends |data| of the given |size| to the payload.
+  void AppendData(const void* data, size_t size);
+
+  // Appends a single byte to the payload.
+  void AppendByte(uint8_t byte);
+
+  // Returns true iff the remaining capacity in |payload_| is large enough to
+  // accommodate |value_size| bytes. The space required to log the event type
+  // is included in the internal calculation so must not be passed in to
+  // |value_size|.
+  bool IsSpaceAvailable(size_t value_size);
+
+  // The number of items in the EventLog list.
+  size_t payload_count_;
+
+  // The size of the data stored in |payload_|. Used to track where to insert
+  // new data.
+  size_t payload_size_;
+
+  // The payload constructed by calls to log*. The payload may only contain
+  // MAX_EVENT_PAYLOAD (512) bytes.
+  std::unique_ptr<uint8_t[]> payload_;
+
+  DISALLOW_COPY_AND_ASSIGN(EventLogListBuilder);
+};
+
+ #endif  // EVENT_LOG_LIST_BUILDER_H_
diff --git a/bootstat/event_log_list_builder_test.cpp b/bootstat/event_log_list_builder_test.cpp
new file mode 100644
index 0000000..affb4bf
--- /dev/null
+++ b/bootstat/event_log_list_builder_test.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2016 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 "event_log_list_builder.h"
+
+#include <inttypes.h>
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+#include <log/log.h>
+
+using testing::ElementsAreArray;
+
+TEST(EventLogListBuilder, Empty) {
+  EventLogListBuilder builder;
+
+  const uint8_t EXPECTED_LOG[] = {
+    EVENT_TYPE_LIST,
+    0,  // Number of items in the list.
+  };
+
+  std::unique_ptr<uint8_t[]> log;
+  size_t size;
+  builder.Release(&log, &size);
+  EXPECT_EQ(2U, size);
+
+  uint8_t* log_data = log.get();
+  EXPECT_THAT(std::vector<uint8_t>(log_data, log_data + size),
+              ElementsAreArray(EXPECTED_LOG));
+}
+
+TEST(EventLogListBuilder, SingleInt) {
+  EventLogListBuilder builder;
+
+  const uint8_t EXPECTED_LOG[] = {
+    EVENT_TYPE_LIST,
+    1,                // Number of items in the list.
+    EVENT_TYPE_INT,
+    42, 0, 0, 0,      // 4 byte integer value.
+  };
+
+  builder.Append(42);
+
+  std::unique_ptr<uint8_t[]> log;
+  size_t size;
+  builder.Release(&log, &size);
+  EXPECT_EQ(7U, size);
+
+  uint8_t* log_data = log.get();
+  EXPECT_THAT(std::vector<uint8_t>(log_data, log_data + size),
+              ElementsAreArray(EXPECTED_LOG));
+}
+
+TEST(EventLogListBuilder, SingleString) {
+  EventLogListBuilder builder;
+
+  const uint8_t EXPECTED_LOG[] = {
+    EVENT_TYPE_LIST,
+    1,                        // Number of items in the list.
+    EVENT_TYPE_STRING,
+    5, 0, 0, 0,               // 4 byte length of the string.
+    'D', 'r', 'o', 'i', 'd',
+  };
+
+  builder.Append("Droid");
+
+  std::unique_ptr<uint8_t[]> log;
+  size_t size;
+  builder.Release(&log, &size);
+  EXPECT_EQ(12U, size);
+
+  uint8_t* log_data = log.get();
+  EXPECT_THAT(std::vector<uint8_t>(log_data, log_data + size),
+              ElementsAreArray(EXPECTED_LOG));
+}
+
+TEST(EventLogListBuilder, IntThenString) {
+  EventLogListBuilder builder;
+
+  const uint8_t EXPECTED_LOG[] = {
+    EVENT_TYPE_LIST,
+    2,                        // Number of items in the list.
+    EVENT_TYPE_INT,
+    42, 0, 0, 0,              // 4 byte integer value.
+    EVENT_TYPE_STRING,
+    5, 0, 0, 0,               // 4 byte length of the string.
+    'D', 'r', 'o', 'i', 'd',
+  };
+
+  builder.Append(42);
+  builder.Append("Droid");
+
+  std::unique_ptr<uint8_t[]> log;
+  size_t size;
+  builder.Release(&log, &size);
+  EXPECT_EQ(17U, size);
+
+  uint8_t* log_data = log.get();
+  EXPECT_THAT(std::vector<uint8_t>(log_data, log_data + size),
+              ElementsAreArray(EXPECTED_LOG));
+}
diff --git a/bootstat/testrunner.cpp b/bootstat/testrunner.cpp
new file mode 100644
index 0000000..ff8611e
--- /dev/null
+++ b/bootstat/testrunner.cpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2016 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 <base/logging.h>
+#include <gtest/gtest.h>
+
+int main(int argc, char** argv) {
+  ::testing::InitGoogleTest(&argc, argv);
+  android::base::InitLogging(argv, android::base::StderrLogger);
+  return RUN_ALL_TESTS();
+}