Add an uploader to metrics_daemon

Metrics_daemon will now upload the metrics to Chrome's backend when Chrome is
not available.
This uses //components/metrics from chrome to use its protobuf definition and
the metrics common code.

This functionality is not yet enabled. It will be once the end-to-end test is
enabled.

BUG=chromium:358283, chromium:364579
TEST=FEATURES=test emerge-amd64-generic metrics

CQ-DEPEND=CL:205790
CQ-DEPEND=CL:206055

Change-Id: I87aaf7a2ac041581fa3ffd4ec61f73e933c00a52
Reviewed-on: https://chromium-review.googlesource.com/205810
Reviewed-by: Bertrand Simonnet <bsimonnet@chromium.org>
Tested-by: Bertrand Simonnet <bsimonnet@chromium.org>
Reviewed-by: Luigi Semenzato <semenzato@chromium.org>
Commit-Queue: Bertrand Simonnet <bsimonnet@chromium.org>
diff --git a/metrics/metrics.gyp b/metrics/metrics.gyp
index 3df31ff..044ff66 100644
--- a/metrics/metrics.gyp
+++ b/metrics/metrics.gyp
@@ -12,6 +12,7 @@
         'gthread-2.0',
         'libchrome-<(libbase_ver)',
         'libchromeos-<(libbase_ver)',
+        'libcurl',
       ]
     },
     'cflags_cc': [
@@ -24,6 +25,8 @@
       'type': 'static_library',
       'dependencies': [
         '../metrics/libmetrics-<(libbase_ver).gyp:libmetrics-<(libbase_ver)',
+        'libupload_service',
+        'metrics_proto',
       ],
       'link_settings': {
         'libraries': [
@@ -35,7 +38,8 @@
         'persistent_integer.cc',
         'metrics_daemon.cc',
         'metrics_daemon_main.cc',
-      ]
+      ],
+      'include_dirs': ['.'],
     },
     {
       'target_name': 'metrics_client',
@@ -47,6 +51,67 @@
         'metrics_client.cc',
       ]
     },
+    {
+      'target_name': 'libupload_service',
+      'type': 'static_library',
+      'dependencies': [
+        'metrics_proto'
+      ],
+      'link_settings': {
+        'libraries': [
+          '-lgflags',
+          '-lvboot_host',
+        ],
+      },
+      'variables': {
+        'exported_deps': [
+          'protobuf-lite',
+        ],
+        'deps': [
+          '<@(exported_deps)',
+        ],
+      },
+      'all_dependent_settings': {
+        'variables': {
+          'deps+': [
+            '<@(exported_deps)',
+          ],
+        },
+      },
+      'sources': [
+        'uploader/upload_service.cc',
+        'uploader/metrics_log.cc',
+        'uploader/system_profile_cache.cc',
+        'uploader/curl_sender.cc',
+        'components/metrics/chromeos/metric_sample.cc',
+        'components/metrics/chromeos/serialization_utils.cc',
+        'components/metrics/metrics_log_base.cc',
+        'components/metrics/metrics_log_manager.cc',
+        'components/metrics/metrics_hashes.cc',
+      ],
+      'include_dirs': ['.']
+    },
+    {
+      'target_name': 'metrics_proto',
+      'type': 'static_library',
+      'variables': {
+        'proto_in_dir': 'components/metrics/proto/',
+        'proto_out_dir': 'include/components/metrics/proto',
+      },
+      'sources': [
+        '<(proto_in_dir)/chrome_user_metrics_extension.proto',
+        '<(proto_in_dir)/histogram_event.proto',
+        '<(proto_in_dir)/omnibox_event.proto',
+        '<(proto_in_dir)/perf_data.proto',
+        '<(proto_in_dir)/profiler_event.proto',
+        '<(proto_in_dir)/sampled_profile.proto',
+        '<(proto_in_dir)/system_profile.proto',
+        '<(proto_in_dir)/user_action_event.proto',
+      ],
+      'includes': [
+        '../../platform2/common-mk/protoc.gypi'
+      ],
+    },
   ],
   'conditions': [
     ['USE_passive_metrics == 1', {
@@ -94,6 +159,22 @@
             'timer_test.cc',
           ]
         },
+        {
+          'target_name': 'upload_service_test',
+          'type': 'executable',
+          'sources': [
+            'persistent_integer.cc',
+            'uploader/mock/sender_mock.cc',
+            'uploader/upload_service_test.cc',
+          ],
+          'dependencies': [
+            'libupload_service',
+          ],
+          'includes':[
+            '../../platform2/common-mk/common_test.gypi',
+          ],
+          'include_dirs': ['.']
+        },
       ],
     }],
     ['USE_passive_metrics == 1 and USE_test == 1', {
@@ -107,9 +188,10 @@
           'includes': ['../../platform2/common-mk/common_test.gypi'],
           'sources': [
             'metrics_daemon_test.cc',
-          ]
+          ],
+          'include_dirs': ['.'],
         },
       ],
     }],
-  ],
+  ]
 }
diff --git a/metrics/metrics_daemon.cc b/metrics/metrics_daemon.cc
index e8f0c6c..b9212fb 100644
--- a/metrics/metrics_daemon.cc
+++ b/metrics/metrics_daemon.cc
@@ -14,7 +14,6 @@
 #include <string.h>
 #include <time.h>
 
-#include <base/at_exit.h>
 #include <base/file_util.h>
 #include <base/files/file_path.h>
 #include <base/hash.h>
@@ -26,6 +25,7 @@
 #include <base/sys_info.h>
 #include <chromeos/dbus/service_constants.h>
 #include <dbus/dbus-glib-lowlevel.h>
