Merge branch 'rewrite-metricsd' into merge-metricsd

Initial import of metricsd from platform/system/core.

BUG: 29548040
diff --git a/.clang-format b/.clang-format
new file mode 120000
index 0000000..f9066d4
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1 @@
+../../../build/tools/brillo-clang-format
\ No newline at end of file
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..bb262b4
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,232 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+metrics_cpp_extension := .cc
+libmetrics_sources := \
+  c_metrics_library.cc \
+  metrics_library.cc \
+  timer.cc
+
+metrics_client_sources := \
+  metrics_client.cc
+
+metrics_collector_common := \
+  collectors/averaged_statistics_collector.cc \
+  collectors/cpu_usage_collector.cc \
+  collectors/disk_usage_collector.cc \
+  metrics_collector.cc \
+  metrics_collector_service_impl.cc \
+  persistent_integer.cc
+
+metricsd_common := \
+  persistent_integer.cc \
+  uploader/bn_metricsd_impl.cc \
+  uploader/crash_counters.cc \
+  uploader/metrics_hashes.cc \
+  uploader/metrics_log_base.cc \
+  uploader/metrics_log.cc \
+  uploader/metricsd_service_runner.cc \
+  uploader/sender_http.cc \
+  uploader/system_profile_cache.cc \
+  uploader/upload_service.cc
+
+metrics_collector_tests_sources := \
+  collectors/averaged_statistics_collector_test.cc \
+  collectors/cpu_usage_collector_test.cc \
+  metrics_collector_test.cc \
+  metrics_library_test.cc \
+  persistent_integer_test.cc \
+  timer_test.cc
+
+metricsd_tests_sources := \
+  uploader/metrics_hashes_unittest.cc \
+  uploader/metrics_log_base_unittest.cc \
+  uploader/mock/sender_mock.cc \
+  uploader/upload_service_test.cc
+
+metrics_CFLAGS := -Wall \
+  -Wno-char-subscripts \
+  -Wno-missing-field-initializers \
+  -Wno-unused-parameter \
+  -Werror \
+  -fvisibility=default
+metrics_CPPFLAGS := -Wno-non-virtual-dtor \
+  -Wno-sign-promo \
+  -Wno-strict-aliasing \
+  -fvisibility=default
+metrics_includes := external/gtest/include \
+  $(LOCAL_PATH)/include
+libmetrics_shared_libraries := libchrome libbinder libbrillo libutils
+metrics_collector_shared_libraries := $(libmetrics_shared_libraries) \
+  libbrillo-binder \
+  libbrillo-http \
+  libmetrics \
+  librootdev \
+  libweaved
+
+metrics_collector_static_libraries := libmetricscollectorservice
+
+metricsd_shared_libraries := \
+  libbinder \
+  libbrillo \
+  libbrillo-binder \
+  libbrillo-http \
+  libchrome \
+  libprotobuf-cpp-lite \
+  libupdate_engine_client \
+  libutils
+
+# Static proxy library for the metricsd binder interface.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := metricsd_binder_proxy
+LOCAL_SHARED_LIBRARIES := libbinder libutils
+LOCAL_SRC_FILES := aidl/android/brillo/metrics/IMetricsd.aidl
+include $(BUILD_STATIC_LIBRARY)
+
+# Static library for the metrics_collector binder interface.
+# ==========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libmetricscollectorservice
+LOCAL_CLANG := true
+LOCAL_SHARED_LIBRARIES := libbinder libbrillo-binder libchrome libutils
+LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+LOCAL_SRC_FILES := \
+  aidl/android/brillo/metrics/IMetricsCollectorService.aidl \
+  metrics_collector_service_client.cc
+include $(BUILD_STATIC_LIBRARY)
+
+# Shared library for metrics.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libmetrics
+LOCAL_C_INCLUDES := $(metrics_includes)
+LOCAL_CFLAGS := $(metrics_CFLAGS)
+LOCAL_CLANG := true
+LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
+LOCAL_CPPFLAGS := $(metrics_CPPFLAGS)
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+LOCAL_SHARED_LIBRARIES := $(libmetrics_shared_libraries)
+LOCAL_SRC_FILES := $(libmetrics_sources)
+LOCAL_STATIC_LIBRARIES := metricsd_binder_proxy
+include $(BUILD_SHARED_LIBRARY)
+
+# CLI client for metrics.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := metrics_client
+LOCAL_C_INCLUDES := $(metrics_includes)
+LOCAL_CFLAGS := $(metrics_CFLAGS)
+LOCAL_CLANG := true
+LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
+LOCAL_CPPFLAGS := $(metrics_CPPFLAGS)
+LOCAL_SHARED_LIBRARIES := $(libmetrics_shared_libraries) \
+  libmetrics
+LOCAL_SRC_FILES := $(metrics_client_sources)
+LOCAL_STATIC_LIBRARIES := metricsd_binder_proxy
+include $(BUILD_EXECUTABLE)
+
+# Protobuf library for metricsd.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := metricsd_protos
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+generated_sources_dir := $(call local-generated-sources-dir)
+LOCAL_EXPORT_C_INCLUDE_DIRS += \
+    $(generated_sources_dir)/proto/system/core/metricsd
+LOCAL_SRC_FILES :=  $(call all-proto-files-under,uploader/proto)
+include $(BUILD_STATIC_LIBRARY)
+
+# metrics_collector daemon.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := metrics_collector
+LOCAL_C_INCLUDES := $(metrics_includes)
+LOCAL_CFLAGS := $(metrics_CFLAGS)
+LOCAL_CLANG := true
+LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
+LOCAL_CPPFLAGS := $(metrics_CPPFLAGS)
+LOCAL_INIT_RC := metrics_collector.rc
+LOCAL_REQUIRED_MODULES := metrics.json
+LOCAL_SHARED_LIBRARIES := $(metrics_collector_shared_libraries)
+LOCAL_SRC_FILES := $(metrics_collector_common) \
+  metrics_collector_main.cc
+LOCAL_STATIC_LIBRARIES := metricsd_binder_proxy \
+  $(metrics_collector_static_libraries)
+include $(BUILD_EXECUTABLE)
+
+# metricsd daemon.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := metricsd
+LOCAL_C_INCLUDES := $(metrics_includes)
+LOCAL_CFLAGS := $(metrics_CFLAGS)
+LOCAL_CLANG := true
+LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
+LOCAL_CPPFLAGS := $(metrics_CPPFLAGS)
+LOCAL_INIT_RC := metricsd.rc
+LOCAL_REQUIRED_MODULES := \
+  metrics_collector
+LOCAL_SHARED_LIBRARIES := $(metricsd_shared_libraries)
+LOCAL_STATIC_LIBRARIES := metricsd_protos metricsd_binder_proxy
+LOCAL_SRC_FILES := $(metricsd_common) \
+  metricsd_main.cc
+include $(BUILD_EXECUTABLE)
+
+# Unit tests for metricsd.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := metricsd_tests
+LOCAL_CFLAGS := $(metrics_CFLAGS)
+LOCAL_CLANG := true
+LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
+LOCAL_CPPFLAGS := $(metrics_CPPFLAGS) -Wno-sign-compare
+LOCAL_SHARED_LIBRARIES := $(metricsd_shared_libraries)
+LOCAL_SRC_FILES := $(metricsd_tests_sources) $(metricsd_common)
+LOCAL_STATIC_LIBRARIES := libBionicGtestMain libgmock metricsd_protos metricsd_binder_proxy
+ifdef BRILLO
+LOCAL_MODULE_TAGS := eng
+endif
+include $(BUILD_NATIVE_TEST)
+
+# Unit tests for metrics_collector.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := metrics_collector_tests
+LOCAL_CFLAGS := $(metrics_CFLAGS)
+LOCAL_CLANG := true
+LOCAL_CPP_EXTENSION := $(metrics_cpp_extension)
+LOCAL_CPPFLAGS := $(metrics_CPPFLAGS) -Wno-sign-compare
+LOCAL_SHARED_LIBRARIES := $(metrics_collector_shared_libraries)
+LOCAL_SRC_FILES := $(metrics_collector_tests_sources) \
+  $(metrics_collector_common)
+LOCAL_STATIC_LIBRARIES := libBionicGtestMain libgmock metricsd_binder_proxy \
+  $(metrics_collector_static_libraries)
+ifdef BRILLO
+LOCAL_MODULE_TAGS := eng
+endif
+include $(BUILD_NATIVE_TEST)
+
+# Weave schema files
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := metrics.json
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/weaved/traits
+LOCAL_SRC_FILES := etc/weaved/traits/$(LOCAL_MODULE)
+include $(BUILD_PREBUILT)
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..7f5e50d
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,3 @@
+semenzato@chromium.org
+derat@chromium.org
+bsimonnet@chromium.org
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8d4828c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,124 @@
+Metricsd
+========
+
+The metricsd daemon is used to gather metrics from the platform and application,
+aggregate them and upload them periodically to a server.
+The metrics will then be available in their aggregated form to the developer
+for analysis.
+
+Three components are provided to interact with `metricsd`: `libmetrics`,
+`metrics_collector` and `metrics_client`.
+
+The Metrics Library: libmetrics
+-------------------------------
+
+`libmetrics` is a small library that implements the basic C++ API for
+metrics collection. All metrics collection is funneled through this library. The
+easiest and recommended way for a client-side module to collect user metrics is
+to link `libmetrics` and use its APIs to send metrics to `metricsd` for transport to
+UMA. In order to use the library in a module, you need to do the following:
+
+- Add a dependency on the shared library in your Android.mk file:
+  `LOCAL_SHARED_LIBRARIES += libmetrics`
+
+- To access the metrics library API in the module, include the
+  <metrics/metrics_library.h> header file.
+
+- The API is documented in `metrics_library.h`. Before using the API methods, a
+  MetricsLibrary object needs to be constructed and initialized through its
+  Init method.
+
+- Samples are uploaded only if the `/data/misc/metrics/enabled` file exists.
+
+
+Server Side
+-----------
+
+You will be able to see all uploaded metrics on the metrics dashboard,
+accessible via the developer console.
+
+*** note
+It usually takes a day for metrics to be available on the dashboard.
+***
+
+
+The Metrics Client: metrics_client
+----------------------------------
+
+`metrics_client` is a simple shell command-line utility for sending histogram
+samples and querying `metricsd`. It's installed under `/system/bin` on the target
+platform and uses `libmetrics`.
+
+For usage information and command-line options, run `metrics_client` on the
+target platform or look for "Usage:" in `metrics_client.cc`.
+
+
+The Metrics Daemon: metricsd
+----------------------------
+
+`metricsd` is the daemon that listens for metrics logging calls (via Binder),
+aggregates the metrics and uploads them periodically. This daemon should start as
+early as possible so that depending daemons can log at any time.
+
+`metricsd` is made of two threads that work as follows:
+
+* The binder thread listens for one-way Binder calls, aggregates the metrics in
+  memory (via `base::StatisticsRecorder`) and increments the crash counters when a
+  crash is reported. This thread is kept as simple as possible to ensure the
+  maximum throughput possible.
+* The uploader thread takes care of backing up the metrics to disk periodically
+  (to avoid losing metrics on crashes), collecting metadata about the client
+  (version number, channel, etc..) and uploading the metrics periodically to the
+  server.
+
+
+The Metrics Collector: metrics_collector
+----------------------------------------
+
+metrics_collector is a daemon that runs in the background on the target platform,
+gathers health information about the system and maintains long running counters
+(ex: number of crashes per week).
+
+The recommended way to generate metrics data from a module is to link and use
+libmetrics directly. However, we may not want to add a dependency on libmetrics
+to some modules (ex: kernel). In this case, we can add a collector to
+metrics_collector that will, for example, take measurements and report them
+periodically to metricsd (this is the case for the disk utilization histogram).
+
+
+FAQ
+---
+
+### What should my histogram's |min| and |max| values be set at?
+
+You should set the values to a range that covers the vast majority of samples
+that would appear in the field. Note that samples below the |min| will still
+be collected in the underflow bucket and samples above the |max| will end up
+in the overflow bucket. Also, the reported mean of the data will be correct
+regardless of the range.
+
+### How many buckets should I use in my histogram?
+
+You should allocate as many buckets as necessary to perform proper analysis
+on the collected data. Note, however, that the memory allocated in metricsd
+for each histogram is proportional to the number of buckets. Therefore, it is
+strongly recommended to keep this number low (e.g., 50 is normal, while 100
+is probably high).
+
+### When should I use an enumeration (linear) histogram vs. a regular (exponential) histogram?
+
+Enumeration histograms should really be used only for sampling enumerated
+events and, in some cases, percentages. Normally, you should use a regular
+histogram with exponential bucket layout that provides higher resolution at
+the low end of the range and lower resolution at the high end. Regular
+histograms are generally used for collecting performance data (e.g., timing,
+memory usage, power) as well as aggregated event counts.
+
+### How can I test that my histogram was reported correctly?
+
+* Make sure no error messages appear in logcat when you log a sample.
+* Run `metrics_client -d` to dump the currently aggregated metrics. Your
+  histogram should appear in the list.
+* Make sure that the aggregated metrics were uploaded to the server successfully
+  (check for an OK message from `metricsd` in logcat).
+* After a day, your histogram should be available on the dashboard.
diff --git a/WATCHLISTS b/WATCHLISTS
new file mode 100644
index 0000000..a051f35
--- /dev/null
+++ b/WATCHLISTS
@@ -0,0 +1,16 @@
+# See http://dev.chromium.org/developers/contributing-code/watchlists for
+# a description of this file's format.
+# Please keep these keys in alphabetical order.
+
+{
+  'WATCHLIST_DEFINITIONS': {
+    'all': {
+      'filepath': '.',
+    },
+  },
+  'WATCHLISTS': {
+    'all': ['petkov@chromium.org',
+                'semenzato@chromium.org',
+                'sosa@chromium.org']
+  },
+}
diff --git a/aidl/android/brillo/metrics/IMetricsCollectorService.aidl b/aidl/android/brillo/metrics/IMetricsCollectorService.aidl
new file mode 100644
index 0000000..49f484f
--- /dev/null
+++ b/aidl/android/brillo/metrics/IMetricsCollectorService.aidl
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+package android.brillo.metrics;
+
+interface IMetricsCollectorService {
+  oneway void notifyUserCrash();
+}
diff --git a/aidl/android/brillo/metrics/IMetricsd.aidl b/aidl/android/brillo/metrics/IMetricsd.aidl
new file mode 100644
index 0000000..aa3cb34
--- /dev/null
+++ b/aidl/android/brillo/metrics/IMetricsd.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+package android.brillo.metrics;
+
+interface IMetricsd {
+  oneway void recordHistogram(String name, int sample, int min, int max,
+                              int nbuckets);
+  oneway void recordLinearHistogram(String name, int sample, int max);
+  oneway void recordSparseHistogram(String name, int sample);
+  oneway void recordCrash(String type);
+  String getHistogramsDump();
+}
diff --git a/c_metrics_library.cc b/c_metrics_library.cc
new file mode 100644
index 0000000..47a543e
--- /dev/null
+++ b/c_metrics_library.cc
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+//
+// C wrapper to libmetrics
+//
+
+#include "metrics/c_metrics_library.h"
+
+#include <string>
+
+#include "metrics/metrics_library.h"
+
+extern "C" CMetricsLibrary CMetricsLibraryNew(void) {
+  MetricsLibrary* lib = new MetricsLibrary;
+  return reinterpret_cast<CMetricsLibrary>(lib);
+}
+
+extern "C" void CMetricsLibraryDelete(CMetricsLibrary handle) {
+  MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+  delete lib;
+}
+
+extern "C" void CMetricsLibraryInit(CMetricsLibrary handle) {
+  MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+  if (lib != NULL)
+    lib->Init();
+}
+
+extern "C" int CMetricsLibrarySendToUMA(CMetricsLibrary handle,
+                                        const char* name, int sample,
+                                        int min, int max, int nbuckets) {
+  MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+  if (lib == NULL)
+    return 0;
+  return lib->SendToUMA(std::string(name), sample, min, max, nbuckets);
+}
+
+extern "C" int CMetricsLibrarySendEnumToUMA(CMetricsLibrary handle,
+                                            const char* name, int sample,
+                                            int max) {
+  MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+  if (lib == NULL)
+    return 0;
+  return lib->SendEnumToUMA(std::string(name), sample, max);
+}
+
+extern "C" int CMetricsLibrarySendSparseToUMA(CMetricsLibrary handle,
+                                              const char* name, int sample) {
+  MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+  if (lib == NULL)
+    return 0;
+  return lib->SendSparseToUMA(std::string(name), sample);
+}
+
+extern "C" int CMetricsLibrarySendCrashToUMA(CMetricsLibrary handle,
+                                            const char* crash_kind) {
+  MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+  if (lib == NULL)
+    return 0;
+  return lib->SendCrashToUMA(crash_kind);
+}
+
+extern "C" int CMetricsLibraryAreMetricsEnabled(CMetricsLibrary handle) {
+  MetricsLibrary* lib = reinterpret_cast<MetricsLibrary*>(handle);
+  if (lib == NULL)
+    return 0;
+  return lib->AreMetricsEnabled();
+}
diff --git a/collectors/averaged_statistics_collector.cc b/collectors/averaged_statistics_collector.cc
new file mode 100644
index 0000000..a3aaa98
--- /dev/null
+++ b/collectors/averaged_statistics_collector.cc
@@ -0,0 +1,217 @@
+/*
+ * 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 "averaged_statistics_collector.h"
+
+#include <base/bind.h>
+#include <base/files/file_util.h>
+#include <base/files/file_path.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+
+#include "metrics_collector.h"
+
+namespace {
+
+// disk stats metrics
+
+// The {Read,Write}Sectors numbers are in sectors/second.
+// A sector is usually 512 bytes.
+const char kReadSectorsHistogramName[] = "Platform.ReadSectors";
+const char kWriteSectorsHistogramName[] = "Platform.WriteSectors";
+const int kDiskMetricsStatItemCount = 11;
+
+// Assume a max rate of 250Mb/s for reads (worse for writes) and 512 byte
+// sectors.
+const int kSectorsIOMax = 500000;  // sectors/second
+const int kSectorsBuckets = 50;    // buckets
+
+// Page size is 4k, sector size is 0.5k.  We're not interested in page fault
+// rates that the disk cannot sustain.
+const int kPageFaultsMax = kSectorsIOMax / 8;  // Page faults/second
+const int kPageFaultsBuckets = 50;
+
+// Major page faults, i.e. the ones that require data to be read from disk.
+const char kPageFaultsHistogramName[] = "Platform.PageFaults";
+
+// Swap in and Swap out
+const char kSwapInHistogramName[] = "Platform.SwapIn";
+const char kSwapOutHistogramName[] = "Platform.SwapOut";
+
+const int kIntervalBetweenCollection = 60;  // seconds
+const int kCollectionDuration = 1;  // seconds
+
+}  // namespace
+
+AveragedStatisticsCollector::AveragedStatisticsCollector(
+    MetricsLibraryInterface* metrics_library,
+    const std::string& diskstats_path,
+    const std::string& vmstats_path) :
+  metrics_lib_(metrics_library),
+  diskstats_path_(diskstats_path),
+  vmstats_path_(vmstats_path) {
+}
+
+void AveragedStatisticsCollector::ScheduleWait() {
+  base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+      base::Bind(&AveragedStatisticsCollector::WaitCallback,
+                 base::Unretained(this)),
+      base::TimeDelta::FromSeconds(
+          kIntervalBetweenCollection - kCollectionDuration));
+}
+
+void AveragedStatisticsCollector::ScheduleCollect() {
+  base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+      base::Bind(&AveragedStatisticsCollector::CollectCallback,
+                 base::Unretained(this)),
+      base::TimeDelta::FromSeconds(kCollectionDuration));
+}
+
+void AveragedStatisticsCollector::WaitCallback() {
+  ReadInitialValues();
+  ScheduleCollect();
+}
+
+void AveragedStatisticsCollector::CollectCallback() {
+  Collect();
+  ScheduleWait();
+}
+
+void AveragedStatisticsCollector::ReadInitialValues() {
+  stats_start_time_ = MetricsCollector::GetActiveTime();
+  DiskStatsReadStats(&read_sectors_, &write_sectors_);
+  VmStatsReadStats(&vmstats_);
+}
+
+bool AveragedStatisticsCollector::DiskStatsReadStats(
+    uint64_t* read_sectors, uint64_t* write_sectors) {
+  CHECK(read_sectors);
+  CHECK(write_sectors);
+  std::string line;
+  if (diskstats_path_.empty()) {
+    return false;
+  }
+
+  if (!base::ReadFileToString(base::FilePath(diskstats_path_), &line)) {
+    PLOG(WARNING) << "Could not read disk stats from "
+                  << diskstats_path_.value();
+    return false;
+  }
+
+  std::vector<std::string> parts = base::SplitString(
+      line, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+  if (parts.size() != kDiskMetricsStatItemCount) {
+    LOG(ERROR) << "Could not parse disk stat correctly. Expected "
+               << kDiskMetricsStatItemCount << " elements but got "
+               << parts.size();
+    return false;
+  }
+  if (!base::StringToUint64(parts[2], read_sectors)) {
+    LOG(ERROR) << "Couldn't convert read sectors " << parts[2] << " to uint64";
+    return false;
+  }
+  if (!base::StringToUint64(parts[6], write_sectors)) {
+    LOG(ERROR) << "Couldn't convert write sectors " << parts[6] << " to uint64";
+    return false;
+  }
+
+  return true;
+}
+
+bool AveragedStatisticsCollector::VmStatsParseStats(
+    const char* stats, struct VmstatRecord* record) {
+  CHECK(stats);
+  CHECK(record);
+  base::StringPairs pairs;
+  base::SplitStringIntoKeyValuePairs(stats, ' ', '\n', &pairs);
+
+  for (base::StringPairs::iterator it = pairs.begin();
+       it != pairs.end(); ++it) {
+    if (it->first == "pgmajfault" &&
+        !base::StringToUint64(it->second, &record->page_faults)) {
+      return false;
+    }
+    if (it->first == "pswpin" &&
+        !base::StringToUint64(it->second, &record->swap_in)) {
+      return false;
+    }
+    if (it->first == "pswpout" &&
+        !base::StringToUint64(it->second, &record->swap_out)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool AveragedStatisticsCollector::VmStatsReadStats(struct VmstatRecord* stats) {
+  CHECK(stats);
+  std::string value_string;
+  if (!base::ReadFileToString(vmstats_path_, &value_string)) {
+    LOG(WARNING) << "cannot read " << vmstats_path_.value();
+    return false;
+  }
+  return VmStatsParseStats(value_string.c_str(), stats);
+}
+
+void AveragedStatisticsCollector::Collect() {
+  uint64_t read_sectors_now, write_sectors_now;
+  struct VmstatRecord vmstats_now;
+  double time_now = MetricsCollector::GetActiveTime();
+  double delta_time = time_now - stats_start_time_;
+  bool diskstats_success = DiskStatsReadStats(&read_sectors_now,
+                                              &write_sectors_now);
+
+  int delta_read = read_sectors_now - read_sectors_;
+  int delta_write = write_sectors_now - write_sectors_;
+  int read_sectors_per_second = delta_read / delta_time;
+  int write_sectors_per_second = delta_write / delta_time;
+  bool vmstats_success = VmStatsReadStats(&vmstats_now);
+  uint64_t delta_faults = vmstats_now.page_faults - vmstats_.page_faults;
+  uint64_t delta_swap_in = vmstats_now.swap_in - vmstats_.swap_in;
+  uint64_t delta_swap_out = vmstats_now.swap_out - vmstats_.swap_out;
+  uint64_t page_faults_per_second = delta_faults / delta_time;
+  uint64_t swap_in_per_second = delta_swap_in / delta_time;
+  uint64_t swap_out_per_second = delta_swap_out / delta_time;
+  if (diskstats_success) {
+    metrics_lib_->SendToUMA(kReadSectorsHistogramName,
+                            read_sectors_per_second,
+                            1,
+                            kSectorsIOMax,
+                            kSectorsBuckets);
+    metrics_lib_->SendToUMA(kWriteSectorsHistogramName,
+                            write_sectors_per_second,
+                            1,
+                            kSectorsIOMax,
+                            kSectorsBuckets);
+  }
+  if (vmstats_success) {
+    metrics_lib_->SendToUMA(kPageFaultsHistogramName,
+                            page_faults_per_second,
+                            1,
+                            kPageFaultsMax,
+                            kPageFaultsBuckets);
+    metrics_lib_->SendToUMA(kSwapInHistogramName,
+                            swap_in_per_second,
+                            1,
+                            kPageFaultsMax,
+                            kPageFaultsBuckets);
+    metrics_lib_->SendToUMA(kSwapOutHistogramName,
+                            swap_out_per_second,
+                            1,
+                            kPageFaultsMax,
+                            kPageFaultsBuckets);
+  }
+}
diff --git a/collectors/averaged_statistics_collector.h b/collectors/averaged_statistics_collector.h
new file mode 100644
index 0000000..753f70c
--- /dev/null
+++ b/collectors/averaged_statistics_collector.h
@@ -0,0 +1,79 @@
+/*
+ * 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 METRICSD_COLLECTORS_AVERAGED_STATISTICS_COLLECTOR_H_
+#define METRICSD_COLLECTORS_AVERAGED_STATISTICS_COLLECTOR_H_
+
+#include "metrics/metrics_library.h"
+
+class AveragedStatisticsCollector {
+ public:
+  AveragedStatisticsCollector(MetricsLibraryInterface* metrics_library,
+                              const std::string& diskstats_path,
+                              const std::string& vmstat_path);
+
+  // Schedule a wait period.
+  void ScheduleWait();
+
+  // Schedule a collection period.
+  void ScheduleCollect();
+
+  // Callback used by the main loop.
+  void CollectCallback();
+
+  // Callback used by the main loop.
+  void WaitCallback();
+
+  // Read and store the initial values at the beginning of a collection cycle.
+  void ReadInitialValues();
+
+  // Collect the disk usage statistics and report them.
+  void Collect();
+
+ private:
+  friend class AveragedStatisticsTest;
+  FRIEND_TEST(AveragedStatisticsTest, ParseDiskStats);
+  FRIEND_TEST(AveragedStatisticsTest, ParseVmStats);
+
+  // Record for retrieving and reporting values from /proc/vmstat
+  struct VmstatRecord {
+    uint64_t page_faults;    // major faults
+    uint64_t swap_in;        // pages swapped in
+    uint64_t swap_out;       // pages swapped out
+  };
+
+  // Read the disk read/write statistics for the main disk.
+  bool DiskStatsReadStats(uint64_t* read_sectors, uint64_t* write_sectors);
+
+  // Parse the content of the vmstats file into |record|.
+  bool VmStatsParseStats(const char* stats, struct VmstatRecord* record);
+
+  // Read the vmstats into |stats|.
+  bool VmStatsReadStats(struct VmstatRecord* stats);
+
+  MetricsLibraryInterface* metrics_lib_;
+  base::FilePath diskstats_path_;
+  base::FilePath vmstats_path_;
+
+  // Values observed at the beginning of the collection period.
+  uint64_t read_sectors_;
+  uint64_t write_sectors_;
+  struct VmstatRecord vmstats_;
+
+  double stats_start_time_;
+};
+
+#endif  // METRICSD_COLLECTORS_AVERAGED_STATISTICS_COLLECTOR_H_
diff --git a/collectors/averaged_statistics_collector_test.cc b/collectors/averaged_statistics_collector_test.cc
new file mode 100644
index 0000000..68f9f2f
--- /dev/null
+++ b/collectors/averaged_statistics_collector_test.cc
@@ -0,0 +1,100 @@
+/*
+ * 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 "averaged_statistics_collector.h"
+
+#include <memory>
+
+#include <inttypes.h>
+
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/strings/stringprintf.h>
+#include <gtest/gtest.h>
+
+
+static const char kFakeDiskStatsFormat[] =
+    "    1793     1788    %" PRIu64 "   105580    "
+    "    196      175     %" PRIu64 "    30290    "
+    "    0    44060   135850\n";
+static const uint64_t kFakeReadSectors[] = {80000, 100000};
+static const uint64_t kFakeWriteSectors[] = {3000, 4000};
+
+
+class AveragedStatisticsTest : public testing::Test {
+ protected:
+  std::string kFakeDiskStats0;
+  std::string kFakeDiskStats1;
+
+  virtual void SetUp() {
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+    disk_stats_path_ = temp_dir_.path().Append("disk_stats");
+    collector_.reset(new AveragedStatisticsCollector(
+        &metrics_lib_, disk_stats_path_.value(), ""));
+
+    kFakeDiskStats0 = base::StringPrintf(kFakeDiskStatsFormat,
+                                         kFakeReadSectors[0],
+                                         kFakeWriteSectors[0]);
+    kFakeDiskStats1 = base::StringPrintf(kFakeDiskStatsFormat,
+                                         kFakeReadSectors[1],
+                                         kFakeWriteSectors[1]);
+
+    CreateFakeDiskStatsFile(kFakeDiskStats0);
+  }
+
+  // Creates or overwrites an input file containing fake disk stats.
+  void CreateFakeDiskStatsFile(const std::string& fake_stats) {
+    EXPECT_EQ(base::WriteFile(disk_stats_path_,
+                              fake_stats.data(), fake_stats.size()),
+              fake_stats.size());
+  }
+
+  // Collector used for tests.
+  std::unique_ptr<AveragedStatisticsCollector> collector_;
+
+  // Temporary directory used for tests.
+  base::ScopedTempDir temp_dir_;
+
+  // Path for the fake files.
+  base::FilePath disk_stats_path_;
+
+  MetricsLibrary metrics_lib_;
+};
+
+TEST_F(AveragedStatisticsTest, ParseDiskStats) {
+  uint64_t read_sectors_now, write_sectors_now;
+  CreateFakeDiskStatsFile(kFakeDiskStats0);
+  ASSERT_TRUE(collector_->DiskStatsReadStats(&read_sectors_now,
+                                             &write_sectors_now));
+  EXPECT_EQ(read_sectors_now, kFakeReadSectors[0]);
+  EXPECT_EQ(write_sectors_now, kFakeWriteSectors[0]);
+
+  CreateFakeDiskStatsFile(kFakeDiskStats1);
+  ASSERT_TRUE(collector_->DiskStatsReadStats(&read_sectors_now,
+                                             &write_sectors_now));
+  EXPECT_EQ(read_sectors_now, kFakeReadSectors[1]);
+  EXPECT_EQ(write_sectors_now, kFakeWriteSectors[1]);
+}
+
+TEST_F(AveragedStatisticsTest, ParseVmStats) {
+  static char kVmStats[] = "pswpin 1345\npswpout 8896\n"
+    "foo 100\nbar 200\npgmajfault 42\netcetc 300\n";
+  struct AveragedStatisticsCollector::VmstatRecord stats;
+  EXPECT_TRUE(collector_->VmStatsParseStats(kVmStats, &stats));
+  EXPECT_EQ(stats.page_faults, 42);
+  EXPECT_EQ(stats.swap_in, 1345);
+  EXPECT_EQ(stats.swap_out, 8896);
+}
diff --git a/collectors/cpu_usage_collector.cc b/collectors/cpu_usage_collector.cc
new file mode 100644
index 0000000..9b0bb34
--- /dev/null
+++ b/collectors/cpu_usage_collector.cc
@@ -0,0 +1,126 @@
+/*
+ * 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 "collectors/cpu_usage_collector.h"
+
+#include <base/bind.h>
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/message_loop/message_loop.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/sys_info.h>
+
+#include "metrics/metrics_library.h"
+
+namespace {
+
+const char kCpuUsagePercent[] = "Platform.CpuUsage.Percent";
+const char kMetricsProcStatFileName[] = "/proc/stat";
+const int kMetricsProcStatFirstLineItemsCount = 11;
+
+// Collect every minute.
+const int kCollectionIntervalSecs = 60;
+
+}  // namespace
+
+using base::TimeDelta;
+
+CpuUsageCollector::CpuUsageCollector(MetricsLibraryInterface* metrics_library) {
+  CHECK(metrics_library);
+  metrics_lib_ = metrics_library;
+  collect_interval_ = TimeDelta::FromSeconds(kCollectionIntervalSecs);
+}
+
+void CpuUsageCollector::Init() {
+  num_cpu_ = base::SysInfo::NumberOfProcessors();
+
+  // Get ticks per second (HZ) on this system.
+  // Sysconf cannot fail, so no sanity checks are needed.
+  ticks_per_second_ = sysconf(_SC_CLK_TCK);
+  CHECK_GT(ticks_per_second_, uint64_t(0))
+      << "Number of ticks per seconds should be positive.";
+
+  latest_cpu_use_ = GetCumulativeCpuUse();
+}
+
+void CpuUsageCollector::CollectCallback() {
+  Collect();
+  Schedule();
+}
+
+void CpuUsageCollector::Schedule() {
+  base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+      base::Bind(&CpuUsageCollector::CollectCallback, base::Unretained(this)),
+      collect_interval_);
+}
+
+void CpuUsageCollector::Collect() {
+  TimeDelta cpu_use = GetCumulativeCpuUse();
+  TimeDelta diff_per_cpu = (cpu_use - latest_cpu_use_) / num_cpu_;
+  latest_cpu_use_ = cpu_use;
+
+  // Report the cpu usage as a percentage of the total cpu usage possible.
+  int percent_use = diff_per_cpu.InMilliseconds() * 100 /
+      (kCollectionIntervalSecs * 1000);
+
+  metrics_lib_->SendEnumToUMA(kCpuUsagePercent, percent_use, 101);
+}
+
+TimeDelta CpuUsageCollector::GetCumulativeCpuUse() {
+  base::FilePath proc_stat_path(kMetricsProcStatFileName);
+  std::string proc_stat_string;
+  if (!base::ReadFileToString(proc_stat_path, &proc_stat_string)) {
+    LOG(WARNING) << "cannot open " << kMetricsProcStatFileName;
+    return TimeDelta();
+  }
+
+  uint64_t user_ticks, user_nice_ticks, system_ticks;
+  if (!ParseProcStat(proc_stat_string, &user_ticks, &user_nice_ticks,
+                     &system_ticks)) {
+    return TimeDelta();
+  }
+
+  uint64_t total = user_ticks + user_nice_ticks + system_ticks;
+  return TimeDelta::FromMicroseconds(
+      total * 1000 * 1000 / ticks_per_second_);
+}
+
+bool CpuUsageCollector::ParseProcStat(const std::string& stat_content,
+                                      uint64_t *user_ticks,
+                                      uint64_t *user_nice_ticks,
+                                      uint64_t *system_ticks) {
+  std::vector<std::string> proc_stat_lines = base::SplitString(
+      stat_content, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+  if (proc_stat_lines.empty()) {
+    LOG(WARNING) << "No lines found in " << kMetricsProcStatFileName;
+    return false;
+  }
+  std::vector<std::string> proc_stat_totals =
+      base::SplitString(proc_stat_lines[0], base::kWhitespaceASCII,
+                        base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+
+  if (proc_stat_totals.size() != kMetricsProcStatFirstLineItemsCount ||
+      proc_stat_totals[0] != "cpu" ||
+      !base::StringToUint64(proc_stat_totals[1], user_ticks) ||
+      !base::StringToUint64(proc_stat_totals[2], user_nice_ticks) ||
+      !base::StringToUint64(proc_stat_totals[3], system_ticks)) {
+    LOG(WARNING) << "cannot parse first line: " << proc_stat_lines[0];
+    return false;
+  }
+  return true;
+}
diff --git a/collectors/cpu_usage_collector.h b/collectors/cpu_usage_collector.h
new file mode 100644
index 0000000..f81dfcb
--- /dev/null
+++ b/collectors/cpu_usage_collector.h
@@ -0,0 +1,59 @@
+/*
+ * 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 METRICSD_COLLECTORS_CPU_USAGE_COLLECTOR_H_
+#define METRICSD_COLLECTORS_CPU_USAGE_COLLECTOR_H_
+
+#include <base/time/time.h>
+
+#include "metrics/metrics_library.h"
+
+class CpuUsageCollector {
+ public:
+  CpuUsageCollector(MetricsLibraryInterface* metrics_library);
+
+  // Initialize this collector's state.
+  void Init();
+
+  // Schedule a collection interval.
+  void Schedule();
+
+  // Callback called at the end of the collection interval.
+  void CollectCallback();
+
+  // Measure the cpu use and report it.
+  void Collect();
+
+  // Gets the current cumulated Cpu usage.
+  base::TimeDelta GetCumulativeCpuUse();
+
+ private:
+  FRIEND_TEST(CpuUsageTest, ParseProcStat);
+  bool ParseProcStat(const std::string& stat_content,
+                     uint64_t *user_ticks,
+                     uint64_t *user_nice_ticks,
+                     uint64_t *system_ticks);
+
+  int num_cpu_;
+  uint32_t ticks_per_second_;
+
+  base::TimeDelta collect_interval_;
+  base::TimeDelta latest_cpu_use_;
+
+  MetricsLibraryInterface* metrics_lib_;
+};
+
+#endif  // METRICSD_COLLECTORS_CPU_USAGE_COLLECTOR_H_
diff --git a/collectors/cpu_usage_collector_test.cc b/collectors/cpu_usage_collector_test.cc
new file mode 100644
index 0000000..ee5c92b
--- /dev/null
+++ b/collectors/cpu_usage_collector_test.cc
@@ -0,0 +1,50 @@
+/*
+ * 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 <gtest/gtest.h>
+
+#include "collectors/cpu_usage_collector.h"
+#include "metrics/metrics_library_mock.h"
+
+
+TEST(CpuUsageTest, ParseProcStat) {
+  MetricsLibraryMock metrics_lib_mock;
+  CpuUsageCollector collector(&metrics_lib_mock);
+  std::vector<std::string> invalid_contents = {
+    "",
+    // First line does not start with cpu.
+    "spu  17191 11 36579 151118 289 0 2 0 0 0\n"
+    "cpu0 1564 2 866 48650 68 0 2 0 0 0\n"
+    "cpu1 14299 0 35116 1844 81 0 0 0 0 0\n",
+    // One of the field is not a number.
+    "cpu  a17191 11 36579 151118 289 0 2 0 0 0",
+    // To many numbers in the first line.
+    "cpu  17191 11 36579 151118 289 0 2 0 0 0 102"
+  };
+
+  uint64_t user, nice, system;
+  for (int i = 0; i < invalid_contents.size(); i++) {
+    ASSERT_FALSE(collector.ParseProcStat(invalid_contents[i], &user, &nice,
+                                         &system));
+  }
+
+  ASSERT_TRUE(collector.ParseProcStat(
+      std::string("cpu  17191 11 36579 151118 289 0 2 0 0 0"),
+      &user, &nice, &system));
+  ASSERT_EQ(17191, user);
+  ASSERT_EQ(11, nice);
+  ASSERT_EQ(36579, system);
+}
diff --git a/collectors/disk_usage_collector.cc b/collectors/disk_usage_collector.cc
new file mode 100644
index 0000000..5ab51fb
--- /dev/null
+++ b/collectors/disk_usage_collector.cc
@@ -0,0 +1,75 @@
+/*
+ * 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 "collectors/disk_usage_collector.h"
+
+#include <base/bind.h>
+#include <base/bind_helpers.h>
+#include <base/message_loop/message_loop.h>
+#include <sys/statvfs.h>
+
+#include "metrics/metrics_library.h"
+
+namespace {
+
+const char kDiskUsageMB[] = "Platform.DataPartitionUsed.MB";
+const char kDiskUsagePercent[] = "Platform.DataPartitionUsed.Percent";
+const char kDataPartitionPath[] = "/data";
+
+// Collect every 15 minutes.
+const int kDiskUsageCollectorIntervalSeconds = 900;
+
+}  // namespace
+
+DiskUsageCollector::DiskUsageCollector(
+    MetricsLibraryInterface* metrics_library) {
+  collect_interval_ = base::TimeDelta::FromSeconds(
+      kDiskUsageCollectorIntervalSeconds);
+  CHECK(metrics_library);
+  metrics_lib_ = metrics_library;
+}
+
+void DiskUsageCollector::Collect() {
+  struct statvfs buf;
+  int result = statvfs(kDataPartitionPath, &buf);
+  if (result != 0) {
+    PLOG(ERROR) << "Failed to check the available space in "
+                << kDataPartitionPath;
+    return;
+  }
+
+  unsigned long total_space = buf.f_blocks * buf.f_bsize;
+  unsigned long used_space = (buf.f_blocks - buf.f_bfree) * buf.f_bsize;
+  int percent_used = (used_space * 100) / total_space;
+
+  metrics_lib_->SendToUMA(kDiskUsageMB,
+                          used_space / (1024 * 1024),
+                          0,
+                          1024, // up to 1 GB.
+                          100);
+  metrics_lib_->SendEnumToUMA(kDiskUsagePercent, percent_used, 101);
+}
+
+void DiskUsageCollector::CollectCallback() {
+  Collect();
+  Schedule();
+}
+
+void DiskUsageCollector::Schedule() {
+  base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+      base::Bind(&DiskUsageCollector::CollectCallback, base::Unretained(this)),
+      collect_interval_);
+}
diff --git a/collectors/disk_usage_collector.h b/collectors/disk_usage_collector.h
new file mode 100644
index 0000000..c1d4546
--- /dev/null
+++ b/collectors/disk_usage_collector.h
@@ -0,0 +1,42 @@
+/*
+ * 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 METRICSD_COLLECTORS_DISK_USAGE_COLLECTOR_H_
+#define METRICSD_COLLECTORS_DISK_USAGE_COLLECTOR_H_
+
+#include <base/time/time.h>
+
+#include "metrics/metrics_library.h"
+
+class DiskUsageCollector {
+ public:
+  DiskUsageCollector(MetricsLibraryInterface* metrics_library);
+
+  // Schedule the next collection.
+  void Schedule();
+
+  // Callback used by the main loop.
+  void CollectCallback();
+
+  // Collect the disk usage statistics and report them.
+  void Collect();
+
+ private:
+  base::TimeDelta collect_interval_;
+  MetricsLibraryInterface* metrics_lib_;
+};
+
+#endif  // METRICSD_COLLECTORS_DISK_USAGE_COLLECTOR_H_
diff --git a/constants.h b/constants.h
new file mode 100644
index 0000000..b702737
--- /dev/null
+++ b/constants.h
@@ -0,0 +1,42 @@
+//
+// 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 METRICS_CONSTANTS_H_
+#define METRICS_CONSTANTS_H_
+
+namespace metrics {
+static const char kSharedMetricsDirectory[] = "/data/misc/metrics/";
+static const char kMetricsdDirectory[] = "/data/misc/metricsd/";
+static const char kMetricsCollectorDirectory[] =
+    "/data/misc/metrics_collector/";
+static const char kMetricsGUIDFileName[] = "Sysinfo.GUID";
+static const char kMetricsServer[] = "https://clients4.google.com/uma/v2";
+static const char kConsentFileName[] = "enabled";
+static const char kStagedLogName[] = "staged_log";
+static const char kSavedLogName[] = "saved_log";
+static const char kFailedUploadCountName[] = "failed_upload_count";
+static const char kDefaultVersion[] = "0.0.0.0";
+
+// Build time properties name.
+static const char kProductId[] = "product_id";
+static const char kProductVersion[] = "product_version";
+
+// Weave configuration.
+static const char kWeaveConfigurationFile[] = "/system/etc/weaved/weaved.conf";
+static const char kModelManifestId[] = "model_id";
+}  // namespace metrics
+
+#endif  // METRICS_CONSTANTS_H_
diff --git a/etc/weaved/traits/metrics.json b/etc/weaved/traits/metrics.json
new file mode 100644
index 0000000..7583270
--- /dev/null
+++ b/etc/weaved/traits/metrics.json
@@ -0,0 +1,20 @@
+{
+  "_metrics": {
+    "commands": {
+      "enableAnalyticsReporting": {
+        "minimalRole": "manager",
+        "parameters": {}
+      },
+      "disableAnalyticsReporting": {
+        "minimalRole": "manager",
+        "parameters": {}
+      }
+    },
+    "state": {
+      "analyticsReportingState": {
+        "type": "string",
+        "enum": [ "enabled", "disabled" ]
+      }
+    }
+  }
+}
diff --git a/include/metrics/c_metrics_library.h b/include/metrics/c_metrics_library.h
new file mode 100644
index 0000000..1e597c2
--- /dev/null
+++ b/include/metrics/c_metrics_library.h
@@ -0,0 +1,57 @@
+/*
+ * 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 METRICS_C_METRICS_LIBRARY_H_
+#define METRICS_C_METRICS_LIBRARY_H_
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+typedef struct CMetricsLibraryOpaque* CMetricsLibrary;
+
+// C wrapper for MetricsLibrary::MetricsLibrary.
+CMetricsLibrary CMetricsLibraryNew(void);
+
+// C wrapper for MetricsLibrary::~MetricsLibrary.
+void CMetricsLibraryDelete(CMetricsLibrary handle);
+
+// C wrapper for MetricsLibrary::Init.
+void CMetricsLibraryInit(CMetricsLibrary handle);
+
+// C wrapper for MetricsLibrary::SendToUMA.
+int CMetricsLibrarySendToUMA(CMetricsLibrary handle,
+                             const char* name, int sample,
+                             int min, int max, int nbuckets);
+
+// C wrapper for MetricsLibrary::SendEnumToUMA.
+int CMetricsLibrarySendEnumToUMA(CMetricsLibrary handle,
+                                 const char* name, int sample, int max);
+
+// C wrapper for MetricsLibrary::SendSparseToUMA.
+int CMetricsLibrarySendSparseToUMA(CMetricsLibrary handle,
+                                   const char* name, int sample);
+
+// C wrapper for MetricsLibrary::SendCrashToUMA.
+int CMetricsLibrarySendCrashToUMA(CMetricsLibrary handle,
+                                  const char* crash_kind);
+
+// C wrapper for MetricsLibrary::AreMetricsEnabled.
+int CMetricsLibraryAreMetricsEnabled(CMetricsLibrary handle);
+
+#if defined(__cplusplus)
+}
+#endif
+#endif  // METRICS_C_METRICS_LIBRARY_H_
diff --git a/include/metrics/metrics_collector_service_client.h b/include/metrics/metrics_collector_service_client.h
new file mode 100644
index 0000000..c800eae
--- /dev/null
+++ b/include/metrics/metrics_collector_service_client.h
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+// Client interface to IMetricsCollectorService.
+
+#ifndef METRICS_METRICS_COLLECTOR_SERVICE_CLIENT_H_
+#define METRICS_METRICS_COLLECTOR_SERVICE_CLIENT_H_
+
+#include "android/brillo/metrics/IMetricsCollectorService.h"
+
+class MetricsCollectorServiceClient {
+ public:
+  MetricsCollectorServiceClient() = default;
+  ~MetricsCollectorServiceClient() = default;
+
+  // Initialize.  Returns true if OK, or false if IMetricsCollectorService
+  // is not registered.
+  bool Init();
+
+  // Called by crash_reporter to report a userspace crash event.  Returns
+  // true if successfully called the IMetricsCollectorService method of the
+  // same name, or false if the service was not registered at Init() time.
+  bool notifyUserCrash();
+
+ private:
+  // IMetricsCollectorService binder proxy
+  android::sp<android::brillo::metrics::IMetricsCollectorService>
+      metrics_collector_service_;
+};
+
+#endif  // METRICS_METRICS_COLLECTOR_SERVICE_CLIENT_H_
diff --git a/include/metrics/metrics_library.h b/include/metrics/metrics_library.h
new file mode 100644
index 0000000..a1bb926
--- /dev/null
+++ b/include/metrics/metrics_library.h
@@ -0,0 +1,175 @@
+/*
+ * 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 METRICS_METRICS_LIBRARY_H_
+#define METRICS_METRICS_LIBRARY_H_
+
+#include <sys/types.h>
+#include <string>
+#include <unistd.h>
+
+#include <base/compiler_specific.h>
+#include <base/files/file_path.h>
+#include <base/macros.h>
+#include <binder/IServiceManager.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+namespace android {
+namespace brillo {
+namespace metrics {
+class IMetricsd;
+}  // namespace metrics
+}  // namespace brillo
+}  // namespace android
+
+class MetricsLibraryInterface {
+ public:
+  virtual void Init() = 0;
+  virtual bool AreMetricsEnabled() = 0;
+  virtual bool SendToUMA(const std::string& name, int sample,
+                         int min, int max, int nbuckets) = 0;
+  virtual bool SendEnumToUMA(const std::string& name, int sample, int max) = 0;
+  virtual bool SendBoolToUMA(const std::string& name, bool sample) = 0;
+  virtual bool SendSparseToUMA(const std::string& name, int sample) = 0;
+  virtual ~MetricsLibraryInterface() {}
+};
+
+// Library used to send metrics to Chrome/UMA.
+class MetricsLibrary : public MetricsLibraryInterface {
+ public:
+  MetricsLibrary();
+  virtual ~MetricsLibrary();
+
+  // Initializes the library.
+  void Init() override;
+
+  // Initializes the library and disables the cache of whether or not the
+  // metrics collection is enabled.
+  // By disabling this, we may have to check for the metrics status more often
+  // but the result will never be stale.
+  void InitWithNoCaching();
+
+  // Returns whether or not the machine is running in guest mode.
+  bool IsGuestMode();
+
+  // Returns whether or not metrics collection is enabled.
+  bool AreMetricsEnabled() override;
+
+  // Sends histogram data to Chrome for transport to UMA and returns
+  // true on success. This method results in the equivalent of an
+  // asynchronous non-blocking RPC to UMA_HISTOGRAM_CUSTOM_COUNTS
+  // inside Chrome (see base/histogram.h).
+  //
+  // |sample| is the sample value to be recorded (|min| <= |sample| < |max|).
+  // |min| is the minimum value of the histogram samples (|min| > 0).
+  // |max| is the maximum value of the histogram samples.
+  // |nbuckets| is the number of histogram buckets.
+  // [0,min) is the implicit underflow bucket.
+  // [|max|,infinity) is the implicit overflow bucket.
+  //
+  // Note that the memory allocated in Chrome for each histogram is
+  // proportional to the number of buckets. Therefore, it is strongly
+  // recommended to keep this number low (e.g., 50 is normal, while
+  // 100 is high).
+  bool SendToUMA(const std::string& name, int sample,
+                 int min, int max, int nbuckets) override;
+
+  // Sends linear histogram data to Chrome for transport to UMA and
+  // returns true on success. This method results in the equivalent of
+  // an asynchronous non-blocking RPC to UMA_HISTOGRAM_ENUMERATION
+  // inside Chrome (see base/histogram.h).
+  //
+  // |sample| is the sample value to be recorded (1 <= |sample| < |max|).
+  // |max| is the maximum value of the histogram samples.
+  // 0 is the implicit underflow bucket.
+  // [|max|,infinity) is the implicit overflow bucket.
+  //
+  // An enumeration histogram requires |max| + 1 number of
+  // buckets. Note that the memory allocated in Chrome for each
+  // histogram is proportional to the number of buckets. Therefore, it
+  // is strongly recommended to keep this number low (e.g., 50 is
+  // normal, while 100 is high).
+  bool SendEnumToUMA(const std::string& name, int sample, int max) override;
+
+  // Specialization of SendEnumToUMA for boolean values.
+  bool SendBoolToUMA(const std::string& name, bool sample) override;
+
+  // Sends sparse histogram sample to Chrome for transport to UMA.  Returns
+  // true on success.
+  //
+  // |sample| is the 32-bit integer value to be recorded.
+  bool SendSparseToUMA(const std::string& name, int sample) override;
+
+  // Sends a signal to UMA that a crash of the given |crash_kind|
+  // has occurred.  Used by UMA to generate stability statistics.
+  bool SendCrashToUMA(const char *crash_kind);
+
+  // Sends a "generic Chrome OS event" to UMA.  This is an event name
+  // that is translated into an enumerated histogram entry.  Event names
+  // are added to metrics_library.cc.  Optionally, they can be added
+  // to histograms.xml---but part of the reason for this is to simplify
+  // the addition of events (at the cost of having to look them up by
+  // number in the histograms dashboard).
+  bool SendCrosEventToUMA(const std::string& event);
+
+  // Debugging only.
+  // Dumps the histograms aggregated since metricsd started into |dump|.
+  // Returns true iff the dump succeeds.
+  bool GetHistogramsDump(std::string* dump);
+
+ private:
+  friend class CMetricsLibraryTest;
+  friend class MetricsLibraryTest;
+  friend class UploadServiceTest;
+  FRIEND_TEST(MetricsLibraryTest, AreMetricsEnabled);
+  FRIEND_TEST(MetricsLibraryTest, AreMetricsEnabledNoCaching);
+  FRIEND_TEST(MetricsLibraryTest, FormatChromeMessage);
+  FRIEND_TEST(MetricsLibraryTest, FormatChromeMessageTooLong);
+  FRIEND_TEST(MetricsLibraryTest, IsDeviceMounted);
+  FRIEND_TEST(MetricsLibraryTest, SendMessageToChrome);
+  FRIEND_TEST(MetricsLibraryTest, SendMessageToChromeUMAEventsBadFileLocation);
+
+  void InitForTest(const base::FilePath& metrics_directory);
+
+  // Sets |*result| to whether or not the |mounts_file| indicates that
+  // the |device_name| is currently mounted.  Uses |buffer| of
+  // |buffer_size| to read the file.  Returns false if any error.
+  bool IsDeviceMounted(const char* device_name,
+                       const char* mounts_file,
+                       char* buffer, int buffer_size,
+                       bool* result);
+
+  // Connects to IMetricsd if the proxy does not exist or is not alive.
+  // Don't block if we fail to get the proxy for any reason.
+  bool CheckService();
+
+  // Time at which we last checked if metrics were enabled.
+  time_t cached_enabled_time_;
+
+  // Cached state of whether or not metrics were enabled.
+  bool cached_enabled_;
+
+  // True iff we should cache the enabled/disabled status.
+  bool use_caching_;
+
+  android::sp<android::IServiceManager> manager_;
+  android::sp<android::brillo::metrics::IMetricsd> metricsd_proxy_;
+  base::FilePath consent_file_;
+
+  DISALLOW_COPY_AND_ASSIGN(MetricsLibrary);
+};
+
+#endif  // METRICS_METRICS_LIBRARY_H_
diff --git a/include/metrics/metrics_library_mock.h b/include/metrics/metrics_library_mock.h
new file mode 100644
index 0000000..3b0b24d
--- /dev/null
+++ b/include/metrics/metrics_library_mock.h
@@ -0,0 +1,41 @@
+/*
+ * 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 METRICS_METRICS_LIBRARY_MOCK_H_
+#define METRICS_METRICS_LIBRARY_MOCK_H_
+
+#include <string>
+
+#include "metrics/metrics_library.h"
+
+#include <gmock/gmock.h>
+
+class MetricsLibraryMock : public MetricsLibraryInterface {
+ public:
+  bool metrics_enabled_ = true;
+
+  MOCK_METHOD0(Init, void());
+  MOCK_METHOD5(SendToUMA, bool(const std::string& name, int sample,
+                               int min, int max, int nbuckets));
+  MOCK_METHOD3(SendEnumToUMA, bool(const std::string& name, int sample,
+                                   int max));
+  MOCK_METHOD2(SendBoolToUMA, bool(const std::string& name, bool sample));
+  MOCK_METHOD2(SendSparseToUMA, bool(const std::string& name, int sample));
+
+  bool AreMetricsEnabled() override {return metrics_enabled_;};
+};
+
+#endif  // METRICS_METRICS_LIBRARY_MOCK_H_
diff --git a/include/metrics/timer.h b/include/metrics/timer.h
new file mode 100644
index 0000000..c1b8ede
--- /dev/null
+++ b/include/metrics/timer.h
@@ -0,0 +1,170 @@
+/*
+ * 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.
+ */
+
+// Timer - class that provides timer tracking.
+
+#ifndef METRICS_TIMER_H_
+#define METRICS_TIMER_H_
+
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+#include <base/time/time.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+class MetricsLibraryInterface;
+
+namespace chromeos_metrics {
+
+class TimerInterface {
+ public:
+  virtual ~TimerInterface() {}
+
+  virtual bool Start() = 0;
+  virtual bool Stop() = 0;
+  virtual bool Reset() = 0;
+  virtual bool HasStarted() const = 0;
+};
+
+// Wrapper for calls to the system clock.
+class ClockWrapper {
+ public:
+  ClockWrapper() {}
+  virtual ~ClockWrapper() {}
+
+  // Returns the current time from the system.
+  virtual base::TimeTicks GetCurrentTime() const;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ClockWrapper);
+};
+
+// Implements a Timer.
+class Timer : public TimerInterface {
+ public:
+  Timer();
+  virtual ~Timer() {}
+
+  // Starts the timer. If a timer is already running, also resets current
+  // timer. Always returns true.
+  virtual bool Start();
+
+  // Stops the timer and calculates the total time elapsed between now and when
+  // Start() was called. Note that this method needs a prior call to Start().
+  // Otherwise, it fails (returns false).
+  virtual bool Stop();
+
+  // Pauses a timer.  If the timer is stopped, this call starts the timer in
+  // the paused state. Fails (returns false) if the timer is already paused.
+  virtual bool Pause();
+
+  // Restarts a paused timer (or starts a stopped timer). This method fails
+  // (returns false) if the timer is already running; otherwise, returns true.
+  virtual bool Resume();
+
+  // Resets the timer, erasing the current duration being tracked. Always
+  // returns true.
+  virtual bool Reset();
+
+  // Returns whether the timer has started or not.
+  virtual bool HasStarted() const;
+
+  // Stores the current elapsed time in |elapsed_time|. If timer is stopped,
+  // stores the elapsed time from when Stop() was last called. Otherwise,
+  // calculates and stores the elapsed time since the last Start().
+  // Returns false if the timer was never Start()'ed or if called with a null
+  // pointer argument.
+  virtual bool GetElapsedTime(base::TimeDelta* elapsed_time) const;
+
+ private:
+  enum TimerState { kTimerStopped, kTimerRunning, kTimerPaused };
+  friend class TimerTest;
+  friend class TimerReporterTest;
+  FRIEND_TEST(TimerReporterTest, StartStopReport);
+  FRIEND_TEST(TimerTest, InvalidElapsedTime);
+  FRIEND_TEST(TimerTest, InvalidStop);
+  FRIEND_TEST(TimerTest, PauseResumeStop);
+  FRIEND_TEST(TimerTest, PauseStartStopResume);
+  FRIEND_TEST(TimerTest, PauseStop);
+  FRIEND_TEST(TimerTest, Reset);
+  FRIEND_TEST(TimerTest, ReStart);
+  FRIEND_TEST(TimerTest, ResumeStartStopPause);
+  FRIEND_TEST(TimerTest, SeparatedTimers);
+  FRIEND_TEST(TimerTest, StartPauseResumePauseResumeStop);
+  FRIEND_TEST(TimerTest, StartPauseResumePauseStop);
+  FRIEND_TEST(TimerTest, StartPauseResumeStop);
+  FRIEND_TEST(TimerTest, StartPauseStop);
+  FRIEND_TEST(TimerTest, StartResumeStop);
+  FRIEND_TEST(TimerTest, StartStop);
+
+  // Elapsed time of the last use of the timer.
+  base::TimeDelta elapsed_time_;
+
+  // Starting time value.
+  base::TimeTicks start_time_;
+
+  // Whether the timer is running, stopped, or paused.
+  TimerState timer_state_;
+
+  // Wrapper for the calls to the system clock.
+  std::unique_ptr<ClockWrapper> clock_wrapper_;
+
+  DISALLOW_COPY_AND_ASSIGN(Timer);
+};
+
+// Extends the Timer class to report the elapsed time in milliseconds through
+// the UMA metrics library.
+class TimerReporter : public Timer {
+ public:
+  // Initializes the timer by providing a |histogram_name| to report to with
+  // |min|, |max| and |num_buckets| attributes for the histogram.
+  TimerReporter(const std::string& histogram_name, int min, int max,
+                int num_buckets);
+  virtual ~TimerReporter() {}
+
+  // Sets the metrics library used by all instances of this class.
+  static void set_metrics_lib(MetricsLibraryInterface* metrics_lib) {
+    metrics_lib_ = metrics_lib;
+  }
+
+  // Reports the current duration to UMA, in milliseconds. Returns false if
+  // there is nothing to report, e.g. a metrics library is not set.
+  virtual bool ReportMilliseconds() const;
+
+  // Accessor methods.
+  const std::string& histogram_name() const { return histogram_name_; }
+  int min() const { return min_; }
+  int max() const { return max_; }
+  int num_buckets() const { return num_buckets_; }
+
+ private:
+  friend class TimerReporterTest;
+  FRIEND_TEST(TimerReporterTest, StartStopReport);
+  FRIEND_TEST(TimerReporterTest, InvalidReport);
+
+  static MetricsLibraryInterface* metrics_lib_;
+  std::string histogram_name_;
+  int min_;
+  int max_;
+  int num_buckets_;
+
+  DISALLOW_COPY_AND_ASSIGN(TimerReporter);
+};
+
+}  // namespace chromeos_metrics
+
+#endif  // METRICS_TIMER_H_
diff --git a/include/metrics/timer_mock.h b/include/metrics/timer_mock.h
new file mode 100644
index 0000000..200ee9a
--- /dev/null
+++ b/include/metrics/timer_mock.h
@@ -0,0 +1,59 @@
+/*
+ * 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 METRICS_TIMER_MOCK_H_
+#define METRICS_TIMER_MOCK_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "metrics/timer.h"
+
+namespace chromeos_metrics {
+
+class TimerMock : public Timer {
+ public:
+  MOCK_METHOD0(Start, bool());
+  MOCK_METHOD0(Stop, bool());
+  MOCK_METHOD0(Reset, bool());
+  MOCK_CONST_METHOD0(HasStarted, bool());
+  MOCK_CONST_METHOD1(GetElapsedTime, bool(base::TimeDelta* elapsed_time));
+};
+
+class TimerReporterMock : public TimerReporter {
+ public:
+  TimerReporterMock() : TimerReporter("", 0, 0, 0) {}
+  MOCK_METHOD0(Start, bool());
+  MOCK_METHOD0(Stop, bool());
+  MOCK_METHOD0(Reset, bool());
+  MOCK_CONST_METHOD0(HasStarted, bool());
+  MOCK_CONST_METHOD1(GetElapsedTime, bool(base::TimeDelta* elapsed_time));
+  MOCK_CONST_METHOD0(ReportMilliseconds, bool());
+  MOCK_CONST_METHOD0(histogram_name, std::string&());
+  MOCK_CONST_METHOD0(min, int());
+  MOCK_CONST_METHOD0(max, int());
+  MOCK_CONST_METHOD0(num_buckets, int());
+};
+
+class ClockWrapperMock : public ClockWrapper {
+ public:
+  MOCK_CONST_METHOD0(GetCurrentTime, base::TimeTicks());
+};
+
+}  // namespace chromeos_metrics
+
+#endif  // METRICS_TIMER_MOCK_H_
diff --git a/libmetrics-369476.gyp b/libmetrics-369476.gyp
new file mode 100644
index 0000000..b545d35
--- /dev/null
+++ b/libmetrics-369476.gyp
@@ -0,0 +1,8 @@
+{
+  'variables': {
+    'libbase_ver': 369476,
+  },
+  'includes': [
+    'libmetrics.gypi',
+  ],
+}
diff --git a/libmetrics.gypi b/libmetrics.gypi
new file mode 100644
index 0000000..3c8fc7c
--- /dev/null
+++ b/libmetrics.gypi
@@ -0,0 +1,33 @@
+{
+  'target_defaults': {
+    'variables': {
+      'deps': [
+        'libbrillo-<(libbase_ver)',
+        'libchrome-<(libbase_ver)',
+      ]
+    },
+    'cflags_cc': [
+      '-fno-exceptions',
+    ],
+  },
+  'targets': [
+    {
+      'target_name': 'libmetrics-<(libbase_ver)',
+      'type': 'shared_library',
+      'cflags': [
+        '-fvisibility=default',
+      ],
+      'libraries+': [
+        '-lpolicy-<(libbase_ver)',
+      ],
+      'sources': [
+        'c_metrics_library.cc',
+        'metrics_library.cc',
+        'serialization/metric_sample.cc',
+        'serialization/serialization_utils.cc',
+        'timer.cc',
+      ],
+      'include_dirs': ['.'],
+    },
+  ],
+}
diff --git a/libmetrics.pc.in b/libmetrics.pc.in
new file mode 100644
index 0000000..233f318
--- /dev/null
+++ b/libmetrics.pc.in
@@ -0,0 +1,7 @@
+bslot=@BSLOT@
+
+Name: libmetrics
+Description: Chrome OS metrics library
+Version: ${bslot}
+Requires.private: libchrome-${bslot}
+Libs: -lmetrics-${bslot}
diff --git a/metrics.gyp b/metrics.gyp
new file mode 100644
index 0000000..c9c02d7
--- /dev/null
+++ b/metrics.gyp
@@ -0,0 +1,184 @@
+{
+  'target_defaults': {
+    'variables': {
+      'deps': [
+        'dbus-1',
+        'libbrillo-<(libbase_ver)',
+        'libchrome-<(libbase_ver)',
+      ]
+    },
+    'cflags_cc': [
+      '-fno-exceptions',
+    ],
+  },
+  'targets': [
+    {
+      'target_name': 'libmetrics_daemon',
+      'type': 'static_library',
+      'dependencies': [
+        '../metrics/libmetrics-<(libbase_ver).gyp:libmetrics-<(libbase_ver)',
+        'libupload_service',
+        'metrics_proto',
+      ],
+      'link_settings': {
+        'libraries': [
+          '-lrootdev',
+        ],
+      },
+      'sources': [
+        'persistent_integer.cc',
+        'metrics_daemon.cc',
+        'metrics_daemon_main.cc',
+      ],
+      'include_dirs': ['.'],
+    },
+    {
+      'target_name': 'metrics_client',
+      'type': 'executable',
+      'dependencies': [
+        '../metrics/libmetrics-<(libbase_ver).gyp:libmetrics-<(libbase_ver)',
+      ],
+      'sources': [
+        'metrics_client.cc',
+      ]
+    },
+    {
+      'target_name': 'libupload_service',
+      'type': 'static_library',
+      'dependencies': [
+        'metrics_proto',
+        '../metrics/libmetrics-<(libbase_ver).gyp:libmetrics-<(libbase_ver)',
+      ],
+      'link_settings': {
+        'libraries': [
+          '-lvboot_host',
+        ],
+      },
+      'variables': {
+        'exported_deps': [
+          'protobuf-lite',
+        ],
+        'deps': [
+          '<@(exported_deps)',
+        ],
+      },
+      'all_dependent_settings': {
+        'variables': {
+          'deps+': [
+            '<@(exported_deps)',
+          ],
+        },
+      },
+      'sources': [
+        'uploader/upload_service.cc',
+        'uploader/metrics_hashes.cc',
+        'uploader/metrics_log.cc',
+        'uploader/metrics_log_base.cc',
+        'uploader/system_profile_cache.cc',
+        'uploader/sender_http.cc',
+      ],
+      'include_dirs': ['.']
+    },
+    {
+      'target_name': 'metrics_proto',
+      'type': 'static_library',
+      'variables': {
+        'proto_in_dir': 'uploader/proto',
+        'proto_out_dir': 'include/metrics/uploader/proto',
+      },
+      'sources': [
+        '<(proto_in_dir)/chrome_user_metrics_extension.proto',
+        '<(proto_in_dir)/histogram_event.proto',
+        '<(proto_in_dir)/system_profile.proto',
+        '<(proto_in_dir)/user_action_event.proto',
+      ],
+      'includes': [
+        '../common-mk/protoc.gypi'
+      ],
+    },
+  ],
+  'conditions': [
+    ['USE_passive_metrics == 1', {
+      'targets': [
+        {
+          'target_name': 'metrics_daemon',
+          'type': 'executable',
+          'dependencies': ['libmetrics_daemon'],
+        },
+      ],
+    }],
+    ['USE_test == 1', {
+      'targets': [
+        {
+          'target_name': 'persistent_integer_test',
+          'type': 'executable',
+          'includes': ['../common-mk/common_test.gypi'],
+          'sources': [
+            'persistent_integer.cc',
+            'persistent_integer_test.cc',
+          ]
+        },
+        {
+          'target_name': 'metrics_library_test',
+          'type': 'executable',
+          'dependencies': [
+            '../metrics/libmetrics-<(libbase_ver).gyp:libmetrics-<(libbase_ver)',
+          ],
+          'includes': ['../common-mk/common_test.gypi'],
+          'sources': [
+            'metrics_library_test.cc',
+            'serialization/serialization_utils_unittest.cc',
+          ],
+          'link_settings': {
+            'libraries': [
+              '-lpolicy-<(libbase_ver)',
+            ]
+          }
+        },
+        {
+          'target_name': 'timer_test',
+          'type': 'executable',
+          'includes': ['../common-mk/common_test.gypi'],
+          'sources': [
+            'timer.cc',
+            'timer_test.cc',
+          ]
+        },
+        {
+          'target_name': 'upload_service_test',
+          'type': 'executable',
+          'sources': [
+            'persistent_integer.cc',
+            'uploader/metrics_hashes_unittest.cc',
+            'uploader/metrics_log_base_unittest.cc',
+            'uploader/mock/sender_mock.cc',
+            'uploader/upload_service_test.cc',
+          ],
+          'dependencies': [
+            'libupload_service',
+          ],
+          'includes':[
+            '../common-mk/common_test.gypi',
+          ],
+          'include_dirs': ['.']
+        },
+      ],
+    }],
+    ['USE_passive_metrics == 1 and USE_test == 1', {
+      'targets': [
+        {
+          'target_name': 'metrics_daemon_test',
+          'type': 'executable',
+          'dependencies': [
+            'libmetrics_daemon',
+          ],
+          'includes': ['../common-mk/common_test.gypi'],
+          'sources': [
+            'metrics_daemon_test.cc',
+          ],
+          'include_dirs': ['.'],
+        },
+      ],
+    }],
+  ]
+}
diff --git a/metrics_client.cc b/metrics_client.cc
new file mode 100644
index 0000000..c66b975
--- /dev/null
+++ b/metrics_client.cc
@@ -0,0 +1,214 @@
+/*
+ * 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 <cstdio>
+#include <cstdlib>
+
+#include "constants.h"
+#include "metrics/metrics_library.h"
+
+enum Mode {
+    kModeDumpHistograms,
+    kModeSendSample,
+    kModeSendEnumSample,
+    kModeSendSparseSample,
+    kModeSendCrosEvent,
+    kModeHasConsent,
+    kModeIsGuestMode,
+};
+
+void ShowUsage() {
+  fprintf(stderr,
+          "Usage:  metrics_client [-t] name sample min max nbuckets\n"
+          "        metrics_client -e   name sample max\n"
+          "        metrics_client -s   name sample\n"
+          "        metrics_client -v   event\n"
+          "        metrics_client [-cdg]\n"
+          "\n"
+          "  default: send metric with integer values \n"
+          "           |min| > 0, |min| <= sample < |max|\n"
+          "  -c: return exit status 0 if user consents to stats, 1 otherwise,\n"
+          "      in guest mode always return 1\n"
+          "  -d: dump the histograms recorded by metricsd to stdout\n"
+          "  -e: send linear/enumeration histogram data\n"
+          "  -g: return exit status 0 if machine in guest mode, 1 otherwise\n"
+          "  -s: send a sparse histogram sample\n"
+          "  -t: convert sample from double seconds to int milliseconds\n"
+          "  -v: send a Platform.CrOSEvent enum histogram sample\n");
+  exit(1);
+}
+
+static int ParseInt(const char *arg) {
+  char *endptr;
+  int value = strtol(arg, &endptr, 0);
+  if (*endptr != '\0') {
+    fprintf(stderr, "metrics client: bad integer \"%s\"\n", arg);
+    ShowUsage();
+  }
+  return value;
+}
+
+static double ParseDouble(const char *arg) {
+  char *endptr;
+  double value = strtod(arg, &endptr);
+  if (*endptr != '\0') {
+    fprintf(stderr, "metrics client: bad double \"%s\"\n", arg);
+    ShowUsage();
+  }
+  return value;
+}
+
+static int DumpHistograms() {
+  MetricsLibrary metrics_lib;
+  metrics_lib.Init();
+
+  std::string dump;
+  if (!metrics_lib.GetHistogramsDump(&dump)) {
+    printf("Failed to dump the histograms.");
+    return 1;
+  }
+
+  printf("%s\n", dump.c_str());
+  return 0;
+}
+
+static int SendStats(char* argv[],
+                     int name_index,
+                     enum Mode mode,
+                     bool secs_to_msecs) {
+  const char* name = argv[name_index];
+  int sample;
+  if (secs_to_msecs) {
+    sample = static_cast<int>(ParseDouble(argv[name_index + 1]) * 1000.0);
+  } else {
+    sample = ParseInt(argv[name_index + 1]);
+  }
+
+  MetricsLibrary metrics_lib;
+  metrics_lib.Init();
+  if (mode == kModeSendSparseSample) {
+    metrics_lib.SendSparseToUMA(name, sample);
+  } else if (mode == kModeSendEnumSample) {
+    int max = ParseInt(argv[name_index + 2]);
+    metrics_lib.SendEnumToUMA(name, sample, max);
+  } else {
+    int min = ParseInt(argv[name_index + 2]);
+    int max = ParseInt(argv[name_index + 3]);
+    int nbuckets = ParseInt(argv[name_index + 4]);
+    metrics_lib.SendToUMA(name, sample, min, max, nbuckets);
+  }
+  return 0;
+}
+
+static int SendCrosEvent(char* argv[], int action_index) {
+  const char* event = argv[action_index];
+  bool result;
+  MetricsLibrary metrics_lib;
+  metrics_lib.Init();
+  result = metrics_lib.SendCrosEventToUMA(event);
+  if (!result) {
+    fprintf(stderr, "metrics_client: could not send event %s\n", event);
+    return 1;
+  }
+  return 0;
+}
+
+static int HasConsent() {
+  MetricsLibrary metrics_lib;
+  metrics_lib.Init();
+  return metrics_lib.AreMetricsEnabled() ? 0 : 1;
+}
+
+static int IsGuestMode() {
+  MetricsLibrary metrics_lib;
+  metrics_lib.Init();
+  return metrics_lib.IsGuestMode() ? 0 : 1;
+}
+
+int main(int argc, char** argv) {
+  enum Mode mode = kModeSendSample;
+  bool secs_to_msecs = false;
+
+  // Parse arguments
+  int flag;
+  while ((flag = getopt(argc, argv, "abcdegstv")) != -1) {
+    switch (flag) {
+      case 'c':
+        mode = kModeHasConsent;
+        break;
+      case 'd':
+        mode = kModeDumpHistograms;
+        break;
+      case 'e':
+        mode = kModeSendEnumSample;
+        break;
+      case 'g':
+        mode = kModeIsGuestMode;
+        break;
+      case 's':
+        mode = kModeSendSparseSample;
+        break;
+      case 't':
+        secs_to_msecs = true;
+        break;
+      case 'v':
+        mode = kModeSendCrosEvent;
+        break;
+      default:
+        ShowUsage();
+        break;
+    }
+  }
+  int arg_index = optind;
+
+  int expected_args = 0;
+  if (mode == kModeSendSample)
+    expected_args = 5;
+  else if (mode == kModeSendEnumSample)
+    expected_args = 3;
+  else if (mode == kModeSendSparseSample)
+    expected_args = 2;
+  else if (mode == kModeSendCrosEvent)
+    expected_args = 1;
+
+  if ((arg_index + expected_args) != argc) {
+    ShowUsage();
+  }
+
+  switch (mode) {
+    case kModeDumpHistograms:
+      return DumpHistograms();
+    case kModeSendSample:
+    case kModeSendEnumSample:
+    case kModeSendSparseSample:
+      if ((mode != kModeSendSample) && secs_to_msecs) {
+        ShowUsage();
+      }
+      return SendStats(argv,
+                       arg_index,
+                       mode,
+                       secs_to_msecs);
+    case kModeSendCrosEvent:
+      return SendCrosEvent(argv, arg_index);
+    case kModeHasConsent:
+      return HasConsent();
+    case kModeIsGuestMode:
+      return IsGuestMode();
+    default:
+      ShowUsage();
+      return 0;
+  }
+}
diff --git a/metrics_collector.cc b/metrics_collector.cc
new file mode 100644
index 0000000..45ae0a4
--- /dev/null
+++ b/metrics_collector.cc
@@ -0,0 +1,756 @@
+/*
+ * 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 "metrics_collector.h"
+
+#include <sysexits.h>
+#include <time.h>
+
+#include <memory>
+
+#include <base/bind.h>
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/hash.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/binder_watcher.h>
+#include <brillo/osrelease_reader.h>
+
+#include "constants.h"
+#include "metrics_collector_service_impl.h"
+
+using base::FilePath;
+using base::StringPrintf;
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+using chromeos_metrics::PersistentInteger;
+using std::map;
+using std::string;
+using std::vector;
+
+namespace {
+
+const int kSecondsPerMinute = 60;
+const int kMinutesPerHour = 60;
+const int kHoursPerDay = 24;
+const int kMinutesPerDay = kHoursPerDay * kMinutesPerHour;
+const int kSecondsPerDay = kSecondsPerMinute * kMinutesPerDay;
+const int kDaysPerWeek = 7;
+const int kSecondsPerWeek = kSecondsPerDay * kDaysPerWeek;
+
+// Interval between calls to UpdateStats().
+const uint32_t kUpdateStatsIntervalMs = 300000;
+
+const char kKernelCrashDetectedFile[] =
+    "/data/misc/crash_reporter/run/kernel-crash-detected";
+const char kUncleanShutdownDetectedFile[] =
+    "/var/run/unclean-shutdown-detected";
+
+const int kMetricMeminfoInterval = 30;    // seconds
+
+const char kMeminfoFileName[] = "/proc/meminfo";
+const char kVmStatFileName[] = "/proc/vmstat";
+
+const char kWeaveComponent[] = "metrics";
+const char kWeaveTrait[] = "_metrics";
+
+}  // namespace
+
+// Zram sysfs entries.
+
+const char MetricsCollector::kComprDataSizeName[] = "compr_data_size";
+const char MetricsCollector::kOrigDataSizeName[] = "orig_data_size";
+const char MetricsCollector::kZeroPagesName[] = "zero_pages";
+
+// Memory use stats collection intervals.  We collect some memory use interval
+// at these intervals after boot, and we stop collecting after the last one,
+// with the assumption that in most cases the memory use won't change much
+// after that.
+static const int kMemuseIntervals[] = {
+  1 * kSecondsPerMinute,    // 1 minute mark
+  4 * kSecondsPerMinute,    // 5 minute mark
+  25 * kSecondsPerMinute,   // 0.5 hour mark
+  120 * kSecondsPerMinute,  // 2.5 hour mark
+  600 * kSecondsPerMinute,  // 12.5 hour mark
+};
+
+MetricsCollector::MetricsCollector()
+    : memuse_final_time_(0),
+      memuse_interval_index_(0) {}
+
+MetricsCollector::~MetricsCollector() {
+}
+
+// static
+double MetricsCollector::GetActiveTime() {
+  struct timespec ts;
+  int r = clock_gettime(CLOCK_MONOTONIC, &ts);
+  if (r < 0) {
+    PLOG(WARNING) << "clock_gettime(CLOCK_MONOTONIC) failed";
+    return 0;
+  } else {
+    return ts.tv_sec + static_cast<double>(ts.tv_nsec) / (1000 * 1000 * 1000);
+  }
+}
+
+int MetricsCollector::Run() {
+  if (CheckSystemCrash(kKernelCrashDetectedFile)) {
+    ProcessKernelCrash();
+  }
+
+  if (CheckSystemCrash(kUncleanShutdownDetectedFile)) {
+    ProcessUncleanShutdown();
+  }
+
+  // On OS version change, clear version stats (which are reported daily).
+  int32_t version = GetOsVersionHash();
+  if (version_cycle_->Get() != version) {
+    version_cycle_->Set(version);
+    kernel_crashes_version_count_->Set(0);
+    version_cumulative_active_use_->Set(0);
+    version_cumulative_cpu_use_->Set(0);
+  }
+
+  // Start metricscollectorservice
+  android::sp<BnMetricsCollectorServiceImpl> metrics_collector_service =
+      new BnMetricsCollectorServiceImpl(this);
+  android::status_t status = android::defaultServiceManager()->addService(
+      metrics_collector_service->getInterfaceDescriptor(),
+      metrics_collector_service);
+  CHECK(status == android::OK)
+      << "failed to register service metricscollectorservice";
+
+  // Watch Binder events in the main loop
+  brillo::BinderWatcher binder_watcher;
+  CHECK(binder_watcher.Init()) << "Binder FD watcher init failed";
+  return brillo::Daemon::Run();
+}
+
+uint32_t MetricsCollector::GetOsVersionHash() {
+  brillo::OsReleaseReader reader;
+  reader.Load();
+  string version;
+  if (!reader.GetString(metrics::kProductVersion, &version)) {
+    LOG(ERROR) << "failed to read the product version.";
+    version = metrics::kDefaultVersion;
+  }
+
+  uint32_t version_hash = base::Hash(version);
+  if (testing_) {
+    version_hash = 42;  // return any plausible value for the hash
+  }
+  return version_hash;
+}
+
+void MetricsCollector::Init(bool testing, MetricsLibraryInterface* metrics_lib,
+                            const string& diskstats_path,
+                            const base::FilePath& private_metrics_directory,
+                            const base::FilePath& shared_metrics_directory) {
+  CHECK(metrics_lib);
+  testing_ = testing;
+  shared_metrics_directory_ = shared_metrics_directory;
+  metrics_lib_ = metrics_lib;
+
+  daily_active_use_.reset(new PersistentInteger("Platform.UseTime.PerDay",
+                                                private_metrics_directory));
+  version_cumulative_active_use_.reset(new PersistentInteger(
+      "Platform.CumulativeUseTime", private_metrics_directory));
+  version_cumulative_cpu_use_.reset(new PersistentInteger(
+      "Platform.CumulativeCpuTime", private_metrics_directory));
+
+  kernel_crash_interval_.reset(new PersistentInteger(
+      "Platform.KernelCrashInterval", private_metrics_directory));
+  unclean_shutdown_interval_.reset(new PersistentInteger(
+      "Platform.UncleanShutdownInterval", private_metrics_directory));
+  user_crash_interval_.reset(new PersistentInteger("Platform.UserCrashInterval",
+                                                   private_metrics_directory));
+
+  any_crashes_daily_count_.reset(new PersistentInteger(
+      "Platform.AnyCrashes.PerDay", private_metrics_directory));
+  any_crashes_weekly_count_.reset(new PersistentInteger(
+      "Platform.AnyCrashes.PerWeek", private_metrics_directory));
+  user_crashes_daily_count_.reset(new PersistentInteger(
+      "Platform.UserCrashes.PerDay", private_metrics_directory));
+  user_crashes_weekly_count_.reset(new PersistentInteger(
+      "Platform.UserCrashes.PerWeek", private_metrics_directory));
+  kernel_crashes_daily_count_.reset(new PersistentInteger(
+      "Platform.KernelCrashes.PerDay", private_metrics_directory));
+  kernel_crashes_weekly_count_.reset(new PersistentInteger(
+      "Platform.KernelCrashes.PerWeek", private_metrics_directory));
+  kernel_crashes_version_count_.reset(new PersistentInteger(
+      "Platform.KernelCrashesSinceUpdate", private_metrics_directory));
+  unclean_shutdowns_daily_count_.reset(new PersistentInteger(
+      "Platform.UncleanShutdown.PerDay", private_metrics_directory));
+  unclean_shutdowns_weekly_count_.reset(new PersistentInteger(
+      "Platform.UncleanShutdowns.PerWeek", private_metrics_directory));
+
+  daily_cycle_.reset(
+      new PersistentInteger("daily.cycle", private_metrics_directory));
+  weekly_cycle_.reset(
+      new PersistentInteger("weekly.cycle", private_metrics_directory));
+  version_cycle_.reset(
+      new PersistentInteger("version.cycle", private_metrics_directory));
+
+  disk_usage_collector_.reset(new DiskUsageCollector(metrics_lib_));
+  averaged_stats_collector_.reset(
+      new AveragedStatisticsCollector(metrics_lib_, diskstats_path,
+                                      kVmStatFileName));
+  cpu_usage_collector_.reset(new CpuUsageCollector(metrics_lib_));
+}
+
+int MetricsCollector::OnInit() {
+  int return_code = brillo::Daemon::OnInit();
+  if (return_code != EX_OK)
+    return return_code;
+
+  StatsReporterInit();
+
+  // Start collecting meminfo stats.
+  ScheduleMeminfoCallback(kMetricMeminfoInterval);
+  memuse_final_time_ = GetActiveTime() + kMemuseIntervals[0];
+  ScheduleMemuseCallback(kMemuseIntervals[0]);
+
+  if (testing_)
+    return EX_OK;
+
+  weave_service_subscription_ = weaved::Service::Connect(
+      brillo::MessageLoop::current(),
+      base::Bind(&MetricsCollector::OnWeaveServiceConnected,
+                 weak_ptr_factory_.GetWeakPtr()));
+
+  latest_cpu_use_microseconds_ = cpu_usage_collector_->GetCumulativeCpuUse();
+  base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+      base::Bind(&MetricsCollector::HandleUpdateStatsTimeout,
+                 weak_ptr_factory_.GetWeakPtr()),
+      base::TimeDelta::FromMilliseconds(kUpdateStatsIntervalMs));
+
+  return EX_OK;
+}
+
+void MetricsCollector::OnWeaveServiceConnected(
+    const std::weak_ptr<weaved::Service>& service) {
+  service_ = service;
+  auto weave_service = service_.lock();
+  if (!weave_service)
+    return;
+
+  weave_service->AddComponent(kWeaveComponent, {kWeaveTrait}, nullptr);
+  weave_service->AddCommandHandler(
+      kWeaveComponent, kWeaveTrait, "enableAnalyticsReporting",
+      base::Bind(&MetricsCollector::OnEnableMetrics,
+                 weak_ptr_factory_.GetWeakPtr()));
+  weave_service->AddCommandHandler(
+      kWeaveComponent, kWeaveTrait, "disableAnalyticsReporting",
+      base::Bind(&MetricsCollector::OnDisableMetrics,
+                 weak_ptr_factory_.GetWeakPtr()));
+
+  UpdateWeaveState();
+}
+
+void MetricsCollector::OnEnableMetrics(
+    std::unique_ptr<weaved::Command> command) {
+  if (base::WriteFile(
+          shared_metrics_directory_.Append(metrics::kConsentFileName), "", 0) !=
+      0) {
+    PLOG(ERROR) << "Could not create the consent file.";
+    command->Abort("metrics_error", "Could not create the consent file",
+                   nullptr);
+    return;
+  }
+
+  UpdateWeaveState();
+  command->Complete({}, nullptr);
+}
+
+void MetricsCollector::OnDisableMetrics(
+    std::unique_ptr<weaved::Command> command) {
+  if (!base::DeleteFile(
+          shared_metrics_directory_.Append(metrics::kConsentFileName), false)) {
+    PLOG(ERROR) << "Could not delete the consent file.";
+    command->Abort("metrics_error", "Could not delete the consent file",
+                   nullptr);
+    return;
+  }
+
+  UpdateWeaveState();
+  command->Complete({}, nullptr);
+}
+
+void MetricsCollector::UpdateWeaveState() {
+  auto weave_service = service_.lock();
+  if (!weave_service)
+    return;
+
+  std::string enabled =
+      metrics_lib_->AreMetricsEnabled() ? "enabled" : "disabled";
+
+  if (!weave_service->SetStateProperty(kWeaveComponent, kWeaveTrait,
+                                       "analyticsReportingState",
+                                       *brillo::ToValue(enabled),
+                                       nullptr)) {
+    LOG(ERROR) << "failed to update weave's state";
+  }
+}
+
+void MetricsCollector::ProcessUserCrash() {
+  // Counts the active time up to now.
+  UpdateStats(TimeTicks::Now(), Time::Now());
+
+  // Reports the active use time since the last crash and resets it.
+  SendAndResetCrashIntervalSample(user_crash_interval_);
+
+  any_crashes_daily_count_->Add(1);
+  any_crashes_weekly_count_->Add(1);
+  user_crashes_daily_count_->Add(1);
+  user_crashes_weekly_count_->Add(1);
+}
+
+void MetricsCollector::ProcessKernelCrash() {
+  // Counts the active time up to now.
+  UpdateStats(TimeTicks::Now(), Time::Now());
+
+  // Reports the active use time since the last crash and resets it.
+  SendAndResetCrashIntervalSample(kernel_crash_interval_);
+
+  any_crashes_daily_count_->Add(1);
+  any_crashes_weekly_count_->Add(1);
+  kernel_crashes_daily_count_->Add(1);
+  kernel_crashes_weekly_count_->Add(1);
+
+  kernel_crashes_version_count_->Add(1);
+}
+
+void MetricsCollector::ProcessUncleanShutdown() {
+  // Counts the active time up to now.
+  UpdateStats(TimeTicks::Now(), Time::Now());
+
+  // Reports the active use time since the last crash and resets it.
+  SendAndResetCrashIntervalSample(unclean_shutdown_interval_);
+
+  unclean_shutdowns_daily_count_->Add(1);
+  unclean_shutdowns_weekly_count_->Add(1);
+  any_crashes_daily_count_->Add(1);
+  any_crashes_weekly_count_->Add(1);
+}
+
+bool MetricsCollector::CheckSystemCrash(const string& crash_file) {
+  FilePath crash_detected(crash_file);
+  if (!base::PathExists(crash_detected))
+    return false;
+
+  // Deletes the crash-detected file so that the daemon doesn't report
+  // another kernel crash in case it's restarted.
+  base::DeleteFile(crash_detected, false);  // not recursive
+  return true;
+}
+
+void MetricsCollector::StatsReporterInit() {
+  disk_usage_collector_->Schedule();
+
+  cpu_usage_collector_->Init();
+  cpu_usage_collector_->Schedule();
+
+  // Don't start a collection cycle during the first run to avoid delaying the
+  // boot.
+  averaged_stats_collector_->ScheduleWait();
+}
+
+void MetricsCollector::ScheduleMeminfoCallback(int wait) {
+  if (testing_) {
+    return;
+  }
+  base::TimeDelta waitDelta = base::TimeDelta::FromSeconds(wait);
+  base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+      base::Bind(&MetricsCollector::MeminfoCallback,
+                 weak_ptr_factory_.GetWeakPtr(), waitDelta),
+      waitDelta);
+}
+
+void MetricsCollector::MeminfoCallback(base::TimeDelta wait) {
+  string meminfo_raw;
+  const FilePath meminfo_path(kMeminfoFileName);
+  if (!base::ReadFileToString(meminfo_path, &meminfo_raw)) {
+    LOG(WARNING) << "cannot read " << meminfo_path.value().c_str();
+    return;
+  }
+  // Make both calls even if the first one fails.
+  if (ProcessMeminfo(meminfo_raw)) {
+    base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+        base::Bind(&MetricsCollector::MeminfoCallback,
+                   weak_ptr_factory_.GetWeakPtr(), wait),
+        wait);
+  }
+}
+
+// static
+bool MetricsCollector::ReadFileToUint64(const base::FilePath& path,
+                                         uint64_t* value) {
+  std::string content;
+  if (!base::ReadFileToString(path, &content)) {
+    PLOG(WARNING) << "cannot read " << path.MaybeAsASCII();
+    return false;
+  }
+  // Remove final newline.
+  base::TrimWhitespaceASCII(content, base::TRIM_TRAILING, &content);
+  if (!base::StringToUint64(content, value)) {
+    LOG(WARNING) << "invalid integer: " << content;
+    return false;
+  }
+  return true;
+}
+
+bool MetricsCollector::ReportZram(const base::FilePath& zram_dir) {
+  // Data sizes are in bytes.  |zero_pages| is in number of pages.
+  uint64_t compr_data_size, orig_data_size, zero_pages;
+  const size_t page_size = 4096;
+
+  if (!ReadFileToUint64(zram_dir.Append(kComprDataSizeName),
+                        &compr_data_size) ||
+      !ReadFileToUint64(zram_dir.Append(kOrigDataSizeName), &orig_data_size) ||
+      !ReadFileToUint64(zram_dir.Append(kZeroPagesName), &zero_pages)) {
+    return false;
+  }
+
+  // |orig_data_size| does not include zero-filled pages.
+  orig_data_size += zero_pages * page_size;
+
+  const int compr_data_size_mb = compr_data_size >> 20;
+  const int savings_mb = (orig_data_size - compr_data_size) >> 20;
+  const int zero_ratio_percent = zero_pages * page_size * 100 / orig_data_size;
+
+  // Report compressed size in megabytes.  100 MB or less has little impact.
+  SendSample("Platform.ZramCompressedSize", compr_data_size_mb, 100, 4000, 50);
+  SendSample("Platform.ZramSavings", savings_mb, 100, 4000, 50);
+  // The compression ratio is multiplied by 100 for better resolution.  The
+  // ratios of interest are between 1 and 6 (100% and 600% as reported).  We
+  // don't want samples when very little memory is being compressed.
+  if (compr_data_size_mb >= 1) {
+    SendSample("Platform.ZramCompressionRatioPercent",
+               orig_data_size * 100 / compr_data_size, 100, 600, 50);
+  }
+  // The values of interest for zero_pages are between 1MB and 1GB.  The units
+  // are number of pages.
+  SendSample("Platform.ZramZeroPages", zero_pages, 256, 256 * 1024, 50);
+  SendSample("Platform.ZramZeroRatioPercent", zero_ratio_percent, 1, 50, 50);
+
+  return true;
+}
+
+bool MetricsCollector::ProcessMeminfo(const string& meminfo_raw) {
+  static const MeminfoRecord fields_array[] = {
+    { "MemTotal", "MemTotal" },  // SPECIAL CASE: total system memory
+    { "MemFree", "MemFree" },
+    { "Buffers", "Buffers" },
+    { "Cached", "Cached" },
+    // { "SwapCached", "SwapCached" },
+    { "Active", "Active" },
+    { "Inactive", "Inactive" },
+    { "ActiveAnon", "Active(anon)" },
+    { "InactiveAnon", "Inactive(anon)" },
+    { "ActiveFile" , "Active(file)" },
+    { "InactiveFile", "Inactive(file)" },
+    { "Unevictable", "Unevictable", kMeminfoOp_HistLog },
+    // { "Mlocked", "Mlocked" },
+    { "SwapTotal", "SwapTotal", kMeminfoOp_SwapTotal },
+    { "SwapFree", "SwapFree", kMeminfoOp_SwapFree },
+    // { "Dirty", "Dirty" },
+    // { "Writeback", "Writeback" },
+    { "AnonPages", "AnonPages" },
+    { "Mapped", "Mapped" },
+    { "Shmem", "Shmem", kMeminfoOp_HistLog },
+    { "Slab", "Slab", kMeminfoOp_HistLog },
+    // { "SReclaimable", "SReclaimable" },
+    // { "SUnreclaim", "SUnreclaim" },
+  };
+  vector<MeminfoRecord> fields(fields_array,
+                               fields_array + arraysize(fields_array));
+  if (!FillMeminfo(meminfo_raw, &fields)) {
+    return false;
+  }
+  int total_memory = fields[0].value;
+  if (total_memory == 0) {
+    // this "cannot happen"
+    LOG(WARNING) << "borked meminfo parser";
+    return false;
+  }
+  int swap_total = 0;
+  int swap_free = 0;
+  // Send all fields retrieved, except total memory.
+  for (unsigned int i = 1; i < fields.size(); i++) {
+    string metrics_name = base::StringPrintf("Platform.Meminfo%s",
+                                             fields[i].name);
+    int percent;
+    switch (fields[i].op) {
+      case kMeminfoOp_HistPercent:
+        // report value as percent of total memory
+        percent = fields[i].value * 100 / total_memory;
+        SendLinearSample(metrics_name, percent, 100, 101);
+        break;
+      case kMeminfoOp_HistLog:
+        // report value in kbytes, log scale, 4Gb max
+        SendSample(metrics_name, fields[i].value, 1, 4 * 1000 * 1000, 100);
+        break;
+      case kMeminfoOp_SwapTotal:
+        swap_total = fields[i].value;
+      case kMeminfoOp_SwapFree:
+        swap_free = fields[i].value;
+        break;
+    }
+  }
+  if (swap_total > 0) {
+    int swap_used = swap_total - swap_free;
+    int swap_used_percent = swap_used * 100 / swap_total;
+    SendSample("Platform.MeminfoSwapUsed", swap_used, 1, 8 * 1000 * 1000, 100);
+    SendLinearSample("Platform.MeminfoSwapUsed.Percent", swap_used_percent,
+                     100, 101);
+  }
+  return true;
+}
+
+bool MetricsCollector::FillMeminfo(const string& meminfo_raw,
+                                    vector<MeminfoRecord>* fields) {
+  vector<std::string> lines =
+      base::SplitString(meminfo_raw, "\n", base::KEEP_WHITESPACE,
+                        base::SPLIT_WANT_NONEMPTY);
+
+  // Scan meminfo output and collect field values.  Each field name has to
+  // match a meminfo entry (case insensitive) after removing non-alpha
+  // characters from the entry.
+  size_t ifield = 0;
+  for (size_t iline = 0;
+       iline < lines.size() && ifield < fields->size();
+       iline++) {
+    vector<string> tokens =
+        base::SplitString(lines[iline], ": ", base::KEEP_WHITESPACE,
+                          base::SPLIT_WANT_NONEMPTY);
+    if (strcmp((*fields)[ifield].match, tokens[0].c_str()) == 0) {
+      // Name matches. Parse value and save.
+      if (!base::StringToInt(tokens[1], &(*fields)[ifield].value)) {
+        LOG(WARNING) << "Cound not convert " << tokens[1] << " to int";
+        return false;
+      }
+      ifield++;
+    }
+  }
+  if (ifield < fields->size()) {
+    // End of input reached while scanning.
+    LOG(WARNING) << "cannot find field " << (*fields)[ifield].match
+                 << " and following";
+    return false;
+  }
+  return true;
+}
+
+void MetricsCollector::ScheduleMemuseCallback(double interval) {
+  if (testing_) {
+    return;
+  }
+  base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+      base::Bind(&MetricsCollector::MemuseCallback,
+                 weak_ptr_factory_.GetWeakPtr()),
+      base::TimeDelta::FromSeconds(interval));
+}
+
+void MetricsCollector::MemuseCallback() {
+  // Since we only care about active time (i.e. uptime minus sleep time) but
+  // the callbacks are driven by real time (uptime), we check if we should
+  // reschedule this callback due to intervening sleep periods.
+  double now = GetActiveTime();
+  // Avoid intervals of less than one second.
+  double remaining_time = ceil(memuse_final_time_ - now);
+  if (remaining_time > 0) {
+    ScheduleMemuseCallback(remaining_time);
+  } else {
+    // Report stats and advance the measurement interval unless there are
+    // errors or we've completed the last interval.
+    if (MemuseCallbackWork() &&
+        memuse_interval_index_ < arraysize(kMemuseIntervals)) {
+      double interval = kMemuseIntervals[memuse_interval_index_++];
+      memuse_final_time_ = now + interval;
+      ScheduleMemuseCallback(interval);
+    }
+  }
+}
+
+bool MetricsCollector::MemuseCallbackWork() {
+  string meminfo_raw;
+  const FilePath meminfo_path(kMeminfoFileName);
+  if (!base::ReadFileToString(meminfo_path, &meminfo_raw)) {
+    LOG(WARNING) << "cannot read " << meminfo_path.value().c_str();
+    return false;
+  }
+  return ProcessMemuse(meminfo_raw);
+}
+
+bool MetricsCollector::ProcessMemuse(const string& meminfo_raw) {
+  static const MeminfoRecord fields_array[] = {
+    { "MemTotal", "MemTotal" },  // SPECIAL CASE: total system memory
+    { "ActiveAnon", "Active(anon)" },
+    { "InactiveAnon", "Inactive(anon)" },
+  };
+  vector<MeminfoRecord> fields(fields_array,
+                               fields_array + arraysize(fields_array));
+  if (!FillMeminfo(meminfo_raw, &fields)) {
+    return false;
+  }
+  int total = fields[0].value;
+  int active_anon = fields[1].value;
+  int inactive_anon = fields[2].value;
+  if (total == 0) {
+    // this "cannot happen"
+    LOG(WARNING) << "borked meminfo parser";
+    return false;
+  }
+  string metrics_name = base::StringPrintf("Platform.MemuseAnon%d",
+                                           memuse_interval_index_);
+  SendLinearSample(metrics_name, (active_anon + inactive_anon) * 100 / total,
+                   100, 101);
+  return true;
+}
+
+void MetricsCollector::SendSample(const string& name, int sample,
+                                   int min, int max, int nbuckets) {
+  metrics_lib_->SendToUMA(name, sample, min, max, nbuckets);
+}
+
+void MetricsCollector::SendKernelCrashesCumulativeCountStats() {
+  // Report the number of crashes for this OS version, but don't clear the
+  // counter.  It is cleared elsewhere on version change.
+  int64_t crashes_count = kernel_crashes_version_count_->Get();
+  SendSample(kernel_crashes_version_count_->Name(),
+             crashes_count,
+             1,                         // value of first bucket
+             500,                       // value of last bucket
+             100);                      // number of buckets
+
+
+  int64_t cpu_use_ms = version_cumulative_cpu_use_->Get();
+  SendSample(version_cumulative_cpu_use_->Name(),
+             cpu_use_ms / 1000,         // stat is in seconds
+             1,                         // device may be used very little...
+             8 * 1000 * 1000,           // ... or a lot (a little over 90 days)
+             100);
+
+  // On the first run after an autoupdate, cpu_use_ms and active_use_seconds
+  // can be zero.  Avoid division by zero.
+  if (cpu_use_ms > 0) {
+    // Send the crash frequency since update in number of crashes per CPU year.
+    SendSample("Logging.KernelCrashesPerCpuYear",
+               crashes_count * kSecondsPerDay * 365 * 1000 / cpu_use_ms,
+               1,
+               1000 * 1000,     // about one crash every 30s of CPU time
+               100);
+  }
+
+  int64_t active_use_seconds = version_cumulative_active_use_->Get();
+  if (active_use_seconds > 0) {
+    SendSample(version_cumulative_active_use_->Name(),
+               active_use_seconds,
+               1,                          // device may be used very little...
+               8 * 1000 * 1000,            // ... or a lot (about 90 days)
+               100);
+    // Same as above, but per year of active time.
+    SendSample("Logging.KernelCrashesPerActiveYear",
+               crashes_count * kSecondsPerDay * 365 / active_use_seconds,
+               1,
+               1000 * 1000,     // about one crash every 30s of active time
+               100);
+  }
+}
+
+void MetricsCollector::SendAndResetDailyUseSample(
+    const unique_ptr<PersistentInteger>& use) {
+  SendSample(use->Name(),
+             use->GetAndClear(),
+             1,                        // value of first bucket
+             kSecondsPerDay,           // value of last bucket
+             50);                      // number of buckets
+}
+
+void MetricsCollector::SendAndResetCrashIntervalSample(
+    const unique_ptr<PersistentInteger>& interval) {
+  SendSample(interval->Name(),
+             interval->GetAndClear(),
+             1,                        // value of first bucket
+             4 * kSecondsPerWeek,      // value of last bucket
+             50);                      // number of buckets
+}
+
+void MetricsCollector::SendAndResetCrashFrequencySample(
+    const unique_ptr<PersistentInteger>& frequency) {
+  SendSample(frequency->Name(),
+             frequency->GetAndClear(),
+             1,                        // value of first bucket
+             100,                      // value of last bucket
+             50);                      // number of buckets
+}
+
+void MetricsCollector::SendLinearSample(const string& name, int sample,
+                                         int max, int nbuckets) {
+  // TODO(semenzato): add a proper linear histogram to the Chrome external
+  // metrics API.
+  LOG_IF(FATAL, nbuckets != max + 1) << "unsupported histogram scale";
+  metrics_lib_->SendEnumToUMA(name, sample, max);
+}
+
+void MetricsCollector::UpdateStats(TimeTicks now_ticks,
+                                    Time now_wall_time) {
+  const int elapsed_seconds = (now_ticks - last_update_stats_time_).InSeconds();
+  daily_active_use_->Add(elapsed_seconds);
+  version_cumulative_active_use_->Add(elapsed_seconds);
+  user_crash_interval_->Add(elapsed_seconds);
+  kernel_crash_interval_->Add(elapsed_seconds);
+  TimeDelta cpu_use = cpu_usage_collector_->GetCumulativeCpuUse();
+  version_cumulative_cpu_use_->Add(
+      (cpu_use - latest_cpu_use_microseconds_).InMilliseconds());
+  latest_cpu_use_microseconds_ = cpu_use;
+  last_update_stats_time_ = now_ticks;
+
+  const TimeDelta since_epoch = now_wall_time - Time::UnixEpoch();
+  const int day = since_epoch.InDays();
+  const int week = day / 7;
+
+  if (daily_cycle_->Get() != day) {
+    daily_cycle_->Set(day);
+    SendAndResetDailyUseSample(daily_active_use_);
+    SendAndResetCrashFrequencySample(any_crashes_daily_count_);
+    SendAndResetCrashFrequencySample(user_crashes_daily_count_);
+    SendAndResetCrashFrequencySample(kernel_crashes_daily_count_);
+    SendAndResetCrashFrequencySample(unclean_shutdowns_daily_count_);
+    SendKernelCrashesCumulativeCountStats();
+  }
+
+  if (weekly_cycle_->Get() != week) {
+    weekly_cycle_->Set(week);
+    SendAndResetCrashFrequencySample(any_crashes_weekly_count_);
+    SendAndResetCrashFrequencySample(user_crashes_weekly_count_);
+    SendAndResetCrashFrequencySample(kernel_crashes_weekly_count_);
+    SendAndResetCrashFrequencySample(unclean_shutdowns_weekly_count_);
+  }
+}
+
+void MetricsCollector::HandleUpdateStatsTimeout() {
+  UpdateStats(TimeTicks::Now(), Time::Now());
+  base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
+      base::Bind(&MetricsCollector::HandleUpdateStatsTimeout,
+                 weak_ptr_factory_.GetWeakPtr()),
+      base::TimeDelta::FromMilliseconds(kUpdateStatsIntervalMs));
+}
diff --git a/metrics_collector.h b/metrics_collector.h
new file mode 100644
index 0000000..30659bd
--- /dev/null
+++ b/metrics_collector.h
@@ -0,0 +1,284 @@
+/*
+ * 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 METRICS_METRICS_COLLECTOR_H_
+#define METRICS_METRICS_COLLECTOR_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/memory/weak_ptr.h>
+#include <base/time/time.h>
+#include <brillo/binder_watcher.h>
+#include <brillo/daemons/daemon.h>
+#include <libweaved/command.h>
+#include <libweaved/service.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+#include "collectors/averaged_statistics_collector.h"
+#include "collectors/cpu_usage_collector.h"
+#include "collectors/disk_usage_collector.h"
+#include "metrics/metrics_library.h"
+#include "persistent_integer.h"
+
+using chromeos_metrics::PersistentInteger;
+using std::unique_ptr;
+
+class MetricsCollector : public brillo::Daemon {
+ public:
+  MetricsCollector();
+  ~MetricsCollector();
+
+  // Initializes metrics class variables.
+  void Init(bool testing,
+            MetricsLibraryInterface* metrics_lib,
+            const std::string& diskstats_path,
+            const base::FilePath& private_metrics_directory,
+            const base::FilePath& shared_metrics_directory);
+
+  // Initializes the daemon.
+  int OnInit() override;
+
+  // Does all the work.
+  int Run() override;
+
+  // Returns the active time since boot (uptime minus sleep time) in seconds.
+  static double GetActiveTime();
+
+  // Updates the active use time and logs time between user-space
+  // process crashes.  Called via MetricsCollectorServiceTrampoline.
+  void ProcessUserCrash();
+
+ protected:
+  // Used also by the unit tests.
+  static const char kComprDataSizeName[];
+  static const char kOrigDataSizeName[];
+  static const char kZeroPagesName[];
+
+ private:
+  friend class MetricsCollectorTest;
+  FRIEND_TEST(MetricsCollectorTest, CheckSystemCrash);
+  FRIEND_TEST(MetricsCollectorTest, ComputeEpochNoCurrent);
+  FRIEND_TEST(MetricsCollectorTest, ComputeEpochNoLast);
+  FRIEND_TEST(MetricsCollectorTest, GetHistogramPath);
+  FRIEND_TEST(MetricsCollectorTest, IsNewEpoch);
+  FRIEND_TEST(MetricsCollectorTest, MessageFilter);
+  FRIEND_TEST(MetricsCollectorTest, ProcessKernelCrash);
+  FRIEND_TEST(MetricsCollectorTest, ProcessMeminfo);
+  FRIEND_TEST(MetricsCollectorTest, ProcessMeminfo2);
+  FRIEND_TEST(MetricsCollectorTest, ProcessUncleanShutdown);
+  FRIEND_TEST(MetricsCollectorTest, ProcessUserCrash);
+  FRIEND_TEST(MetricsCollectorTest, ReportCrashesDailyFrequency);
+  FRIEND_TEST(MetricsCollectorTest, ReportKernelCrashInterval);
+  FRIEND_TEST(MetricsCollectorTest, ReportUncleanShutdownInterval);
+  FRIEND_TEST(MetricsCollectorTest, ReportUserCrashInterval);
+  FRIEND_TEST(MetricsCollectorTest, SendSample);
+  FRIEND_TEST(MetricsCollectorTest, SendZramMetrics);
+
+  // Type of scale to use for meminfo histograms.  For most of them we use
+  // percent of total RAM, but for some we use absolute numbers, usually in
+  // megabytes, on a log scale from 0 to 4000, and 0 to 8000 for compressed
+  // swap (since it can be larger than total RAM).
+  enum MeminfoOp {
+    kMeminfoOp_HistPercent = 0,
+    kMeminfoOp_HistLog,
+    kMeminfoOp_SwapTotal,
+    kMeminfoOp_SwapFree,
+  };
+
+  // Record for retrieving and reporting values from /proc/meminfo.
+  struct MeminfoRecord {
+    const char* name;        // print name
+    const char* match;       // string to match in output of /proc/meminfo
+    MeminfoOp op;            // histogram scale selector, or other operator
+    int value;               // value from /proc/meminfo
+  };
+
+  // Enables metrics reporting.
+  void OnEnableMetrics(std::unique_ptr<weaved::Command> command);
+
+  // Disables metrics reporting.
+  void OnDisableMetrics(std::unique_ptr<weaved::Command> command);
+
+  // Updates the weave device state.
+  void UpdateWeaveState();
+
+  // Updates the active use time and logs time between kernel crashes.
+  void ProcessKernelCrash();
+
+  // Updates the active use time and logs time between unclean shutdowns.
+  void ProcessUncleanShutdown();
+
+  // Checks if a kernel crash has been detected and returns true if
+  // so.  The method assumes that a kernel crash has happened if
+  // |crash_file| exists.  It removes the file immediately if it
+  // exists, so it must not be called more than once.
+  bool CheckSystemCrash(const std::string& crash_file);
+
+  // Sends a regular (exponential) histogram sample to Chrome for
+  // transport to UMA. See MetricsLibrary::SendToUMA in
+  // metrics_library.h for a description of the arguments.
+  void SendSample(const std::string& name, int sample,
+                  int min, int max, int nbuckets);
+
+  // Sends a linear histogram sample to Chrome for transport to UMA. See
+  // MetricsLibrary::SendToUMA in metrics_library.h for a description of the
+  // arguments.
+  void SendLinearSample(const std::string& name, int sample,
+                        int max, int nbuckets);
+
+  // Sends various cumulative kernel crash-related stats, for instance the
+  // total number of kernel crashes since the last version update.
+  void SendKernelCrashesCumulativeCountStats();
+
+  // Sends a sample representing the number of seconds of active use
+  // for a 24-hour period and reset |use|.
+  void SendAndResetDailyUseSample(const unique_ptr<PersistentInteger>& use);
+
+  // Sends a sample representing a time interval between two crashes of the
+  // same type and reset |interval|.
+  void SendAndResetCrashIntervalSample(
+      const unique_ptr<PersistentInteger>& interval);
+
+  // Sends a sample representing a frequency of crashes of some type and reset
+  // |frequency|.
+  void SendAndResetCrashFrequencySample(
+      const unique_ptr<PersistentInteger>& frequency);
+
+  // Initializes vm and disk stats reporting.
+  void StatsReporterInit();
+
+  // Schedules meminfo collection callback.
+  void ScheduleMeminfoCallback(int wait);
+
+  // Reports memory statistics.  Reschedules callback on success.
+  void MeminfoCallback(base::TimeDelta wait);
+
+  // Parses content of /proc/meminfo and sends fields of interest to UMA.
+  // Returns false on errors.  |meminfo_raw| contains the content of
+  // /proc/meminfo.
+  bool ProcessMeminfo(const std::string& meminfo_raw);
+
+  // Parses meminfo data from |meminfo_raw|.  |fields| is a vector containing
+  // the fields of interest.  The order of the fields must be the same in which
+  // /proc/meminfo prints them.  The result of parsing fields[i] is placed in
+  // fields[i].value.
+  bool FillMeminfo(const std::string& meminfo_raw,
+                   std::vector<MeminfoRecord>* fields);
+
+  // Schedule a memory use callback in |interval| seconds.
+  void ScheduleMemuseCallback(double interval);
+
+  // Calls MemuseCallbackWork, and possibly schedules next callback, if enough
+  // active time has passed.  Otherwise reschedules itself to simulate active
+  // time callbacks (i.e. wall clock time minus sleep time).
+  void MemuseCallback();
+
+  // Reads /proc/meminfo and sends total anonymous memory usage to UMA.
+  bool MemuseCallbackWork();
+
+  // Parses meminfo data and sends it to UMA.
+  bool ProcessMemuse(const std::string& meminfo_raw);
+
+  // Reads the current OS version from /etc/lsb-release and hashes it
+  // to a unsigned 32-bit int.
+  uint32_t GetOsVersionHash();
+
+  // Updates stats, additionally sending them to UMA if enough time has elapsed
+  // since the last report.
+  void UpdateStats(base::TimeTicks now_ticks, base::Time now_wall_time);
+
+  // Invoked periodically by |update_stats_timeout_id_| to call UpdateStats().
+  void HandleUpdateStatsTimeout();
+
+  // Reports zram statistics.
+  bool ReportZram(const base::FilePath& zram_dir);
+
+  // Reads a string from a file and converts it to uint64_t.
+  static bool ReadFileToUint64(const base::FilePath& path, uint64_t* value);
+
+  // Callback invoked when a connection to weaved's service is established
+  // over Binder interface.
+  void OnWeaveServiceConnected(const std::weak_ptr<weaved::Service>& service);
+
+  // VARIABLES
+
+  // Test mode.
+  bool testing_;
+
+  // Publicly readable metrics directory.
+  base::FilePath shared_metrics_directory_;
+
+  // The metrics library handle.
+  MetricsLibraryInterface* metrics_lib_;
+
+  // The last time that UpdateStats() was called.
+  base::TimeTicks last_update_stats_time_;
+
+  // End time of current memuse stat collection interval.
+  double memuse_final_time_;
+
+  // Selects the wait time for the next memory use callback.
+  unsigned int memuse_interval_index_;
+
+  // Used internally by GetIncrementalCpuUse() to return the CPU utilization
+  // between calls.
+  base::TimeDelta latest_cpu_use_microseconds_;
+
+  // Persistent values and accumulators for crash statistics.
+  unique_ptr<PersistentInteger> daily_cycle_;
+  unique_ptr<PersistentInteger> weekly_cycle_;
+  unique_ptr<PersistentInteger> version_cycle_;
+
+  // Active use accumulated in a day.
+  unique_ptr<PersistentInteger> daily_active_use_;
+  // Active use accumulated since the latest version update.
+  unique_ptr<PersistentInteger> version_cumulative_active_use_;
+
+  // The CPU time accumulator.  This contains the CPU time, in milliseconds,
+  // used by the system since the most recent OS version update.
+  unique_ptr<PersistentInteger> version_cumulative_cpu_use_;
+
+  unique_ptr<PersistentInteger> user_crash_interval_;
+  unique_ptr<PersistentInteger> kernel_crash_interval_;
+  unique_ptr<PersistentInteger> unclean_shutdown_interval_;
+
+  unique_ptr<PersistentInteger> any_crashes_daily_count_;
+  unique_ptr<PersistentInteger> any_crashes_weekly_count_;
+  unique_ptr<PersistentInteger> user_crashes_daily_count_;
+  unique_ptr<PersistentInteger> user_crashes_weekly_count_;
+  unique_ptr<PersistentInteger> kernel_crashes_daily_count_;
+  unique_ptr<PersistentInteger> kernel_crashes_weekly_count_;
+  unique_ptr<PersistentInteger> kernel_crashes_version_count_;
+  unique_ptr<PersistentInteger> unclean_shutdowns_daily_count_;
+  unique_ptr<PersistentInteger> unclean_shutdowns_weekly_count_;
+
+  unique_ptr<CpuUsageCollector> cpu_usage_collector_;
+  unique_ptr<DiskUsageCollector> disk_usage_collector_;
+  unique_ptr<AveragedStatisticsCollector> averaged_stats_collector_;
+
+  unique_ptr<weaved::Service::Subscription> weave_service_subscription_;
+  std::weak_ptr<weaved::Service> service_;
+
+  base::WeakPtrFactory<MetricsCollector> weak_ptr_factory_{this};
+};
+
+#endif  // METRICS_METRICS_COLLECTOR_H_
diff --git a/metrics_collector.rc b/metrics_collector.rc
new file mode 100644
index 0000000..2d7667d
--- /dev/null
+++ b/metrics_collector.rc
@@ -0,0 +1,4 @@
+service metricscollector /system/bin/metrics_collector --foreground --logtosyslog
+    class late_start
+    user metrics_coll
+    group metrics_coll
diff --git a/metrics_collector_main.cc b/metrics_collector_main.cc
new file mode 100644
index 0000000..14bb935
--- /dev/null
+++ b/metrics_collector_main.cc
@@ -0,0 +1,98 @@
+/*
+ * 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 <base/at_exit.h>
+#include <base/command_line.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <brillo/flag_helper.h>
+#include <brillo/syslog_logging.h>
+#include <rootdev.h>
+
+#include "constants.h"
+#include "metrics_collector.h"
+
+
+// Returns the path to the disk stats in the sysfs.  Returns the null string if
+// it cannot find the disk stats file.
+static
+const std::string MetricsMainDiskStatsPath() {
+  char dev_path_cstr[PATH_MAX];
+  std::string dev_prefix = "/dev/block/";
+  std::string dev_path;
+
+  int ret = rootdev(dev_path_cstr, sizeof(dev_path_cstr), true, true);
+  if (ret != 0) {
+    LOG(WARNING) << "error " << ret << " determining root device";
+    return "";
+  }
+  dev_path = dev_path_cstr;
+  // Check that rootdev begins with "/dev/block/".
+  if (!base::StartsWith(dev_path, dev_prefix,
+                        base::CompareCase::INSENSITIVE_ASCII)) {
+    LOG(WARNING) << "unexpected root device " << dev_path;
+    return "";
+  }
+  return "/sys/class/block/" + dev_path.substr(dev_prefix.length()) + "/stat";
+}
+
+int main(int argc, char** argv) {
+  DEFINE_bool(foreground, false, "Don't daemonize");
+
+  DEFINE_string(private_directory, metrics::kMetricsCollectorDirectory,
+                "Path to the private directory used by metrics_collector "
+                "(testing only)");
+  DEFINE_string(shared_directory, metrics::kSharedMetricsDirectory,
+                "Path to the shared metrics directory, used by "
+                "metrics_collector, metricsd and all metrics clients "
+                "(testing only)");
+
+  DEFINE_bool(logtostderr, false, "Log to standard error");
+  DEFINE_bool(logtosyslog, false, "Log to syslog");
+
+  brillo::FlagHelper::Init(argc, argv, "Chromium OS Metrics Daemon");
+
+  int logging_location = (FLAGS_foreground ? brillo::kLogToStderr
+                          : brillo::kLogToSyslog);
+  if (FLAGS_logtosyslog)
+    logging_location = brillo::kLogToSyslog;
+
+  if (FLAGS_logtostderr)
+    logging_location = brillo::kLogToStderr;
+
+  // Also log to stderr when not running as daemon.
+  brillo::InitLog(logging_location | brillo::kLogHeader);
+
+  if (FLAGS_logtostderr && FLAGS_logtosyslog) {
+    LOG(ERROR) << "only one of --logtosyslog and --logtostderr can be set";
+    return 1;
+  }
+
+  if (!FLAGS_foreground && daemon(0, 0) != 0) {
+    return errno;
+  }
+
+  MetricsLibrary metrics_lib;
+  metrics_lib.InitWithNoCaching();
+  MetricsCollector daemon;
+  daemon.Init(false,
+              &metrics_lib,
+              MetricsMainDiskStatsPath(),
+              base::FilePath(FLAGS_private_directory),
+              base::FilePath(FLAGS_shared_directory));
+
+  daemon.Run();
+}
diff --git a/metrics_collector_service_client.cc b/metrics_collector_service_client.cc
new file mode 100644
index 0000000..08aaa4a
--- /dev/null
+++ b/metrics_collector_service_client.cc
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+// Client interface to IMetricsCollectorService.
+
+#include "metrics/metrics_collector_service_client.h"
+
+#include <base/logging.h>
+#include <binder/IServiceManager.h>
+#include <utils/String16.h>
+
+#include "android/brillo/metrics/IMetricsCollectorService.h"
+
+namespace {
+const char kMetricsCollectorServiceName[] =
+    "android.brillo.metrics.IMetricsCollectorService";
+}
+
+bool MetricsCollectorServiceClient::Init() {
+  const android::String16 name(kMetricsCollectorServiceName);
+  metrics_collector_service_ = android::interface_cast<
+      android::brillo::metrics::IMetricsCollectorService>(
+      android::defaultServiceManager()->checkService(name));
+
+  if (metrics_collector_service_ == nullptr)
+    LOG(ERROR) << "Unable to lookup service " << kMetricsCollectorServiceName;
+
+  return metrics_collector_service_ != nullptr;
+}
+
+bool MetricsCollectorServiceClient::notifyUserCrash() {
+  if (metrics_collector_service_ == nullptr)
+    return false;
+
+  metrics_collector_service_->notifyUserCrash();
+  return true;
+}
diff --git a/metrics_collector_service_impl.cc b/metrics_collector_service_impl.cc
new file mode 100644
index 0000000..4d9a05a
--- /dev/null
+++ b/metrics_collector_service_impl.cc
@@ -0,0 +1,35 @@
+/*
+ * 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 "metrics_collector_service_impl.h"
+
+#include <binder/IServiceManager.h>
+#include <binder/Status.h>
+#include <utils/Errors.h>
+
+#include "metrics_collector.h"
+
+using namespace android;
+
+BnMetricsCollectorServiceImpl::BnMetricsCollectorServiceImpl(
+    MetricsCollector* metrics_collector)
+    : metrics_collector_(metrics_collector) {
+}
+
+android::binder::Status BnMetricsCollectorServiceImpl::notifyUserCrash() {
+  metrics_collector_->ProcessUserCrash();
+  return android::binder::Status::ok();
+}
diff --git a/metrics_collector_service_impl.h b/metrics_collector_service_impl.h
new file mode 100644
index 0000000..8db418a
--- /dev/null
+++ b/metrics_collector_service_impl.h
@@ -0,0 +1,48 @@
+/*
+ * 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 METRICSD_METRICS_COLLECTOR_SERVICE_IMPL_H_
+#define METRICSD_METRICS_COLLECTOR_SERVICE_IMPL_H_
+
+// metrics_collector binder service implementation.  Constructed by
+// MetricsCollector.
+
+#include "android/brillo/metrics/BnMetricsCollectorService.h"
+
+#include <binder/Status.h>
+
+class MetricsCollector;
+
+class BnMetricsCollectorServiceImpl
+    : public android::brillo::metrics::BnMetricsCollectorService {
+ public:
+  // Passed a this pointer from the MetricsCollector object that constructs us.
+  explicit BnMetricsCollectorServiceImpl(
+      MetricsCollector* metrics_collector_service);
+
+  virtual ~BnMetricsCollectorServiceImpl() = default;
+
+  // Called by crash_reporter to report a userspace crash event.  We relay
+  // this to MetricsCollector.
+  android::binder::Status notifyUserCrash();
+
+ private:
+  // MetricsCollector object that constructs us, we use this to call back
+  // to it.
+  MetricsCollector* metrics_collector_;
+};
+
+#endif  // METRICSD_METRICS_COLLECTOR_SERVICE_IMPL_H_
diff --git a/metrics_collector_test.cc b/metrics_collector_test.cc
new file mode 100644
index 0000000..8dda529
--- /dev/null
+++ b/metrics_collector_test.cc
@@ -0,0 +1,170 @@
+/*
+ * 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 <vector>
+
+#include <base/at_exit.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/strings/string_number_conversions.h>
+#include <brillo/flag_helper.h>
+#include <gtest/gtest.h>
+
+#include "constants.h"
+#include "metrics_collector.h"
+#include "metrics/metrics_library_mock.h"
+#include "persistent_integer_mock.h"
+
+using base::FilePath;
+using base::TimeDelta;
+using std::string;
+using std::vector;
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::AtLeast;
+using ::testing::Return;
+using ::testing::StrictMock;
+using chromeos_metrics::PersistentIntegerMock;
+
+
+class MetricsCollectorTest : public testing::Test {
+ protected:
+  virtual void SetUp() {
+    brillo::FlagHelper::Init(0, nullptr, "");
+    EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
+
+    base::FilePath private_dir = temp_dir_.path().Append("private");
+    base::FilePath shared_dir = temp_dir_.path().Append("shared");
+
+    EXPECT_TRUE(base::CreateDirectory(private_dir));
+    EXPECT_TRUE(base::CreateDirectory(shared_dir));
+
+    daemon_.Init(true, &metrics_lib_, "", private_dir, shared_dir);
+  }
+
+  // Adds a metrics library mock expectation that the specified metric
+  // will be generated.
+  void ExpectSample(const std::string& name, int sample) {
+    EXPECT_CALL(metrics_lib_, SendToUMA(name, sample, _, _, _))
+        .Times(1)
+        .WillOnce(Return(true))
+        .RetiresOnSaturation();
+  }
+
+  // Creates or overwrites the file in |path| so that it contains the printable
+  // representation of |value|.
+  void CreateUint64ValueFile(const base::FilePath& path, uint64_t value) {
+    std::string value_string = base::Uint64ToString(value);
+    ASSERT_EQ(value_string.length(),
+              base::WriteFile(path, value_string.c_str(),
+                              value_string.length()));
+  }
+
+  // The MetricsCollector under test.
+  MetricsCollector daemon_;
+
+  // Temporary directory used for tests.
+  base::ScopedTempDir temp_dir_;
+
+  // Mocks. They are strict mock so that all unexpected
+  // calls are marked as failures.
+  StrictMock<MetricsLibraryMock> metrics_lib_;
+};
+
+TEST_F(MetricsCollectorTest, SendSample) {
+  ExpectSample("Dummy.Metric", 3);
+  daemon_.SendSample("Dummy.Metric", /* sample */ 3,
+                     /* min */ 1, /* max */ 100, /* buckets */ 50);
+}
+
+TEST_F(MetricsCollectorTest, ProcessMeminfo) {
+  string meminfo =
+      "MemTotal:        2000000 kB\nMemFree:          500000 kB\n"
+      "Buffers:         1000000 kB\nCached:           213652 kB\n"
+      "SwapCached:            0 kB\nActive:           133400 kB\n"
+      "Inactive:         183396 kB\nActive(anon):      92984 kB\n"
+      "Inactive(anon):    58860 kB\nActive(file):      40416 kB\n"
+      "Inactive(file):   124536 kB\nUnevictable:           0 kB\n"
+      "Mlocked:               0 kB\nSwapTotal:             0 kB\n"
+      "SwapFree:              0 kB\nDirty:                40 kB\n"
+      "Writeback:             0 kB\nAnonPages:         92652 kB\n"
+      "Mapped:            59716 kB\nShmem:             59196 kB\n"
+      "Slab:              16656 kB\nSReclaimable:       6132 kB\n"
+      "SUnreclaim:        10524 kB\nKernelStack:        1648 kB\n"
+      "PageTables:         2780 kB\nNFS_Unstable:          0 kB\n"
+      "Bounce:                0 kB\nWritebackTmp:          0 kB\n"
+      "CommitLimit:      970656 kB\nCommitted_AS:    1260528 kB\n"
+      "VmallocTotal:     122880 kB\nVmallocUsed:       12144 kB\n"
+      "VmallocChunk:     103824 kB\nDirectMap4k:        9636 kB\n"
+      "DirectMap2M:     1955840 kB\n";
+
+  // All enum calls must report percents.
+  EXPECT_CALL(metrics_lib_, SendEnumToUMA(_, _, 100)).Times(AtLeast(1));
+  // Check that MemFree is correctly computed at 25%.
+  EXPECT_CALL(metrics_lib_, SendEnumToUMA("Platform.MeminfoMemFree", 25, 100))
+      .Times(AtLeast(1));
+  // Check that we call SendToUma at least once (log histogram).
+  EXPECT_CALL(metrics_lib_, SendToUMA(_, _, _, _, _))
+      .Times(AtLeast(1));
+  // Make sure we don't report fields not in the list.
+  EXPECT_CALL(metrics_lib_, SendToUMA("Platform.MeminfoMlocked", _, _, _, _))
+      .Times(0);
+  EXPECT_CALL(metrics_lib_, SendEnumToUMA("Platform.MeminfoMlocked", _, _))
+      .Times(0);
+  EXPECT_TRUE(daemon_.ProcessMeminfo(meminfo));
+}
+
+TEST_F(MetricsCollectorTest, ProcessMeminfo2) {
+  string meminfo = "MemTotal:        2000000 kB\nMemFree:         1000000 kB\n";
+  // Not enough fields.
+  EXPECT_FALSE(daemon_.ProcessMeminfo(meminfo));
+}
+
+TEST_F(MetricsCollectorTest, SendZramMetrics) {
+  EXPECT_TRUE(daemon_.testing_);
+
+  // |compr_data_size| is the size in bytes of compressed data.
+  const uint64_t compr_data_size = 50 * 1000 * 1000;
+  // The constant '3' is a realistic but random choice.
+  // |orig_data_size| does not include zero pages.
+  const uint64_t orig_data_size = compr_data_size * 3;
+  const uint64_t page_size = 4096;
+  const uint64_t zero_pages = 10 * 1000 * 1000 / page_size;
+
+  CreateUint64ValueFile(
+      temp_dir_.path().Append(MetricsCollector::kComprDataSizeName),
+      compr_data_size);
+  CreateUint64ValueFile(
+      temp_dir_.path().Append(MetricsCollector::kOrigDataSizeName),
+      orig_data_size);
+  CreateUint64ValueFile(
+      temp_dir_.path().Append(MetricsCollector::kZeroPagesName), zero_pages);
+
+  const uint64_t real_orig_size = orig_data_size + zero_pages * page_size;
+  const uint64_t zero_ratio_percent =
+      zero_pages * page_size * 100 / real_orig_size;
+  // Ratio samples are in percents.
+  const uint64_t actual_ratio_sample = real_orig_size * 100 / compr_data_size;
+
+  EXPECT_CALL(metrics_lib_, SendToUMA(_, compr_data_size >> 20, _, _, _));
+  EXPECT_CALL(metrics_lib_,
+              SendToUMA(_, (real_orig_size - compr_data_size) >> 20, _, _, _));
+  EXPECT_CALL(metrics_lib_, SendToUMA(_, actual_ratio_sample, _, _, _));
+  EXPECT_CALL(metrics_lib_, SendToUMA(_, zero_pages, _, _, _));
+  EXPECT_CALL(metrics_lib_, SendToUMA(_, zero_ratio_percent, _, _, _));
+
+  EXPECT_TRUE(daemon_.ReportZram(temp_dir_.path()));
+}
diff --git a/metrics_library.cc b/metrics_library.cc
new file mode 100644
index 0000000..d211ab4
--- /dev/null
+++ b/metrics_library.cc
@@ -0,0 +1,226 @@
+/*
+ * 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 "metrics/metrics_library.h"
+
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <binder/IServiceManager.h>
+#include <errno.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <utils/String16.h>
+
+#include <cstdio>
+#include <cstring>
+
+#include "android/brillo/metrics/IMetricsd.h"
+#include "constants.h"
+
+static const char kCrosEventHistogramName[] = "Platform.CrOSEvent";
+static const int kCrosEventHistogramMax = 100;
+static const char kMetricsServiceName[] = "android.brillo.metrics.IMetricsd";
+
+/* Add new cros events here.
+ *
+ * The index of the event is sent in the message, so please do not
+ * reorder the names.
+ */
+static const char *kCrosEventNames[] = {
+  "ModemManagerCommandSendFailure",  // 0
+  "HwWatchdogReboot",  // 1
+  "Cras.NoCodecsFoundAtBoot",  // 2
+  "Chaps.DatabaseCorrupted",  // 3
+  "Chaps.DatabaseRepairFailure",  // 4
+  "Chaps.DatabaseCreateFailure",  // 5
+  "Attestation.OriginSpecificExhausted",  // 6
+  "SpringPowerSupply.Original.High",  // 7
+  "SpringPowerSupply.Other.High",  // 8
+  "SpringPowerSupply.Original.Low",  // 9
+  "SpringPowerSupply.ChargerIdle",  // 10
+  "TPM.NonZeroDictionaryAttackCounter",  // 11
+  "TPM.EarlyResetDuringCommand",  // 12
+};
+
+using android::binder::Status;
+using android::brillo::metrics::IMetricsd;
+using android::String16;
+
+MetricsLibrary::MetricsLibrary() {}
+MetricsLibrary::~MetricsLibrary() {}
+
+// We take buffer and buffer_size as parameters in order to simplify testing
+// of various alignments of the |device_name| with |buffer_size|.
+bool MetricsLibrary::IsDeviceMounted(const char* device_name,
+                                     const char* mounts_file,
+                                     char* buffer,
+                                     int buffer_size,
+                                     bool* result) {
+  if (buffer == nullptr || buffer_size < 1)
+    return false;
+  int mounts_fd = open(mounts_file, O_RDONLY);
+  if (mounts_fd < 0)
+    return false;
+  // match_offset describes:
+  //   -1 -- not beginning of line
+  //   0..strlen(device_name)-1 -- this offset in device_name is next to match
+  //   strlen(device_name) -- matched full name, just need a space.
+  int match_offset = 0;
+  bool match = false;
+  while (!match) {
+    int read_size = read(mounts_fd, buffer, buffer_size);
+    if (read_size <= 0) {
+      if (errno == -EINTR)
+        continue;
+      break;
+    }
+    for (int i = 0; i < read_size; ++i) {
+      if (buffer[i] == '\n') {
+        match_offset = 0;
+        continue;
+      }
+      if (match_offset < 0) {
+        continue;
+      }
+      if (device_name[match_offset] == '\0') {
+        if (buffer[i] == ' ') {
+          match = true;
+          break;
+        }
+        match_offset = -1;
+        continue;
+      }
+
+      if (buffer[i] == device_name[match_offset]) {
+        ++match_offset;
+      } else {
+        match_offset = -1;
+      }
+    }
+  }
+  close(mounts_fd);
+  *result = match;
+  return true;
+}
+
+bool MetricsLibrary::IsGuestMode() {
+  char buffer[256];
+  bool result = false;
+  if (!IsDeviceMounted("guestfs",
+                       "/proc/mounts",
+                       buffer,
+                       sizeof(buffer),
+                       &result)) {
+    return false;
+  }
+  return result && (access("/var/run/state/logged-in", F_OK) == 0);
+}
+
+bool MetricsLibrary::CheckService() {
+  if (metricsd_proxy_.get() &&
+      android::IInterface::asBinder(metricsd_proxy_)->isBinderAlive())
+    return true;
+
+  const String16 name(kMetricsServiceName);
+  metricsd_proxy_ = android::interface_cast<IMetricsd>(
+      android::defaultServiceManager()->checkService(name));
+  return metricsd_proxy_.get();
+}
+
+bool MetricsLibrary::AreMetricsEnabled() {
+  static struct stat stat_buffer;
+  time_t this_check_time = time(nullptr);
+  if (!use_caching_ || this_check_time != cached_enabled_time_) {
+    cached_enabled_time_ = this_check_time;
+    cached_enabled_ = stat(consent_file_.value().data(), &stat_buffer) >= 0;
+  }
+  return cached_enabled_;
+}
+
+void MetricsLibrary::Init() {
+  base::FilePath dir = base::FilePath(metrics::kSharedMetricsDirectory);
+  consent_file_ = dir.Append(metrics::kConsentFileName);
+  cached_enabled_ = false;
+  cached_enabled_time_ = 0;
+  use_caching_ = true;
+}
+
+void MetricsLibrary::InitWithNoCaching() {
+  Init();
+  use_caching_ = false;
+}
+
+void MetricsLibrary::InitForTest(const base::FilePath& metrics_directory) {
+  consent_file_ = metrics_directory.Append(metrics::kConsentFileName);
+  cached_enabled_ = false;
+  cached_enabled_time_ = 0;
+  use_caching_ = true;
+}
+
+bool MetricsLibrary::SendToUMA(
+    const std::string& name, int sample, int min, int max, int nbuckets) {
+  return CheckService() &&
+         metricsd_proxy_->recordHistogram(String16(name.c_str()), sample, min,
+                                          max, nbuckets)
+             .isOk();
+}
+
+bool MetricsLibrary::SendEnumToUMA(const std::string& name,
+                                   int sample,
+                                   int max) {
+  return CheckService() &&
+         metricsd_proxy_->recordLinearHistogram(String16(name.c_str()), sample,
+                                                max)
+             .isOk();
+}
+
+bool MetricsLibrary::SendBoolToUMA(const std::string& name, bool sample) {
+  return CheckService() &&
+         metricsd_proxy_->recordLinearHistogram(String16(name.c_str()),
+                                                sample ? 1 : 0, 2)
+             .isOk();
+}
+
+bool MetricsLibrary::SendSparseToUMA(const std::string& name, int sample) {
+  return CheckService() &&
+         metricsd_proxy_->recordSparseHistogram(String16(name.c_str()), sample)
+             .isOk();
+}
+
+bool MetricsLibrary::SendCrashToUMA(const char* crash_kind) {
+  return CheckService() &&
+         metricsd_proxy_->recordCrash(String16(crash_kind)).isOk();
+}
+
+bool MetricsLibrary::SendCrosEventToUMA(const std::string& event) {
+  for (size_t i = 0; i < arraysize(kCrosEventNames); i++) {
+    if (strcmp(event.c_str(), kCrosEventNames[i]) == 0) {
+      return SendEnumToUMA(kCrosEventHistogramName, i, kCrosEventHistogramMax);
+    }
+  }
+  return false;
+}
+
+bool MetricsLibrary::GetHistogramsDump(std::string* dump) {
+  android::String16 temp_dump;
+  if (!CheckService() ||
+      !metricsd_proxy_->getHistogramsDump(&temp_dump).isOk()) {
+    return false;
+  }
+
+  *dump = android::String8(temp_dump).string();
+  return true;
+}
diff --git a/metrics_library_test.cc b/metrics_library_test.cc
new file mode 100644
index 0000000..52fcce3
--- /dev/null
+++ b/metrics_library_test.cc
@@ -0,0 +1,107 @@
+/*
+ * 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 <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "metrics/c_metrics_library.h"
+#include "metrics/metrics_library.h"
+
+
+class MetricsLibraryTest : public testing::Test {
+ protected:
+  virtual void SetUp() {
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+    lib_.InitForTest(temp_dir_.path());
+    // Defeat metrics enabled caching between tests.
+    lib_.cached_enabled_time_ = 0;
+  }
+
+  void SetMetricsConsent(bool enabled) {
+    if (enabled) {
+      ASSERT_EQ(base::WriteFile(lib_.consent_file_, "", 0), 0);
+    } else {
+      ASSERT_TRUE(base::DeleteFile(lib_.consent_file_, false));
+    }
+  }
+
+  void VerifyEnabledCacheHit(bool to_value);
+  void VerifyEnabledCacheEviction(bool to_value);
+
+  MetricsLibrary lib_;
+  base::ScopedTempDir temp_dir_;
+};
+
+TEST_F(MetricsLibraryTest, AreMetricsEnabledFalse) {
+  SetMetricsConsent(false);
+  EXPECT_FALSE(lib_.AreMetricsEnabled());
+}
+
+TEST_F(MetricsLibraryTest, AreMetricsEnabledTrue) {
+  SetMetricsConsent(true);
+  EXPECT_TRUE(lib_.AreMetricsEnabled());
+}
+
+void MetricsLibraryTest::VerifyEnabledCacheHit(bool to_value) {
+  // We might step from one second to the next one time, but not 100
+  // times in a row.
+  for (int i = 0; i < 100; ++i) {
+    lib_.cached_enabled_time_ = 0;
+    SetMetricsConsent(to_value);
+    lib_.AreMetricsEnabled();
+    // If we check the metrics status twice in a row, we use the cached value
+    // the second time.
+    SetMetricsConsent(!to_value);
+    if (lib_.AreMetricsEnabled() == to_value)
+      return;
+  }
+  ADD_FAILURE() << "Did not see evidence of caching";
+}
+
+void MetricsLibraryTest::VerifyEnabledCacheEviction(bool to_value) {
+  lib_.cached_enabled_time_ = 0;
+  SetMetricsConsent(!to_value);
+  ASSERT_EQ(!to_value, lib_.AreMetricsEnabled());
+
+  SetMetricsConsent(to_value);
+  // Sleep one second (or cheat to be faster) and check that we are not using
+  // the cached value.
+  --lib_.cached_enabled_time_;
+  ASSERT_EQ(to_value, lib_.AreMetricsEnabled());
+}
+
+TEST_F(MetricsLibraryTest, AreMetricsEnabledCaching) {
+  VerifyEnabledCacheHit(false);
+  VerifyEnabledCacheHit(true);
+  VerifyEnabledCacheEviction(false);
+  VerifyEnabledCacheEviction(true);
+}
+
+TEST_F(MetricsLibraryTest, AreMetricsEnabledNoCaching) {
+  // disable caching.
+  lib_.use_caching_ = false;
+
+  // Checking the consent repeatedly should return the right result.
+  for (int i=0; i<100; ++i) {
+    SetMetricsConsent(true);
+    ASSERT_EQ(true, lib_.AreMetricsEnabled());
+    SetMetricsConsent(false);
+    ASSERT_EQ(false, lib_.AreMetricsEnabled());
+  }
+}
diff --git a/metricsd.rc b/metricsd.rc
new file mode 100644
index 0000000..3d3e695
--- /dev/null
+++ b/metricsd.rc
@@ -0,0 +1,9 @@
+on post-fs-data
+    mkdir /data/misc/metrics 0750 metrics_coll system
+    mkdir /data/misc/metricsd 0700 metricsd metricsd
+    mkdir /data/misc/metrics_collector 0700 metrics_coll metrics_coll
+
+service metricsd /system/bin/metricsd --foreground --logtosyslog
+    class late_start
+    user metricsd
+    group system inet
diff --git a/metricsd_main.cc b/metricsd_main.cc
new file mode 100644
index 0000000..0178342
--- /dev/null
+++ b/metricsd_main.cc
@@ -0,0 +1,83 @@
+/*
+ * 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 <base/command_line.h>
+#include <base/files/file_path.h>
+#include <base/logging.h>
+#include <base/time/time.h>
+#include <brillo/flag_helper.h>
+#include <brillo/syslog_logging.h>
+
+#include "constants.h"
+#include "uploader/metricsd_service_runner.h"
+#include "uploader/upload_service.h"
+
+int main(int argc, char** argv) {
+  DEFINE_bool(foreground, false, "Don't daemonize");
+
+  // Upload the metrics once and exit. (used for testing)
+  DEFINE_bool(uploader_test, false, "run the uploader once and exit");
+
+  // Upload Service flags.
+  DEFINE_int32(upload_interval_secs, 1800,
+               "Interval at which metricsd uploads the metrics.");
+  DEFINE_int32(disk_persistence_interval_secs, 300,
+               "Interval at which metricsd saves the aggregated metrics to "
+               "disk to avoid losing them if metricsd stops in between "
+               "two uploads.");
+  DEFINE_string(server, metrics::kMetricsServer,
+                "Server to upload the metrics to.");
+  DEFINE_string(private_directory, metrics::kMetricsdDirectory,
+                "Path to the private directory used by metricsd "
+                "(testing only)");
+  DEFINE_string(shared_directory, metrics::kSharedMetricsDirectory,
+                "Path to the shared metrics directory, used by "
+                "metrics_collector, metricsd and all metrics clients "
+                "(testing only)");
+
+  DEFINE_bool(logtostderr, false, "Log to standard error");
+  DEFINE_bool(logtosyslog, false, "Log to syslog");
+
+  brillo::FlagHelper::Init(argc, argv, "Brillo metrics daemon.");
+
+  int logging_location =
+      (FLAGS_foreground ? brillo::kLogToStderr : brillo::kLogToSyslog);
+  if (FLAGS_logtosyslog)
+    logging_location = brillo::kLogToSyslog;
+
+  if (FLAGS_logtostderr)
+    logging_location = brillo::kLogToStderr;
+
+  // Also log to stderr when not running as daemon.
+  brillo::InitLog(logging_location | brillo::kLogHeader);
+
+  if (FLAGS_logtostderr && FLAGS_logtosyslog) {
+    LOG(ERROR) << "only one of --logtosyslog and --logtostderr can be set";
+    return 1;
+  }
+
+  if (!FLAGS_foreground && daemon(0, 0) != 0) {
+    return errno;
+  }
+
+  UploadService upload_service(
+      FLAGS_server, base::TimeDelta::FromSeconds(FLAGS_upload_interval_secs),
+      base::TimeDelta::FromSeconds(FLAGS_disk_persistence_interval_secs),
+      base::FilePath(FLAGS_private_directory),
+      base::FilePath(FLAGS_shared_directory));
+
+  return upload_service.Run();
+}
diff --git a/persistent_integer.cc b/persistent_integer.cc
new file mode 100644
index 0000000..7fe355e
--- /dev/null
+++ b/persistent_integer.cc
@@ -0,0 +1,96 @@
+/*
+ * 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 "persistent_integer.h"
+
+#include <fcntl.h>
+
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+
+#include "constants.h"
+
+namespace chromeos_metrics {
+
+PersistentInteger::PersistentInteger(const std::string& name,
+                                     const base::FilePath& directory)
+    : value_(0),
+      version_(kVersion),
+      name_(name),
+      backing_file_path_(directory.Append(name_)),
+      synced_(false) {}
+
+PersistentInteger::~PersistentInteger() {}
+
+void PersistentInteger::Set(int64_t value) {
+  value_ = value;
+  Write();
+}
+
+int64_t PersistentInteger::Get() {
+  // If not synced, then read.  If the read fails, it's a good idea to write.
+  if (!synced_ && !Read())
+    Write();
+  return value_;
+}
+
+int64_t PersistentInteger::GetAndClear() {
+  int64_t v = Get();
+  Set(0);
+  return v;
+}
+
+void PersistentInteger::Add(int64_t x) {
+  Set(Get() + x);
+}
+
+void PersistentInteger::Write() {
+  int fd = HANDLE_EINTR(open(backing_file_path_.value().c_str(),
+                             O_WRONLY | O_CREAT | O_TRUNC,
+                             S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH));
+  PCHECK(fd >= 0) << "cannot open " << backing_file_path_.value()
+                  << " for writing";
+  PCHECK((HANDLE_EINTR(write(fd, &version_, sizeof(version_))) ==
+          sizeof(version_)) &&
+         (HANDLE_EINTR(write(fd, &value_, sizeof(value_))) ==
+          sizeof(value_)))
+      << "cannot write to " << backing_file_path_.value();
+  close(fd);
+  synced_ = true;
+}
+
+bool PersistentInteger::Read() {
+  int fd = HANDLE_EINTR(open(backing_file_path_.value().c_str(), O_RDONLY));
+  if (fd < 0) {
+    PLOG(WARNING) << "cannot open " << backing_file_path_.value()
+                  << " for reading";
+    return false;
+  }
+  int32_t version;
+  int64_t value;
+  bool read_succeeded = false;
+  if (HANDLE_EINTR(read(fd, &version, sizeof(version))) == sizeof(version) &&
+      version == version_ &&
+      HANDLE_EINTR(read(fd, &value, sizeof(value))) == sizeof(value)) {
+    value_ = value;
+    read_succeeded = true;
+    synced_ = true;
+  }
+  close(fd);
+  return read_succeeded;
+}
+
+}  // namespace chromeos_metrics
diff --git a/persistent_integer.h b/persistent_integer.h
new file mode 100644
index 0000000..96d9fc0
--- /dev/null
+++ b/persistent_integer.h
@@ -0,0 +1,75 @@
+/*
+ * 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 METRICS_PERSISTENT_INTEGER_H_
+#define METRICS_PERSISTENT_INTEGER_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include <base/files/file_path.h>
+
+namespace chromeos_metrics {
+
+// PersistentIntegers is a named 64-bit integer value backed by a file.
+// The in-memory value acts as a write-through cache of the file value.
+// If the backing file doesn't exist or has bad content, the value is 0.
+
+class PersistentInteger {
+ public:
+  PersistentInteger(const std::string& name, const base::FilePath& directory);
+
+  // Virtual only because of mock.
+  virtual ~PersistentInteger();
+
+  // Sets the value.  This writes through to the backing file.
+  void Set(int64_t v);
+
+  // Gets the value.  May sync from backing file first.
+  int64_t Get();
+
+  // Returns the name of the object.
+  std::string Name() { return name_; }
+
+  // Convenience function for Get() followed by Set(0).
+  int64_t GetAndClear();
+
+  // Convenience function for v = Get, Set(v + x).
+  // Virtual only because of mock.
+  virtual void Add(int64_t x);
+
+ private:
+  static const int kVersion = 1001;
+
+  // Writes |value_| to the backing file, creating it if necessary.
+  void Write();
+
+  // Reads the value from the backing file, stores it in |value_|, and returns
+  // true if the backing file is valid.  Returns false otherwise, and creates
+  // a valid backing file as a side effect.
+  bool Read();
+
+  int64_t value_;
+  int32_t version_;
+  std::string name_;
+  base::FilePath backing_file_path_;
+  bool synced_;
+};
+
+}  // namespace chromeos_metrics
+
+#endif  // METRICS_PERSISTENT_INTEGER_H_
diff --git a/persistent_integer_mock.h b/persistent_integer_mock.h
new file mode 100644
index 0000000..0be54d4
--- /dev/null
+++ b/persistent_integer_mock.h
@@ -0,0 +1,38 @@
+/*
+ * 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 METRICS_PERSISTENT_INTEGER_MOCK_H_
+#define METRICS_PERSISTENT_INTEGER_MOCK_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "persistent_integer.h"
+
+namespace chromeos_metrics {
+
+class PersistentIntegerMock : public PersistentInteger {
+ public:
+  explicit PersistentIntegerMock(const std::string& name,
+                                 const base::FilePath& directory)
+      : PersistentInteger(name, directory) {}
+  MOCK_METHOD1(Add, void(int64_t count));
+};
+
+}  // namespace chromeos_metrics
+
+#endif  // METRICS_PERSISTENT_INTEGER_MOCK_H_
diff --git a/persistent_integer_test.cc b/persistent_integer_test.cc
new file mode 100644
index 0000000..bf76261
--- /dev/null
+++ b/persistent_integer_test.cc
@@ -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.
+ */
+
+#include <memory>
+
+#include <base/compiler_specific.h>
+#include <base/files/file_enumerator.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <gtest/gtest.h>
+
+#include "persistent_integer.h"
+
+const char kBackingFileName[] = "1.pibakf";
+
+using chromeos_metrics::PersistentInteger;
+
+class PersistentIntegerTest : public testing::Test {
+  void SetUp() override {
+    // Set testing mode.
+    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+  }
+
+ protected:
+  base::ScopedTempDir temp_dir_;
+};
+
+TEST_F(PersistentIntegerTest, BasicChecks) {
+  std::unique_ptr<PersistentInteger> pi(
+      new PersistentInteger(kBackingFileName, temp_dir_.path()));
+
+  // Test initialization.
+  EXPECT_EQ(0, pi->Get());
+  EXPECT_EQ(kBackingFileName, pi->Name());  // boring
+
+  // Test set and add.
+  pi->Set(2);
+  pi->Add(3);
+  EXPECT_EQ(5, pi->Get());
+
+  // Test persistence.
+  pi.reset(new PersistentInteger(kBackingFileName, temp_dir_.path()));
+  EXPECT_EQ(5, pi->Get());
+
+  // Test GetAndClear.
+  EXPECT_EQ(5, pi->GetAndClear());
+  EXPECT_EQ(pi->Get(), 0);
+
+  // Another persistence test.
+  pi.reset(new PersistentInteger(kBackingFileName, temp_dir_.path()));
+  EXPECT_EQ(0, pi->Get());
+}
diff --git a/timer.cc b/timer.cc
new file mode 100644
index 0000000..06fc336
--- /dev/null
+++ b/timer.cc
@@ -0,0 +1,118 @@
+/*
+ * 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 "metrics/timer.h"
+
+#include <string>
+
+#include "metrics/metrics_library.h"
+
+namespace chromeos_metrics {
+
+base::TimeTicks ClockWrapper::GetCurrentTime() const {
+  return base::TimeTicks::Now();
+}
+
+Timer::Timer()
+    : timer_state_(kTimerStopped),
+      clock_wrapper_(new ClockWrapper()) {}
+
+bool Timer::Start() {
+  elapsed_time_ = base::TimeDelta();  // Sets elapsed_time_ to zero.
+  start_time_ = clock_wrapper_->GetCurrentTime();
+  timer_state_ = kTimerRunning;
+  return true;
+}
+
+bool Timer::Stop() {
+  if (timer_state_ == kTimerStopped)
+    return false;
+  if (timer_state_ == kTimerRunning)
+    elapsed_time_ += clock_wrapper_->GetCurrentTime() - start_time_;
+  timer_state_ = kTimerStopped;
+  return true;
+}
+
+bool Timer::Pause() {
+  switch (timer_state_) {
+    case kTimerStopped:
+      if (!Start())
+        return false;
+      timer_state_ = kTimerPaused;
+      return true;
+    case kTimerRunning:
+      timer_state_ = kTimerPaused;
+      elapsed_time_ += clock_wrapper_->GetCurrentTime() - start_time_;
+      return true;
+    default:
+      return false;
+  }
+}
+
+bool Timer::Resume() {
+  switch (timer_state_) {
+    case kTimerStopped:
+      return Start();
+    case kTimerPaused:
+      start_time_ = clock_wrapper_->GetCurrentTime();
+      timer_state_ = kTimerRunning;
+      return true;
+    default:
+      return false;
+  }
+}
+
+bool Timer::Reset() {
+  elapsed_time_ = base::TimeDelta();  // Sets elapsed_time_ to zero.
+  timer_state_ = kTimerStopped;
+  return true;
+}
+
+bool Timer::HasStarted() const {
+  return timer_state_ != kTimerStopped;
+}
+
+bool Timer::GetElapsedTime(base::TimeDelta* elapsed_time) const {
+  if (start_time_.is_null() || !elapsed_time)
+    return false;
+  *elapsed_time = elapsed_time_;
+  if (timer_state_ == kTimerRunning) {
+    *elapsed_time += clock_wrapper_->GetCurrentTime() - start_time_;
+  }
+  return true;
+}
+
+// static
+MetricsLibraryInterface* TimerReporter::metrics_lib_ = nullptr;
+
+TimerReporter::TimerReporter(const std::string& histogram_name, int min,
+                             int max, int num_buckets)
+    : histogram_name_(histogram_name),
+      min_(min),
+      max_(max),
+      num_buckets_(num_buckets) {}
+
+bool TimerReporter::ReportMilliseconds() const {
+  base::TimeDelta elapsed_time;
+  if (!metrics_lib_ || !GetElapsedTime(&elapsed_time)) return false;
+  return metrics_lib_->SendToUMA(histogram_name_,
+                                 elapsed_time.InMilliseconds(),
+                                 min_,
+                                 max_,
+                                 num_buckets_);
+}
+
+}  // namespace chromeos_metrics
diff --git a/timer_test.cc b/timer_test.cc
new file mode 100644
index 0000000..cfbcd8a
--- /dev/null
+++ b/timer_test.cc
@@ -0,0 +1,464 @@
+/*
+ * 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 <stdint.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <memory>
+
+#include "metrics/metrics_library_mock.h"
+#include "metrics/timer.h"
+#include "metrics/timer_mock.h"
+
+using ::testing::_;
+using ::testing::Return;
+
+namespace chromeos_metrics {
+
+namespace {
+const int64_t kStime1MSec = 1400;
+const int64_t kEtime1MSec = 3000;
+const int64_t kDelta1MSec = 1600;
+
+const int64_t kStime2MSec = 4200;
+const int64_t kEtime2MSec = 5000;
+const int64_t kDelta2MSec = 800;
+
+const int64_t kStime3MSec = 6600;
+const int64_t kEtime3MSec = 6800;
+const int64_t kDelta3MSec = 200;
+}  // namespace
+
+class TimerTest : public testing::Test {
+ public:
+  TimerTest() : clock_wrapper_mock_(new ClockWrapperMock()) {}
+
+ protected:
+  virtual void SetUp() {
+    EXPECT_EQ(Timer::kTimerStopped, timer_.timer_state_);
+    stime += base::TimeDelta::FromMilliseconds(kStime1MSec);
+    etime += base::TimeDelta::FromMilliseconds(kEtime1MSec);
+    stime2 += base::TimeDelta::FromMilliseconds(kStime2MSec);
+    etime2 += base::TimeDelta::FromMilliseconds(kEtime2MSec);
+    stime3 += base::TimeDelta::FromMilliseconds(kStime3MSec);
+    etime3 += base::TimeDelta::FromMilliseconds(kEtime3MSec);
+  }
+
+  virtual void TearDown() {}
+
+  Timer timer_;
+  std::unique_ptr<ClockWrapperMock> clock_wrapper_mock_;
+  base::TimeTicks stime, etime, stime2, etime2, stime3, etime3;
+};
+
+TEST_F(TimerTest, StartStop) {
+  EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+      .WillOnce(Return(stime))
+      .WillOnce(Return(etime));
+  timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+  ASSERT_TRUE(timer_.Start());
+  ASSERT_TRUE(timer_.start_time_ == stime);
+  ASSERT_TRUE(timer_.HasStarted());
+  ASSERT_TRUE(timer_.Stop());
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+
+  base::TimeDelta elapsed_time;
+  ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+            elapsed_time.InMilliseconds());
+
+  ASSERT_FALSE(timer_.HasStarted());
+}
+
+TEST_F(TimerTest, ReStart) {
+  EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+      .WillOnce(Return(stime))
+      .WillOnce(Return(etime));
+  timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+  timer_.Start();
+  base::TimeTicks buffer = timer_.start_time_;
+  timer_.Start();
+  ASSERT_FALSE(timer_.start_time_ == buffer);
+}
+
+TEST_F(TimerTest, Reset) {
+  EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+      .WillOnce(Return(stime));
+  timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+  timer_.Start();
+  ASSERT_TRUE(timer_.Reset());
+  ASSERT_FALSE(timer_.HasStarted());
+}
+
+TEST_F(TimerTest, SeparatedTimers) {
+  EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+      .WillOnce(Return(stime))
+      .WillOnce(Return(etime))
+      .WillOnce(Return(stime2))
+      .WillOnce(Return(etime2));
+  timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+  ASSERT_TRUE(timer_.Start());
+  ASSERT_TRUE(timer_.Stop());
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+  ASSERT_TRUE(timer_.Start());
+  ASSERT_TRUE(timer_.start_time_ == stime2);
+  ASSERT_TRUE(timer_.Stop());
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta2MSec);
+  ASSERT_FALSE(timer_.HasStarted());
+
+  base::TimeDelta elapsed_time;
+  ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+            elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, InvalidStop) {
+  EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+      .WillOnce(Return(stime))
+      .WillOnce(Return(etime));
+  timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+  ASSERT_FALSE(timer_.Stop());
+  // Now we try it again, but after a valid start/stop.
+  timer_.Start();
+  timer_.Stop();
+  base::TimeDelta elapsed_time = timer_.elapsed_time_;
+  ASSERT_FALSE(timer_.Stop());
+  ASSERT_TRUE(elapsed_time == timer_.elapsed_time_);
+}
+
+TEST_F(TimerTest, InvalidElapsedTime) {
+  base::TimeDelta elapsed_time;
+  ASSERT_FALSE(timer_.GetElapsedTime(&elapsed_time));
+}
+
+TEST_F(TimerTest, PauseStartStopResume) {
+  EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+      .WillOnce(Return(stime))
+      .WillOnce(Return(stime2))
+      .WillOnce(Return(etime2))
+      .WillOnce(Return(stime3))
+      .WillOnce(Return(etime3));
+  timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+  ASSERT_TRUE(timer_.Pause());  // Starts timer paused.
+  ASSERT_TRUE(timer_.start_time_ == stime);
+  ASSERT_TRUE(timer_.HasStarted());
+
+  ASSERT_TRUE(timer_.Start());  // Restarts timer.
+  ASSERT_TRUE(timer_.start_time_ == stime2);
+  ASSERT_TRUE(timer_.HasStarted());
+
+  ASSERT_TRUE(timer_.Stop());
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta2MSec);
+  ASSERT_FALSE(timer_.HasStarted());
+  base::TimeDelta elapsed_time;
+  ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+            elapsed_time.InMilliseconds());
+
+  ASSERT_TRUE(timer_.Resume());
+  ASSERT_TRUE(timer_.HasStarted());
+  ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+  ASSERT_EQ(kDelta3MSec, elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, ResumeStartStopPause) {
+  EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+      .WillOnce(Return(stime))
+      .WillOnce(Return(stime2))
+      .WillOnce(Return(etime2))
+      .WillOnce(Return(stime3));
+  timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+  ASSERT_TRUE(timer_.Resume());
+  ASSERT_TRUE(timer_.start_time_ == stime);
+  ASSERT_TRUE(timer_.HasStarted());
+
+  ASSERT_TRUE(timer_.Start());
+  ASSERT_TRUE(timer_.start_time_ == stime2);
+  ASSERT_TRUE(timer_.HasStarted());
+
+  ASSERT_TRUE(timer_.Stop());
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta2MSec);
+  ASSERT_FALSE(timer_.HasStarted());
+  base::TimeDelta elapsed_time;
+  ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+            elapsed_time.InMilliseconds());
+
+  ASSERT_TRUE(timer_.Pause());
+  ASSERT_TRUE(timer_.HasStarted());
+  ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+  ASSERT_EQ(0, elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, StartResumeStop) {
+  EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+      .WillOnce(Return(stime))
+      .WillOnce(Return(etime));
+  timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+  ASSERT_TRUE(timer_.Start());
+  ASSERT_TRUE(timer_.start_time_ == stime);
+  ASSERT_TRUE(timer_.HasStarted());
+
+  ASSERT_FALSE(timer_.Resume());
+  ASSERT_TRUE(timer_.start_time_ == stime);
+  ASSERT_TRUE(timer_.HasStarted());
+
+  ASSERT_TRUE(timer_.Stop());
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+  ASSERT_FALSE(timer_.HasStarted());
+  base::TimeDelta elapsed_time;
+  ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+            elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, StartPauseStop) {
+  EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+      .WillOnce(Return(stime))
+      .WillOnce(Return(etime));
+  timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+  ASSERT_TRUE(timer_.Start());
+  ASSERT_TRUE(timer_.start_time_ == stime);
+  ASSERT_TRUE(timer_.HasStarted());
+
+  ASSERT_TRUE(timer_.Pause());
+  ASSERT_TRUE(timer_.HasStarted());
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+  base::TimeDelta elapsed_time;
+  ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+            elapsed_time.InMilliseconds());
+
+  ASSERT_TRUE(timer_.Stop());
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+  ASSERT_FALSE(timer_.HasStarted());
+  ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+            elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, StartPauseResumeStop) {
+  EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+      .WillOnce(Return(stime))
+      .WillOnce(Return(etime))
+      .WillOnce(Return(stime2))
+      .WillOnce(Return(etime2));
+  timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+  ASSERT_TRUE(timer_.Start());
+  ASSERT_TRUE(timer_.start_time_ == stime);
+  ASSERT_TRUE(timer_.HasStarted());
+
+  ASSERT_TRUE(timer_.Pause());
+  ASSERT_TRUE(timer_.HasStarted());
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+  base::TimeDelta elapsed_time;
+  ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+            elapsed_time.InMilliseconds());
+
+  ASSERT_TRUE(timer_.Resume());
+  ASSERT_TRUE(timer_.HasStarted());
+
+  ASSERT_TRUE(timer_.Stop());
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec + kDelta2MSec);
+  ASSERT_FALSE(timer_.HasStarted());
+  ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+            elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, PauseStop) {
+  EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+      .WillOnce(Return(stime));
+  timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+  ASSERT_TRUE(timer_.Pause());
+  ASSERT_TRUE(timer_.start_time_ == stime);
+  ASSERT_TRUE(timer_.HasStarted());
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), 0);
+
+  ASSERT_TRUE(timer_.Stop());
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), 0);
+  ASSERT_FALSE(timer_.HasStarted());
+  base::TimeDelta elapsed_time;
+  ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+            elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, PauseResumeStop) {
+  EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+      .WillOnce(Return(stime))
+      .WillOnce(Return(stime2))
+      .WillOnce(Return(etime2));
+  timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+  ASSERT_TRUE(timer_.Pause());
+  ASSERT_TRUE(timer_.start_time_ == stime);
+  ASSERT_TRUE(timer_.HasStarted());
+
+  ASSERT_TRUE(timer_.Resume());
+  ASSERT_TRUE(timer_.HasStarted());
+
+  ASSERT_TRUE(timer_.Stop());
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta2MSec);
+  ASSERT_FALSE(timer_.HasStarted());
+  base::TimeDelta elapsed_time;
+  ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+            elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, StartPauseResumePauseStop) {
+  EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+      .WillOnce(Return(stime))
+      .WillOnce(Return(etime))
+      .WillOnce(Return(stime2))
+      .WillOnce(Return(stime3))
+      .WillOnce(Return(etime3));
+  timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+  ASSERT_TRUE(timer_.Start());
+  ASSERT_TRUE(timer_.start_time_ == stime);
+  ASSERT_TRUE(timer_.HasStarted());
+
+  ASSERT_TRUE(timer_.Pause());
+  ASSERT_TRUE(timer_.HasStarted());
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+  base::TimeDelta elapsed_time;
+  ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+            elapsed_time.InMilliseconds());
+
+  ASSERT_TRUE(timer_.Resume());
+  ASSERT_TRUE(timer_.HasStarted());
+  // Make sure GetElapsedTime works while we're running.
+  ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+  ASSERT_EQ(kDelta1MSec + kStime3MSec - kStime2MSec,
+            elapsed_time.InMilliseconds());
+
+  ASSERT_TRUE(timer_.Pause());
+  ASSERT_TRUE(timer_.HasStarted());
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+            kDelta1MSec + kEtime3MSec - kStime2MSec);
+  ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+            elapsed_time.InMilliseconds());
+
+  ASSERT_TRUE(timer_.Stop());
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+            kDelta1MSec + kEtime3MSec - kStime2MSec);
+  ASSERT_FALSE(timer_.HasStarted());
+  ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+            elapsed_time.InMilliseconds());
+}
+
+TEST_F(TimerTest, StartPauseResumePauseResumeStop) {
+  EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+      .WillOnce(Return(stime))
+      .WillOnce(Return(etime))
+      .WillOnce(Return(stime2))
+      .WillOnce(Return(etime2))
+      .WillOnce(Return(stime3))
+      .WillOnce(Return(etime3));
+  timer_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+  ASSERT_TRUE(timer_.Start());
+  ASSERT_TRUE(timer_.start_time_ == stime);
+  ASSERT_TRUE(timer_.HasStarted());
+
+  ASSERT_TRUE(timer_.Pause());
+  ASSERT_TRUE(timer_.HasStarted());
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec);
+  base::TimeDelta elapsed_time;
+  ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+            elapsed_time.InMilliseconds());
+
+  ASSERT_TRUE(timer_.Resume());
+  ASSERT_TRUE(timer_.HasStarted());
+
+  ASSERT_TRUE(timer_.Pause());
+  ASSERT_TRUE(timer_.HasStarted());
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), kDelta1MSec + kDelta2MSec);
+  ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+            elapsed_time.InMilliseconds());
+
+  ASSERT_TRUE(timer_.Resume());
+  ASSERT_TRUE(timer_.HasStarted());
+
+  ASSERT_TRUE(timer_.Stop());
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+            kDelta1MSec + kDelta2MSec + kDelta3MSec);
+  ASSERT_FALSE(timer_.HasStarted());
+  ASSERT_TRUE(timer_.GetElapsedTime(&elapsed_time));
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(),
+            elapsed_time.InMilliseconds());
+}
+
+static const char kMetricName[] = "test-timer";
+static const int kMinSample = 0;
+static const int kMaxSample = 120 * 1E6;
+static const int kNumBuckets = 50;
+
+class TimerReporterTest : public testing::Test {
+ public:
+  TimerReporterTest() : timer_reporter_(kMetricName, kMinSample, kMaxSample,
+                                        kNumBuckets),
+                        clock_wrapper_mock_(new ClockWrapperMock()) {}
+
+ protected:
+  virtual void SetUp() {
+    timer_reporter_.set_metrics_lib(&lib_);
+    EXPECT_EQ(timer_reporter_.histogram_name_, kMetricName);
+    EXPECT_EQ(timer_reporter_.min_, kMinSample);
+    EXPECT_EQ(timer_reporter_.max_, kMaxSample);
+    EXPECT_EQ(timer_reporter_.num_buckets_, kNumBuckets);
+    stime += base::TimeDelta::FromMilliseconds(kStime1MSec);
+    etime += base::TimeDelta::FromMilliseconds(kEtime1MSec);
+  }
+
+  virtual void TearDown() {
+    timer_reporter_.set_metrics_lib(nullptr);
+  }
+
+  TimerReporter timer_reporter_;
+  MetricsLibraryMock lib_;
+  std::unique_ptr<ClockWrapperMock> clock_wrapper_mock_;
+  base::TimeTicks stime, etime;
+};
+
+TEST_F(TimerReporterTest, StartStopReport) {
+  EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+      .WillOnce(Return(stime))
+      .WillOnce(Return(etime));
+  timer_reporter_.clock_wrapper_ = std::move(clock_wrapper_mock_);
+  EXPECT_CALL(lib_, SendToUMA(kMetricName, kDelta1MSec, kMinSample, kMaxSample,
+                              kNumBuckets)).WillOnce(Return(true));
+  ASSERT_TRUE(timer_reporter_.Start());
+  ASSERT_TRUE(timer_reporter_.Stop());
+  ASSERT_TRUE(timer_reporter_.ReportMilliseconds());
+}
+
+TEST_F(TimerReporterTest, InvalidReport) {
+  ASSERT_FALSE(timer_reporter_.ReportMilliseconds());
+}
+
+}  // namespace chromeos_metrics
+
+int main(int argc, char **argv) {
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/uploader/bn_metricsd_impl.cc b/uploader/bn_metricsd_impl.cc
new file mode 100644
index 0000000..219ed60
--- /dev/null
+++ b/uploader/bn_metricsd_impl.cc
@@ -0,0 +1,98 @@
+/*
+ * 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 "uploader/bn_metricsd_impl.h"
+
+#include <base/metrics/histogram.h>
+#include <base/metrics/sparse_histogram.h>
+#include <base/metrics/statistics_recorder.h>
+#include <utils/Errors.h>
+#include <utils/String16.h>
+#include <utils/String8.h>
+
+using android::binder::Status;
+using android::String16;
+
+static const char16_t kCrashTypeKernel[] = u"kernel";
+static const char16_t kCrashTypeUncleanShutdown[] = u"uncleanshutdown";
+static const char16_t kCrashTypeUser[] = u"user";
+
+BnMetricsdImpl::BnMetricsdImpl(const std::shared_ptr<CrashCounters>& counters)
+    : counters_(counters) {
+  CHECK(counters_) << "Invalid counters argument to constructor";
+}
+
+Status BnMetricsdImpl::recordHistogram(
+    const String16& name, int sample, int min, int max, int nbuckets) {
+  base::HistogramBase* histogram = base::Histogram::FactoryGet(
+      android::String8(name).string(), min, max, nbuckets,
+      base::Histogram::kUmaTargetedHistogramFlag);
+  // |histogram| may be null if a client reports two contradicting histograms
+  // with the same name but different limits.
+  // FactoryGet will print a useful message if that is the case.
+  if (histogram) {
+    histogram->Add(sample);
+  }
+  return Status::ok();
+}
+
+Status BnMetricsdImpl::recordLinearHistogram(const String16& name,
+                                             int sample,
+                                             int max) {
+  base::HistogramBase* histogram = base::LinearHistogram::FactoryGet(
+      android::String8(name).string(), 1, max, max + 1,
+      base::Histogram::kUmaTargetedHistogramFlag);
+  // |histogram| may be null if a client reports two contradicting histograms
+  // with the same name but different limits.
+  // FactoryGet will print a useful message if that is the case.
+  if (histogram) {
+    histogram->Add(sample);
+  }
+  return Status::ok();
+}
+
+Status BnMetricsdImpl::recordSparseHistogram(const String16& name, int sample) {
+  base::HistogramBase* histogram = base::SparseHistogram::FactoryGet(
+      android::String8(name).string(),
+      base::Histogram::kUmaTargetedHistogramFlag);
+  // |histogram| may be null if a client reports two contradicting histograms
+  // with the same name but different limits.
+  // FactoryGet will print a useful message if that is the case.
+  if (histogram) {
+    histogram->Add(sample);
+  }
+  return Status::ok();
+}
+
+Status BnMetricsdImpl::recordCrash(const String16& type) {
+  if (type == kCrashTypeUser) {
+    counters_->IncrementUserCrashCount();
+  } else if (type == kCrashTypeKernel) {
+    counters_->IncrementKernelCrashCount();
+  } else if (type == kCrashTypeUncleanShutdown) {
+    counters_->IncrementUncleanShutdownCount();
+  } else {
+    LOG(ERROR) << "Unknown crash type received: " << type;
+  }
+  return Status::ok();
+}
+
+Status BnMetricsdImpl::getHistogramsDump(String16* dump) {
+  std::string str_dump;
+  base::StatisticsRecorder::WriteGraph(std::string(), &str_dump);
+  *dump = String16(str_dump.c_str());
+  return Status::ok();
+}
diff --git a/uploader/bn_metricsd_impl.h b/uploader/bn_metricsd_impl.h
new file mode 100644
index 0000000..bf47e80
--- /dev/null
+++ b/uploader/bn_metricsd_impl.h
@@ -0,0 +1,54 @@
+/*
+ * 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 METRICSD_UPLOADER_BN_METRICSD_IMPL_H_
+#define METRICSD_UPLOADER_BN_METRICSD_IMPL_H_
+
+#include "android/brillo/metrics/BnMetricsd.h"
+#include "uploader/crash_counters.h"
+
+class BnMetricsdImpl : public android::brillo::metrics::BnMetricsd {
+ public:
+  explicit BnMetricsdImpl(const std::shared_ptr<CrashCounters>& counters);
+  virtual ~BnMetricsdImpl() = default;
+
+  // Records a histogram.
+  android::binder::Status recordHistogram(const android::String16& name,
+                                          int sample,
+                                          int min,
+                                          int max,
+                                          int nbuckets) override;
+
+  // Records a linear histogram.
+  android::binder::Status recordLinearHistogram(const android::String16& name,
+                                                int sample,
+                                                int max) override;
+
+  // Records a sparse histogram.
+  android::binder::Status recordSparseHistogram(const android::String16& name,
+                                                int sample) override;
+
+  // Records a crash.
+  android::binder::Status recordCrash(const android::String16& type) override;
+
+  // Returns a dump of the histograms aggregated in memory.
+  android::binder::Status getHistogramsDump(android::String16* dump) override;
+
+ private:
+  std::shared_ptr<CrashCounters> counters_;
+};
+
+#endif  // METRICSD_UPLOADER_BN_METRICSD_IMPL_H_
diff --git a/uploader/crash_counters.cc b/uploader/crash_counters.cc
new file mode 100644
index 0000000..1478b9a
--- /dev/null
+++ b/uploader/crash_counters.cc
@@ -0,0 +1,44 @@
+/*
+ * 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 "uploader/crash_counters.h"
+
+CrashCounters::CrashCounters()
+    : kernel_crashes_(0), unclean_shutdowns_(0), user_crashes_(0) {}
+
+void CrashCounters::IncrementKernelCrashCount() {
+  kernel_crashes_++;
+}
+
+unsigned int CrashCounters::GetAndResetKernelCrashCount() {
+  return kernel_crashes_.exchange(0);
+}
+
+void CrashCounters::IncrementUncleanShutdownCount() {
+  unclean_shutdowns_++;
+}
+
+unsigned int CrashCounters::GetAndResetUncleanShutdownCount() {
+  return unclean_shutdowns_.exchange(0);
+}
+
+void CrashCounters::IncrementUserCrashCount() {
+  user_crashes_++;
+}
+
+unsigned int CrashCounters::GetAndResetUserCrashCount() {
+  return user_crashes_.exchange(0);
+}
diff --git a/uploader/crash_counters.h b/uploader/crash_counters.h
new file mode 100644
index 0000000..3fdbf3f
--- /dev/null
+++ b/uploader/crash_counters.h
@@ -0,0 +1,45 @@
+/*
+ * 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 METRICSD_UPLOADER_CRASH_COUNTERS_H_
+#define METRICSD_UPLOADER_CRASH_COUNTERS_H_
+
+#include <atomic>
+
+// This class is used to keep track of the crash counters.
+// An instance of it will be used by both the binder thread (to increment the
+// counters) and the uploader thread (to gather and reset the counters).
+// As such, the internal counters are atomic uints to allow concurrent access.
+class CrashCounters {
+ public:
+  CrashCounters();
+
+  void IncrementKernelCrashCount();
+  unsigned int GetAndResetKernelCrashCount();
+
+  void IncrementUserCrashCount();
+  unsigned int GetAndResetUserCrashCount();
+
+  void IncrementUncleanShutdownCount();
+  unsigned int GetAndResetUncleanShutdownCount();
+
+ private:
+  std::atomic_uint kernel_crashes_;
+  std::atomic_uint unclean_shutdowns_;
+  std::atomic_uint user_crashes_;
+};
+
+#endif  // METRICSD_UPLOADER_CRASH_COUNTERS_H_
diff --git a/uploader/metrics_hashes.cc b/uploader/metrics_hashes.cc
new file mode 100644
index 0000000..208c560
--- /dev/null
+++ b/uploader/metrics_hashes.cc
@@ -0,0 +1,51 @@
+/*
+ * 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 "uploader/metrics_hashes.h"
+
+#include "base/logging.h"
+#include "base/md5.h"
+#include "base/sys_byteorder.h"
+
+namespace metrics {
+
+namespace {
+
+// Converts the 8-byte prefix of an MD5 hash into a uint64 value.
+inline uint64_t HashToUInt64(const std::string& hash) {
+  uint64_t value;
+  DCHECK_GE(hash.size(), sizeof(value));
+  memcpy(&value, hash.data(), sizeof(value));
+  return base::HostToNet64(value);
+}
+
+}  // namespace
+
+uint64_t HashMetricName(const std::string& name) {
+  // Create an MD5 hash of the given |name|, represented as a byte buffer
+  // encoded as an std::string.
+  base::MD5Context context;
+  base::MD5Init(&context);
+  base::MD5Update(&context, name);
+
+  base::MD5Digest digest;
+  base::MD5Final(&digest, &context);
+
+  std::string hash_str(reinterpret_cast<char*>(digest.a), arraysize(digest.a));
+  return HashToUInt64(hash_str);
+}
+
+}  // namespace metrics
diff --git a/uploader/metrics_hashes.h b/uploader/metrics_hashes.h
new file mode 100644
index 0000000..1082b42
--- /dev/null
+++ b/uploader/metrics_hashes.h
@@ -0,0 +1,30 @@
+/*
+ * 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 METRICS_UPLOADER_METRICS_HASHES_H_
+#define METRICS_UPLOADER_METRICS_HASHES_H_
+
+#include <string>
+
+namespace metrics {
+
+// Computes a uint64 hash of a given string based on its MD5 hash. Suitable for
+// metric names.
+uint64_t HashMetricName(const std::string& name);
+
+}  // namespace metrics
+
+#endif  // METRICS_UPLOADER_METRICS_HASHES_H_
diff --git a/uploader/metrics_hashes_unittest.cc b/uploader/metrics_hashes_unittest.cc
new file mode 100644
index 0000000..b8c2575
--- /dev/null
+++ b/uploader/metrics_hashes_unittest.cc
@@ -0,0 +1,44 @@
+/*
+ * 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 "uploader/metrics_hashes.h"
+
+#include <base/format_macros.h>
+#include <base/macros.h>
+#include <base/strings/stringprintf.h>
+#include <gtest/gtest.h>
+
+namespace metrics {
+
+// Make sure our ID hashes are the same as what we see on the server side.
+TEST(MetricsUtilTest, HashMetricName) {
+  static const struct {
+    std::string input;
+    std::string output;
+  } cases[] = {
+    {"Back", "0x0557fa923dcee4d0"},
+    {"Forward", "0x67d2f6740a8eaebf"},
+    {"NewTab", "0x290eb683f96572f1"},
+  };
+
+  for (size_t i = 0; i < arraysize(cases); ++i) {
+    uint64_t hash = HashMetricName(cases[i].input);
+    std::string hash_hex = base::StringPrintf("0x%016" PRIx64, hash);
+    EXPECT_EQ(cases[i].output, hash_hex);
+  }
+}
+
+}  // namespace metrics
diff --git a/uploader/metrics_log.cc b/uploader/metrics_log.cc
new file mode 100644
index 0000000..fcaa8c1
--- /dev/null
+++ b/uploader/metrics_log.cc
@@ -0,0 +1,90 @@
+/*
+ * 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 "uploader/metrics_log.h"
+
+#include <string>
+
+#include <base/files/file_util.h>
+
+#include "uploader/proto/system_profile.pb.h"
+#include "uploader/system_profile_setter.h"
+
+// We use default values for the MetricsLogBase constructor as the setter will
+// override them.
+MetricsLog::MetricsLog()
+    : MetricsLogBase("", 0, metrics::MetricsLogBase::ONGOING_LOG, "") {
+}
+
+bool MetricsLog::LoadFromFile(const base::FilePath& saved_log) {
+  std::string encoded_log;
+  if (!base::ReadFileToString(saved_log, &encoded_log)) {
+    LOG(ERROR) << "Failed to read the metrics log backup from "
+               << saved_log.value();
+    return false;
+  }
+
+  if (!uma_proto()->ParseFromString(encoded_log)) {
+    LOG(ERROR) << "Failed to parse log from " << saved_log.value()
+               << ", deleting the log";
+    base::DeleteFile(saved_log, false);
+    uma_proto()->Clear();
+    return false;
+  }
+
+  VLOG(1) << uma_proto()->histogram_event_size() << " histograms loaded from "
+          << saved_log.value();
+
+  return true;
+}
+
+bool MetricsLog::SaveToFile(const base::FilePath& path) {
+  std::string encoded_log;
+  GetEncodedLog(&encoded_log);
+
+  if (static_cast<int>(encoded_log.size()) !=
+      base::WriteFile(path, encoded_log.data(), encoded_log.size())) {
+    LOG(ERROR) << "Failed to persist the current log to " << path.value();
+    return false;
+  }
+  return true;
+}
+
+void MetricsLog::IncrementUserCrashCount(unsigned int count) {
+  metrics::SystemProfileProto::Stability* stability(
+      uma_proto()->mutable_system_profile()->mutable_stability());
+  int current = stability->other_user_crash_count();
+  stability->set_other_user_crash_count(current + count);
+}
+
+void MetricsLog::IncrementKernelCrashCount(unsigned int count) {
+  metrics::SystemProfileProto::Stability* stability(
+      uma_proto()->mutable_system_profile()->mutable_stability());
+  int current = stability->kernel_crash_count();
+  stability->set_kernel_crash_count(current + count);
+}
+
+void MetricsLog::IncrementUncleanShutdownCount(unsigned int count) {
+  metrics::SystemProfileProto::Stability* stability(
+      uma_proto()->mutable_system_profile()->mutable_stability());
+  int current = stability->unclean_system_shutdown_count();
+  stability->set_unclean_system_shutdown_count(current + count);
+}
+
+bool MetricsLog::PopulateSystemProfile(SystemProfileSetter* profile_setter) {
+  CHECK(profile_setter);
+  return profile_setter->Populate(uma_proto());
+}
diff --git a/uploader/metrics_log.h b/uploader/metrics_log.h
new file mode 100644
index 0000000..9e60b97
--- /dev/null
+++ b/uploader/metrics_log.h
@@ -0,0 +1,67 @@
+/*
+ * 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 METRICSD_UPLOADER_METRICS_LOG_H_
+#define METRICSD_UPLOADER_METRICS_LOG_H_
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/macros.h>
+
+#include "uploader/metrics_log_base.h"
+
+// This file defines a set of user experience metrics data recorded by
+// the MetricsService. This is the unit of data that is sent to the server.
+class SystemProfileSetter;
+
+// This class provides base functionality for logging metrics data.
+class MetricsLog : public metrics::MetricsLogBase {
+ public:
+  // The constructor doesn't set any metadata. The metadata is only set by a
+  // SystemProfileSetter.
+  MetricsLog();
+
+  // Increment the crash counters in the protobuf.
+  // These methods don't have to be thread safe as metrics logs are only
+  // accessed by the uploader thread.
+  void IncrementUserCrashCount(unsigned int count);
+  void IncrementKernelCrashCount(unsigned int count);
+  void IncrementUncleanShutdownCount(unsigned int count);
+
+  // Populate the system profile with system information using setter.
+  bool PopulateSystemProfile(SystemProfileSetter* setter);
+
+  // Load the log from |path|.
+  bool LoadFromFile(const base::FilePath& path);
+
+  // Save this log to |path|.
+  bool SaveToFile(const base::FilePath& path);
+
+ private:
+  friend class UploadServiceTest;
+  FRIEND_TEST(UploadServiceTest, CurrentLogSavedAndResumed);
+  FRIEND_TEST(UploadServiceTest, LogContainsAggregatedValues);
+  FRIEND_TEST(UploadServiceTest, LogContainsCrashCounts);
+  FRIEND_TEST(UploadServiceTest, LogKernelCrash);
+  FRIEND_TEST(UploadServiceTest, LogUncleanShutdown);
+  FRIEND_TEST(UploadServiceTest, LogUserCrash);
+  FRIEND_TEST(UploadServiceTest, UnknownCrashIgnored);
+
+  DISALLOW_COPY_AND_ASSIGN(MetricsLog);
+};
+
+#endif  // METRICSD_UPLOADER_METRICS_LOG_H_
diff --git a/uploader/metrics_log_base.cc b/uploader/metrics_log_base.cc
new file mode 100644
index 0000000..f23bd63
--- /dev/null
+++ b/uploader/metrics_log_base.cc
@@ -0,0 +1,154 @@
+/*
+ * 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 "uploader/metrics_log_base.h"
+
+#include <memory>
+
+#include "base/build_time.h"
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/histogram_samples.h"
+#include "uploader/metrics_hashes.h"
+#include "uploader/proto/histogram_event.pb.h"
+#include "uploader/proto/system_profile.pb.h"
+#include "uploader/proto/user_action_event.pb.h"
+
+using base::Histogram;
+using base::HistogramBase;
+using base::HistogramSamples;
+using base::SampleCountIterator;
+using base::Time;
+using base::TimeDelta;
+using metrics::HistogramEventProto;
+using metrics::SystemProfileProto;
+using metrics::UserActionEventProto;
+
+namespace metrics {
+namespace {
+
+// Any id less than 16 bytes is considered to be a testing id.
+bool IsTestingID(const std::string& id) {
+  return id.size() < 16;
+}
+
+}  // namespace
+
+MetricsLogBase::MetricsLogBase(const std::string& client_id,
+                               int session_id,
+                               LogType log_type,
+                               const std::string& version_string)
+    : num_events_(0),
+      locked_(false),
+      log_type_(log_type) {
+  DCHECK_NE(NO_LOG, log_type);
+  if (IsTestingID(client_id))
+    uma_proto_.set_client_id(0);
+  else
+    uma_proto_.set_client_id(Hash(client_id));
+
+  uma_proto_.set_session_id(session_id);
+  uma_proto_.mutable_system_profile()->set_build_timestamp(GetBuildTime());
+  uma_proto_.mutable_system_profile()->set_app_version(version_string);
+}
+
+MetricsLogBase::~MetricsLogBase() {}
+
+// static
+uint64_t MetricsLogBase::Hash(const std::string& value) {
+  uint64_t hash = metrics::HashMetricName(value);
+
+  // The following log is VERY helpful when folks add some named histogram into
+  // the code, but forgot to update the descriptive list of histograms.  When
+  // that happens, all we get to see (server side) is a hash of the histogram
+  // name.  We can then use this logging to find out what histogram name was
+  // being hashed to a given MD5 value by just running the version of Chromium
+  // in question with --enable-logging.
+  VLOG(1) << "Metrics: Hash numeric [" << value << "]=[" << hash << "]";
+
+  return hash;
+}
+
+// static
+int64_t MetricsLogBase::GetBuildTime() {
+  static int64_t integral_build_time = 0;
+  if (!integral_build_time) {
+    Time time = base::GetBuildTime();
+    integral_build_time = static_cast<int64_t>(time.ToTimeT());
+  }
+  return integral_build_time;
+}
+
+// static
+int64_t MetricsLogBase::GetCurrentTime() {
+  return (base::TimeTicks::Now() - base::TimeTicks()).InSeconds();
+}
+
+void MetricsLogBase::CloseLog() {
+  DCHECK(!locked_);
+  locked_ = true;
+}
+
+void MetricsLogBase::GetEncodedLog(std::string* encoded_log) {
+  DCHECK(locked_);
+  uma_proto_.SerializeToString(encoded_log);
+}
+
+void MetricsLogBase::RecordUserAction(const std::string& key) {
+  DCHECK(!locked_);
+
+  UserActionEventProto* user_action = uma_proto_.add_user_action_event();
+  user_action->set_name_hash(Hash(key));
+  user_action->set_time(GetCurrentTime());
+
+  ++num_events_;
+}
+
+void MetricsLogBase::RecordHistogramDelta(const std::string& histogram_name,
+                                          const HistogramSamples& snapshot) {
+  DCHECK(!locked_);
+  DCHECK_NE(0, snapshot.TotalCount());
+
+  // We will ignore the MAX_INT/infinite value in the last element of range[].
+
+  HistogramEventProto* histogram_proto = uma_proto_.add_histogram_event();
+  histogram_proto->set_name_hash(Hash(histogram_name));
+  histogram_proto->set_sum(snapshot.sum());
+
+  for (std::unique_ptr<SampleCountIterator> it = snapshot.Iterator(); !it->Done();
+       it->Next()) {
+    HistogramBase::Sample min;
+    HistogramBase::Sample max;
+    HistogramBase::Count count;
+    it->Get(&min, &max, &count);
+    HistogramEventProto::Bucket* bucket = histogram_proto->add_bucket();
+    bucket->set_min(min);
+    bucket->set_max(max);
+    bucket->set_count(count);
+  }
+
+  // Omit fields to save space (see rules in histogram_event.proto comments).
+  for (int i = 0; i < histogram_proto->bucket_size(); ++i) {
+    HistogramEventProto::Bucket* bucket = histogram_proto->mutable_bucket(i);
+    if (i + 1 < histogram_proto->bucket_size() &&
+        bucket->max() == histogram_proto->bucket(i + 1).min()) {
+      bucket->clear_max();
+    } else if (bucket->max() == bucket->min() + 1) {
+      bucket->clear_min();
+    }
+  }
+}
+
+}  // namespace metrics
diff --git a/uploader/metrics_log_base.h b/uploader/metrics_log_base.h
new file mode 100644
index 0000000..f4e1995
--- /dev/null
+++ b/uploader/metrics_log_base.h
@@ -0,0 +1,122 @@
+/*
+ * 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.
+ */
+
+// This file defines a set of user experience metrics data recorded by
+// the MetricsService.  This is the unit of data that is sent to the server.
+
+#ifndef METRICS_UPLOADER_METRICS_LOG_BASE_H_
+#define METRICS_UPLOADER_METRICS_LOG_BASE_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/metrics/histogram.h"
+#include "base/time/time.h"
+#include "uploader/proto/chrome_user_metrics_extension.pb.h"
+
+namespace base {
+class HistogramSamples;
+}  // namespace base
+
+namespace metrics {
+
+// This class provides base functionality for logging metrics data.
+class MetricsLogBase {
+ public:
+  // TODO(asvitkine): Remove the NO_LOG value.
+  enum LogType {
+    INITIAL_STABILITY_LOG,  // The initial log containing stability stats.
+    ONGOING_LOG,            // Subsequent logs in a session.
+    NO_LOG,                 // Placeholder value for when there is no log.
+  };
+
+  // Creates a new metrics log of the specified type.
+  // client_id is the identifier for this profile on this installation
+  // session_id is an integer that's incremented on each application launch
+  MetricsLogBase(const std::string& client_id,
+                 int session_id,
+                 LogType log_type,
+                 const std::string& version_string);
+  virtual ~MetricsLogBase();
+
+  // Computes the MD5 hash of the given string, and returns the first 8 bytes of
+  // the hash.
+  static uint64_t Hash(const std::string& value);
+
+  // Get the GMT buildtime for the current binary, expressed in seconds since
+  // January 1, 1970 GMT.
+  // The value is used to identify when a new build is run, so that previous
+  // reliability stats, from other builds, can be abandoned.
+  static int64_t GetBuildTime();
+
+  // Convenience function to return the current time at a resolution in seconds.
+  // This wraps base::TimeTicks, and hence provides an abstract time that is
+  // always incrementing for use in measuring time durations.
+  static int64_t GetCurrentTime();
+
+  // Records a user-initiated action.
+  void RecordUserAction(const std::string& key);
+
+  // Record any changes in a given histogram for transmission.
+  void RecordHistogramDelta(const std::string& histogram_name,
+                            const base::HistogramSamples& snapshot);
+
+  // Stop writing to this record and generate the encoded representation.
+  // None of the Record* methods can be called after this is called.
+  void CloseLog();
+
+  // Fills |encoded_log| with the serialized protobuf representation of the
+  // record.  Must only be called after CloseLog() has been called.
+  void GetEncodedLog(std::string* encoded_log);
+
+  int num_events() { return num_events_; }
+
+  void set_hardware_class(const std::string& hardware_class) {
+    uma_proto_.mutable_system_profile()->mutable_hardware()->set_hardware_class(
+        hardware_class);
+  }
+
+  LogType log_type() const { return log_type_; }
+
+ protected:
+  bool locked() const { return locked_; }
+
+  metrics::ChromeUserMetricsExtension* uma_proto() { return &uma_proto_; }
+  const metrics::ChromeUserMetricsExtension* uma_proto() const {
+    return &uma_proto_;
+  }
+
+  // TODO(isherman): Remove this once the XML pipeline is outta here.
+  int num_events_;  // the number of events recorded in this log
+
+ private:
+  // locked_ is true when record has been packed up for sending, and should
+  // no longer be written to.  It is only used for sanity checking and is
+  // not a real lock.
+  bool locked_;
+
+  // The type of the log, i.e. initial or ongoing.
+  const LogType log_type_;
+
+  // Stores the protocol buffer representation for this log.
+  metrics::ChromeUserMetricsExtension uma_proto_;
+
+  DISALLOW_COPY_AND_ASSIGN(MetricsLogBase);
+};
+
+}  // namespace metrics
+
+#endif  // METRICS_UPLOADER_METRICS_LOG_BASE_H_
diff --git a/uploader/metrics_log_base_unittest.cc b/uploader/metrics_log_base_unittest.cc
new file mode 100644
index 0000000..980afd5
--- /dev/null
+++ b/uploader/metrics_log_base_unittest.cc
@@ -0,0 +1,137 @@
+/*
+ * 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 "uploader/metrics_log_base.h"
+
+#include <string>
+
+#include <base/metrics/bucket_ranges.h>
+#include <base/metrics/sample_vector.h>
+#include <gtest/gtest.h>
+
+#include "uploader/proto/chrome_user_metrics_extension.pb.h"
+
+namespace metrics {
+
+namespace {
+
+class TestMetricsLogBase : public MetricsLogBase {
+ public:
+  TestMetricsLogBase()
+      : MetricsLogBase("client_id", 1, MetricsLogBase::ONGOING_LOG, "1.2.3.4") {
+  }
+  virtual ~TestMetricsLogBase() {}
+
+  using MetricsLogBase::uma_proto;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TestMetricsLogBase);
+};
+
+}  // namespace
+
+TEST(MetricsLogBaseTest, LogType) {
+  MetricsLogBase log1("id", 0, MetricsLogBase::ONGOING_LOG, "1.2.3");
+  EXPECT_EQ(MetricsLogBase::ONGOING_LOG, log1.log_type());
+
+  MetricsLogBase log2("id", 0, MetricsLogBase::INITIAL_STABILITY_LOG, "1.2.3");
+  EXPECT_EQ(MetricsLogBase::INITIAL_STABILITY_LOG, log2.log_type());
+}
+
+TEST(MetricsLogBaseTest, EmptyRecord) {
+  MetricsLogBase log("totally bogus client ID", 137,
+                     MetricsLogBase::ONGOING_LOG, "bogus version");
+  log.set_hardware_class("sample-class");
+  log.CloseLog();
+
+  std::string encoded;
+  log.GetEncodedLog(&encoded);
+
+  // A couple of fields are hard to mock, so these will be copied over directly
+  // for the expected output.
+  metrics::ChromeUserMetricsExtension parsed;
+  ASSERT_TRUE(parsed.ParseFromString(encoded));
+
+  metrics::ChromeUserMetricsExtension expected;
+  expected.set_client_id(5217101509553811875);  // Hashed bogus client ID
+  expected.set_session_id(137);
+  expected.mutable_system_profile()->set_build_timestamp(
+      parsed.system_profile().build_timestamp());
+  expected.mutable_system_profile()->set_app_version("bogus version");
+  expected.mutable_system_profile()->mutable_hardware()->set_hardware_class(
+      "sample-class");
+
+  EXPECT_EQ(expected.SerializeAsString(), encoded);
+}
+
+TEST(MetricsLogBaseTest, HistogramBucketFields) {
+  // Create buckets: 1-5, 5-7, 7-8, 8-9, 9-10, 10-11, 11-12.
+  base::BucketRanges ranges(8);
+  ranges.set_range(0, 1);
+  ranges.set_range(1, 5);
+  ranges.set_range(2, 7);
+  ranges.set_range(3, 8);
+  ranges.set_range(4, 9);
+  ranges.set_range(5, 10);
+  ranges.set_range(6, 11);
+  ranges.set_range(7, 12);
+
+  base::SampleVector samples(&ranges);
+  samples.Accumulate(3, 1);   // Bucket 1-5.
+  samples.Accumulate(6, 1);   // Bucket 5-7.
+  samples.Accumulate(8, 1);   // Bucket 8-9. (7-8 skipped)
+  samples.Accumulate(10, 1);  // Bucket 10-11. (9-10 skipped)
+  samples.Accumulate(11, 1);  // Bucket 11-12.
+
+  TestMetricsLogBase log;
+  log.RecordHistogramDelta("Test", samples);
+
+  const metrics::ChromeUserMetricsExtension* uma_proto = log.uma_proto();
+  const metrics::HistogramEventProto& histogram_proto =
+      uma_proto->histogram_event(uma_proto->histogram_event_size() - 1);
+
+  // Buckets with samples: 1-5, 5-7, 8-9, 10-11, 11-12.
+  // Should become: 1-/, 5-7, /-9, 10-/, /-12.
+  ASSERT_EQ(5, histogram_proto.bucket_size());
+
+  // 1-5 becomes 1-/ (max is same as next min).
+  EXPECT_TRUE(histogram_proto.bucket(0).has_min());
+  EXPECT_FALSE(histogram_proto.bucket(0).has_max());
+  EXPECT_EQ(1, histogram_proto.bucket(0).min());
+
+  // 5-7 stays 5-7 (no optimization possible).
+  EXPECT_TRUE(histogram_proto.bucket(1).has_min());
+  EXPECT_TRUE(histogram_proto.bucket(1).has_max());
+  EXPECT_EQ(5, histogram_proto.bucket(1).min());
+  EXPECT_EQ(7, histogram_proto.bucket(1).max());
+
+  // 8-9 becomes /-9 (min is same as max - 1).
+  EXPECT_FALSE(histogram_proto.bucket(2).has_min());
+  EXPECT_TRUE(histogram_proto.bucket(2).has_max());
+  EXPECT_EQ(9, histogram_proto.bucket(2).max());
+
+  // 10-11 becomes 10-/ (both optimizations apply, omit max is prioritized).
+  EXPECT_TRUE(histogram_proto.bucket(3).has_min());
+  EXPECT_FALSE(histogram_proto.bucket(3).has_max());
+  EXPECT_EQ(10, histogram_proto.bucket(3).min());
+
+  // 11-12 becomes /-12 (last record must keep max, min is same as max - 1).
+  EXPECT_FALSE(histogram_proto.bucket(4).has_min());
+  EXPECT_TRUE(histogram_proto.bucket(4).has_max());
+  EXPECT_EQ(12, histogram_proto.bucket(4).max());
+}
+
+}  // namespace metrics
diff --git a/uploader/metricsd_service_runner.cc b/uploader/metricsd_service_runner.cc
new file mode 100644
index 0000000..4361cac
--- /dev/null
+++ b/uploader/metricsd_service_runner.cc
@@ -0,0 +1,63 @@
+/*
+ * 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 "uploader/metricsd_service_runner.h"
+
+#include <thread>
+
+#include <binder/IServiceManager.h>
+#include <brillo/binder_watcher.h>
+#include <brillo/message_loops/base_message_loop.h>
+#include <utils/Errors.h>
+
+#include "uploader/bn_metricsd_impl.h"
+
+MetricsdServiceRunner::MetricsdServiceRunner(
+    std::shared_ptr<CrashCounters> counters)
+    : counters_(counters) {}
+
+void MetricsdServiceRunner::Start() {
+  thread_.reset(new std::thread(&MetricsdServiceRunner::Run, this));
+}
+
+void MetricsdServiceRunner::Run() {
+  android::sp<BnMetricsdImpl> metrics_service(new BnMetricsdImpl(counters_));
+
+  android::status_t status = android::defaultServiceManager()->addService(
+      metrics_service->getInterfaceDescriptor(), metrics_service);
+  CHECK(status == android::OK) << "Metricsd service registration failed";
+
+  message_loop_for_io_.reset(new base::MessageLoopForIO);
+  message_loop_.reset(new brillo::BaseMessageLoop(message_loop_for_io_.get()));
+
+  brillo::BinderWatcher watcher(message_loop_.get());
+  CHECK(watcher.Init()) << "failed to initialize the binder file descriptor "
+                        << "watcher";
+
+  message_loop_->Run();
+
+  // Delete the message loop here as it needs to be deconstructed in the thread
+  // it is attached to.
+  message_loop_.reset();
+  message_loop_for_io_.reset();
+}
+
+void MetricsdServiceRunner::Stop() {
+  message_loop_for_io_->PostTask(FROM_HERE,
+                                 message_loop_for_io_->QuitWhenIdleClosure());
+
+  thread_->join();
+}
diff --git a/uploader/metricsd_service_runner.h b/uploader/metricsd_service_runner.h
new file mode 100644
index 0000000..f5dad21
--- /dev/null
+++ b/uploader/metricsd_service_runner.h
@@ -0,0 +1,49 @@
+/*
+ * 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 METRICS_UPLOADER_METRISCD_SERVICE_RUNNER_H_
+#define METRICS_UPLOADER_METRISCD_SERVICE_RUNNER_H_
+
+#include <memory>
+#include <thread>
+
+#include <base/message_loop/message_loop.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "uploader/crash_counters.h"
+
+class MetricsdServiceRunner {
+ public:
+  MetricsdServiceRunner(std::shared_ptr<CrashCounters> counters);
+
+  // Start the Metricsd Binder service in a new thread.
+  void Start();
+
+  // Stop the Metricsd service and wait for its thread to exit.
+  void Stop();
+
+ private:
+  // Creates and run the main loop for metricsd's Binder service.
+  void Run();
+
+  std::unique_ptr<base::MessageLoopForIO> message_loop_for_io_;
+  std::unique_ptr<brillo::MessageLoop> message_loop_;
+
+  std::unique_ptr<std::thread> thread_;
+  std::shared_ptr<CrashCounters> counters_;
+};
+
+#endif  // METRICS_UPLOADER_METRISCD_SERVICE_RUNNER_H_
diff --git a/uploader/mock/mock_system_profile_setter.h b/uploader/mock/mock_system_profile_setter.h
new file mode 100644
index 0000000..9b20291
--- /dev/null
+++ b/uploader/mock/mock_system_profile_setter.h
@@ -0,0 +1,34 @@
+/*
+ * 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 METRICS_UPLOADER_MOCK_MOCK_SYSTEM_PROFILE_SETTER_H_
+#define METRICS_UPLOADER_MOCK_MOCK_SYSTEM_PROFILE_SETTER_H_
+
+#include "uploader/system_profile_setter.h"
+
+namespace metrics {
+class ChromeUserMetricsExtension;
+}
+
+// Mock profile setter used for testing.
+class MockSystemProfileSetter : public SystemProfileSetter {
+ public:
+  bool Populate(metrics::ChromeUserMetricsExtension* profile_proto) override {
+    return true;
+  }
+};
+
+#endif  // METRICS_UPLOADER_MOCK_MOCK_SYSTEM_PROFILE_SETTER_H_
diff --git a/uploader/mock/sender_mock.cc b/uploader/mock/sender_mock.cc
new file mode 100644
index 0000000..bb4dc7d
--- /dev/null
+++ b/uploader/mock/sender_mock.cc
@@ -0,0 +1,36 @@
+/*
+ * 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 "uploader/mock/sender_mock.h"
+
+SenderMock::SenderMock() {
+  Reset();
+}
+
+bool SenderMock::Send(const std::string& content, const std::string& hash) {
+  send_call_count_ += 1;
+  last_message_ = content;
+  is_good_proto_ = last_message_proto_.ParseFromString(content);
+  return should_succeed_;
+}
+
+void SenderMock::Reset() {
+  send_call_count_ = 0;
+  last_message_ = "";
+  should_succeed_ = true;
+  last_message_proto_.Clear();
+  is_good_proto_ = false;
+}
diff --git a/uploader/mock/sender_mock.h b/uploader/mock/sender_mock.h
new file mode 100644
index 0000000..e79233f
--- /dev/null
+++ b/uploader/mock/sender_mock.h
@@ -0,0 +1,60 @@
+/*
+ * 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 METRICS_UPLOADER_MOCK_SENDER_MOCK_H_
+#define METRICS_UPLOADER_MOCK_SENDER_MOCK_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "uploader/proto/chrome_user_metrics_extension.pb.h"
+#include "uploader/sender.h"
+
+class SenderMock : public Sender {
+ public:
+  SenderMock();
+
+  bool Send(const std::string& content, const std::string& hash) override;
+  void Reset();
+
+  bool is_good_proto() { return is_good_proto_; }
+  int send_call_count() { return send_call_count_; }
+  const std::string last_message() { return last_message_; }
+  metrics::ChromeUserMetricsExtension last_message_proto() {
+    return last_message_proto_;
+  }
+  void set_should_succeed(bool succeed) { should_succeed_ = succeed; }
+
+ private:
+  // Is set to true if the proto was parsed successfully.
+  bool is_good_proto_;
+
+  // If set to true, the Send method will return true to simulate a successful
+  // send.
+  bool should_succeed_;
+
+  // Count of how many times Send was called since the last reset.
+  int send_call_count_;
+
+  // Last message received by Send.
+  std::string last_message_;
+
+  // If is_good_proto is true, last_message_proto is the deserialized
+  // representation of last_message.
+  metrics::ChromeUserMetricsExtension last_message_proto_;
+};
+
+#endif  // METRICS_UPLOADER_MOCK_SENDER_MOCK_H_
diff --git a/uploader/proto/README b/uploader/proto/README
new file mode 100644
index 0000000..4292a40
--- /dev/null
+++ b/uploader/proto/README
@@ -0,0 +1,37 @@
+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.
+
+
+
+
+This directory contains the protocol buffers used by the standalone metrics
+uploader. Those protobuffers are copied from the chromium protobuffers from
+https://chromium.googlesource.com/chromium/src/+/master/components/metrics/proto/
+at 3bfe5f2b4c03d2cac718d137ed14cd2c6354bfed.
+
+Any change to this protobuf must first be made to the backend's protobuf and be
+compatible with the chromium protobuffers.
+
+
+Q: Why fork the chromium protobuffers ?
+A: The standalone metrics uploader needs chromium os fields that are not defined
+by the chromium protobufs. Instead of pushing chromium os specific changes to
+chromium, we can add them only to chromium os (and to the backend of course).
+
+
+Q: What's the difference between those protobuffers and chromium's protobuffers?
+A: When the protobuffers were copied, some chromium specific protobuffers were
+not imported:
+* omnibox related protobuffers.
+* performance profiling protobuffers (not used in chromium os).
diff --git a/uploader/proto/chrome_user_metrics_extension.proto b/uploader/proto/chrome_user_metrics_extension.proto
new file mode 100644
index 0000000..a07830f
--- /dev/null
+++ b/uploader/proto/chrome_user_metrics_extension.proto
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+//
+// Protocol buffer for Chrome UMA (User Metrics Analysis).
+//
+// Note: this protobuf must be compatible with the one in chromium.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "ChromeUserMetricsExtensionProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+import "system/core/metricsd/uploader/proto/histogram_event.proto";
+import "system/core/metricsd/uploader/proto/system_profile.proto";
+import "system/core/metricsd/uploader/proto/user_action_event.proto";
+
+// Next tag: 13
+message ChromeUserMetricsExtension {
+  // The product (i.e. end user application) for a given UMA log.
+  enum Product {
+    // Google Chrome product family.
+    CHROME = 0;
+  }
+  // The product corresponding to this log. The field type is int32 instead of
+  // Product so that downstream users of the Chromium metrics component can
+  // introduce products without needing to make changes to the Chromium code
+  // (though they still need to add the new product to the server-side enum).
+  // Note: The default value is Chrome, so Chrome products will not transmit
+  // this field.
+  optional int32 product = 10 [default = 0];
+
+  // The id of the client install that generated these events.
+  //
+  // For Chrome clients, this id is unique to a top-level (one level above the
+  // "Default" directory) Chrome user data directory [1], and so is shared among
+  // all Chrome user profiles contained in this user data directory.
+  // An id of 0 is reserved for test data (monitoring and internal testing) and
+  // should normally be ignored in analysis of the data.
+  // [1] http://www.chromium.org/user-experience/user-data-directory
+  optional fixed64 client_id = 1;
+
+  // The session id for this user.
+  // Values such as tab ids are only meaningful within a particular session.
+  // The client keeps track of the session id and sends it with each event.
+  // The session id is simply an integer that is incremented each time the user
+  // relaunches Chrome.
+  optional int32 session_id = 2;
+
+  // Information about the user's browser and system configuration.
+  optional SystemProfileProto system_profile = 3;
+
+  // This message will log one or more of the following event types:
+  repeated UserActionEventProto user_action_event = 4;
+  repeated HistogramEventProto histogram_event = 6;
+
+}
diff --git a/uploader/proto/histogram_event.proto b/uploader/proto/histogram_event.proto
new file mode 100644
index 0000000..3825063
--- /dev/null
+++ b/uploader/proto/histogram_event.proto
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+//
+// Histogram-collected metrics.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "HistogramEventProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Next tag: 4
+message HistogramEventProto {
+  // The name of the histogram, hashed.
+  optional fixed64 name_hash = 1;
+
+  // The sum of all the sample values.
+  // Together with the total count of the sample values, this allows us to
+  // compute the average value.  The count of all sample values is just the sum
+  // of the counts of all the buckets.
+  optional int64 sum = 2;
+
+  // The per-bucket data.
+  message Bucket {
+    // Each bucket's range is bounded by min <= x < max.
+    // It is valid to omit one of these two fields in a bucket, but not both.
+    // If the min field is omitted, its value is assumed to be equal to max - 1.
+    // If the max field is omitted, its value is assumed to be equal to the next
+    // bucket's min value (possibly computed per above).  The last bucket in a
+    // histogram should always include the max field.
+    optional int64 min = 1;
+    optional int64 max = 2;
+
+    // The bucket's index in the list of buckets, sorted in ascending order.
+    // This field was intended to provide extra redundancy to detect corrupted
+    // records, but was never used.  As of M31, it is no longer sent by Chrome
+    // clients to reduce the UMA upload size.
+    optional int32 bucket_index = 3 [deprecated = true];
+
+    // The number of entries in this bucket.
+    optional int64 count = 4;
+  }
+  repeated Bucket bucket = 3;
+}
diff --git a/uploader/proto/system_profile.proto b/uploader/proto/system_profile.proto
new file mode 100644
index 0000000..bac828b
--- /dev/null
+++ b/uploader/proto/system_profile.proto
@@ -0,0 +1,759 @@
+/*
+ * 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.
+ */
+//
+// Stores information about the user's brower and system configuration.
+// The system configuration fields are recorded once per client session.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "SystemProfileProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Next tag: 21
+message SystemProfileProto {
+  // The time when the client was compiled/linked, in seconds since the epoch.
+  optional int64 build_timestamp = 1;
+
+  // A version number string for the application.
+  // Most commonly this is the browser version number found in a user agent
+  // string, and is typically a 4-tuple of numbers separated by periods.  In
+  // cases where the user agent version might be ambiguous (example: Linux 64-
+  // bit build, rather than 32-bit build, or a Windows version used in some
+  // special context, such as ChromeFrame running in IE), then this may include
+  // some additional postfix to provide clarification not available in the UA
+  // string.
+  //
+  // An example of a browser version 4-tuple is "5.0.322.0".  Currently used
+  // postfixes are:
+  //
+  //   "-64": a 64-bit build
+  //   "-F": Chrome is running under control of ChromeFrame
+  //   "-devel": this is not an official build of Chrome
+  //
+  // A full version number string could look similar to:
+  // "5.0.322.0-F-devel".
+  //
+  // This value, when available, is more trustworthy than the UA string
+  // associated with the request; and including the postfix, may be more
+  // specific.
+  optional string app_version = 2;
+
+  // The brand code or distribution tag assigned to a partner, if available.
+  // Brand codes are only available on Windows.  Not every Windows install
+  // though will have a brand code.
+  optional string brand_code = 12;
+
+  // The possible channels for an installation, from least to most stable.
+  enum Channel {
+    CHANNEL_UNKNOWN = 0;  // Unknown channel -- perhaps an unofficial build?
+    CHANNEL_CANARY = 1;
+    CHANNEL_DEV = 2;
+    CHANNEL_BETA = 3;
+    CHANNEL_STABLE = 4;
+  }
+  optional Channel channel = 10;
+
+  // True if Chrome build is ASan-instrumented.
+  optional bool is_asan_build = 20 [default = false];
+
+  // The date the user enabled UMA, in seconds since the epoch.
+  // If the user has toggled the UMA enabled state multiple times, this will
+  // be the most recent date on which UMA was enabled.
+  // For privacy, this is rounded to the nearest hour.
+  optional int64 uma_enabled_date = 3;
+
+  // The time when the client was installed, in seconds since the epoch.
+  // For privacy, this is rounded to the nearest hour.
+  optional int64 install_date = 16;
+
+  // The user's selected application locale, i.e. the user interface language.
+  // The locale includes a language code and, possibly, also a country code,
+  // e.g. "en-US".
+  optional string application_locale = 4;
+
+  message BrilloDeviceData {
+    optional string product_id = 1;
+  }
+  optional BrilloDeviceData brillo = 21;
+
+  // Information on the user's operating system.
+  message OS {
+    // The user's operating system. This should be one of:
+    // - Android
+    // - Windows NT
+    // - Linux (includes ChromeOS)
+    // - iPhone OS
+    // - Mac OS X
+    optional string name = 1;
+
+    // The version of the OS.  The meaning of this field is OS-dependent.
+    optional string version = 2;
+
+    // The fingerprint of the build.  This field is used only on Android.
+    optional string fingerprint = 3;
+
+    // Whether the version of iOS appears to be "jailbroken". This field is
+    // used only on iOS. Chrome for iOS detects whether device contains a
+    // DynamicLibraries/ directory. It's a necessary but insufficient indicator
+    // of whether the operating system has been jailbroken.
+    optional bool is_jailbroken = 4;
+  }
+  optional OS os = 5;
+
+  // Next tag for Hardware: 18
+  // Information on the user's hardware.
+  message Hardware {
+    // The CPU architecture (x86, PowerPC, x86_64, ...)
+    optional string cpu_architecture = 1;
+
+    // The amount of RAM present on the system, in megabytes.
+    optional int64 system_ram_mb = 2;
+
+    // The base memory address that chrome.dll was loaded at.
+    // (Logged only on Windows.)
+    optional int64 dll_base = 3;
+
+    // The Chrome OS device hardware class ID is a unique string associated with
+    // each Chrome OS device product revision generally assigned at hardware
+    // qualification time.  The hardware class effectively identifies the
+    // configured system components such as CPU, WiFi adapter, etc.
+    //
+    // An example of such a hardware class is "IEC MARIO PONY 6101".  An
+    // internal database associates this hardware class with the qualified
+    // device specifications including OEM information, schematics, hardware
+    // qualification reports, test device tags, etc.
+    optional string hardware_class = 4;
+
+    // The number of physical screens.
+    optional int32 screen_count = 5;
+
+    // The screen dimensions of the primary screen, in pixels.
+    optional int32 primary_screen_width = 6;
+    optional int32 primary_screen_height = 7;
+
+    // The device scale factor of the primary screen.
+    optional float primary_screen_scale_factor = 12;
+
+    // Max DPI for any attached screen. (Windows only)
+    optional float max_dpi_x = 9;
+    optional float max_dpi_y = 10;
+
+    // Information on the CPU obtained by CPUID.
+    message CPU {
+      // A 12 character string naming the vendor, e.g. "GeniuneIntel".
+      optional string vendor_name = 1;
+
+      // The signature reported by CPUID (from EAX).
+      optional uint32 signature = 2;
+
+      // Number of logical processors/cores on the current machine.
+      optional uint32 num_cores = 3;
+    }
+    optional CPU cpu = 13;
+
+    // Information on the GPU
+    message Graphics {
+      // The GPU manufacturer's vendor id.
+      optional uint32 vendor_id = 1;
+
+      // The GPU manufacturer's device id for the chip set.
+      optional uint32 device_id = 2;
+
+      // The driver version on the GPU.
+      optional string driver_version = 3;
+
+      // The driver date on the GPU.
+      optional string driver_date = 4;
+
+      // The GL_VENDOR string. An example of a gl_vendor string is
+      // "Imagination Technologies". "" if we are not using OpenGL.
+      optional string gl_vendor = 6;
+
+      // The GL_RENDERER string. An example of a gl_renderer string is
+      // "PowerVR SGX 540". "" if we are not using OpenGL.
+      optional string gl_renderer = 7;
+    }
+    optional Graphics gpu = 8;
+
+    // Information about Bluetooth devices paired with the system.
+    message Bluetooth {
+      // Whether Bluetooth is present on this system.
+      optional bool is_present = 1;
+
+      // Whether Bluetooth is enabled on this system.
+      optional bool is_enabled = 2;
+
+      // Describes a paired device.
+      message PairedDevice {
+        // Assigned class of the device. This is a bitfield according to the
+        // Bluetooth specification available at the following URL:
+        // https://www.bluetooth.org/en-us/specification/assigned-numbers-overview/baseband
+        optional uint32 bluetooth_class = 1;
+
+        // Decoded device type.
+        enum Type {
+          DEVICE_UNKNOWN = 0;
+          DEVICE_COMPUTER = 1;
+          DEVICE_PHONE = 2;
+          DEVICE_MODEM = 3;
+          DEVICE_AUDIO = 4;
+          DEVICE_CAR_AUDIO = 5;
+          DEVICE_VIDEO = 6;
+          DEVICE_PERIPHERAL = 7;
+          DEVICE_JOYSTICK = 8;
+          DEVICE_GAMEPAD = 9;
+          DEVICE_KEYBOARD = 10;
+          DEVICE_MOUSE = 11;
+          DEVICE_TABLET = 12;
+          DEVICE_KEYBOARD_MOUSE_COMBO = 13;
+        }
+        optional Type type = 2;
+
+        // Vendor prefix of the Bluetooth address, these are OUI registered by
+        // the IEEE and are encoded with the first byte in bits 16-23, the
+        // second byte in bits 8-15 and the third byte in bits 0-7.
+        //
+        // ie. Google's OUI (00:1A:11) is encoded as 0x00001A11
+        optional uint32 vendor_prefix = 4;
+
+        // The Vendor ID of a device, returned in vendor_id below, can be
+        // either allocated by the Bluetooth SIG or USB IF, providing two
+        // completely overlapping namespaces for identifiers.
+        //
+        // This field should be read along with vendor_id to correctly
+        // identify the vendor. For example Google is identified by either
+        // vendor_id_source = VENDOR_ID_BLUETOOTH, vendor_id = 0x00E0 or
+        // vendor_id_source = VENDOR_ID_USB, vendor_id = 0x18D1.
+        //
+        // If the device does not support the Device ID specification the
+        // unknown value will be set.
+        enum VendorIDSource {
+          VENDOR_ID_UNKNOWN = 0;
+          VENDOR_ID_BLUETOOTH = 1;
+          VENDOR_ID_USB = 2;
+        }
+        optional VendorIDSource vendor_id_source = 8;
+
+        // Vendor ID of the device, where available.
+        optional uint32 vendor_id = 5;
+
+        // Product ID of the device, where available.
+        optional uint32 product_id = 6;
+
+        // Device ID of the device, generally the release or version number in
+        // BCD format, where available.
+        optional uint32 device_id = 7;
+      }
+      repeated PairedDevice paired_device = 3;
+    }
+    optional Bluetooth bluetooth = 11;
+
+    // Whether the internal display produces touch events. Omitted if unknown.
+    // Logged on ChromeOS only.
+    optional bool internal_display_supports_touch = 14;
+
+    // Vendor ids and product ids of external touchscreens.
+    message TouchScreen {
+      // Touch screen vendor id.
+      optional uint32 vendor_id = 1;
+      // Touch screen product id.
+      optional uint32 product_id = 2;
+    }
+    // Lists vendor and product ids of external touchscreens.
+    // Logged on ChromeOS only.
+    repeated TouchScreen external_touchscreen = 15;
+
+    // Drive messages are currently logged on Windows 7+, iOS, and Android.
+    message Drive {
+      // Whether this drive incurs a time penalty when randomly accessed. This
+      // should be true for spinning disks but false for SSDs or other
+      // flash-based drives.
+      optional bool has_seek_penalty = 1;
+    }
+    // The drive that the application executable was loaded from.
+    optional Drive app_drive = 16;
+    // The drive that the current user data directory was loaded from.
+    optional Drive user_data_drive = 17;
+  }
+  optional Hardware hardware = 6;
+
+  // Information about the network connection.
+  message Network {
+    // Set to true if connection_type changed during the lifetime of the log.
+    optional bool connection_type_is_ambiguous = 1;
+
+    // See net::NetworkChangeNotifier::ConnectionType.
+    enum ConnectionType {
+      CONNECTION_UNKNOWN = 0;
+      CONNECTION_ETHERNET = 1;
+      CONNECTION_WIFI = 2;
+      CONNECTION_2G = 3;
+      CONNECTION_3G = 4;
+      CONNECTION_4G = 5;
+      CONNECTION_BLUETOOTH = 6;
+    }
+    // The connection type according to NetworkChangeNotifier.
+    optional ConnectionType connection_type = 2;
+
+    // Set to true if wifi_phy_layer_protocol changed during the lifetime of the log.
+    optional bool wifi_phy_layer_protocol_is_ambiguous = 3;
+
+    // See net::WifiPHYLayerProtocol.
+    enum WifiPHYLayerProtocol {
+      WIFI_PHY_LAYER_PROTOCOL_NONE = 0;
+      WIFI_PHY_LAYER_PROTOCOL_ANCIENT = 1;
+      WIFI_PHY_LAYER_PROTOCOL_A = 2;
+      WIFI_PHY_LAYER_PROTOCOL_B = 3;
+      WIFI_PHY_LAYER_PROTOCOL_G = 4;
+      WIFI_PHY_LAYER_PROTOCOL_N = 5;
+      WIFI_PHY_LAYER_PROTOCOL_UNKNOWN = 6;
+    }
+    // The physical layer mode of the associated wifi access point, if any.
+    optional WifiPHYLayerProtocol wifi_phy_layer_protocol = 4;
+
+    // Describe wifi access point information.
+    message WifiAccessPoint {
+      // Vendor prefix of the access point's BSSID, these are OUIs
+      // (Organizationally Unique Identifiers) registered by
+      // the IEEE and are encoded with the first byte in bits 16-23, the
+      // second byte in bits 8-15 and the third byte in bits 0-7.
+      optional uint32 vendor_prefix = 1;
+
+      // Access point seurity mode definitions.
+      enum SecurityMode {
+        SECURITY_UNKNOWN = 0;
+        SECURITY_WPA = 1;
+        SECURITY_WEP = 2;
+        SECURITY_RSN = 3;
+        SECURITY_802_1X = 4;
+        SECURITY_PSK = 5;
+        SECURITY_NONE = 6;
+      }
+      // The security mode of the access point.
+      optional SecurityMode security_mode = 2;
+
+      // Vendor specific information.
+      message VendorInformation {
+        // The model number, for example "0".
+        optional string model_number = 1;
+
+        // The model name (sometimes the same as the model_number),
+        // for example "WZR-HP-AG300H".
+        optional string model_name = 2;
+
+        // The device name (sometimes the same as the model_number),
+        // for example "Dummynet"
+        optional string device_name = 3;
+
+        // The list of vendor-specific OUIs (Organziationally Unqiue
+        // Identifiers). These are provided by the vendor through WPS
+        // (Wireless Provisioning Service) information elements, which
+        // identifies the content of the element.
+        repeated uint32 element_identifier = 4;
+      }
+      // The wireless access point vendor information.
+      optional VendorInformation vendor_info = 3;
+    }
+    // Information of the wireless AP that device is connected to.
+    optional WifiAccessPoint access_point_info = 5;
+  }
+  optional Network network = 13;
+
+  // Information on the Google Update install that is managing this client.
+  message GoogleUpdate {
+    // Whether the Google Update install is system-level or user-level.
+    optional bool is_system_install = 1;
+
+    // The date at which Google Update last started performing an automatic
+    // update check, in seconds since the Unix epoch.
+    optional int64 last_automatic_start_timestamp = 2;
+
+    // The date at which Google Update last successfully sent an update check
+    // and recieved an intact response from the server, in seconds since the
+    // Unix epoch. (The updates don't need to be successfully installed.)
+    optional int64 last_update_check_timestamp = 3;
+
+    // Describes a product being managed by Google Update. (This can also
+    // describe Google Update itself.)
+    message ProductInfo {
+      // The current version of the product that is installed.
+      optional string version = 1;
+
+      // The date at which Google Update successfully updated this product,
+      // stored in seconds since the Unix epoch.  This is updated when an update
+      // is successfully applied, or if the server reports that no update
+      // is available.
+      optional int64 last_update_success_timestamp = 2;
+
+      // The result reported by the product updater on its last run.
+      enum InstallResult {
+        INSTALL_RESULT_SUCCESS = 0;
+        INSTALL_RESULT_FAILED_CUSTOM_ERROR = 1;
+        INSTALL_RESULT_FAILED_MSI_ERROR = 2;
+        INSTALL_RESULT_FAILED_SYSTEM_ERROR = 3;
+        INSTALL_RESULT_EXIT_CODE = 4;
+      }
+      optional InstallResult last_result = 3;
+
+      // The error code reported by the product updater on its last run.  This
+      // will typically be a error code specific to the product installer.
+      optional int32 last_error = 4;
+
+      // The extra error code reported by the product updater on its last run.
+      // This will typically be a Win32 error code.
+      optional int32 last_extra_error = 5;
+    }
+    optional ProductInfo google_update_status = 4;
+    optional ProductInfo client_status = 5;
+  }
+  optional GoogleUpdate google_update = 11;
+
+  // Information on all installed plugins.
+  message Plugin {
+    // The plugin's self-reported name and filename (without path).
+    optional string name = 1;
+    optional string filename = 2;
+
+    // The plugin's version.
+    optional string version = 3;
+
+    // True if the plugin is disabled.
+    // If a client has multiple local Chrome user accounts, this is logged based
+    // on the first user account launched during the current session.
+    optional bool is_disabled = 4;
+
+    // True if the plugin is PPAPI.
+    optional bool is_pepper = 5;
+  }
+  repeated Plugin plugin = 7;
+
+  // Figures that can be used to generate application stability metrics.
+  // All values are counts of events since the last time that these
+  // values were reported.
+  // Next tag: 24
+  message Stability {
+    // Total amount of time that the program was running, in seconds,
+    // since the last time a log was recorded, as measured using a client-side
+    // clock implemented via TimeTicks, which guarantees that it is monotonic
+    // and does not jump if the user changes his/her clock.  The TimeTicks
+    // implementation also makes the clock not count time the computer is
+    // suspended.
+    optional int64 incremental_uptime_sec = 1;
+
+    // Total amount of time that the program was running, in seconds,
+    // since startup, as measured using a client-side clock implemented
+    // via TimeTicks, which guarantees that it is monotonic and does not
+    // jump if the user changes his/her clock.  The TimeTicks implementation
+    // also makes the clock not count time the computer is suspended.
+    // This field was added for M-35.
+    optional int64 uptime_sec = 23;
+
+    // Page loads along with renderer crashes and hangs, since page load count
+    // roughly corresponds to usage.
+    optional int32 page_load_count = 2;
+    optional int32 renderer_crash_count = 3;
+    optional int32 renderer_hang_count = 4;
+
+    // Number of renderer crashes that were for extensions. These crashes are
+    // not counted in renderer_crash_count.
+    optional int32 extension_renderer_crash_count = 5;
+
+    // Number of non-renderer child process crashes.
+    optional int32 child_process_crash_count = 6;
+
+    // Number of times the browser has crashed while logged in as the "other
+    // user" (guest) account.
+    // Logged on ChromeOS only.
+    optional int32 other_user_crash_count = 7;
+
+    // Number of times the kernel has crashed.
+    // Logged on ChromeOS only.
+    optional int32 kernel_crash_count = 8;
+
+    // Number of times the system has shut down uncleanly.
+    // Logged on ChromeOS only.
+    optional int32 unclean_system_shutdown_count = 9;
+
+    //
+    // All the remaining fields in the Stability are recorded at most once per
+    // client session.
+    //
+
+    // The number of times the program was launched.
+    // This will typically be equal to 1.  However, it is possible that Chrome
+    // was unable to upload stability metrics for previous launches (e.g. due to
+    // crashing early during startup), and hence this value might be greater
+    // than 1.
+    optional int32 launch_count = 15;
+    // The number of times that it didn't exit cleanly (which we assume to be
+    // mostly crashes).
+    optional int32 crash_count = 16;
+
+    // The number of times the program began, but did not complete, the shutdown
+    // process.  (For example, this may occur when Windows is shutting down, and
+    // it only gives the process a few seconds to clean up.)
+    optional int32 incomplete_shutdown_count = 17;
+
+    // The number of times the program was able register with breakpad crash
+    // services.
+    optional int32 breakpad_registration_success_count = 18;
+
+    // The number of times the program failed to register with breakpad crash
+    // services.  If crash registration fails then when the program crashes no
+    // crash report will be generated.
+    optional int32 breakpad_registration_failure_count = 19;
+
+    // The number of times the program has run under a debugger.  This should
+    // be an exceptional condition.  Running under a debugger prevents crash
+    // dumps from being generated.
+    optional int32 debugger_present_count = 20;
+
+    // The number of times the program has run without a debugger attached.
+    // This should be most common scenario and should be very close to
+    // |launch_count|.
+    optional int32 debugger_not_present_count = 21;
+
+    // Stability information for all installed plugins.
+    message PluginStability {
+      // The relevant plugin's information (name, etc.)
+      optional Plugin plugin = 1;
+
+      // The number of times this plugin's process was launched.
+      optional int32 launch_count = 2;
+
+      // The number of times this plugin was instantiated on a web page.
+      // This will be >= |launch_count|.
+      // (A page load with multiple sections drawn by this plugin will
+      // increase this count multiple times.)
+      optional int32 instance_count = 3;
+
+      // The number of times this plugin process crashed.
+      // This value will be <= |launch_count|.
+      optional int32 crash_count = 4;
+
+      // The number of times this plugin could not be loaded.
+      optional int32 loading_error_count = 5;
+    }
+    repeated PluginStability plugin_stability = 22;
+  }
+  optional Stability stability = 8;
+
+  // Description of a field trial or experiment that the user is currently
+  // enrolled in.
+  // All metrics reported in this upload can potentially be influenced by the
+  // field trial.
+  message FieldTrial {
+    // The name of the field trial, as a 32-bit identifier.
+    // Currently, the identifier is a hash of the field trial's name.
+    optional fixed32 name_id = 1;
+
+    // The user's group within the field trial, as a 32-bit identifier.
+    // Currently, the identifier is a hash of the group's name.
+    optional fixed32 group_id = 2;
+  }
+  repeated FieldTrial field_trial = 9;
+
+  // Information about the A/V output device(s) (typically just a TV).
+  // However, a configuration may have one or more intermediate A/V devices
+  // between the source device and the TV (e.g. an A/V receiver, video
+  // processor, etc.).
+  message ExternalAudioVideoDevice {
+    // The manufacturer name (possibly encoded as a 3-letter code, e.g. "YMH"
+    // for Yamaha).
+    optional string manufacturer_name = 1;
+
+    // The model name (e.g. "RX-V1900"). Some devices may report generic names
+    // like "receiver" or use the full manufacturer name (e.g "PHILIPS").
+    optional string model_name = 2;
+
+    // The product code (e.g. "0218").
+    optional string product_code = 3;
+
+    // The device types. A single device can have multiple types (e.g. a set-top
+    // box could be both a tuner and a player).  The same type may even be
+    // repeated (e.g a device that reports two tuners).
+    enum AVDeviceType {
+      AV_DEVICE_TYPE_UNKNOWN = 0;
+      AV_DEVICE_TYPE_TV = 1;
+      AV_DEVICE_TYPE_RECORDER = 2;
+      AV_DEVICE_TYPE_TUNER = 3;
+      AV_DEVICE_TYPE_PLAYER = 4;
+      AV_DEVICE_TYPE_AUDIO_SYSTEM = 5;
+    }
+    repeated AVDeviceType av_device_type = 4;
+
+    // The year of manufacture.
+    optional int32 manufacture_year = 5;
+
+    // The week of manufacture.
+    // Note: per the Wikipedia EDID article, numbering for this field may not
+    // be consistent between manufacturers.
+    optional int32 manufacture_week = 6;
+
+    // Max horizontal resolution in pixels.
+    optional int32 horizontal_resolution = 7;
+
+    // Max vertical resolution in pixels.
+    optional int32 vertical_resolution = 8;
+
+    // Audio capabilities of the device.
+    // Ref: http://en.wikipedia.org/wiki/Extended_display_identification_data
+    message AudioDescription {
+      // Audio format
+      enum AudioFormat {
+        AUDIO_FORMAT_UNKNOWN = 0;
+        AUDIO_FORMAT_LPCM = 1;
+        AUDIO_FORMAT_AC_3 = 2;
+        AUDIO_FORMAT_MPEG1 = 3;
+        AUDIO_FORMAT_MP3 = 4;
+        AUDIO_FORMAT_MPEG2 = 5;
+        AUDIO_FORMAT_AAC = 6;
+        AUDIO_FORMAT_DTS = 7;
+        AUDIO_FORMAT_ATRAC = 8;
+        AUDIO_FORMAT_ONE_BIT = 9;
+        AUDIO_FORMAT_DD_PLUS = 10;
+        AUDIO_FORMAT_DTS_HD = 11;
+        AUDIO_FORMAT_MLP_DOLBY_TRUEHD = 12;
+        AUDIO_FORMAT_DST_AUDIO = 13;
+        AUDIO_FORMAT_MICROSOFT_WMA_PRO = 14;
+      }
+      optional AudioFormat audio_format = 1;
+
+      // Number of channels (e.g. 1, 2, 8, etc.).
+      optional int32 num_channels = 2;
+
+      // Supported sample frequencies in Hz (e.g. 32000, 44100, etc.).
+      // Multiple frequencies may be specified.
+      repeated int32 sample_frequency_hz = 3;
+
+      // Maximum bit rate in bits/s.
+      optional int32 max_bit_rate_per_second = 4;
+
+      // Bit depth (e.g. 16, 20, 24, etc.).
+      optional int32 bit_depth = 5;
+    }
+    repeated AudioDescription audio_description = 9;
+
+    // The position in AV setup.
+    // A value of 0 means this device is the TV.
+    // A value of 1 means this device is directly connected to one of
+    // the TV's inputs.
+    // Values > 1 indicate there are 1 or more devices between this device
+    // and the TV.
+    optional int32 position_in_setup = 10;
+
+    // Whether this device is in the path to the TV.
+    optional bool is_in_path_to_tv = 11;
+
+    // The CEC version the device supports.
+    // CEC stands for Consumer Electronics Control, a part of the HDMI
+    // specification.  Not all HDMI devices support CEC.
+    // Only devices that support CEC will report a value here.
+    optional int32 cec_version = 12;
+
+    // This message reports CEC commands seen by a device.
+    // After each log is sent, this information is cleared and gathered again.
+    // By collecting CEC status information by opcode we can determine
+    // which CEC features can be supported.
+    message CECCommand {
+      // The CEC command opcode.  CEC supports up to 256 opcodes.
+      // We add only one CECCommand message per unique opcode.  Only opcodes
+      // seen by the device will be reported. The remainder of the message
+      // accumulates status for this opcode (and device).
+      optional int32 opcode = 1;
+
+      // The total number of commands received from the external device.
+      optional int32 num_received_direct = 2;
+
+      // The number of commands received from the external device as part of a
+      // broadcast message.
+      optional int32 num_received_broadcast = 3;
+
+      // The total number of commands sent to the external device.
+      optional int32 num_sent_direct = 4;
+
+      // The number of commands sent to the external device as part of a
+      // broadcast message.
+      optional int32 num_sent_broadcast = 5;
+
+      // The number of aborted commands for unknown reasons.
+      optional int32 num_aborted_unknown_reason = 6;
+
+      // The number of aborted commands because of an unrecognized opcode.
+      optional int32 num_aborted_unrecognized = 7;
+    }
+    repeated CECCommand cec_command = 13;
+  }
+  repeated ExternalAudioVideoDevice external_audio_video_device = 14;
+
+  // Information about the current wireless access point. Collected directly
+  // from the wireless access point via standard apis if the device is
+  // connected to the Internet wirelessly. Introduced for Chrome on TV devices
+  // but also can be collected by ChromeOS, Android or other clients.
+  message ExternalAccessPoint {
+    // The manufacturer name, for example "ASUSTeK Computer Inc.".
+    optional string manufacturer = 1;
+
+    // The model name, for example "Wi-Fi Protected Setup Router".
+    optional string model_name = 2;
+
+    // The model number, for example "RT-N16".
+    optional string model_number = 3;
+
+    // The device name (sometime same as model_number), for example "RT-N16".
+    optional string device_name = 4;
+  }
+  optional ExternalAccessPoint external_access_point = 15;
+
+  // Number of users currently signed into a multiprofile session.
+  // A zero value indicates that the user count changed while the log is open.
+  // Logged only on ChromeOS.
+  optional uint32 multi_profile_user_count = 17;
+
+  // Information about extensions that are installed, masked to provide better
+  // privacy.  Only extensions from a single profile are reported; this will
+  // generally be the profile used when the browser is started.  The profile
+  // reported on will remain consistent at least until the browser is
+  // relaunched (or the profile is deleted by the user).
+  //
+  // Each client first picks a value for client_key derived from its UMA
+  // client_id:
+  //   client_key = client_id % 4096
+  // Then, each installed extension is mapped into a hash bucket according to
+  //   bucket = CityHash64(StringPrintf("%d:%s",
+  //                                    client_key, extension_id)) % 1024
+  // The client reports the set of hash buckets occupied by all installed
+  // extensions.  If multiple extensions map to the same bucket, that bucket is
+  // still only reported once.
+  repeated int32 occupied_extension_bucket = 18;
+
+  // The state of loaded extensions for this system. The system can have either
+  // no applicable extensions, extensions only from the webstore and verified by
+  // the webstore, extensions only from the webstore but not verified, or
+  // extensions not from the store. If there is a single off-store extension,
+  // then HAS_OFFSTORE is reported. This should be kept in sync with the
+  // corresponding enum in chrome/browser/metrics/extensions_metrics_provider.cc
+  enum ExtensionsState {
+    NO_EXTENSIONS = 0;
+    NO_OFFSTORE_VERIFIED = 1;
+    NO_OFFSTORE_UNVERIFIED = 2;
+    HAS_OFFSTORE = 3;
+  }
+  optional ExtensionsState offstore_extensions_state = 19;
+}
diff --git a/uploader/proto/user_action_event.proto b/uploader/proto/user_action_event.proto
new file mode 100644
index 0000000..464f3c8
--- /dev/null
+++ b/uploader/proto/user_action_event.proto
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+//
+// Stores information about an event that occurs in response to a user action,
+// e.g. an interaction with a browser UI element.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+option java_outer_classname = "UserActionEventProtos";
+option java_package = "org.chromium.components.metrics";
+
+package metrics;
+
+// Next tag: 3
+message UserActionEventProto {
+  // The name of the action, hashed.
+  optional fixed64 name_hash = 1;
+
+  // The timestamp for the event, in seconds since the epoch.
+  optional int64 time = 2;
+}
diff --git a/uploader/sender.h b/uploader/sender.h
new file mode 100644
index 0000000..369c9c2
--- /dev/null
+++ b/uploader/sender.h
@@ -0,0 +1,30 @@
+/*
+ * 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 METRICS_UPLOADER_SENDER_H_
+#define METRICS_UPLOADER_SENDER_H_
+
+#include <string>
+
+// Abstract class for a Sender that uploads a metrics message.
+class Sender {
+ public:
+  virtual ~Sender() {}
+  // Sends a message |content| with its sha1 hash |hash|
+  virtual bool Send(const std::string& content, const std::string& hash) = 0;
+};
+
+#endif  // METRICS_UPLOADER_SENDER_H_
diff --git a/uploader/sender_http.cc b/uploader/sender_http.cc
new file mode 100644
index 0000000..4b572a6
--- /dev/null
+++ b/uploader/sender_http.cc
@@ -0,0 +1,50 @@
+/*
+ * 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 "uploader/sender_http.h"
+
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <brillo/http/http_utils.h>
+#include <brillo/mime_utils.h>
+
+HttpSender::HttpSender(const std::string server_url)
+    : server_url_(server_url) {}
+
+bool HttpSender::Send(const std::string& content,
+                      const std::string& content_hash) {
+  const std::string hash =
+      base::HexEncode(content_hash.data(), content_hash.size());
+
+  brillo::http::HeaderList headers = {{"X-Chrome-UMA-Log-SHA1", hash}};
+  brillo::ErrorPtr error;
+  auto response = brillo::http::PostTextAndBlock(
+      server_url_,
+      content,
+      brillo::mime::application::kWwwFormUrlEncoded,
+      headers,
+      brillo::http::Transport::CreateDefault(),
+      &error);
+  if (!response || response->ExtractDataAsString() != "OK") {
+    if (error) {
+      DLOG(ERROR) << "Failed to send data: " << error->GetMessage();
+    }
+    return false;
+  }
+  return true;
+}
diff --git a/uploader/sender_http.h b/uploader/sender_http.h
new file mode 100644
index 0000000..4f1c08f
--- /dev/null
+++ b/uploader/sender_http.h
@@ -0,0 +1,41 @@
+/*
+ * 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 METRICS_UPLOADER_SENDER_HTTP_H_
+#define METRICS_UPLOADER_SENDER_HTTP_H_
+
+#include <string>
+
+#include <base/macros.h>
+
+#include "uploader/sender.h"
+
+// Sender implemented using http_utils from libbrillo
+class HttpSender : public Sender {
+ public:
+  explicit HttpSender(std::string server_url);
+  ~HttpSender() override = default;
+  // Sends |content| whose SHA1 hash is |hash| to server_url with a synchronous
+  // POST request to server_url.
+  bool Send(const std::string& content, const std::string& hash) override;
+
+ private:
+  const std::string server_url_;
+
+  DISALLOW_COPY_AND_ASSIGN(HttpSender);
+};
+
+#endif  // METRICS_UPLOADER_SENDER_HTTP_H_
diff --git a/uploader/system_profile_cache.cc b/uploader/system_profile_cache.cc
new file mode 100644
index 0000000..e6f6617
--- /dev/null
+++ b/uploader/system_profile_cache.cc
@@ -0,0 +1,198 @@
+/*
+ * 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 "uploader/system_profile_cache.h"
+
+#include <base/files/file_util.h>
+#include <base/guid.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <brillo/osrelease_reader.h>
+#include <string>
+#include <update_engine/client.h>
+#include <vector>
+
+#include "constants.h"
+#include "persistent_integer.h"
+#include "uploader/metrics_log_base.h"
+#include "uploader/proto/chrome_user_metrics_extension.pb.h"
+
+namespace {
+
+const char kPersistentSessionIdFilename[] = "Sysinfo.SessionId";
+
+}  // namespace
+
+std::string ChannelToString(
+    const metrics::SystemProfileProto_Channel& channel) {
+  switch (channel) {
+    case metrics::SystemProfileProto::CHANNEL_STABLE:
+    return "STABLE";
+  case metrics::SystemProfileProto::CHANNEL_DEV:
+    return "DEV";
+  case metrics::SystemProfileProto::CHANNEL_BETA:
+    return "BETA";
+  case metrics::SystemProfileProto::CHANNEL_CANARY:
+    return "CANARY";
+  default:
+    return "UNKNOWN";
+  }
+}
+
+SystemProfileCache::SystemProfileCache()
+    : initialized_(false),
+      testing_(false),
+      metrics_directory_(metrics::kMetricsdDirectory),
+      session_id_(new chromeos_metrics::PersistentInteger(
+          kPersistentSessionIdFilename, metrics_directory_)) {}
+
+SystemProfileCache::SystemProfileCache(bool testing,
+                                       const base::FilePath& metrics_directory)
+    : initialized_(false),
+      testing_(testing),
+      metrics_directory_(metrics_directory),
+      session_id_(new chromeos_metrics::PersistentInteger(
+          kPersistentSessionIdFilename, metrics_directory)) {}
+
+bool SystemProfileCache::Initialize() {
+  CHECK(!initialized_)
+      << "this should be called only once in the metrics_daemon lifetime.";
+
+  brillo::OsReleaseReader reader;
+  std::string channel;
+  if (testing_) {
+    reader.LoadTestingOnly(metrics_directory_);
+    channel = "unknown";
+  } else {
+    reader.Load();
+    auto client = update_engine::UpdateEngineClient::CreateInstance();
+    if (!client) {
+      LOG(ERROR) << "failed to create the update engine client";
+      return false;
+    }
+    if (!client->GetChannel(&channel)) {
+      LOG(ERROR) << "failed to read the current channel from update engine.";
+      return false;
+    }
+  }
+
+  if (!reader.GetString(metrics::kProductId, &profile_.product_id)
+      || profile_.product_id.empty()) {
+    LOG(ERROR) << "product_id is not set.";
+    return false;
+  }
+
+  if (!reader.GetString(metrics::kProductVersion, &profile_.version)) {
+    LOG(ERROR) << "failed to read the product version";
+  }
+
+  if (channel.empty() || profile_.version.empty()) {
+    // If the channel or version is missing, the image is not official.
+    // In this case, set the channel to unknown and the version to 0.0.0.0 to
+    // avoid polluting the production data.
+    channel = "";
+    profile_.version = metrics::kDefaultVersion;
+  }
+  std::string guid_path = metrics_directory_.Append(
+      metrics::kMetricsGUIDFileName).value();
+  profile_.client_id = testing_ ?
+      "client_id_test" :
+      GetPersistentGUID(guid_path);
+  profile_.model_manifest_id = "unknown";
+  if (!testing_) {
+    brillo::KeyValueStore weave_config;
+    if (!weave_config.Load(base::FilePath(metrics::kWeaveConfigurationFile))) {
+      LOG(ERROR) << "Failed to load the weave configuration file.";
+    } else if (!weave_config.GetString(metrics::kModelManifestId,
+                                       &profile_.model_manifest_id)) {
+      LOG(ERROR) << "The model manifest id (model_id) is undefined in "
+                 << metrics::kWeaveConfigurationFile;
+    }
+  }
+
+  profile_.channel = ProtoChannelFromString(channel);
+
+  // Increment the session_id everytime we initialize this. If metrics_daemon
+  // does not crash, this should correspond to the number of reboots of the
+  // system.
+  session_id_->Add(1);
+  profile_.session_id = static_cast<int32_t>(session_id_->Get());
+
+  initialized_ = true;
+  return initialized_;
+}
+
+bool SystemProfileCache::InitializeOrCheck() {
+  return initialized_ || Initialize();
+}
+
+bool SystemProfileCache::Populate(
+    metrics::ChromeUserMetricsExtension* metrics_proto) {
+  CHECK(metrics_proto);
+  if (not InitializeOrCheck()) {
+    return false;
+  }
+
+  // The client id is hashed before being sent.
+  metrics_proto->set_client_id(
+      metrics::MetricsLogBase::Hash(profile_.client_id));
+  metrics_proto->set_session_id(profile_.session_id);
+
+  // Sets the product id.
+  metrics_proto->set_product(9);
+
+  metrics::SystemProfileProto* profile_proto =
+      metrics_proto->mutable_system_profile();
+  profile_proto->mutable_hardware()->set_hardware_class(
+      profile_.model_manifest_id);
+  profile_proto->set_app_version(profile_.version);
+  profile_proto->set_channel(profile_.channel);
+  metrics::SystemProfileProto_BrilloDeviceData* device_data =
+      profile_proto->mutable_brillo();
+  device_data->set_product_id(profile_.product_id);
+
+  return true;
+}
+
+std::string SystemProfileCache::GetPersistentGUID(
+    const std::string& filename) {
+  std::string guid;
+  base::FilePath filepath(filename);
+  if (!base::ReadFileToString(filepath, &guid)) {
+    guid = base::GenerateGUID();
+    // If we can't read or write the file, the guid will not be preserved during
+    // the next reboot. Crash.
+    CHECK(base::WriteFile(filepath, guid.c_str(), guid.size()));
+  }
+  return guid;
+}
+
+metrics::SystemProfileProto_Channel SystemProfileCache::ProtoChannelFromString(
+    const std::string& channel) {
+  if (channel == "stable-channel") {
+    return metrics::SystemProfileProto::CHANNEL_STABLE;
+  } else if (channel == "dev-channel") {
+    return metrics::SystemProfileProto::CHANNEL_DEV;
+  } else if (channel == "beta-channel") {
+    return metrics::SystemProfileProto::CHANNEL_BETA;
+  } else if (channel == "canary-channel") {
+    return metrics::SystemProfileProto::CHANNEL_CANARY;
+  }
+
+  DLOG(INFO) << "unknown channel: " << channel;
+  return metrics::SystemProfileProto::CHANNEL_UNKNOWN;
+}
diff --git a/uploader/system_profile_cache.h b/uploader/system_profile_cache.h
new file mode 100644
index 0000000..f9c484c
--- /dev/null
+++ b/uploader/system_profile_cache.h
@@ -0,0 +1,87 @@
+/*
+ * 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 METRICS_UPLOADER_SYSTEM_PROFILE_CACHE_H_
+#define METRICS_UPLOADER_SYSTEM_PROFILE_CACHE_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "persistent_integer.h"
+#include "uploader/proto/system_profile.pb.h"
+#include "uploader/system_profile_setter.h"
+
+namespace metrics {
+class ChromeUserMetricsExtension;
+}
+
+struct SystemProfile {
+  std::string version;
+  std::string model_manifest_id;
+  std::string client_id;
+  int session_id;
+  metrics::SystemProfileProto::Channel channel;
+  std::string product_id;
+};
+
+// Retrieves general system informations needed by the protobuf for context and
+// remembers them to avoid expensive calls.
+//
+// The cache is populated lazily. The only method needed is Populate.
+class SystemProfileCache : public SystemProfileSetter {
+ public:
+  SystemProfileCache();
+
+  SystemProfileCache(bool testing, const base::FilePath& metrics_directory);
+
+  // Populates the ProfileSystem protobuf with system information.
+  bool Populate(metrics::ChromeUserMetricsExtension* metrics_proto) override;
+
+  // Converts a string representation of the channel to a
+  // SystemProfileProto_Channel
+  static metrics::SystemProfileProto_Channel ProtoChannelFromString(
+      const std::string& channel);
+
+  // Gets the persistent GUID and create it if it has not been created yet.
+  static std::string GetPersistentGUID(const std::string& filename);
+
+ private:
+  friend class UploadServiceTest;
+  FRIEND_TEST(UploadServiceTest, ExtractChannelFromDescription);
+  FRIEND_TEST(UploadServiceTest, ReadKeyValueFromFile);
+  FRIEND_TEST(UploadServiceTest, SessionIdIncrementedAtInitialization);
+  FRIEND_TEST(UploadServiceTest, ValuesInConfigFileAreSent);
+  FRIEND_TEST(UploadServiceTest, ProductIdMandatory);
+
+  // Fetches all informations and populates |profile_|
+  bool Initialize();
+
+  // Initializes |profile_| only if it has not been yet initialized.
+  bool InitializeOrCheck();
+
+  bool initialized_;
+  bool testing_;
+  base::FilePath metrics_directory_;
+  std::unique_ptr<chromeos_metrics::PersistentInteger> session_id_;
+  SystemProfile profile_;
+};
+
+#endif  // METRICS_UPLOADER_SYSTEM_PROFILE_CACHE_H_
diff --git a/uploader/system_profile_setter.h b/uploader/system_profile_setter.h
new file mode 100644
index 0000000..bd3ff42
--- /dev/null
+++ b/uploader/system_profile_setter.h
@@ -0,0 +1,33 @@
+/*
+ * 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 METRICS_UPLOADER_SYSTEM_PROFILE_SETTER_H_
+#define METRICS_UPLOADER_SYSTEM_PROFILE_SETTER_H_
+
+namespace metrics {
+class ChromeUserMetricsExtension;
+}
+
+// Abstract class used to delegate populating SystemProfileProto with system
+// information to simplify testing.
+class SystemProfileSetter {
+ public:
+  virtual ~SystemProfileSetter() {}
+  // Populates the protobuf with system informations.
+  virtual bool Populate(metrics::ChromeUserMetricsExtension* profile_proto) = 0;
+};
+
+#endif  // METRICS_UPLOADER_SYSTEM_PROFILE_SETTER_H_
diff --git a/uploader/upload_service.cc b/uploader/upload_service.cc
new file mode 100644
index 0000000..333a7e6
--- /dev/null
+++ b/uploader/upload_service.cc
@@ -0,0 +1,256 @@
+/*
+ * 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 "uploader/upload_service.h"
+
+#include <sysexits.h>
+
+#include <memory>
+#include <string>
+
+#include <base/bind.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/memory/scoped_vector.h>
+#include <base/message_loop/message_loop.h>
+#include <base/metrics/histogram.h>
+#include <base/metrics/histogram_base.h>
+#include <base/metrics/histogram_snapshot_manager.h>
+#include <base/metrics/sparse_histogram.h>
+#include <base/metrics/statistics_recorder.h>
+#include <base/sha1.h>
+
+#include "constants.h"
+#include "uploader/metrics_log.h"
+#include "uploader/sender_http.h"
+#include "uploader/system_profile_setter.h"
+
+const int UploadService::kMaxFailedUpload = 10;
+
+UploadService::UploadService(const std::string& server,
+                             const base::TimeDelta& upload_interval,
+                             const base::TimeDelta& disk_persistence_interval,
+                             const base::FilePath& private_metrics_directory,
+                             const base::FilePath& shared_metrics_directory)
+    : brillo::Daemon(),
+      histogram_snapshot_manager_(this),
+      sender_(new HttpSender(server)),
+      failed_upload_count_(metrics::kFailedUploadCountName,
+                           private_metrics_directory),
+      counters_(new CrashCounters),
+      upload_interval_(upload_interval),
+      disk_persistence_interval_(disk_persistence_interval),
+      metricsd_service_runner_(counters_) {
+  staged_log_path_ = private_metrics_directory.Append(metrics::kStagedLogName);
+  saved_log_path_ = private_metrics_directory.Append(metrics::kSavedLogName);
+  consent_file_ = shared_metrics_directory.Append(metrics::kConsentFileName);
+}
+
+void UploadService::LoadSavedLog() {
+  if (base::PathExists(saved_log_path_)) {
+    GetOrCreateCurrentLog()->LoadFromFile(saved_log_path_);
+  }
+}
+
+int UploadService::OnInit() {
+  brillo::Daemon::OnInit();
+
+  base::StatisticsRecorder::Initialize();
+  metricsd_service_runner_.Start();
+
+  system_profile_setter_.reset(new SystemProfileCache());
+
+  base::MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&UploadService::UploadEventCallback, base::Unretained(this)),
+      upload_interval_);
+
+  base::MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&UploadService::PersistEventCallback, base::Unretained(this)),
+      disk_persistence_interval_);
+
+  LoadSavedLog();
+
+  return EX_OK;
+}
+
+void UploadService::OnShutdown(int* exit_code) {
+  metricsd_service_runner_.Stop();
+  PersistToDisk();
+}
+
+void UploadService::InitForTest(SystemProfileSetter* setter) {
+  LoadSavedLog();
+  system_profile_setter_.reset(setter);
+}
+
+void UploadService::StartNewLog() {
+  current_log_.reset(new MetricsLog());
+}
+
+void UploadService::UploadEventCallback() {
+  UploadEvent();
+
+  base::MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&UploadService::UploadEventCallback, base::Unretained(this)),
+      upload_interval_);
+}
+
+void UploadService::PersistEventCallback() {
+  PersistToDisk();
+
+  base::MessageLoop::current()->PostDelayedTask(
+      FROM_HERE,
+      base::Bind(&UploadService::PersistEventCallback, base::Unretained(this)),
+      disk_persistence_interval_);
+}
+
+void UploadService::PersistToDisk() {
+  GatherHistograms();
+  if (current_log_) {
+    current_log_->SaveToFile(saved_log_path_);
+  }
+}
+
+void UploadService::UploadEvent() {
+  // If the system shutdown or crashed while uploading a report, we may not have
+  // deleted an old log.
+  RemoveFailedLog();
+
+  if (HasStagedLog()) {
+    // Previous upload failed, retry sending the logs.
+    SendStagedLog();
+    return;
+  }
+
+  // Previous upload successful, stage another log.
+  GatherHistograms();
+  StageCurrentLog();
+
+  // If a log is available for upload, upload it.
+  if (HasStagedLog()) {
+    SendStagedLog();
+  }
+}
+
+void UploadService::SendStagedLog() {
+  // If metrics are not enabled, discard the log and exit.
+  if (!AreMetricsEnabled()) {
+    LOG(INFO) << "Metrics disabled. Don't upload metrics samples.";
+    base::DeleteFile(staged_log_path_, false);
+    return;
+  }
+
+  std::string staged_log;
+  CHECK(base::ReadFileToString(staged_log_path_, &staged_log));
+
+  // Increase the failed count in case the daemon crashes while sending the log.
+  failed_upload_count_.Add(1);
+
+  if (!sender_->Send(staged_log, base::SHA1HashString(staged_log))) {
+    LOG(WARNING) << "log failed to upload";
+  } else {
+    VLOG(1) << "uploaded " << staged_log.length() << " bytes";
+    base::DeleteFile(staged_log_path_, false);
+  }
+
+  RemoveFailedLog();
+}
+
+void UploadService::Reset() {
+  base::DeleteFile(staged_log_path_, false);
+  current_log_.reset();
+  failed_upload_count_.Set(0);
+}
+
+void UploadService::GatherHistograms() {
+  base::StatisticsRecorder::Histograms histograms;
+  base::StatisticsRecorder::GetHistograms(&histograms);
+
+  histogram_snapshot_manager_.PrepareDeltas(
+      histograms.begin(), histograms.end(),
+      base::Histogram::kNoFlags, base::Histogram::kUmaTargetedHistogramFlag);
+
+  // Gather and reset the crash counters, shared with the binder threads.
+  unsigned int kernel_crashes = counters_->GetAndResetKernelCrashCount();
+  unsigned int unclean_shutdowns = counters_->GetAndResetUncleanShutdownCount();
+  unsigned int user_crashes = counters_->GetAndResetUserCrashCount();
+
+  // Only create a log if the counters have changed.
+  if (kernel_crashes > 0 || unclean_shutdowns > 0 || user_crashes > 0) {
+    GetOrCreateCurrentLog()->IncrementKernelCrashCount(kernel_crashes);
+    GetOrCreateCurrentLog()->IncrementUncleanShutdownCount(unclean_shutdowns);
+    GetOrCreateCurrentLog()->IncrementUserCrashCount(user_crashes);
+  }
+}
+
+void UploadService::RecordDelta(const base::HistogramBase& histogram,
+                                const base::HistogramSamples& snapshot) {
+  GetOrCreateCurrentLog()->RecordHistogramDelta(histogram.histogram_name(),
+                                                snapshot);
+}
+
+void UploadService::StageCurrentLog() {
+  // If we haven't logged anything since the last upload, don't upload an empty
+  // report.
+  if (!current_log_)
+    return;
+
+  std::unique_ptr<MetricsLog> staged_log;
+  staged_log.swap(current_log_);
+  staged_log->CloseLog();
+  if (!staged_log->PopulateSystemProfile(system_profile_setter_.get())) {
+    LOG(WARNING) << "Error while adding metadata to the log. Discarding the "
+                 << "log.";
+    return;
+  }
+
+  if (!base::DeleteFile(saved_log_path_, false)) {
+    // There is a chance that we will upload the same metrics twice but, if we
+    // are lucky, the backup should be overridden before that. In doubt, try not
+    // to lose any metrics.
+    LOG(ERROR) << "failed to delete the last backup of the current log.";
+  }
+
+  failed_upload_count_.Set(0);
+  staged_log->SaveToFile(staged_log_path_);
+}
+
+MetricsLog* UploadService::GetOrCreateCurrentLog() {
+  if (!current_log_) {
+    StartNewLog();
+  }
+  return current_log_.get();
+}
+
+bool UploadService::HasStagedLog() {
+  return base::PathExists(staged_log_path_);
+}
+
+void UploadService::RemoveFailedLog() {
+  if (failed_upload_count_.Get() > kMaxFailedUpload) {
+    LOG(INFO) << "log failed more than " << kMaxFailedUpload << " times.";
+    CHECK(base::DeleteFile(staged_log_path_, false))
+        << "failed to delete staged log at " << staged_log_path_.value();
+    failed_upload_count_.Set(0);
+  }
+}
+
+bool UploadService::AreMetricsEnabled() {
+  return base::PathExists(consent_file_);
+}
diff --git a/uploader/upload_service.h b/uploader/upload_service.h
new file mode 100644
index 0000000..a1d9d3b
--- /dev/null
+++ b/uploader/upload_service.h
@@ -0,0 +1,183 @@
+/*
+ * 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 METRICS_UPLOADER_UPLOAD_SERVICE_H_
+#define METRICS_UPLOADER_UPLOAD_SERVICE_H_
+
+#include <memory>
+#include <string>
+
+#include <base/metrics/histogram_base.h>
+#include <base/metrics/histogram_flattener.h>
+#include <base/metrics/histogram_snapshot_manager.h>
+#include <brillo/daemons/daemon.h>
+
+#include "persistent_integer.h"
+#include "uploader/crash_counters.h"
+#include "uploader/metrics_log.h"
+#include "uploader/metricsd_service_runner.h"
+#include "uploader/proto/chrome_user_metrics_extension.pb.h"
+#include "uploader/sender.h"
+#include "uploader/system_profile_cache.h"
+
+class SystemProfileSetter;
+
+// Service responsible for backing up the currently aggregated metrics to disk
+// and uploading them periodically to the server.
+//
+// A given metrics sample can be in one of three locations.
+// * in-memory metrics: in memory aggregated metrics, waiting to be staged for
+//   upload.
+// * saved log: protobuf message, written to disk periodically and on shutdown
+//   to make a backup of metrics data for uploading later.
+// * staged log: protobuf message waiting to be uploaded.
+//
+// The service works as follows:
+// On startup, we create the in-memory metrics from the saved log if it exists.
+//
+// Periodically (every |disk_persistence_interval_| seconds), we take a snapshot
+// of the in-memory metrics and save them to disk.
+//
+// Periodically (every |upload_interval| seconds), we:
+// * take a snapshot of the in-memory metrics and create the staged log
+// * save the staged log to disk to avoid losing it if metricsd or the system
+//   crashes between two uploads.
+// * delete the last saved log: all the metrics contained in it are also in the
+//   newly created staged log.
+//
+// On shutdown (SIGINT or SIGTERM), we save the in-memory metrics to disk.
+//
+// Note: the in-memory metrics can be stored in |current_log_| or
+// base::StatisticsRecorder.
+class UploadService : public base::HistogramFlattener, public brillo::Daemon {
+ public:
+  UploadService(const std::string& server,
+                const base::TimeDelta& upload_interval,
+                const base::TimeDelta& disk_persistence_interval,
+                const base::FilePath& private_metrics_directory,
+                const base::FilePath& shared_metrics_directory);
+
+  // Initializes the upload service.
+  int OnInit() override;
+
+  // Cleans up the internal state before exiting.
+  void OnShutdown(int* exit_code) override;
+
+  // Starts a new log. The log needs to be regenerated after each successful
+  // launch as it is destroyed when staging the log.
+  void StartNewLog();
+
+  // Saves the current metrics to a file.
+  void PersistToDisk();
+
+  // Triggers an upload event.
+  void UploadEvent();
+
+  // Sends the staged log.
+  void SendStagedLog();
+
+  // Implements inconsistency detection to match HistogramFlattener's
+  // interface.
+  void InconsistencyDetected(
+      base::HistogramBase::Inconsistency problem) override {}
+  void UniqueInconsistencyDetected(
+      base::HistogramBase::Inconsistency problem) override {}
+  void InconsistencyDetectedInLoggedCount(int amount) override {}
+
+ private:
+  friend class UploadServiceTest;
+
+  FRIEND_TEST(UploadServiceTest, CanSendMultipleTimes);
+  FRIEND_TEST(UploadServiceTest, CorruptedSavedLog);
+  FRIEND_TEST(UploadServiceTest, CurrentLogSavedAndResumed);
+  FRIEND_TEST(UploadServiceTest, DiscardLogsAfterTooManyFailedUpload);
+  FRIEND_TEST(UploadServiceTest, EmptyLogsAreNotSent);
+  FRIEND_TEST(UploadServiceTest, FailedSendAreRetried);
+  FRIEND_TEST(UploadServiceTest, LogContainsAggregatedValues);
+  FRIEND_TEST(UploadServiceTest, LogContainsCrashCounts);
+  FRIEND_TEST(UploadServiceTest, LogEmptyAfterUpload);
+  FRIEND_TEST(UploadServiceTest, LogEmptyByDefault);
+  FRIEND_TEST(UploadServiceTest, LogFromTheMetricsLibrary);
+  FRIEND_TEST(UploadServiceTest, LogKernelCrash);
+  FRIEND_TEST(UploadServiceTest, LogUncleanShutdown);
+  FRIEND_TEST(UploadServiceTest, LogUserCrash);
+  FRIEND_TEST(UploadServiceTest, PersistEmptyLog);
+  FRIEND_TEST(UploadServiceTest, UnknownCrashIgnored);
+  FRIEND_TEST(UploadServiceTest, ValuesInConfigFileAreSent);
+
+  // Initializes the upload service for testing.
+  void InitForTest(SystemProfileSetter* setter);
+
+  // If a staged log fails to upload more than kMaxFailedUpload times, it
+  // will be discarded.
+  static const int kMaxFailedUpload;
+
+  // Loads the log saved to disk if it exists.
+  void LoadSavedLog();
+
+  // Resets the internal state.
+  void Reset();
+
+  // Returns true iff metrics reporting is enabled.
+  bool AreMetricsEnabled();
+
+  // Event callback for handling Upload events.
+  void UploadEventCallback();
+
+  // Event callback for handling Persist events.
+  void PersistEventCallback();
+
+  // Aggregates all histogram available in memory and store them in the current
+  // log.
+  void GatherHistograms();
+
+  // Callback for HistogramSnapshotManager to store the histograms.
+  void RecordDelta(const base::HistogramBase& histogram,
+                   const base::HistogramSamples& snapshot) override;
+
+  // Compiles all the samples received into a single protobuf and adds all
+  // system information.
+  void StageCurrentLog();
+
+  // Returns true iff a log is staged.
+  bool HasStagedLog();
+
+  // Remove the staged log iff the upload failed more than |kMaxFailedUpload|.
+  void RemoveFailedLog();
+
+  // Returns the current log. If there is no current log, creates it first.
+  MetricsLog* GetOrCreateCurrentLog();
+
+  std::unique_ptr<SystemProfileSetter> system_profile_setter_;
+  base::HistogramSnapshotManager histogram_snapshot_manager_;
+  std::unique_ptr<Sender> sender_;
+  chromeos_metrics::PersistentInteger failed_upload_count_;
+  std::unique_ptr<MetricsLog> current_log_;
+  std::shared_ptr<CrashCounters> counters_;
+
+  base::TimeDelta upload_interval_;
+  base::TimeDelta disk_persistence_interval_;
+
+  MetricsdServiceRunner metricsd_service_runner_;
+
+  base::FilePath consent_file_;
+  base::FilePath staged_log_path_;
+  base::FilePath saved_log_path_;
+
+  bool testing_;
+};
+
+#endif  // METRICS_UPLOADER_UPLOAD_SERVICE_H_
diff --git a/uploader/upload_service_test.cc b/uploader/upload_service_test.cc
new file mode 100644
index 0000000..0f77fe4
--- /dev/null
+++ b/uploader/upload_service_test.cc
@@ -0,0 +1,339 @@
+/*
+ * 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 <memory>
+
+#include <base/at_exit.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/logging.h>
+#include <base/metrics/sparse_histogram.h>
+#include <base/metrics/statistics_recorder.h>
+#include <base/sys_info.h>
+#include <gtest/gtest.h>
+
+#include "constants.h"
+#include "persistent_integer.h"
+#include "uploader/metrics_log.h"
+#include "uploader/mock/mock_system_profile_setter.h"
+#include "uploader/mock/sender_mock.h"
+#include "uploader/proto/chrome_user_metrics_extension.pb.h"
+#include "uploader/proto/histogram_event.pb.h"
+#include "uploader/proto/system_profile.pb.h"
+#include "uploader/system_profile_cache.h"
+#include "uploader/upload_service.h"
+
+class UploadServiceTest : public testing::Test {
+ protected:
+  virtual void SetUp() {
+    CHECK(dir_.CreateUniqueTempDir());
+    // Make sure the statistics recorder is inactive (contains no metrics) then
+    // initialize it.
+    ASSERT_FALSE(base::StatisticsRecorder::IsActive());
+    base::StatisticsRecorder::Initialize();
+
+    private_dir_ = dir_.path().Append("private");
+    shared_dir_ = dir_.path().Append("shared");
+
+    EXPECT_TRUE(base::CreateDirectory(private_dir_));
+    EXPECT_TRUE(base::CreateDirectory(shared_dir_));
+
+    ASSERT_EQ(0, base::WriteFile(shared_dir_.Append(metrics::kConsentFileName),
+                                 "", 0));
+
+    upload_service_.reset(new UploadService(
+        "", base::TimeDelta(), base::TimeDelta(), private_dir_, shared_dir_));
+    counters_ = upload_service_->counters_;
+
+    upload_service_->sender_.reset(new SenderMock);
+    upload_service_->InitForTest(new MockSystemProfileSetter);
+    upload_service_->GatherHistograms();
+    upload_service_->Reset();
+  }
+
+  void SendSparseHistogram(const std::string& name, int sample) {
+    base::HistogramBase* histogram = base::SparseHistogram::FactoryGet(
+        name, base::Histogram::kUmaTargetedHistogramFlag);
+    histogram->Add(sample);
+  }
+
+  void SendHistogram(
+      const std::string& name, int sample, int min, int max, int nbuckets) {
+    base::HistogramBase* histogram = base::Histogram::FactoryGet(
+        name, min, max, nbuckets, base::Histogram::kUmaTargetedHistogramFlag);
+    histogram->Add(sample);
+  }
+
+  void SetTestingProperty(const std::string& name, const std::string& value) {
+    base::FilePath filepath =
+        dir_.path().Append("etc/os-release.d").Append(name);
+    ASSERT_TRUE(base::CreateDirectory(filepath.DirName()));
+    ASSERT_EQ(value.size(),
+              base::WriteFile(filepath, value.data(), value.size()));
+  }
+
+  const metrics::SystemProfileProto_Stability GetCurrentStability() {
+    EXPECT_TRUE(upload_service_->current_log_.get());
+
+    return upload_service_->current_log_->uma_proto()
+        ->system_profile()
+        .stability();
+  }
+
+  base::ScopedTempDir dir_;
+  std::unique_ptr<UploadService> upload_service_;
+
+  std::unique_ptr<base::AtExitManager> exit_manager_;
+  std::shared_ptr<CrashCounters> counters_;
+  base::FilePath private_dir_;
+  base::FilePath shared_dir_;
+};
+
+TEST_F(UploadServiceTest, FailedSendAreRetried) {
+  SenderMock* sender = new SenderMock();
+  upload_service_->sender_.reset(sender);
+
+  sender->set_should_succeed(false);
+
+  SendSparseHistogram("hello", 1);
+  upload_service_->UploadEvent();
+  EXPECT_EQ(1, sender->send_call_count());
+  std::string sent_string = sender->last_message();
+
+  upload_service_->UploadEvent();
+  EXPECT_EQ(2, sender->send_call_count());
+  EXPECT_EQ(sent_string, sender->last_message());
+}
+
+TEST_F(UploadServiceTest, DiscardLogsAfterTooManyFailedUpload) {
+  SenderMock* sender = new SenderMock();
+  upload_service_->sender_.reset(sender);
+
+  sender->set_should_succeed(false);
+
+  SendSparseHistogram("hello", 1);
+
+  for (int i = 0; i < UploadService::kMaxFailedUpload; i++) {
+    upload_service_->UploadEvent();
+  }
+
+  EXPECT_TRUE(upload_service_->HasStagedLog());
+  upload_service_->UploadEvent();
+  EXPECT_FALSE(upload_service_->HasStagedLog());
+
+  // Log a new sample. The failed upload counter should be reset.
+  SendSparseHistogram("hello", 1);
+  for (int i = 0; i < UploadService::kMaxFailedUpload; i++) {
+    upload_service_->UploadEvent();
+  }
+  // The log is not discarded after multiple failed uploads.
+  EXPECT_TRUE(upload_service_->HasStagedLog());
+}
+
+TEST_F(UploadServiceTest, EmptyLogsAreNotSent) {
+  SenderMock* sender = new SenderMock();
+  upload_service_->sender_.reset(sender);
+  upload_service_->UploadEvent();
+  EXPECT_FALSE(upload_service_->current_log_);
+  EXPECT_EQ(0, sender->send_call_count());
+}
+
+TEST_F(UploadServiceTest, LogEmptyByDefault) {
+  // current_log_ should be initialized later as it needs AtExitManager to exist
+  // in order to gather system information from SysInfo.
+  EXPECT_FALSE(upload_service_->current_log_);
+}
+
+TEST_F(UploadServiceTest, CanSendMultipleTimes) {
+  SenderMock* sender = new SenderMock();
+  upload_service_->sender_.reset(sender);
+
+  SendSparseHistogram("hello", 1);
+
+  upload_service_->UploadEvent();
+
+  std::string first_message = sender->last_message();
+  SendSparseHistogram("hello", 2);
+
+  upload_service_->UploadEvent();
+
+  EXPECT_NE(first_message, sender->last_message());
+}
+
+TEST_F(UploadServiceTest, LogEmptyAfterUpload) {
+  SendSparseHistogram("hello", 2);
+
+  upload_service_->UploadEvent();
+  EXPECT_FALSE(upload_service_->current_log_);
+}
+
+TEST_F(UploadServiceTest, LogContainsAggregatedValues) {
+  SendHistogram("foo", 11, 0, 42, 10);
+  SendHistogram("foo", 12, 0, 42, 10);
+
+  upload_service_->GatherHistograms();
+  metrics::ChromeUserMetricsExtension* proto =
+      upload_service_->current_log_->uma_proto();
+  EXPECT_EQ(1, proto->histogram_event().size());
+}
+
+TEST_F(UploadServiceTest, LogContainsCrashCounts) {
+  // By default, there is no current log.
+  upload_service_->GatherHistograms();
+  EXPECT_FALSE(upload_service_->current_log_);
+
+  // If the user crash counter is incremented, we add the count to the current
+  // log.
+  counters_->IncrementUserCrashCount();
+  upload_service_->GatherHistograms();
+  EXPECT_EQ(1, GetCurrentStability().other_user_crash_count());
+
+  // If the kernel crash counter is incremented, we add the count to the current
+  // log.
+  counters_->IncrementKernelCrashCount();
+  upload_service_->GatherHistograms();
+  EXPECT_EQ(1, GetCurrentStability().kernel_crash_count());
+
+  // If the kernel crash counter is incremented, we add the count to the current
+  // log.
+  counters_->IncrementUncleanShutdownCount();
+  counters_->IncrementUncleanShutdownCount();
+  upload_service_->GatherHistograms();
+  EXPECT_EQ(2, GetCurrentStability().unclean_system_shutdown_count());
+
+  // If no counter is incremented, the reported numbers don't change.
+  upload_service_->GatherHistograms();
+  EXPECT_EQ(1, GetCurrentStability().other_user_crash_count());
+  EXPECT_EQ(1, GetCurrentStability().kernel_crash_count());
+  EXPECT_EQ(2, GetCurrentStability().unclean_system_shutdown_count());
+}
+
+TEST_F(UploadServiceTest, ExtractChannelFromString) {
+  EXPECT_EQ(SystemProfileCache::ProtoChannelFromString("developer-build"),
+            metrics::SystemProfileProto::CHANNEL_UNKNOWN);
+
+  EXPECT_EQ(metrics::SystemProfileProto::CHANNEL_DEV,
+            SystemProfileCache::ProtoChannelFromString("dev-channel"));
+
+  EXPECT_EQ(metrics::SystemProfileProto::CHANNEL_STABLE,
+            SystemProfileCache::ProtoChannelFromString("stable-channel"));
+
+  EXPECT_EQ(metrics::SystemProfileProto::CHANNEL_UNKNOWN,
+            SystemProfileCache::ProtoChannelFromString("this is a test"));
+}
+
+TEST_F(UploadServiceTest, ValuesInConfigFileAreSent) {
+  SenderMock* sender = new SenderMock();
+  upload_service_->sender_.reset(sender);
+
+  SetTestingProperty(metrics::kProductId, "hello");
+  SetTestingProperty(metrics::kProductVersion, "1.2.3.4");
+
+  SendSparseHistogram("hello", 1);
+
+  // Reset to create the new log with the profile setter.
+  upload_service_->system_profile_setter_.reset(
+      new SystemProfileCache(true, dir_.path()));
+  upload_service_->Reset();
+  upload_service_->UploadEvent();
+
+  EXPECT_EQ(1, sender->send_call_count());
+  EXPECT_TRUE(sender->is_good_proto());
+  EXPECT_EQ(1, sender->last_message_proto().histogram_event().size());
+
+  EXPECT_NE(0, sender->last_message_proto().client_id());
+  EXPECT_NE(0, sender->last_message_proto().system_profile().build_timestamp());
+  EXPECT_NE(0, sender->last_message_proto().session_id());
+}
+
+TEST_F(UploadServiceTest, PersistentGUID) {
+  std::string tmp_file = dir_.path().Append("tmpfile").value();
+
+  std::string first_guid = SystemProfileCache::GetPersistentGUID(tmp_file);
+  std::string second_guid = SystemProfileCache::GetPersistentGUID(tmp_file);
+
+  // The GUID are cached.
+  EXPECT_EQ(first_guid, second_guid);
+
+  base::DeleteFile(base::FilePath(tmp_file), false);
+
+  first_guid = SystemProfileCache::GetPersistentGUID(tmp_file);
+  base::DeleteFile(base::FilePath(tmp_file), false);
+  second_guid = SystemProfileCache::GetPersistentGUID(tmp_file);
+
+  // Random GUIDs are generated (not all the same).
+  EXPECT_NE(first_guid, second_guid);
+}
+
+TEST_F(UploadServiceTest, SessionIdIncrementedAtInitialization) {
+  SetTestingProperty(metrics::kProductId, "hello");
+  SystemProfileCache cache(true, dir_.path());
+  cache.Initialize();
+  int session_id = cache.profile_.session_id;
+  cache.initialized_ = false;
+  cache.Initialize();
+  EXPECT_EQ(cache.profile_.session_id, session_id + 1);
+}
+
+// The product id must be set for metrics to be uploaded.
+// If it is not set, the system profile cache should fail to initialize.
+TEST_F(UploadServiceTest, ProductIdMandatory) {
+  SystemProfileCache cache(true, dir_.path());
+  ASSERT_FALSE(cache.Initialize());
+  SetTestingProperty(metrics::kProductId, "");
+  ASSERT_FALSE(cache.Initialize());
+  SetTestingProperty(metrics::kProductId, "hello");
+  ASSERT_TRUE(cache.Initialize());
+}
+
+TEST_F(UploadServiceTest, CurrentLogSavedAndResumed) {
+  SendHistogram("hello", 10, 0, 100, 10);
+  upload_service_->PersistToDisk();
+  EXPECT_EQ(
+      1, upload_service_->current_log_->uma_proto()->histogram_event().size());
+  // Destroy the old service before creating a new one.
+  upload_service_.reset();
+  upload_service_.reset(new UploadService(
+      "", base::TimeDelta(), base::TimeDelta(), private_dir_, shared_dir_));
+  upload_service_->InitForTest(nullptr);
+
+  SendHistogram("hello", 10, 0, 100, 10);
+  upload_service_->GatherHistograms();
+  EXPECT_EQ(2, upload_service_->GetOrCreateCurrentLog()
+                   ->uma_proto()
+                   ->histogram_event()
+                   .size());
+}
+
+TEST_F(UploadServiceTest, PersistEmptyLog) {
+  upload_service_->PersistToDisk();
+  EXPECT_FALSE(base::PathExists(upload_service_->saved_log_path_));
+}
+
+TEST_F(UploadServiceTest, CorruptedSavedLog) {
+  // Write a bogus saved log.
+  EXPECT_EQ(5, base::WriteFile(upload_service_->saved_log_path_, "hello", 5));
+
+  // Destroy the old service before creating a new one.
+  upload_service_.reset();
+  upload_service_.reset(new UploadService(
+      "", base::TimeDelta(), base::TimeDelta(), private_dir_, shared_dir_));
+
+  upload_service_->InitForTest(nullptr);
+  // If the log is unreadable, we drop it and continue execution.
+  ASSERT_NE(nullptr, upload_service_->GetOrCreateCurrentLog());
+  ASSERT_FALSE(base::PathExists(upload_service_->saved_log_path_));
+}