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();
+}