+#include "uploader/upload_service.h"
 
 using base::FilePath;
 using base::StringPrintf;
@@ -163,8 +163,6 @@
 }
 
 void MetricsDaemon::Run(bool run_as_daemon) {
-  base::AtExitManager at_exit_manager;
-
   if (run_as_daemon && daemon(0, 0) != 0)
     return;
 
@@ -188,6 +186,10 @@
   Loop();
 }
 
+void MetricsDaemon::RunUploaderTest() {
+  upload_service_->UploadEvent();
+}
+
 uint32 MetricsDaemon::GetOsVersionHash() {
   static uint32 cached_version_hash = 0;
   static bool version_hash_is_cached = false;
@@ -205,7 +207,9 @@
   return cached_version_hash;
 }
 
-void MetricsDaemon::Init(bool testing, MetricsLibraryInterface* metrics_lib,
+void MetricsDaemon::Init(bool testing,
+                         bool uploader_active,
+                         MetricsLibraryInterface* metrics_lib,
                          const string& diskstats_path,
                          const string& vmstats_path,
                          const string& scaling_max_freq_path,
@@ -305,6 +309,11 @@
 
   update_stats_timeout_id_ =
       g_timeout_add(kUpdateStatsIntervalMs, &HandleUpdateStatsTimeout, this);
+
+  if (uploader_active) {
+    upload_service_.reset(new UploadService());
+    upload_service_->Init();
+  }
 }
 
 void MetricsDaemon::Loop() {
diff --git a/metrics/metrics_daemon.h b/metrics/metrics_daemon.h
index 9c234a1..2bd12b0 100644
--- a/metrics/metrics_daemon.h
+++ b/metrics/metrics_daemon.h
@@ -18,6 +18,7 @@
 
 #include "metrics/metrics_library.h"
 #include "metrics/persistent_integer.h"
+#include "uploader/upload_service.h"
 
 using chromeos_metrics::PersistentInteger;
 
@@ -27,7 +28,9 @@
   ~MetricsDaemon();
 
   // Initializes.
-  void Init(bool testing, MetricsLibraryInterface* metrics_lib,
+  void Init(bool testing,
+            bool uploader_active,
+            MetricsLibraryInterface* metrics_lib,
             const std::string& diskstats_path,
             const std::string& vmstats_path,
             const std::string& cpuinfo_max_freq_path,
@@ -37,6 +40,9 @@
   // forking.
   void Run(bool run_as_daemon);
 
+  // Triggers an upload event and exit. (Used to test UploadService)
+  void RunUploaderTest();
+
  protected:
   // Used also by the unit tests.
   static const char kComprDataSizeName[];
@@ -363,6 +369,8 @@
   std::string vmstats_path_;
   std::string scaling_max_freq_path_;
   std::string cpuinfo_max_freq_path_;
+
+  scoped_ptr<UploadService> upload_service_;
 };
 
 #endif  // METRICS_METRICS_DAEMON_H_
diff --git a/metrics/metrics_daemon_main.cc b/metrics/metrics_daemon_main.cc
index ffe74f2..8ecb885 100644
--- a/metrics/metrics_daemon_main.cc
+++ b/metrics/metrics_daemon_main.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-
+#include <base/at_exit.h>
 #include <base/command_line.h>
 #include <base/logging.h>
 #include <base/strings/string_util.h>
@@ -17,8 +17,19 @@
 const char kCpuinfoMaxFreqPath[] =
     "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq";
 
+// TODO(bsimonnet): Change this to use base::CommandLine (crbug.com/375017)
 DEFINE_bool(daemon, true, "run as daemon (use -nodaemon for debugging)");
 
+// The uploader is disabled by default on ChromeOS as Chrome is responsible for
+// sending the metrics.
+DEFINE_bool(uploader, false, "activate the uploader");
+
+// Upload the metrics once and exit. (used for testing)
+DEFINE_bool(
+    uploader_test,
+    false,
+    "run the uploader once and exit");
+
 // Returns the path to the disk stats in the sysfs.  Returns the null string if
 // it cannot find the disk stats file.
 static
@@ -51,11 +62,23 @@
   // Also log to stderr when not running as daemon.
   chromeos::InitLog(chromeos::kLogToSyslog | chromeos::kLogHeader |
                     (FLAGS_daemon ? 0 : chromeos::kLogToStderr));
-
   MetricsLibrary metrics_lib;
   metrics_lib.Init();
   MetricsDaemon daemon;
-  daemon.Init(false, &metrics_lib, MetricsMainDiskStatsPath(),
-      "/proc/vmstat", kScalingMaxFreqPath, kCpuinfoMaxFreqPath);
+  daemon.Init(false,
+              FLAGS_uploader | FLAGS_uploader_test,
+              &metrics_lib,
+              MetricsMainDiskStatsPath(),
+              "/proc/vmstat",
+              kScalingMaxFreqPath,
+              kCpuinfoMaxFreqPath);
+
+
+  base::AtExitManager at_exit_manager;
+  if (FLAGS_uploader_test) {
+    daemon.RunUploaderTest();
+    return 0;
+  }
+
   daemon.Run(FLAGS_daemon);
 }
diff --git a/metrics/metrics_daemon_test.cc b/metrics/metrics_daemon_test.cc
index 3d5f5d7..3973ecb 100644
--- a/metrics/metrics_daemon_test.cc
+++ b/metrics/metrics_daemon_test.cc
@@ -58,8 +58,13 @@
     CreateUint64ValueFile(base::FilePath(kFakeScalingMaxFreqPath), 10000000);
 
     chromeos_metrics::PersistentInteger::SetTestingMode(true);
-    daemon_.Init(true, &metrics_lib_, kFakeDiskStatsName, kFakeVmStatsName,
-        kFakeScalingMaxFreqPath, kFakeCpuinfoMaxFreqPath);
+    daemon_.Init(true,
+                 false,
+                 &metrics_lib_,
+                 kFakeDiskStatsName,
+                 kFakeVmStatsName,
+                 kFakeScalingMaxFreqPath,
+                 kFakeCpuinfoMaxFreqPath);
 
     // Replace original persistent values with mock ones.
     daily_active_use_mock_ =
diff --git a/metrics/uploader/curl_sender.cc b/metrics/uploader/curl_sender.cc
new file mode 100644
index 0000000..023b5f0
--- /dev/null
+++ b/metrics/uploader/curl_sender.cc
@@ -0,0 +1,70 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "uploader/curl_sender.h"
+
+#include <curl/curl.h>
+#include <string>
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+
+CurlSender::CurlSender(const std::string server_url)
+    : server_url_(server_url) {}
+
+bool CurlSender::Send(const std::string& content,
+                      const std::string& content_hash) {
+  CURL* postrequest = curl_easy_init();
+
+  if (!postrequest) {
+    DLOG(ERROR) << "Error creating the post request";
+    return false;
+  }
+
+  curl_easy_setopt(postrequest, CURLOPT_URL, server_url_.c_str());
+  curl_easy_setopt(postrequest, CURLOPT_POST, 1);
+
+  const std::string hash =
+      base::HexEncode(content_hash.data(), content_hash.size());
+
+  curl_slist* headers =
+      curl_slist_append(NULL, ("X-Chrome-UMA-Log-SHA1: " + hash).c_str());
+  if (!headers) {
+    DLOG(ERROR) << "failed setting the headers";
+    curl_easy_cleanup(postrequest);
+    return false;
+  }
+
+  std::string output;
+
+  curl_easy_setopt(postrequest, CURLOPT_HTTPHEADER, headers);
+  curl_easy_setopt(postrequest, CURLOPT_POSTFIELDSIZE, content.size());
+  curl_easy_setopt(postrequest, CURLOPT_POSTFIELDS, content.c_str());
+
+  // Set the callback function used to read the response and the destination.
+  curl_easy_setopt(postrequest, CURLOPT_WRITEFUNCTION, ReadData);
+  curl_easy_setopt(postrequest, CURLOPT_WRITEDATA, &output);
+
+  CURLcode result = curl_easy_perform(postrequest);
+
+  if (result == CURLE_OK && output == "OK") {
+    curl_easy_cleanup(postrequest);
+    return true;
+  }
+
+  curl_easy_cleanup(postrequest);
+
+  return false;
+}
+
+// static
+size_t CurlSender::ReadData(void* buffer, size_t size, size_t nmember,
+                            std::string* out) {
+  CHECK(out);
+
+  // This function might be called several time so we want to append the data at
+  // the end of the string.
+  *out += std::string(static_cast<char*>(buffer), size * nmember);
+  return size * nmember;
+}
diff --git a/metrics/uploader/curl_sender.h b/metrics/uploader/curl_sender.h
new file mode 100644
index 0000000..0113601
--- /dev/null
+++ b/metrics/uploader/curl_sender.h
@@ -0,0 +1,34 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_UPLOADER_CURL_SENDER_H_
+#define METRICS_UPLOADER_CURL_SENDER_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "uploader/sender.h"
+
+// Sender implemented using libcurl
+class CurlSender : public Sender {
+ public:
+  explicit CurlSender(std::string server_url);
+
+  // Sends |content| whose SHA1 hash is |hash| to server_url with a synchronous
+  // POST request to server_url.
+  virtual bool Send(const std::string& content,
+                    const std::string& hash) OVERRIDE;
+
+  // Static callback required by curl to retrieve the response data.
+  //
+  // Copies |size| * |nmember| bytes of data from |buffer| to |out|.
+  // Returns the number of bytes copied.
+  static size_t ReadData(void* buffer, size_t size, size_t nmember,
+                         std::string* out);
+
+ private:
+  const std::string server_url_;
+};
+
+#endif  // METRICS_UPLOADER_CURL_SENDER_H_
diff --git a/metrics/uploader/metrics_log.cc b/metrics/uploader/metrics_log.cc
new file mode 100644
index 0000000..c3aa69e
--- /dev/null
+++ b/metrics/uploader/metrics_log.cc
@@ -0,0 +1,41 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "uploader/metrics_log.h"
+
+#include <string>
+
+#include "components/metrics/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, "") {
+}
+
+void MetricsLog::IncrementUserCrashCount() {
+  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 + 1);
+}
+
+void MetricsLog::IncrementKernelCrashCount() {
+  metrics::SystemProfileProto::Stability* stability(
+      uma_proto()->mutable_system_profile()->mutable_stability());
+  int current = stability->kernel_crash_count();
+  stability->set_kernel_crash_count(current + 1);
+}
+
+void MetricsLog::IncrementUncleanShutdownCount() {
+  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 + 1);
+}
+
+void MetricsLog::PopulateSystemProfile(SystemProfileSetter* profile_setter) {
+  profile_setter->Populate(uma_proto());
+}
diff --git a/metrics/uploader/metrics_log.h b/metrics/uploader/metrics_log.h
new file mode 100644
index 0000000..bd1958c
--- /dev/null
+++ b/metrics/uploader/metrics_log.h
@@ -0,0 +1,40 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_UPLOADER_METRICS_LOG_H_
+#define METRICS_UPLOADER_METRICS_LOG_H_
+
+#include <string>
+
+#include "components/metrics/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();
+
+  void IncrementUserCrashCount();
+  void IncrementKernelCrashCount();
+  void IncrementUncleanShutdownCount();
+
+  // Populate the system profile with system information using setter.
+  void PopulateSystemProfile(SystemProfileSetter* setter);
+
+ private:
+  FRIEND_TEST(UploadServiceTest, LogContainsAggregatedValues);
+  FRIEND_TEST(UploadServiceTest, LogKernelCrash);
+  FRIEND_TEST(UploadServiceTest, LogUncleanShutdown);
+  FRIEND_TEST(UploadServiceTest, LogUserCrash);
+  FRIEND_TEST(UploadServiceTest, UnknownCrashIgnored);
+
+  DISALLOW_COPY_AND_ASSIGN(MetricsLog);
+};
+
+#endif  // METRICS_UPLOADER_METRICS_LOG_H_
diff --git a/metrics/uploader/mock/mock_system_profile_setter.h b/metrics/uploader/mock/mock_system_profile_setter.h
new file mode 100644
index 0000000..0614eb9
--- /dev/null
+++ b/metrics/uploader/mock/mock_system_profile_setter.h
@@ -0,0 +1,20 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#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:
+  void Populate(metrics::ChromeUserMetricsExtension* profile_proto) OVERRIDE {}
+};
+
+#endif  // METRICS_UPLOADER_MOCK_MOCK_SYSTEM_PROFILE_SETTER_H_
diff --git a/metrics/uploader/mock/sender_mock.cc b/metrics/uploader/mock/sender_mock.cc
new file mode 100644
index 0000000..064ec6d
--- /dev/null
+++ b/metrics/uploader/mock/sender_mock.cc
@@ -0,0 +1,24 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#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/metrics/uploader/mock/sender_mock.h b/metrics/uploader/mock/sender_mock.h
new file mode 100644
index 0000000..f6f19b9
--- /dev/null
+++ b/metrics/uploader/mock/sender_mock.h
@@ -0,0 +1,48 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_UPLOADER_MOCK_SENDER_MOCK_H_
+#define METRICS_UPLOADER_MOCK_SENDER_MOCK_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "components/metrics/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/metrics/uploader/sender.h b/metrics/uploader/sender.h
new file mode 100644
index 0000000..70d29cb
--- /dev/null
+++ b/metrics/uploader/sender.h
@@ -0,0 +1,17 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#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:
+  // 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/metrics/uploader/system_profile_cache.cc b/metrics/uploader/system_profile_cache.cc
new file mode 100644
index 0000000..0090d38
--- /dev/null
+++ b/metrics/uploader/system_profile_cache.cc
@@ -0,0 +1,140 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "uploader/system_profile_cache.h"
+
+#include <glib.h>
+#include <string>
+#include <vector>
+
+#include "base/file_util.h"
+#include "base/guid.h"
+#include "base/logging.h"
+#include "base/sys_info.h"
+#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
+#include "components/metrics/metrics_log_base.h"
+#include "metrics/persistent_integer.h"
+#include "vboot/crossystem.h"
+
+const char* SystemProfileCache::kPersistentGUIDFile =
+    "/var/lib/metrics/Sysinfo.GUID";
+const char* SystemProfileCache::kPersistentSessionIdFilename =
+    "Sysinfo.SessionId";
+
+SystemProfileCache::SystemProfileCache()
+    : initialized_(false),
+      is_testing_(false),
+      session_id_(new chromeos_metrics::PersistentInteger(
+          kPersistentSessionIdFilename)) {
+}
+
+bool SystemProfileCache::Initialize() {
+  CHECK(!initialized_)
+      << "this should be called only once in the metrics_daemon lifetime.";
+
+  if (!base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_NAME",
+                                         &profile_.os_name) ||
+      !base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_VERSION",
+                                         &profile_.os_version) ||
+      !base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_DESCRIPTION",
+                                         &profile_.app_version) ||
+      !GetHardwareId(&profile_.hardware_class)) {
+    DLOG(ERROR) << "failing to initialize profile cache";
+    return false;
+  }
+
+  std::string channel_string;
+  base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_TRACK", &channel_string);
+  profile_.channel = ProtoChannelFromString(channel_string);
+
+  profile_.client_id =
+      is_testing_ ? "client_id_test" : GetPersistentGUID(kPersistentGUIDFile);
+
+  // 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.
+  // TODO(bsimonnet): Change this to map to the number of time system-services
+  // is started.
+  session_id_->Add(1);
+  profile_.session_id = static_cast<int32>(session_id_->Get());
+
+  initialized_ = true;
+  return initialized_;
+}
+
+bool SystemProfileCache::InitializeOrCheck() {
+  return initialized_ || Initialize();
+}
+
+void SystemProfileCache::Populate(
+    metrics::ChromeUserMetricsExtension* metrics_proto) {
+  CHECK(metrics_proto);
+  CHECK(InitializeOrCheck())
+      << "failed to initialize system information.";
+
+  // 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);
+
+  metrics::SystemProfileProto* profile_proto =
+      metrics_proto->mutable_system_profile();
+  profile_proto->mutable_hardware()->set_hardware_class(
+      profile_.hardware_class);
+  profile_proto->set_app_version(profile_.app_version);
+  profile_proto->set_channel(profile_.channel);
+
+  metrics::SystemProfileProto_OS* os = profile_proto->mutable_os();
+  os->set_name(profile_.os_name);
+  os->set_version(profile_.os_version);
+}
+
+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;
+}
+
+bool SystemProfileCache::GetHardwareId(std::string* hwid) {
+  CHECK(hwid);
+
+  if (is_testing_) {
+    // if we are in test mode, we do not call crossystem directly.
+    DLOG(INFO) << "skipping hardware id";
+    *hwid = "";
+    return true;
+  }
+
+  char buffer[128];
+  if (buffer != VbGetSystemPropertyString("hwid", buffer, sizeof(buffer))) {
+    LOG(ERROR) << "error getting hwid";
+    return false;
+  }
+
+  *hwid = std::string(buffer);
+  return true;
+}
+
+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/metrics/uploader/system_profile_cache.h b/metrics/uploader/system_profile_cache.h
new file mode 100644
index 0000000..74a3766
--- /dev/null
+++ b/metrics/uploader/system_profile_cache.h
@@ -0,0 +1,76 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_UPLOADER_SYSTEM_PROFILE_CACHE_H_
+#define METRICS_UPLOADER_SYSTEM_PROFILE_CACHE_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "components/metrics/proto/system_profile.pb.h"
+#include "metrics/persistent_integer.h"
+#include "metrics/uploader/system_profile_setter.h"
+
+namespace metrics {
+class ChromeUserMetricsExtension;
+}
+
+struct SystemProfile {
+  std::string os_name;
+  std::string os_version;
+  metrics::SystemProfileProto::Channel channel;
+  std::string app_version;
+  std::string hardware_class;
+  std::string client_id;
+  int32 session_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();
+
+  // Populates the ProfileSystem protobuf with system information.
+  void Populate(metrics::ChromeUserMetricsExtension* profile_proto) OVERRIDE;
+
+  // Converts a string representation of the channel (|channel|-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);
+
+  static const char* kPersistentGUIDFile;
+  static const char* kPersistentSessionIdFilename;
+
+  // Fetches all informations and populates |profile_|
+  bool Initialize();
+
+  // Initializes |profile_| only if it has not been yet initialized.
+  bool InitializeOrCheck();
+
+  // Gets the hardware ID using crossystem
+  bool GetHardwareId(std::string* hwid);
+
+  bool initialized_;
+  bool is_testing_;
+  scoped_ptr<chromeos_metrics::PersistentInteger> session_id_;
+  SystemProfile profile_;
+};
+
+#endif  // METRICS_UPLOADER_SYSTEM_PROFILE_CACHE_H_
diff --git a/metrics/uploader/system_profile_setter.h b/metrics/uploader/system_profile_setter.h
new file mode 100644
index 0000000..1e2baab
--- /dev/null
+++ b/metrics/uploader/system_profile_setter.h
@@ -0,0 +1,20 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#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:
+  // Populates the protobuf with system informations.
+  virtual void Populate(metrics::ChromeUserMetricsExtension* profile_proto) = 0;
+};
+
+#endif  // METRICS_UPLOADER_SYSTEM_PROFILE_SETTER_H_
diff --git a/metrics/uploader/upload_service.cc b/metrics/uploader/upload_service.cc
new file mode 100644
index 0000000..5e05dcf
--- /dev/null
+++ b/metrics/uploader/upload_service.cc
@@ -0,0 +1,205 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "uploader/upload_service.h"
+
+#include <curl/curl.h>
+#include <glib.h>
+#include <string>
+
+#include "base/logging.h"
+#include "base/memory/scoped_vector.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 "components/metrics/chromeos/serialization_utils.h"
+#include "components/metrics/chromeos/metric_sample.h"
+#include "gflags/gflags.h"
+#include "uploader/curl_sender.h"
+#include "uploader/metrics_log.h"
+#include "uploader/system_profile_cache.h"
+
+DEFINE_int32(
+    upload_interval_secs,
+    1800,
+    "Interval at which metrics_daemon sends the metrics. (needs -uploader)");
+
+DEFINE_string(server,
+              "https://clients4.google.com/uma/v2",
+              "Server to upload the metrics to. (needs -uploader)");
+
+DEFINE_string(metrics_file,
+              "/var/run/metrics/uma-events",
+              "File to use as a proxy for uploading the metrics");
+
+const int UploadService::kMaxFailedUpload = 10;
+
+UploadService::UploadService()
+    : system_profile_setter_(new SystemProfileCache()),
+      histogram_snapshot_manager_(this),
+      sender_(new CurlSender(FLAGS_server)) {
+}
+
+void UploadService::Init() {
+  base::StatisticsRecorder::Initialize();
+  g_timeout_add_seconds(FLAGS_upload_interval_secs, &UploadEventStatic, this);
+}
+
+void UploadService::StartNewLog() {
+  CHECK(!staged_log_) << "the staged log should be discarded before starting "
+                         "a new metrics log";
+  MetricsLog* log = new MetricsLog();
+  log->PopulateSystemProfile(system_profile_setter_);
+  current_log_.reset(log);
+}
+
+// static
+int UploadService::UploadEventStatic(void* uploader) {
+  CHECK(uploader);
+  // This is called by glib with a pointer to an UploadEvent object.
+  static_cast<UploadService*>(uploader)->UploadEvent();
+  return 1;
+}
+
+void UploadService::UploadEvent() {
+  if (staged_log_) {
+    // Previous upload failed, retry sending the logs.
+    SendStagedLog();
+    return;
+  }
+
+  // Previous upload successful, reading metrics sample from the file.
+  ReadMetrics();
+  GatherHistograms();
+
+  // No samples found. Exit to avoid sending an empty log.
+  if (!current_log_)
+    return;
+
+  StageCurrentLog();
+  SendStagedLog();
+}
+
+void UploadService::SendStagedLog() {
+  CHECK(staged_log_) << "staged_log_ must exist to be sent";
+
+  std::string log_text;
+  staged_log_->GetEncodedLog(&log_text);
+  if (!sender_->Send(log_text, base::SHA1HashString(log_text))) {
+    ++failed_upload_count_;
+    if (failed_upload_count_ <= kMaxFailedUpload) {
+      LOG(WARNING) << "log upload failed " << failed_upload_count_
+                   << " times. It will be retried later.";
+      return;
+    }
+    LOG(WARNING) << "log failed more than " << kMaxFailedUpload << " times.";
+  }
+  // Discard staged log.
+  staged_log_.reset();
+}
+
+void UploadService::Reset() {
+  staged_log_.reset();
+  current_log_.reset();
+  failed_upload_count_ = 0;
+}
+
+void UploadService::ReadMetrics() {
+  CHECK(!staged_log_)
+      << "cannot read metrics until the old logs have been discarded";
+
+  ScopedVector<metrics::MetricSample> vector;
+  metrics::SerializationUtils::ReadAndTruncateMetricsFromFile(
+      FLAGS_metrics_file, &vector);
+
+  int i = 0;
+  for (ScopedVector<metrics::MetricSample>::iterator it = vector.begin();
+       it != vector.end(); it++) {
+    metrics::MetricSample* sample = *it;
+    AddSample(*sample);
+    i++;
+  }
+  DLOG(INFO) << i << " samples read";
+}
+
+void UploadService::AddSample(const metrics::MetricSample& sample) {
+  base::HistogramBase* counter;
+  switch (sample.type()) {
+    case metrics::MetricSample::CRASH:
+      AddCrash(sample.name());
+      break;
+    case metrics::MetricSample::HISTOGRAM:
+      counter = base::Histogram::FactoryGet(
+          sample.name(), sample.min(), sample.max(), sample.bucket_count(),
+          base::Histogram::kUmaTargetedHistogramFlag);
+      counter->Add(sample.sample());
+      break;
+    case metrics::MetricSample::SPARSE_HISTOGRAM:
+      counter = base::SparseHistogram::FactoryGet(
+          sample.name(), base::HistogramBase::kUmaTargetedHistogramFlag);
+      counter->Add(sample.sample());
+      break;
+    case metrics::MetricSample::LINEAR_HISTOGRAM:
+      counter = base::LinearHistogram::FactoryGet(
+          sample.name(),
+          1,
+          sample.max(),
+          sample.max() + 1,
+          base::Histogram::kUmaTargetedHistogramFlag);
+      counter->Add(sample.sample());
+      break;
+    case metrics::MetricSample::USER_ACTION:
+      GetOrCreateCurrentLog()->RecordUserAction(sample.name());
+      break;
+    default:
+      break;
+  }
+}
+
+void UploadService::AddCrash(const std::string& crash_name) {
+  if (crash_name == "user") {
+    GetOrCreateCurrentLog()->IncrementUserCrashCount();
+  } else if (crash_name == "kernel") {
+    GetOrCreateCurrentLog()->IncrementKernelCrashCount();
+  } else if (crash_name == "uncleanshutdown") {
+    GetOrCreateCurrentLog()->IncrementUncleanShutdownCount();
+  } else {
+    DLOG(ERROR) << "crash name unknown" << crash_name;
+  }
+}
+
+void UploadService::GatherHistograms() {
+  base::StatisticsRecorder::Histograms histograms;
+  base::StatisticsRecorder::GetHistograms(&histograms);
+
+  histogram_snapshot_manager_.PrepareDeltas(
+      base::Histogram::kNoFlags, base::Histogram::kUmaTargetedHistogramFlag);
+}
+
+void UploadService::RecordDelta(const base::HistogramBase& histogram,
+                                const base::HistogramSamples& snapshot) {
+  GetOrCreateCurrentLog()->RecordHistogramDelta(histogram.histogram_name(),
+                                                snapshot);
+}
+
+void UploadService::StageCurrentLog() {
+  CHECK(!staged_log_)
+      << "staged logs must be discarded before another log can be staged";
+
+  if (!current_log_) return;
+
+  staged_log_.swap(current_log_);
+  staged_log_->CloseLog();
+  failed_upload_count_ = 0;
+}
+
+MetricsLog* UploadService::GetOrCreateCurrentLog() {
+  if (!current_log_) {
+    StartNewLog();
+  }
+  return current_log_.get();
+}
diff --git a/metrics/uploader/upload_service.h b/metrics/uploader/upload_service.h
new file mode 100644
index 0000000..7c17488
--- /dev/null
+++ b/metrics/uploader/upload_service.h
@@ -0,0 +1,137 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_UPLOADER_UPLOAD_SERVICE_H_
+#define METRICS_UPLOADER_UPLOAD_SERVICE_H_
+
+#include <string>
+
+#include "base/metrics/histogram_base.h"
+#include "base/metrics/histogram_flattener.h"
+#include "base/metrics/histogram_snapshot_manager.h"
+#include "uploader/metrics_log.h"
+#include "uploader/sender.h"
+
+namespace metrics {
+class ChromeUserMetricsExtension;
+class CrashSample;
+class HistogramSample;
+class LinearHistogramSample;
+class MetricSample;
+class SparseHistogramSample;
+class UserActionSample;
+}
+
+class SystemProfileSetter;
+
+// Service responsible for uploading the metrics periodically to the server.
+// This service works as a simple 2-state state-machine.
+//
+// The two states are the presence or not of a staged log.
+// A staged log is a compressed protobuffer containing both the aggregated
+// metrics and event and information about the client. (product, hardware id,
+// etc...).
+//
+// At regular intervals, the upload event will be triggered and the following
+// will happen:
+// * if a staged log is present:
+//    The previous upload may have failed for various reason. We then retry to
+//    upload the same log.
+//    - if the upload is successful, we discard the log (therefore
+//      transitioning back to no staged log)
+//    - if the upload fails, we keep the log to try again later.
+//    We do not try to read the metrics that are stored on
+//    the disk as we want to avoid storing the metrics in memory.
+//
+// * if no staged logs are present:
+//    Read all metrics from the disk, aggregate them and try to send them.
+//    - if the upload succeeds, we discard the staged log (transitioning back
+//      to the no staged log state)
+//    - if the upload fails, we keep the staged log in memory to retry
+//      uploading later.
+//
+class UploadService : public base::HistogramFlattener {
+ public:
+  UploadService();
+
+  void Init();
+
+  // Starts a new log. The log needs to be regenerated after each successful
+  // launch as it is destroyed when staging the log.
+  void StartNewLog();
+
+  // Glib takes a function pointer and passes the object as a void*.
+  // Uploader is expected to be an UploaderService.
+  static int UploadEventStatic(void* uploader);
+
+  // 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, DiscardLogsAfterTooManyFailedUpload);
+  FRIEND_TEST(UploadServiceTest, EmptyLogsAreNotSent);
+  FRIEND_TEST(UploadServiceTest, FailedSendAreRetried);
+  FRIEND_TEST(UploadServiceTest, LogContainsAggregatedValues);
+  FRIEND_TEST(UploadServiceTest, LogEmptyAfterUpload);
+  FRIEND_TEST(UploadServiceTest, LogEmptyByDefault);
+  FRIEND_TEST(UploadServiceTest, LogKernelCrash);
+  FRIEND_TEST(UploadServiceTest, LogUncleanShutdown);
+  FRIEND_TEST(UploadServiceTest, LogUserCrash);
+  FRIEND_TEST(UploadServiceTest, UnknownCrashIgnored);
+  FRIEND_TEST(UploadServiceTest, ValuesInConfigFileAreSent);
+
+  // If a staged log fails to upload more than kMaxFailedUpload times, it
+  // will be discarded.
+  static const int kMaxFailedUpload;
+
+  // Resets the internal state.
+  void Reset();
+
+  // Reads all the metrics from the disk.
+  void ReadMetrics();
+
+  // Adds a generic sample to the current log.
+  void AddSample(const metrics::MetricSample& sample);
+
+  // Adds a crash to the current log.
+  void AddCrash(const std::string& crash_name);
+
+  // 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 the current log. If there is no current log, creates it first.
+  MetricsLog* GetOrCreateCurrentLog();
+
+  SystemProfileSetter* system_profile_setter_;
+  base::HistogramSnapshotManager histogram_snapshot_manager_;
+  Sender* sender_;
+  int failed_upload_count_;
+  scoped_ptr<MetricsLog> current_log_;
+  scoped_ptr<MetricsLog> staged_log_;
+};
+
+#endif  // METRICS_UPLOADER_UPLOAD_SERVICE_H_
diff --git a/metrics/uploader/upload_service_test.cc b/metrics/uploader/upload_service_test.cc
new file mode 100644
index 0000000..efc382e
--- /dev/null
+++ b/metrics/uploader/upload_service_test.cc
@@ -0,0 +1,243 @@
+// Copyright 2014 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <gtest/gtest.h>
+
+#include "base/at_exit.h"
+#include "base/logging.h"
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/sys_info.h"
+#include "components/metrics/chromeos/metric_sample.h"
+#include "components/metrics/proto/chrome_user_metrics_extension.pb.h"
+#include "components/metrics/proto/system_profile.pb.h"
+#include "components/metrics/proto/histogram_event.pb.h"
+#include "uploader/metrics_log.h"
+#include "uploader/upload_service.h"
+#include "uploader/mock/sender_mock.h"
+#include "uploader/mock/mock_system_profile_setter.h"
+#include "uploader/system_profile_cache.h"
+
+class UploadServiceTest : public testing::Test {
+ protected:
+  UploadServiceTest()
+      : upload_service_(), exit_manager_(new base::AtExitManager()) {
+    upload_service_.sender_ = &sender_;
+    upload_service_.system_profile_setter_ = new MockSystemProfileSetter();
+    upload_service_.Init();
+  }
+
+  virtual void SetUp() {
+    CHECK(dir_.CreateUniqueTempDir());
+    upload_service_.GatherHistograms();
+    upload_service_.Reset();
+    sender_.Reset();
+    cache_.is_testing_ = true;
+
+    chromeos_metrics::PersistentInteger::SetTestingMode(true);
+    cache_.session_id_.reset(new chromeos_metrics::PersistentInteger(
+        dir_.path().Append("session_id").value()));
+  }
+
+  scoped_ptr<metrics::MetricSample> Crash(const std::string& name) {
+    return metrics::MetricSample::CrashSample(name);
+  }
+
+  base::ScopedTempDir dir_;
+  SenderMock sender_;
+  SystemProfileCache cache_;
+  UploadService upload_service_;
+
+  scoped_ptr<base::AtExitManager> exit_manager_;
+};
+
+// Tests that the right crash increments a values.
+TEST_F(UploadServiceTest, LogUserCrash) {
+  upload_service_.AddSample(*Crash("user").get());
+
+  MetricsLog* log = upload_service_.current_log_.get();
+  metrics::ChromeUserMetricsExtension* proto = log->uma_proto();
+
+  EXPECT_EQ(1, proto->system_profile().stability().other_user_crash_count());
+}
+
+TEST_F(UploadServiceTest, LogUncleanShutdown) {
+  upload_service_.AddSample(*Crash("uncleanshutdown"));
+
+  EXPECT_EQ(1, upload_service_.current_log_
+                   ->uma_proto()
+                   ->system_profile()
+                   .stability()
+                   .unclean_system_shutdown_count());
+}
+
+TEST_F(UploadServiceTest, LogKernelCrash) {
+  upload_service_.AddSample(*Crash("kernel"));
+
+  EXPECT_EQ(1, upload_service_.current_log_
+                   ->uma_proto()
+                   ->system_profile()
+                   .stability()
+                   .kernel_crash_count());
+}
+
+TEST_F(UploadServiceTest, UnknownCrashIgnored) {
+  upload_service_.AddSample(*Crash("foo"));
+
+  // The log should be empty.
+  EXPECT_FALSE(upload_service_.current_log_);
+}
+
+TEST_F(UploadServiceTest, FailedSendAreRetried) {
+  sender_.set_should_succeed(false);
+
+  upload_service_.AddSample(*Crash("user"));
+  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) {
+  sender_.set_should_succeed(false);
+  upload_service_.AddSample(*Crash("user"));
+
+  for (int i = 0; i < UploadService::kMaxFailedUpload; i++) {
+    upload_service_.UploadEvent();
+  }
+
+  EXPECT_TRUE(upload_service_.staged_log_);
+  upload_service_.UploadEvent();
+  EXPECT_FALSE(upload_service_.staged_log_);
+}
+
+TEST_F(UploadServiceTest, EmptyLogsAreNotSent) {
+  upload_service_.UploadEvent();
+  EXPECT_FALSE(upload_service_.current_log_);
+  EXPECT_EQ(0, sender_.send_call_count());
+}
+
+TEST_F(UploadServiceTest, LogEmptyByDefault) {
+  UploadService upload_service;
+
+  // current_log_ should be initialized later as it needs AtExitManager to exit
+  // in order to gather system information from SysInfo.
+  EXPECT_FALSE(upload_service.current_log_);
+}
+
+TEST_F(UploadServiceTest, CanSendMultipleTimes) {
+  upload_service_.AddSample(*Crash("user"));
+  upload_service_.UploadEvent();
+
+  std::string first_message = sender_.last_message();
+
+  upload_service_.AddSample(*Crash("kernel"));
+  upload_service_.UploadEvent();
+
+  EXPECT_NE(first_message, sender_.last_message());
+}
+
+TEST_F(UploadServiceTest, LogEmptyAfterUpload) {
+  upload_service_.AddSample(*Crash("user"));
+
+  EXPECT_TRUE(upload_service_.current_log_);
+
+  upload_service_.UploadEvent();
+  EXPECT_FALSE(upload_service_.current_log_);
+}
+
+TEST_F(UploadServiceTest, LogContainsAggregatedValues) {
+  scoped_ptr<metrics::MetricSample> histogram =
+      metrics::MetricSample::HistogramSample("foo", 10, 0, 42, 10);
+  upload_service_.AddSample(*histogram.get());
+
+
+  scoped_ptr<metrics::MetricSample> histogram2 =
+      metrics::MetricSample::HistogramSample("foo", 11, 0, 42, 10);
+  upload_service_.AddSample(*histogram2.get());
+
+  upload_service_.GatherHistograms();
+  metrics::ChromeUserMetricsExtension* proto =
+      upload_service_.current_log_->uma_proto();
+  EXPECT_EQ(1, proto->histogram_event().size());
+}
+
+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_UNKNOWN,
+            SystemProfileCache::ProtoChannelFromString("dev-channel test"));
+}
+
+TEST_F(UploadServiceTest, ValuesInConfigFileAreSent) {
+  std::string name("os name");
+  std::string content(
+      "CHROMEOS_RELEASE_NAME=" + name +
+      "\nCHROMEOS_RELEASE_VERSION=version\n"
+      "CHROMEOS_RELEASE_DESCRIPTION=description beta-channel test\n"
+      "CHROMEOS_RELEASE_TRACK=beta-channel");
+
+  base::SysInfo::SetChromeOSVersionInfoForTest(content, base::Time());
+  scoped_ptr<metrics::MetricSample> histogram =
+      metrics::MetricSample::SparseHistogramSample("myhistogram", 1);
+
+  upload_service_.system_profile_setter_ = &cache_;
+  // Reset to create the new log with the profile setter.
+  upload_service_.Reset();
+  upload_service_.AddSample(*histogram.get());
+  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_EQ(name, sender_.last_message_proto().system_profile().os().name());
+  EXPECT_EQ(metrics::SystemProfileProto::CHANNEL_BETA,
+            sender_.last_message_proto().system_profile().channel());
+  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) {
+  cache_.Initialize();
+  int session_id = cache_.profile_.session_id;
+  cache_.initialized_ = false;
+  cache_.Initialize();
+  EXPECT_EQ(cache_.profile_.session_id, session_id + 1);
+}
+
+int main(int argc, char** argv) {
+  testing::InitGoogleTest(&argc, argv);
+
+  return RUN_ALL_TESTS();
+}