diff options
21 files changed, 1078 insertions, 996 deletions
diff --git a/chrome/browser/metrics/metrics_log_serializer.cc b/chrome/browser/metrics/metrics_log_serializer.cc deleted file mode 100644 index 0d545ec..0000000 --- a/chrome/browser/metrics/metrics_log_serializer.cc +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright (c) 2012 The Chromium 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 "chrome/browser/metrics/metrics_log_serializer.h" - -#include <string> - -#include "base/base64.h" -#include "base/md5.h" -#include "base/metrics/histogram.h" -#include "base/prefs/pref_service.h" -#include "base/prefs/scoped_user_pref_update.h" -#include "chrome/browser/browser_process.h" -#include "chrome/common/pref_names.h" - -using metrics::MetricsLogBase; -using metrics::MetricsLogManager; - -namespace { - -// The number of "initial" logs to save, and hope to send during a future Chrome -// session. Initial logs contain crash stats, and are pretty small. -const size_t kInitialLogsPersistLimit = 20; - -// The number of ongoing logs to save persistently, and hope to -// send during a this or future sessions. Note that each log may be pretty -// large, as presumably the related "initial" log wasn't sent (probably nothing -// was, as the user was probably off-line). As a result, the log probably kept -// accumulating while the "initial" log was stalled, and couldn't be sent. As a -// result, we don't want to save too many of these mega-logs. -// A "standard shutdown" will create a small log, including just the data that -// was not yet been transmitted, and that is normal (to have exactly one -// ongoing_log_ at startup). -const size_t kOngoingLogsPersistLimit = 8; - -// The number of bytes each of initial and ongoing logs that must be stored. -// This ensures that a reasonable amount of history will be stored even if there -// is a long series of very small logs. -const size_t kStorageByteLimitPerLogType = 300000; - -// We append (2) more elements to persisted lists: the size of the list and a -// checksum of the elements. -const size_t kChecksumEntryCount = 2; - -MetricsLogSerializer::LogReadStatus MakeRecallStatusHistogram( - MetricsLogSerializer::LogReadStatus status) { - UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogRecallProtobufs", - status, MetricsLogSerializer::END_RECALL_STATUS); - return status; -} - -} // namespace - - -MetricsLogSerializer::MetricsLogSerializer() {} - -MetricsLogSerializer::~MetricsLogSerializer() {} - -void MetricsLogSerializer::SerializeLogs( - const std::vector<MetricsLogManager::SerializedLog>& logs, - MetricsLogManager::LogType log_type) { - PrefService* local_state = g_browser_process->local_state(); - DCHECK(local_state); - const char* pref = NULL; - size_t store_length_limit = 0; - switch (log_type) { - case MetricsLogBase::INITIAL_STABILITY_LOG: - pref = prefs::kMetricsInitialLogs; - store_length_limit = kInitialLogsPersistLimit; - break; - case MetricsLogBase::ONGOING_LOG: - pref = prefs::kMetricsOngoingLogs; - store_length_limit = kOngoingLogsPersistLimit; - break; - case MetricsLogBase::NO_LOG: - NOTREACHED(); - return; - }; - - ListPrefUpdate update(local_state, pref); - WriteLogsToPrefList(logs, store_length_limit, kStorageByteLimitPerLogType, - update.Get()); -} - -void MetricsLogSerializer::DeserializeLogs( - MetricsLogManager::LogType log_type, - std::vector<MetricsLogManager::SerializedLog>* logs) { - DCHECK(logs); - PrefService* local_state = g_browser_process->local_state(); - DCHECK(local_state); - - const char* pref; - if (log_type == MetricsLogBase::INITIAL_STABILITY_LOG) - pref = prefs::kMetricsInitialLogs; - else - pref = prefs::kMetricsOngoingLogs; - - const base::ListValue* unsent_logs = local_state->GetList(pref); - ReadLogsFromPrefList(*unsent_logs, logs); -} - -// static -void MetricsLogSerializer::WriteLogsToPrefList( - const std::vector<MetricsLogManager::SerializedLog>& local_list, - size_t list_length_limit, - size_t byte_limit, - base::ListValue* list) { - // One of the limit arguments must be non-zero. - DCHECK(list_length_limit > 0 || byte_limit > 0); - - list->Clear(); - if (local_list.size() == 0) - return; - - size_t start = 0; - // If there are too many logs, keep the most recent logs up to the length - // limit, and at least to the minimum number of bytes. - if (local_list.size() > list_length_limit) { - start = local_list.size(); - size_t bytes_used = 0; - for (std::vector<MetricsLogManager::SerializedLog>::const_reverse_iterator - it = local_list.rbegin(); it != local_list.rend(); ++it) { - size_t log_size = it->log_text().length(); - if (bytes_used >= byte_limit && - (local_list.size() - start) >= list_length_limit) - break; - bytes_used += log_size; - --start; - } - } - DCHECK_LT(start, local_list.size()); - if (start >= local_list.size()) - return; - - // Store size at the beginning of the list. - list->Append(base::Value::CreateIntegerValue(local_list.size() - start)); - - base::MD5Context ctx; - base::MD5Init(&ctx); - std::string encoded_log; - for (std::vector<MetricsLogManager::SerializedLog>::const_iterator it = - local_list.begin() + start; - it != local_list.end(); ++it) { - // We encode the compressed log as Value::CreateStringValue() expects to - // take a valid UTF8 string. - base::Base64Encode(it->log_text(), &encoded_log); - base::MD5Update(&ctx, encoded_log); - list->Append(base::Value::CreateStringValue(encoded_log)); - } - - // Append hash to the end of the list. - base::MD5Digest digest; - base::MD5Final(&digest, &ctx); - list->Append(base::Value::CreateStringValue(base::MD5DigestToBase16(digest))); - DCHECK(list->GetSize() >= 3); // Minimum of 3 elements (size, data, hash). -} - -// static -MetricsLogSerializer::LogReadStatus MetricsLogSerializer::ReadLogsFromPrefList( - const base::ListValue& list, - std::vector<MetricsLogManager::SerializedLog>* local_list) { - if (list.GetSize() == 0) - return MakeRecallStatusHistogram(LIST_EMPTY); - if (list.GetSize() < 3) - return MakeRecallStatusHistogram(LIST_SIZE_TOO_SMALL); - - // The size is stored at the beginning of the list. - int size; - bool valid = (*list.begin())->GetAsInteger(&size); - if (!valid) - return MakeRecallStatusHistogram(LIST_SIZE_MISSING); - // Account for checksum and size included in the list. - if (static_cast<unsigned int>(size) != - list.GetSize() - kChecksumEntryCount) { - return MakeRecallStatusHistogram(LIST_SIZE_CORRUPTION); - } - - // Allocate strings for all of the logs we are going to read in. - // Do this ahead of time so that we can decode the string values directly into - // the elements of |local_list|, and thereby avoid making copies of the - // serialized logs, which can be fairly large. - DCHECK(local_list->empty()); - local_list->resize(size); - - base::MD5Context ctx; - base::MD5Init(&ctx); - std::string encoded_log; - size_t local_index = 0; - for (base::ListValue::const_iterator it = list.begin() + 1; - it != list.end() - 1; // Last element is the checksum. - ++it, ++local_index) { - bool valid = (*it)->GetAsString(&encoded_log); - if (!valid) { - local_list->clear(); - return MakeRecallStatusHistogram(LOG_STRING_CORRUPTION); - } - - base::MD5Update(&ctx, encoded_log); - - std::string log_text; - if (!base::Base64Decode(encoded_log, &log_text)) { - local_list->clear(); - return MakeRecallStatusHistogram(DECODE_FAIL); - } - - DCHECK_LT(local_index, local_list->size()); - (*local_list)[local_index].SwapLogText(&log_text); - } - - // Verify checksum. - base::MD5Digest digest; - base::MD5Final(&digest, &ctx); - std::string recovered_md5; - // We store the hash at the end of the list. - valid = (*(list.end() - 1))->GetAsString(&recovered_md5); - if (!valid) { - local_list->clear(); - return MakeRecallStatusHistogram(CHECKSUM_STRING_CORRUPTION); - } - if (recovered_md5 != base::MD5DigestToBase16(digest)) { - local_list->clear(); - return MakeRecallStatusHistogram(CHECKSUM_CORRUPTION); - } - return MakeRecallStatusHistogram(RECALL_SUCCESS); -} diff --git a/chrome/browser/metrics/metrics_log_serializer.h b/chrome/browser/metrics/metrics_log_serializer.h deleted file mode 100644 index 74d39e5..0000000 --- a/chrome/browser/metrics/metrics_log_serializer.h +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) 2012 The Chromium 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 CHROME_BROWSER_METRICS_METRICS_LOG_SERIALIZER_H_ -#define CHROME_BROWSER_METRICS_METRICS_LOG_SERIALIZER_H_ - -#include <vector> - -#include "base/basictypes.h" -#include "base/gtest_prod_util.h" -#include "components/metrics/metrics_log_manager.h" - -namespace base { -class ListValue; -} - -// Serializer for persisting metrics logs to prefs. -class MetricsLogSerializer : public metrics::MetricsLogManager::LogSerializer { - public: - // Used to produce a histogram that keeps track of the status of recalling - // persisted per logs. - enum LogReadStatus { - RECALL_SUCCESS, // We were able to correctly recall a persisted log. - LIST_EMPTY, // Attempting to recall from an empty list. - LIST_SIZE_MISSING, // Failed to recover list size using GetAsInteger(). - LIST_SIZE_TOO_SMALL, // Too few elements in the list (less than 3). - LIST_SIZE_CORRUPTION, // List size is not as expected. - LOG_STRING_CORRUPTION, // Failed to recover log string using GetAsString(). - CHECKSUM_CORRUPTION, // Failed to verify checksum. - CHECKSUM_STRING_CORRUPTION, // Failed to recover checksum string using - // GetAsString(). - DECODE_FAIL, // Failed to decode log. - DEPRECATED_XML_PROTO_MISMATCH, // The XML and protobuf logs have - // inconsistent data. - END_RECALL_STATUS // Number of bins to use to create the histogram. - }; - - MetricsLogSerializer(); - virtual ~MetricsLogSerializer(); - - // Implementation of metrics::MetricsLogManager::LogSerializer - virtual void SerializeLogs( - const std::vector<metrics::MetricsLogManager::SerializedLog>& logs, - metrics::MetricsLogManager::LogType log_type) OVERRIDE; - virtual void DeserializeLogs( - metrics::MetricsLogManager::LogType log_type, - std::vector<metrics::MetricsLogManager::SerializedLog>* logs) OVERRIDE; - - private: - // Encodes the textual log data from |local_list| and writes it to the given - // pref list, along with list size and checksum. Logs will be stored starting - // with the most recent, and working backward until at least - // |list_length_limit| logs and |byte_limit| bytes of logs have been - // stored. At least one of those two arguments must be non-zero. - static void WriteLogsToPrefList( - const std::vector<metrics::MetricsLogManager::SerializedLog>& local_list, - size_t list_length_limit, - size_t byte_limit, - base::ListValue* list); - - // Decodes and verifies the textual log data from |list|, populating - // |local_list| and returning a status code. - static LogReadStatus ReadLogsFromPrefList( - const base::ListValue& list, - std::vector<metrics::MetricsLogManager::SerializedLog>* local_list); - - FRIEND_TEST_ALL_PREFIXES(MetricsLogSerializerTest, EmptyLogList); - FRIEND_TEST_ALL_PREFIXES(MetricsLogSerializerTest, SingleElementLogList); - FRIEND_TEST_ALL_PREFIXES(MetricsLogSerializerTest, LongButTinyLogList); - FRIEND_TEST_ALL_PREFIXES(MetricsLogSerializerTest, LongButSmallLogList); - FRIEND_TEST_ALL_PREFIXES(MetricsLogSerializerTest, ShortButLargeLogList); - FRIEND_TEST_ALL_PREFIXES(MetricsLogSerializerTest, LongAndLargeLogList); - FRIEND_TEST_ALL_PREFIXES(MetricsLogSerializerTest, SmallRecoveredListSize); - FRIEND_TEST_ALL_PREFIXES(MetricsLogSerializerTest, RemoveSizeFromLogList); - FRIEND_TEST_ALL_PREFIXES(MetricsLogSerializerTest, CorruptSizeOfLogList); - FRIEND_TEST_ALL_PREFIXES(MetricsLogSerializerTest, CorruptChecksumOfLogList); - - DISALLOW_COPY_AND_ASSIGN(MetricsLogSerializer); -}; - -#endif // CHROME_BROWSER_METRICS_METRICS_LOG_SERIALIZER_H_ diff --git a/chrome/browser/metrics/metrics_log_serializer_unittest.cc b/chrome/browser/metrics/metrics_log_serializer_unittest.cc deleted file mode 100644 index 386a3cb..0000000 --- a/chrome/browser/metrics/metrics_log_serializer_unittest.cc +++ /dev/null @@ -1,274 +0,0 @@ -// Copyright (c) 2012 The Chromium 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 "base/base64.h" -#include "base/md5.h" -#include "base/values.h" -#include "chrome/browser/metrics/metrics_log_serializer.h" -#include "testing/gtest/include/gtest/gtest.h" - -using metrics::MetricsLogManager; - -namespace { - -const size_t kListLengthLimit = 3; -const size_t kLogByteLimit = 1000; - -void SetLogText(const std::string& log_text, - MetricsLogManager::SerializedLog* log) { - std::string log_text_copy = log_text; - log->SwapLogText(&log_text_copy); -} - -} // namespace - -// Store and retrieve empty list. -TEST(MetricsLogSerializerTest, EmptyLogList) { - base::ListValue list; - std::vector<MetricsLogManager::SerializedLog> local_list; - - MetricsLogSerializer::WriteLogsToPrefList(local_list, kListLengthLimit, - kLogByteLimit, &list); - EXPECT_EQ(0U, list.GetSize()); - - local_list.clear(); // ReadLogsFromPrefList() expects empty |local_list|. - EXPECT_EQ( - MetricsLogSerializer::LIST_EMPTY, - MetricsLogSerializer::ReadLogsFromPrefList(list, &local_list)); - EXPECT_EQ(0U, local_list.size()); -} - -// Store and retrieve a single log value. -TEST(MetricsLogSerializerTest, SingleElementLogList) { - base::ListValue list; - - std::vector<MetricsLogManager::SerializedLog> local_list(1); - SetLogText("Hello world!", &local_list[0]); - - MetricsLogSerializer::WriteLogsToPrefList(local_list, kListLengthLimit, - kLogByteLimit, &list); - - // |list| will now contain the following: - // [1, Base64Encode("Hello world!"), MD5("Hello world!")]. - ASSERT_EQ(3U, list.GetSize()); - - // Examine each element. - base::ListValue::const_iterator it = list.begin(); - int size = 0; - (*it)->GetAsInteger(&size); - EXPECT_EQ(1, size); - - ++it; - std::string str; - (*it)->GetAsString(&str); // Base64 encoded "Hello world!" string. - std::string encoded; - base::Base64Encode("Hello world!", &encoded); - EXPECT_TRUE(encoded == str); - - ++it; - (*it)->GetAsString(&str); // MD5 for encoded "Hello world!" string. - EXPECT_TRUE(base::MD5String(encoded) == str); - - ++it; - EXPECT_TRUE(it == list.end()); // Reached end of list. - - local_list.clear(); - EXPECT_EQ( - MetricsLogSerializer::RECALL_SUCCESS, - MetricsLogSerializer::ReadLogsFromPrefList(list, &local_list)); - EXPECT_EQ(1U, local_list.size()); -} - -// Store a set of logs over the length limit, but smaller than the min number of -// bytes. -TEST(MetricsLogSerializerTest, LongButTinyLogList) { - base::ListValue list; - - size_t log_count = kListLengthLimit * 5; - std::vector<MetricsLogManager::SerializedLog> local_list(log_count); - for (size_t i = 0; i < local_list.size(); ++i) - SetLogText("x", &local_list[i]); - - MetricsLogSerializer::WriteLogsToPrefList(local_list, kListLengthLimit, - kLogByteLimit, &list); - std::vector<MetricsLogManager::SerializedLog> result_list; - EXPECT_EQ( - MetricsLogSerializer::RECALL_SUCCESS, - MetricsLogSerializer::ReadLogsFromPrefList(list, &result_list)); - EXPECT_EQ(local_list.size(), result_list.size()); - - EXPECT_TRUE(result_list.front().log_text().find("x") == 0); -} - -// Store a set of logs over the length limit, but that doesn't reach the minimum -// number of bytes until after passing the length limit. -TEST(MetricsLogSerializerTest, LongButSmallLogList) { - base::ListValue list; - - size_t log_count = kListLengthLimit * 5; - // Make log_count logs each slightly larger than - // kLogByteLimit / (log_count - 2) - // so that the minimum is reached before the oldest (first) two logs. - std::vector<MetricsLogManager::SerializedLog> local_list(log_count); - size_t log_size = (kLogByteLimit / (log_count - 2)) + 2; - SetLogText("one", &local_list[0]); - SetLogText("two", &local_list[1]); - SetLogText("three", &local_list[2]); - SetLogText("last", &local_list[log_count - 1]); - for (size_t i = 0; i < local_list.size(); ++i) { - std::string log_text = local_list[i].log_text(); - log_text.resize(log_size, ' '); - local_list[i].SwapLogText(&log_text); - } - - MetricsLogSerializer::WriteLogsToPrefList(local_list, kListLengthLimit, - kLogByteLimit, &list); - std::vector<MetricsLogManager::SerializedLog> result_list; - EXPECT_EQ( - MetricsLogSerializer::RECALL_SUCCESS, - MetricsLogSerializer::ReadLogsFromPrefList(list, &result_list)); - EXPECT_EQ(local_list.size() - 2, result_list.size()); - - EXPECT_TRUE(result_list.front().log_text().find("three") == 0); - EXPECT_TRUE(result_list.back().log_text().find("last") == 0); -} - -// Store a set of logs within the length limit, but well over the minimum -// number of bytes. -TEST(MetricsLogSerializerTest, ShortButLargeLogList) { - base::ListValue list; - - std::vector<MetricsLogManager::SerializedLog> local_list(kListLengthLimit); - // Make the total byte count about twice the minimum. - size_t log_size = (kLogByteLimit / local_list.size()) * 2; - for (size_t i = 0; i < local_list.size(); ++i) { - std::string log_text = local_list[i].log_text(); - log_text.resize(log_size, ' '); - local_list[i].SwapLogText(&log_text); - } - - MetricsLogSerializer::WriteLogsToPrefList(local_list, kListLengthLimit, - kLogByteLimit, &list); - std::vector<MetricsLogManager::SerializedLog> result_list; - EXPECT_EQ( - MetricsLogSerializer::RECALL_SUCCESS, - MetricsLogSerializer::ReadLogsFromPrefList(list, &result_list)); - EXPECT_EQ(local_list.size(), result_list.size()); -} - -// Store a set of logs over the length limit, and over the minimum number of -// bytes. -TEST(MetricsLogSerializerTest, LongAndLargeLogList) { - base::ListValue list; - - // Include twice the max number of logs. - std::vector<MetricsLogManager::SerializedLog> - local_list(kListLengthLimit * 2); - // Make the total byte count about four times the minimum. - size_t log_size = (kLogByteLimit / local_list.size()) * 4; - SetLogText("First to keep", - &local_list[local_list.size() - kListLengthLimit]); - for (size_t i = 0; i < local_list.size(); ++i) { - std::string log_text = local_list[i].log_text(); - log_text.resize(log_size, ' '); - local_list[i].SwapLogText(&log_text); - } - - MetricsLogSerializer::WriteLogsToPrefList(local_list, kListLengthLimit, - kLogByteLimit, &list); - std::vector<MetricsLogManager::SerializedLog> result_list; - EXPECT_EQ( - MetricsLogSerializer::RECALL_SUCCESS, - MetricsLogSerializer::ReadLogsFromPrefList(list, &result_list)); - // The max length should control the resulting size. - EXPECT_EQ(kListLengthLimit, result_list.size()); - EXPECT_TRUE(result_list.front().log_text().find("First to keep") == 0); -} - -// Induce LIST_SIZE_TOO_SMALL corruption -TEST(MetricsLogSerializerTest, SmallRecoveredListSize) { - base::ListValue list; - - std::vector<MetricsLogManager::SerializedLog> local_list(1); - SetLogText("Hello world!", &local_list[0]); - - MetricsLogSerializer::WriteLogsToPrefList(local_list, kListLengthLimit, - kLogByteLimit, &list); - EXPECT_EQ(3U, list.GetSize()); - - // Remove last element. - list.Remove(list.GetSize() - 1, NULL); - EXPECT_EQ(2U, list.GetSize()); - - local_list.clear(); - EXPECT_EQ( - MetricsLogSerializer::LIST_SIZE_TOO_SMALL, - MetricsLogSerializer::ReadLogsFromPrefList(list, &local_list)); -} - -// Remove size from the stored list. -TEST(MetricsLogSerializerTest, RemoveSizeFromLogList) { - base::ListValue list; - - std::vector<MetricsLogManager::SerializedLog> local_list(2); - SetLogText("one", &local_list[0]); - SetLogText("two", &local_list[1]); - EXPECT_EQ(2U, local_list.size()); - MetricsLogSerializer::WriteLogsToPrefList(local_list, kListLengthLimit, - kLogByteLimit, &list); - EXPECT_EQ(4U, list.GetSize()); - - list.Remove(0, NULL); // Delete size (1st element). - EXPECT_EQ(3U, list.GetSize()); - - local_list.clear(); - EXPECT_EQ( - MetricsLogSerializer::LIST_SIZE_MISSING, - MetricsLogSerializer::ReadLogsFromPrefList(list, &local_list)); -} - -// Corrupt size of stored list. -TEST(MetricsLogSerializerTest, CorruptSizeOfLogList) { - base::ListValue list; - - std::vector<MetricsLogManager::SerializedLog> local_list(1); - SetLogText("Hello world!", &local_list[0]); - - MetricsLogSerializer::WriteLogsToPrefList(local_list, kListLengthLimit, - kLogByteLimit, &list); - EXPECT_EQ(3U, list.GetSize()); - - // Change list size from 1 to 2. - EXPECT_TRUE(list.Set(0, base::Value::CreateIntegerValue(2))); - EXPECT_EQ(3U, list.GetSize()); - - local_list.clear(); - EXPECT_EQ( - MetricsLogSerializer::LIST_SIZE_CORRUPTION, - MetricsLogSerializer::ReadLogsFromPrefList(list, &local_list)); -} - -// Corrupt checksum of stored list. -TEST(MetricsLogSerializerTest, CorruptChecksumOfLogList) { - base::ListValue list; - - std::vector<MetricsLogManager::SerializedLog> local_list(1); - SetLogText("Hello world!", &local_list[0]); - - MetricsLogSerializer::WriteLogsToPrefList(local_list, kListLengthLimit, - kLogByteLimit, &list); - EXPECT_EQ(3U, list.GetSize()); - - // Fetch checksum (last element) and change it. - std::string checksum; - EXPECT_TRUE((*(list.end() - 1))->GetAsString(&checksum)); - checksum[0] = (checksum[0] == 'a') ? 'b' : 'a'; - EXPECT_TRUE(list.Set(2, base::Value::CreateStringValue(checksum))); - EXPECT_EQ(3U, list.GetSize()); - - local_list.clear(); - EXPECT_EQ( - MetricsLogSerializer::CHECKSUM_CORRUPTION, - MetricsLogSerializer::ReadLogsFromPrefList(list, &local_list)); -} diff --git a/chrome/browser/metrics/metrics_service.cc b/chrome/browser/metrics/metrics_service.cc index 1cb20ec..700cc8a 100644 --- a/chrome/browser/metrics/metrics_service.cc +++ b/chrome/browser/metrics/metrics_service.cc @@ -187,7 +187,6 @@ #include "chrome/browser/memory_details.h" #include "chrome/browser/metrics/compression_utils.h" #include "chrome/browser/metrics/metrics_log.h" -#include "chrome/browser/metrics/metrics_log_serializer.h" #include "chrome/browser/metrics/metrics_reporting_scheduler.h" #include "chrome/browser/metrics/metrics_state_manager.h" #include "chrome/browser/metrics/time_ticks_experiment_win.h" @@ -204,6 +203,7 @@ #include "chrome/common/pref_names.h" #include "chrome/common/render_messages.h" #include "components/metrics/metrics_log_manager.h" +#include "components/metrics/metrics_pref_names.h" #include "components/variations/entropy_provider.h" #include "components/variations/metrics_util.h" #include "content/public/browser/child_process_data.h" @@ -444,8 +444,8 @@ void MetricsService::RegisterPrefs(PrefRegistrySimple* registry) { registry->RegisterStringPref(prefs::kStabilitySavedSystemProfileHash, std::string()); - registry->RegisterListPref(prefs::kMetricsInitialLogs); - registry->RegisterListPref(prefs::kMetricsOngoingLogs); + registry->RegisterListPref(metrics::prefs::kMetricsInitialLogs); + registry->RegisterListPref(metrics::prefs::kMetricsOngoingLogs); registry->RegisterInt64Pref(prefs::kInstallDate, 0); registry->RegisterInt64Pref(prefs::kUninstallMetricsPageLoadCount, 0); @@ -460,7 +460,9 @@ void MetricsService::RegisterPrefs(PrefRegistrySimple* registry) { } MetricsService::MetricsService(metrics::MetricsStateManager* state_manager) - : state_manager_(state_manager), + : MetricsServiceBase(g_browser_process->local_state(), + kUploadLogAvoidRetransmitSize), + state_manager_(state_manager), recording_active_(false), reporting_active_(false), test_mode_active_(false), @@ -476,9 +478,6 @@ MetricsService::MetricsService(metrics::MetricsStateManager* state_manager) DCHECK(IsSingleThreaded()); DCHECK(state_manager_); - log_manager_.set_log_serializer(new MetricsLogSerializer); - log_manager_.set_max_ongoing_log_store_size(kUploadLogAvoidRetransmitSize); - BrowserChildProcessObserver::Add(this); } @@ -1188,11 +1187,11 @@ void MetricsService::PushPendingLogsToPersistentStorage() { if (log_manager_.has_staged_log()) { // We may race here, and send second copy of the log later. - MetricsLogManager::StoreType store_type; + metrics::PersistedLogs::StoreType store_type; if (current_fetch_.get()) - store_type = MetricsLogManager::PROVISIONAL_STORE; + store_type = metrics::PersistedLogs::PROVISIONAL_STORE; else - store_type = MetricsLogManager::NORMAL_STORE; + store_type = metrics::PersistedLogs::NORMAL_STORE; log_manager_.StoreStagedLogAsUnsent(store_type); } DCHECK(!log_manager_.has_staged_log()); @@ -1512,7 +1511,7 @@ void MetricsService::PrepareFetchWithStagedLog() { current_fetch_->SetRequestContext( g_browser_process->system_request_context()); - std::string log_text = log_manager_.staged_log_text(); + std::string log_text = log_manager_.staged_log(); std::string compressed_log_text; bool compression_successful = chrome::GzipCompress(log_text, &compressed_log_text); @@ -1566,7 +1565,7 @@ void MetricsService::OnURLFetchComplete(const net::URLFetcher* source) { // Provide boolean for error recovery (allow us to ignore response_code). bool discard_log = false; - const size_t log_size = log_manager_.staged_log_text().length(); + const size_t log_size = log_manager_.staged_log().length(); if (!upload_succeeded && log_size > kUploadLogAvoidRetransmitSize) { UMA_HISTOGRAM_COUNTS("UMA.Large Rejected Log was Discarded", static_cast<int>(log_size)); diff --git a/chrome/browser/metrics/metrics_service_unittest.cc b/chrome/browser/metrics/metrics_service_unittest.cc index a8a12a1..c3bef76 100644 --- a/chrome/browser/metrics/metrics_service_unittest.cc +++ b/chrome/browser/metrics/metrics_service_unittest.cc @@ -219,7 +219,7 @@ TEST_F(MetricsServiceTest, InitialStabilityLogAfterCrash) { EXPECT_TRUE(log_manager->has_staged_log()); metrics::ChromeUserMetricsExtension uma_log; - EXPECT_TRUE(uma_log.ParseFromString(log_manager->staged_log_text())); + EXPECT_TRUE(uma_log.ParseFromString(log_manager->staged_log())); EXPECT_TRUE(uma_log.has_client_id()); EXPECT_TRUE(uma_log.has_session_id()); diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index b38e4ac..704badd 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1193,8 +1193,6 @@ 'browser/metrics/metrics_log.h', 'browser/metrics/metrics_log_chromeos.cc', 'browser/metrics/metrics_log_chromeos.h', - 'browser/metrics/metrics_log_serializer.cc', - 'browser/metrics/metrics_log_serializer.h', 'browser/metrics/metrics_network_observer.cc', 'browser/metrics/metrics_network_observer.h', 'browser/metrics/metrics_reporting_scheduler.cc', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index e153095..4f23d91 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -1080,7 +1080,6 @@ 'browser/metrics/extension_metrics_unittest.cc', 'browser/metrics/machine_id_provider_win_unittest.cc', 'browser/metrics/metrics_log_unittest.cc', - 'browser/metrics/metrics_log_serializer_unittest.cc', 'browser/metrics/metrics_reporting_scheduler_unittest.cc', 'browser/metrics/metrics_service_unittest.cc', 'browser/metrics/metrics_state_manager_unittest.cc', diff --git a/chrome/common/metrics/metrics_service_base.cc b/chrome/common/metrics/metrics_service_base.cc index a0a8182..3bad34e 100644 --- a/chrome/common/metrics/metrics_service_base.cc +++ b/chrome/common/metrics/metrics_service_base.cc @@ -10,8 +10,10 @@ using base::Histogram; -MetricsServiceBase::MetricsServiceBase() - : histogram_snapshot_manager_(this) { +MetricsServiceBase::MetricsServiceBase(PrefService* local_state, + size_t max_ongoing_log_size) + : log_manager_(local_state, max_ongoing_log_size), + histogram_snapshot_manager_(this) { } MetricsServiceBase::~MetricsServiceBase() { diff --git a/chrome/common/metrics/metrics_service_base.h b/chrome/common/metrics/metrics_service_base.h index d2e9043..e777a67 100644 --- a/chrome/common/metrics/metrics_service_base.h +++ b/chrome/common/metrics/metrics_service_base.h @@ -30,7 +30,10 @@ class MetricsServiceBase : public base::HistogramFlattener { virtual void InconsistencyDetectedInLoggedCount(int amount) OVERRIDE; protected: - MetricsServiceBase(); + // The metrics service will persist it's unsent logs by storing them in + // |local_state|, and will not persist ongoing logs over + // |max_ongoing_log_size|. + MetricsServiceBase(PrefService* local_state, size_t max_ongoing_log_size); virtual ~MetricsServiceBase(); // The metrics server's URL. diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc index 65db79a..419a76f 100644 --- a/chrome/common/pref_names.cc +++ b/chrome/common/pref_names.cc @@ -1385,19 +1385,6 @@ const char kCrashReportingEnabled[] = "user_experience_metrics_crash.reporting_enabled"; #endif -// Array of strings that are each UMA logs that were supposed to be sent in the -// first minute of a browser session. These logs include things like crash count -// info, etc. -const char kMetricsInitialLogs[] = - "user_experience_metrics.initial_logs_as_protobufs"; - -// Array of strings that are each UMA logs that were not sent because the -// browser terminated before these accumulated metrics could be sent. These -// logs typically include histograms and memory reports, as well as ongoing -// user activities. -const char kMetricsOngoingLogs[] = - "user_experience_metrics.ongoing_logs_as_protobufs"; - // 64-bit integer serialization of the base::Time from the last successful seed // fetch (i.e. when the Variations server responds with 200 or 304). const char kVariationsLastFetchTime[] = "variations_last_fetch_time"; diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h index 14cd0a2..4f10be7 100644 --- a/chrome/common/pref_names.h +++ b/chrome/common/pref_names.h @@ -441,8 +441,6 @@ extern const char kMetricsResetIds[]; #if defined(OS_ANDROID) extern const char kCrashReportingEnabled[]; #endif -extern const char kMetricsInitialLogs[]; -extern const char kMetricsOngoingLogs[]; extern const char kVariationsLastFetchTime[]; extern const char kVariationsRestrictParameter[]; diff --git a/components/components_tests.gyp b/components/components_tests.gyp index f51868a..6a3052f 100644 --- a/components/components_tests.gyp +++ b/components/components_tests.gyp @@ -106,6 +106,7 @@ 'metrics/metrics_hashes_unittest.cc', 'metrics/metrics_log_base_unittest.cc', 'metrics/metrics_log_manager_unittest.cc', + 'metrics/persisted_logs_unittest.cc', 'navigation_interception/intercept_navigation_resource_throttle_unittest.cc', 'os_crypt/ie7_password_win_unittest.cc', 'os_crypt/keychain_password_mac_unittest.mm', diff --git a/components/metrics.gypi b/components/metrics.gypi index e0fe848..61e9855 100644 --- a/components/metrics.gypi +++ b/components/metrics.gypi @@ -21,6 +21,10 @@ 'metrics/metrics_log_base.h', 'metrics/metrics_log_manager.cc', 'metrics/metrics_log_manager.h', + 'metrics/metrics_pref_names.cc', + 'metrics/metrics_pref_names.h', + 'metrics/persisted_logs.cc', + 'metrics/persisted_logs.h', ], }, { diff --git a/components/metrics/metrics_log_manager.cc b/components/metrics/metrics_log_manager.cc index e537eef..2260b64 100644 --- a/components/metrics/metrics_log_manager.cc +++ b/components/metrics/metrics_log_manager.cc @@ -7,45 +7,50 @@ #include <algorithm> #include "base/metrics/histogram.h" -#include "base/sha1.h" #include "base/strings/string_util.h" #include "base/timer/elapsed_timer.h" #include "components/metrics/metrics_log_base.h" +#include "components/metrics/metrics_pref_names.h" namespace metrics { -MetricsLogManager::SerializedLog::SerializedLog() {} -MetricsLogManager::SerializedLog::~SerializedLog() {} +namespace { -bool MetricsLogManager::SerializedLog::IsEmpty() const { - return log_text_.empty(); -} +// The number of "initial" logs to save, and hope to send during a future Chrome +// session. Initial logs contain crash stats, and are pretty small. +const size_t kInitialLogsPersistLimit = 20; -void MetricsLogManager::SerializedLog::SwapLogText(std::string* log_text) { - log_text_.swap(*log_text); - if (log_text_.empty()) - log_hash_.clear(); - else - log_hash_ = base::SHA1HashString(log_text_); -} +// The number of ongoing logs to save persistently, and hope to +// send during a this or future sessions. Note that each log may be pretty +// large, as presumably the related "initial" log wasn't sent (probably nothing +// was, as the user was probably off-line). As a result, the log probably kept +// accumulating while the "initial" log was stalled, and couldn't be sent. As a +// result, we don't want to save too many of these mega-logs. +// A "standard shutdown" will create a small log, including just the data that +// was not yet been transmitted, and that is normal (to have exactly one +// ongoing_log_ at startup). +const size_t kOngoingLogsPersistLimit = 8; -void MetricsLogManager::SerializedLog::Clear() { - log_text_.clear(); - log_hash_.clear(); -} +// The number of bytes each of initial and ongoing logs that must be stored. +// This ensures that a reasonable amount of history will be stored even if there +// is a long series of very small logs. +const size_t kStorageByteLimitPerLogType = 300000; -void MetricsLogManager::SerializedLog::Swap( - MetricsLogManager::SerializedLog* other) { - log_text_.swap(other->log_text_); - log_hash_.swap(other->log_hash_); -} +} // namespace -MetricsLogManager::MetricsLogManager() +MetricsLogManager::MetricsLogManager(PrefService* local_state, + size_t max_ongoing_log_size) : unsent_logs_loaded_(false), - staged_log_type_(MetricsLogBase::NO_LOG), - max_ongoing_log_store_size_(0), - last_provisional_store_index_(-1), - last_provisional_store_type_(MetricsLogBase::INITIAL_STABILITY_LOG) {} + initial_log_queue_(local_state, + prefs::kMetricsInitialLogs, + kInitialLogsPersistLimit, + kStorageByteLimitPerLogType, + 0), + ongoing_log_queue_(local_state, + prefs::kMetricsOngoingLogs, + kOngoingLogsPersistLimit, + kStorageByteLimitPerLogType, + max_ongoing_log_size) {} MetricsLogManager::~MetricsLogManager() {} @@ -57,46 +62,28 @@ void MetricsLogManager::BeginLoggingWithLog(MetricsLogBase* log) { void MetricsLogManager::FinishCurrentLog() { DCHECK(current_log_.get()); current_log_->CloseLog(); - SerializedLog compressed_log; - CompressCurrentLog(&compressed_log); - if (!compressed_log.IsEmpty()) - StoreLog(&compressed_log, current_log_->log_type(), NORMAL_STORE); + std::string log_text; + current_log_->GetEncodedLog(&log_text); + if (!log_text.empty()) + StoreLog(&log_text, current_log_->log_type()); current_log_.reset(); } void MetricsLogManager::StageNextLogForUpload() { - // Prioritize initial logs for uploading. - std::vector<SerializedLog>* source_list = - unsent_initial_logs_.empty() ? &unsent_ongoing_logs_ - : &unsent_initial_logs_; - LogType source_type = (source_list == &unsent_ongoing_logs_) ? - MetricsLogBase::ONGOING_LOG : MetricsLogBase::INITIAL_STABILITY_LOG; - // CHECK, rather than DCHECK, because swap()ing with an empty list causes - // hard-to-identify crashes much later. - CHECK(!source_list->empty()); - DCHECK(staged_log_.IsEmpty()); - DCHECK_EQ(MetricsLogBase::NO_LOG, staged_log_type_); - staged_log_.Swap(&source_list->back()); - staged_log_type_ = source_type; - source_list->pop_back(); - - // If the staged log was the last provisional store, clear that. - if (last_provisional_store_index_ != -1) { - if (source_type == last_provisional_store_type_ && - static_cast<unsigned int>(last_provisional_store_index_) == - source_list->size()) { - last_provisional_store_index_ = -1; - } - } -} - -bool MetricsLogManager::has_staged_log() const { - return !staged_log_.IsEmpty(); + DCHECK(!has_staged_log()); + if (!initial_log_queue_.empty()) + initial_log_queue_.StageLog(); + else + ongoing_log_queue_.StageLog(); } void MetricsLogManager::DiscardStagedLog() { - staged_log_.Clear(); - staged_log_type_ = MetricsLogBase::NO_LOG; + DCHECK(has_staged_log()); + if (initial_log_queue_.has_staged_log()) + initial_log_queue_.DiscardStagedLog(); + else + ongoing_log_queue_.DiscardStagedLog(); + DCHECK(!has_staged_log()); } void MetricsLogManager::DiscardCurrentLog() { @@ -114,95 +101,49 @@ void MetricsLogManager::ResumePausedLog() { current_log_.reset(paused_log_.release()); } -void MetricsLogManager::StoreStagedLogAsUnsent(StoreType store_type) { - DCHECK(has_staged_log()); - - // If compressing the log failed, there's nothing to store. - if (staged_log_.IsEmpty()) - return; +void MetricsLogManager::StoreLog(std::string* log, LogType log_type) { + DCHECK_NE(MetricsLogBase::NO_LOG, log_type); + metrics::PersistedLogs* destination_queue = + (log_type == MetricsLogBase::INITIAL_STABILITY_LOG) ? + &initial_log_queue_ : &ongoing_log_queue_; - StoreLog(&staged_log_, staged_log_type_, store_type); - DiscardStagedLog(); + destination_queue->StoreLog(log); } -void MetricsLogManager::StoreLog(SerializedLog* log, - LogType log_type, - StoreType store_type) { - DCHECK_NE(MetricsLogBase::NO_LOG, log_type); - std::vector<SerializedLog>* destination_list = - (log_type == MetricsLogBase::INITIAL_STABILITY_LOG) ? - &unsent_initial_logs_ : &unsent_ongoing_logs_; - destination_list->push_back(SerializedLog()); - destination_list->back().Swap(log); - - if (store_type == PROVISIONAL_STORE) { - last_provisional_store_index_ = destination_list->size() - 1; - last_provisional_store_type_ = log_type; - } +void MetricsLogManager::StoreStagedLogAsUnsent( + metrics::PersistedLogs::StoreType store_type) { + DCHECK(has_staged_log()); + if (initial_log_queue_.has_staged_log()) + initial_log_queue_.StoreStagedLogAsUnsent(store_type); + else + ongoing_log_queue_.StoreStagedLogAsUnsent(store_type); } void MetricsLogManager::DiscardLastProvisionalStore() { - if (last_provisional_store_index_ == -1) - return; - std::vector<SerializedLog>* source_list = - (last_provisional_store_type_ == MetricsLogBase::ONGOING_LOG) - ? &unsent_ongoing_logs_ - : &unsent_initial_logs_; - DCHECK_LT(static_cast<unsigned int>(last_provisional_store_index_), - source_list->size()); - source_list->erase(source_list->begin() + last_provisional_store_index_); - last_provisional_store_index_ = -1; + // We have at most one provisional store, (since at most one log is being + // uploaded at a time), so at least one of these will be a no-op. + initial_log_queue_.DiscardLastProvisionalStore(); + ongoing_log_queue_.DiscardLastProvisionalStore(); } void MetricsLogManager::PersistUnsentLogs() { - DCHECK(log_serializer_.get()); - if (!log_serializer_.get()) - return; DCHECK(unsent_logs_loaded_); if (!unsent_logs_loaded_) return; base::ElapsedTimer timer; - // Remove any ongoing logs that are over the serialization size limit. - if (max_ongoing_log_store_size_) { - for (std::vector<SerializedLog>::iterator it = unsent_ongoing_logs_.begin(); - it != unsent_ongoing_logs_.end();) { - size_t log_size = it->log_text().length(); - if (log_size > max_ongoing_log_store_size_) { - UMA_HISTOGRAM_COUNTS("UMA.Large Accumulated Log Not Persisted", - static_cast<int>(log_size)); - it = unsent_ongoing_logs_.erase(it); - } else { - ++it; - } - } - } - log_serializer_->SerializeLogs(unsent_initial_logs_, - MetricsLogBase::INITIAL_STABILITY_LOG); - log_serializer_->SerializeLogs(unsent_ongoing_logs_, - MetricsLogBase::ONGOING_LOG); + initial_log_queue_.SerializeLogs(); + ongoing_log_queue_.SerializeLogs(); UMA_HISTOGRAM_TIMES("UMA.StoreLogsTime", timer.Elapsed()); } void MetricsLogManager::LoadPersistedUnsentLogs() { - DCHECK(log_serializer_.get()); - if (!log_serializer_.get()) - return; - base::ElapsedTimer timer; - log_serializer_->DeserializeLogs(MetricsLogBase::INITIAL_STABILITY_LOG, - &unsent_initial_logs_); - log_serializer_->DeserializeLogs(MetricsLogBase::ONGOING_LOG, - &unsent_ongoing_logs_); + initial_log_queue_.DeserializeLogs(); + ongoing_log_queue_.DeserializeLogs(); UMA_HISTOGRAM_TIMES("UMA.LoadLogsTime", timer.Elapsed()); unsent_logs_loaded_ = true; } -void MetricsLogManager::CompressCurrentLog(SerializedLog* compressed_log) { - std::string log_text; - current_log_->GetEncodedLog(&log_text); - compressed_log->SwapLogText(&log_text); -} - -} // namespace metrics +} // namespace metrics diff --git a/components/metrics/metrics_log_manager.h b/components/metrics/metrics_log_manager.h index 006d4ed..46463eb 100644 --- a/components/metrics/metrics_log_manager.h +++ b/components/metrics/metrics_log_manager.h @@ -11,6 +11,7 @@ #include "base/basictypes.h" #include "base/memory/scoped_ptr.h" #include "components/metrics/metrics_log_base.h" +#include "components/metrics/persisted_logs.h" namespace metrics { @@ -21,46 +22,12 @@ class MetricsLogManager { public: typedef MetricsLogBase::LogType LogType; - MetricsLogManager(); + // The metrics log manager will persist it's unsent logs by storing them in + // |local_state|, and will not persist ongoing logs over + // |max_ongoing_log_size|. + MetricsLogManager(PrefService* local_state, size_t max_ongoing_log_size); ~MetricsLogManager(); - class SerializedLog { - public: - SerializedLog(); - ~SerializedLog(); - - const std::string& log_text() const { return log_text_; } - const std::string& log_hash() const { return log_hash_; } - - // Returns true if the log is empty. - bool IsEmpty() const; - - // Swaps log text with |log_text| and updates the hash. This is more - // performant than a regular setter as it avoids doing a large string copy. - void SwapLogText(std::string* log_text); - - // Clears the log. - void Clear(); - - // Swaps log contents with |other|. - void Swap(SerializedLog* other); - - private: - // Non-human readable log text (serialized proto). - std::string log_text_; - - // Non-human readable SHA1 of |log_text| or empty if |log_text| is empty. - std::string log_hash_; - - // Intentionally omits DISALLOW_COPY_AND_ASSIGN() so that it can be used - // in std::vector<SerializedLog>. - }; - - enum StoreType { - NORMAL_STORE, // A standard store operation. - PROVISIONAL_STORE, // A store operation that can be easily reverted later. - }; - // Takes ownership of |log| and makes it the current_log. This should only be // called if there is not a current log. void BeginLoggingWithLog(MetricsLogBase* log); @@ -74,7 +41,7 @@ class MetricsLogManager { // Returns true if there are any logs waiting to be uploaded. bool has_unsent_logs() const { - return !unsent_initial_logs_.empty() || !unsent_ongoing_logs_.empty(); + return initial_log_queue_.size() || ongoing_log_queue_.size(); } // Populates staged_log_text() with the next stored log to send. @@ -82,15 +49,25 @@ class MetricsLogManager { void StageNextLogForUpload(); // Returns true if there is a log that needs to be, or is being, uploaded. - bool has_staged_log() const; + bool has_staged_log() const { + return initial_log_queue_.has_staged_log() || + ongoing_log_queue_.has_staged_log(); + } - // The text of the staged log, as a serialized protobuf. Empty if there is no - // staged log, or if compression of the staged log failed. - const std::string& staged_log_text() const { return staged_log_.log_text(); } + // The text of the staged log, as a serialized protobuf. + // Will trigger a DCHECK if there is no staged log. + const std::string& staged_log() const { + return initial_log_queue_.has_staged_log() ? + initial_log_queue_.staged_log() : ongoing_log_queue_.staged_log(); + } - // The SHA1 hash (non-human readable) of the staged log or empty if there is - // no staged log. - const std::string& staged_log_hash() const { return staged_log_.log_hash(); } + // The SHA1 hash of the staged log. + // Will trigger a DCHECK if there is no staged log. + const std::string& staged_log_hash() const { + return initial_log_queue_.has_staged_log() ? + initial_log_queue_.staged_log_hash() : + ongoing_log_queue_.staged_log_hash(); + } // Discards the staged log. void DiscardStagedLog(); @@ -116,60 +93,24 @@ class MetricsLogManager { // This is intended to be used when logs are being saved while an upload is in // progress, in case the upload later succeeds. // This can only be called if has_staged_log() is true. - void StoreStagedLogAsUnsent(StoreType store_type); + void StoreStagedLogAsUnsent(metrics::PersistedLogs::StoreType store_type); // Discards the last log stored with StoreStagedLogAsUnsent with |store_type| // set to PROVISIONAL_STORE, as long as it hasn't already been re-staged. If // the log is no longer present, this is a no-op. void DiscardLastProvisionalStore(); - // Sets the threshold for how large an onging log can be and still be written - // to persistant storage. Ongoing logs larger than this will be discarded - // before persisting. 0 is interpreted as no limit. - void set_max_ongoing_log_store_size(size_t max_size) { - max_ongoing_log_store_size_ = max_size; - } - - // Interface for a utility class to serialize and deserialize logs for - // persistent storage. - class LogSerializer { - public: - virtual ~LogSerializer() {} - - // Serializes |logs| to persistent storage, replacing any previously - // serialized logs of the same type. - virtual void SerializeLogs(const std::vector<SerializedLog>& logs, - LogType log_type) = 0; - - // Populates |logs| with logs of type |log_type| deserialized from - // persistent storage. - virtual void DeserializeLogs(LogType log_type, - std::vector<SerializedLog>* logs) = 0; - }; - - // Sets the serializer to use for persisting and loading logs; takes ownership - // of |serializer|. - void set_log_serializer(LogSerializer* serializer) { - log_serializer_.reset(serializer); - } - - // Saves any unsent logs to persistent storage using the current log - // serializer. Can only be called after set_log_serializer. + // Saves any unsent logs to persistent storage. void PersistUnsentLogs(); - // Loads any unsent logs from persistent storage using the current log - // serializer. Can only be called after set_log_serializer. + // Loads any unsent logs from persistent storage. void LoadPersistedUnsentLogs(); private: - // Saves |log| as the given type (or discards it in accordance with - // |max_ongoing_log_store_size_|). + // Saves |log| as the given type. // NOTE: This clears the contents of |log| (to avoid an expensive copy), // so the log should be discarded after this call. - void StoreLog(SerializedLog* log, LogType log_type, StoreType store_type); - - // Compresses |current_log_| into |compressed_log|. - void CompressCurrentLog(SerializedLog* compressed_log); + void StoreLog(std::string* log, LogType log_type); // Tracks whether unsent logs (if any) have been loaded from the serializer. bool unsent_logs_loaded_; @@ -180,30 +121,9 @@ class MetricsLogManager { // A paused, previously-current log. scoped_ptr<MetricsLogBase> paused_log_; - // Helper class to handle serialization/deserialization of logs for persistent - // storage. May be NULL. - scoped_ptr<LogSerializer> log_serializer_; - - // The current staged log, ready for upload to the server. - SerializedLog staged_log_; - LogType staged_log_type_; - - // Logs from a previous session that have not yet been sent. - // Note that the vector has the oldest logs listed first (early in the - // vector), and we'll discard old logs if we have gathered too many logs. - std::vector<SerializedLog> unsent_initial_logs_; - std::vector<SerializedLog> unsent_ongoing_logs_; - - size_t max_ongoing_log_store_size_; - - // The index and type of the last provisional store. If nothing has been - // provisionally stored, or the last provisional store has already been - // re-staged, the index will be -1; - // This is necessary because during an upload there are two logs (staged - // and current) and a client might store them in either order, so it's - // not necessarily the case that the provisional store is the last store. - int last_provisional_store_index_; - LogType last_provisional_store_type_; + // Logs that have not yet been sent. + metrics::PersistedLogs initial_log_queue_; + metrics::PersistedLogs ongoing_log_queue_; DISALLOW_COPY_AND_ASSIGN(MetricsLogManager); }; diff --git a/components/metrics/metrics_log_manager_unittest.cc b/components/metrics/metrics_log_manager_unittest.cc index ec0ca38f..c5c2b1a 100644 --- a/components/metrics/metrics_log_manager_unittest.cc +++ b/components/metrics/metrics_log_manager_unittest.cc @@ -8,8 +8,10 @@ #include <utility> #include <vector> -#include "base/sha1.h" +#include "base/prefs/pref_registry_simple.h" +#include "base/prefs/testing_pref_service.h" #include "components/metrics/metrics_log_base.h" +#include "components/metrics/metrics_pref_names.h" #include "testing/gtest/include/gtest/gtest.h" namespace metrics { @@ -17,34 +19,28 @@ namespace metrics { namespace { // Dummy serializer that just stores logs in memory. -class DummyLogSerializer : public MetricsLogManager::LogSerializer { +class TestLogPrefService : public TestingPrefServiceSimple { public: - virtual void SerializeLogs( - const std::vector<MetricsLogManager::SerializedLog>& logs, - MetricsLogManager::LogType log_type) OVERRIDE { - persisted_logs_[log_type] = logs; + TestLogPrefService() { + registry()->RegisterListPref(prefs::kMetricsInitialLogs); + registry()->RegisterListPref(prefs::kMetricsOngoingLogs); } - - virtual void DeserializeLogs( - MetricsLogManager::LogType log_type, - std::vector<MetricsLogManager::SerializedLog>* logs) OVERRIDE { - ASSERT_NE(static_cast<void*>(NULL), logs); - *logs = persisted_logs_[log_type]; - } - // Returns the number of logs of the given type. size_t TypeCount(MetricsLogManager::LogType log_type) { - return persisted_logs_[log_type].size(); + int list_length = 0; + if (log_type == MetricsLogBase::INITIAL_STABILITY_LOG) + list_length = GetList(prefs::kMetricsInitialLogs)->GetSize(); + else + list_length = GetList(prefs::kMetricsOngoingLogs)->GetSize(); + return list_length ? list_length - 2 : 0; } - - // In-memory "persitent storage". - std::vector<MetricsLogManager::SerializedLog> persisted_logs_[2]; }; } // namespace TEST(MetricsLogManagerTest, StandardFlow) { - MetricsLogManager log_manager; + TestLogPrefService pref_service; + MetricsLogManager log_manager(&pref_service, 0); // Make sure a new manager has a clean slate. EXPECT_EQ(NULL, log_manager.current_log()); @@ -70,19 +66,19 @@ TEST(MetricsLogManagerTest, StandardFlow) { log_manager.StageNextLogForUpload(); EXPECT_TRUE(log_manager.has_staged_log()); - EXPECT_FALSE(log_manager.staged_log_text().empty()); + EXPECT_FALSE(log_manager.staged_log().empty()); log_manager.DiscardStagedLog(); EXPECT_EQ(second_log, log_manager.current_log()); EXPECT_FALSE(log_manager.has_staged_log()); EXPECT_FALSE(log_manager.has_unsent_logs()); - EXPECT_TRUE(log_manager.staged_log_text().empty()); EXPECT_FALSE(log_manager.has_unsent_logs()); } TEST(MetricsLogManagerTest, AbandonedLog) { - MetricsLogManager log_manager; + TestLogPrefService pref_service; + MetricsLogManager log_manager(&pref_service, 0); MetricsLogBase* dummy_log = new MetricsLogBase("id", 0, MetricsLogBase::INITIAL_STABILITY_LOG, "v"); @@ -95,7 +91,8 @@ TEST(MetricsLogManagerTest, AbandonedLog) { } TEST(MetricsLogManagerTest, InterjectedLog) { - MetricsLogManager log_manager; + TestLogPrefService pref_service; + MetricsLogManager log_manager(&pref_service, 0); MetricsLogBase* ongoing_log = new MetricsLogBase("id", 0, MetricsLogBase::ONGOING_LOG, "v"); @@ -123,9 +120,8 @@ TEST(MetricsLogManagerTest, InterjectedLog) { } TEST(MetricsLogManagerTest, InterjectedLogPreservesType) { - MetricsLogManager log_manager; - DummyLogSerializer* serializer = new DummyLogSerializer; - log_manager.set_log_serializer(serializer); + TestLogPrefService pref_service; + MetricsLogManager log_manager(&pref_service, 0); log_manager.LoadPersistedUnsentLogs(); MetricsLogBase* ongoing_log = @@ -145,27 +141,27 @@ TEST(MetricsLogManagerTest, InterjectedLogPreservesType) { // has the right type. log_manager.FinishCurrentLog(); log_manager.PersistUnsentLogs(); - EXPECT_EQ(0U, serializer->TypeCount(MetricsLogBase::INITIAL_STABILITY_LOG)); - EXPECT_EQ(1U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); + EXPECT_EQ(0U, pref_service.TypeCount(MetricsLogBase::INITIAL_STABILITY_LOG)); + EXPECT_EQ(1U, pref_service.TypeCount(MetricsLogBase::ONGOING_LOG)); } TEST(MetricsLogManagerTest, StoreAndLoad) { - std::vector<MetricsLogManager::SerializedLog> initial_logs; - std::vector<MetricsLogManager::SerializedLog> ongoing_logs; - + TestLogPrefService pref_service; // Set up some in-progress logging in a scoped log manager simulating the // leadup to quitting, then persist as would be done on quit. { - MetricsLogManager log_manager; - DummyLogSerializer* serializer = new DummyLogSerializer; - log_manager.set_log_serializer(serializer); + MetricsLogManager log_manager(&pref_service, 0); log_manager.LoadPersistedUnsentLogs(); // Simulate a log having already been unsent from a previous session. - MetricsLogManager::SerializedLog log; - std::string text = "proto"; - log.SwapLogText(&text); - serializer->persisted_logs_[MetricsLogBase::ONGOING_LOG].push_back(log); + { + std::string log("proto"); + metrics::PersistedLogs ongoing_logs( + &pref_service, prefs::kMetricsOngoingLogs, 1, 1, 0); + ongoing_logs.StoreLog(&log); + ongoing_logs.SerializeLogs(); + } + EXPECT_EQ(1U, pref_service.TypeCount(MetricsLogBase::ONGOING_LOG)); EXPECT_FALSE(log_manager.has_unsent_logs()); log_manager.LoadPersistedUnsentLogs(); EXPECT_TRUE(log_manager.has_unsent_logs()); @@ -178,35 +174,24 @@ TEST(MetricsLogManagerTest, StoreAndLoad) { log_manager.FinishCurrentLog(); log_manager.BeginLoggingWithLog(log2); log_manager.StageNextLogForUpload(); - log_manager.StoreStagedLogAsUnsent(MetricsLogManager::NORMAL_STORE); + log_manager.StoreStagedLogAsUnsent(metrics::PersistedLogs::NORMAL_STORE); log_manager.FinishCurrentLog(); // Nothing should be written out until PersistUnsentLogs is called. - EXPECT_EQ(0U, serializer->TypeCount(MetricsLogBase::INITIAL_STABILITY_LOG)); - EXPECT_EQ(1U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); + EXPECT_EQ(0U, pref_service.TypeCount( + MetricsLogBase::INITIAL_STABILITY_LOG)); + EXPECT_EQ(1U, pref_service.TypeCount(MetricsLogBase::ONGOING_LOG)); log_manager.PersistUnsentLogs(); - EXPECT_EQ(1U, serializer->TypeCount(MetricsLogBase::INITIAL_STABILITY_LOG)); - EXPECT_EQ(2U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); - - // Save the logs to transfer over to a new serializer (since log_manager - // owns |serializer|, so it's about to go away. - initial_logs = - serializer->persisted_logs_[MetricsLogBase::INITIAL_STABILITY_LOG]; - ongoing_logs = serializer->persisted_logs_[MetricsLogBase::ONGOING_LOG]; + EXPECT_EQ(1U, pref_service.TypeCount( + MetricsLogBase::INITIAL_STABILITY_LOG)); + EXPECT_EQ(2U, pref_service.TypeCount(MetricsLogBase::ONGOING_LOG)); } // Now simulate the relaunch, ensure that the log manager restores // everything correctly, and verify that once the are handled they are not // re-persisted. { - MetricsLogManager log_manager; - - DummyLogSerializer* serializer = new DummyLogSerializer; - serializer->persisted_logs_[MetricsLogBase::INITIAL_STABILITY_LOG] = - initial_logs; - serializer->persisted_logs_[MetricsLogBase::ONGOING_LOG] = ongoing_logs; - - log_manager.set_log_serializer(serializer); + MetricsLogManager log_manager(&pref_service, 0); log_manager.LoadPersistedUnsentLogs(); EXPECT_TRUE(log_manager.has_unsent_logs()); @@ -215,8 +200,9 @@ TEST(MetricsLogManagerTest, StoreAndLoad) { // The initial log should be sent first; update the persisted storage to // verify. log_manager.PersistUnsentLogs(); - EXPECT_EQ(0U, serializer->TypeCount(MetricsLogBase::INITIAL_STABILITY_LOG)); - EXPECT_EQ(2U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); + EXPECT_EQ(0U, pref_service.TypeCount( + MetricsLogBase::INITIAL_STABILITY_LOG)); + EXPECT_EQ(2U, pref_service.TypeCount(MetricsLogBase::ONGOING_LOG)); // Handle the first ongoing log. log_manager.StageNextLogForUpload(); @@ -230,20 +216,20 @@ TEST(MetricsLogManagerTest, StoreAndLoad) { // Nothing should have changed "on disk" since PersistUnsentLogs hasn't been // called again. - EXPECT_EQ(2U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); + EXPECT_EQ(2U, pref_service.TypeCount(MetricsLogBase::ONGOING_LOG)); // Persist, and make sure nothing is left. log_manager.PersistUnsentLogs(); - EXPECT_EQ(0U, serializer->TypeCount(MetricsLogBase::INITIAL_STABILITY_LOG)); - EXPECT_EQ(0U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); + EXPECT_EQ(0U, pref_service.TypeCount( + MetricsLogBase::INITIAL_STABILITY_LOG)); + EXPECT_EQ(0U, pref_service.TypeCount(MetricsLogBase::ONGOING_LOG)); } } TEST(MetricsLogManagerTest, StoreStagedLogTypes) { // Ensure that types are preserved when storing staged logs. { - MetricsLogManager log_manager; - DummyLogSerializer* serializer = new DummyLogSerializer; - log_manager.set_log_serializer(serializer); + TestLogPrefService pref_service; + MetricsLogManager log_manager(&pref_service, 0); log_manager.LoadPersistedUnsentLogs(); MetricsLogBase* log = @@ -251,17 +237,17 @@ TEST(MetricsLogManagerTest, StoreStagedLogTypes) { log_manager.BeginLoggingWithLog(log); log_manager.FinishCurrentLog(); log_manager.StageNextLogForUpload(); - log_manager.StoreStagedLogAsUnsent(MetricsLogManager::NORMAL_STORE); + log_manager.StoreStagedLogAsUnsent(metrics::PersistedLogs::NORMAL_STORE); log_manager.PersistUnsentLogs(); - EXPECT_EQ(0U, serializer->TypeCount(MetricsLogBase::INITIAL_STABILITY_LOG)); - EXPECT_EQ(1U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); + EXPECT_EQ(0U, pref_service.TypeCount( + MetricsLogBase::INITIAL_STABILITY_LOG)); + EXPECT_EQ(1U, pref_service.TypeCount(MetricsLogBase::ONGOING_LOG)); } { - MetricsLogManager log_manager; - DummyLogSerializer* serializer = new DummyLogSerializer; - log_manager.set_log_serializer(serializer); + TestLogPrefService pref_service; + MetricsLogManager log_manager(&pref_service, 0); log_manager.LoadPersistedUnsentLogs(); MetricsLogBase* log = @@ -269,22 +255,20 @@ TEST(MetricsLogManagerTest, StoreStagedLogTypes) { log_manager.BeginLoggingWithLog(log); log_manager.FinishCurrentLog(); log_manager.StageNextLogForUpload(); - log_manager.StoreStagedLogAsUnsent(MetricsLogManager::NORMAL_STORE); + log_manager.StoreStagedLogAsUnsent(metrics::PersistedLogs::NORMAL_STORE); log_manager.PersistUnsentLogs(); - EXPECT_EQ(1U, serializer->TypeCount(MetricsLogBase::INITIAL_STABILITY_LOG)); - EXPECT_EQ(0U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); + EXPECT_EQ(1U, pref_service.TypeCount( + MetricsLogBase::INITIAL_STABILITY_LOG)); + EXPECT_EQ(0U, pref_service.TypeCount(MetricsLogBase::ONGOING_LOG)); } } TEST(MetricsLogManagerTest, LargeLogDiscarding) { - MetricsLogManager log_manager; - DummyLogSerializer* serializer = new DummyLogSerializer; - log_manager.set_log_serializer(serializer); - log_manager.LoadPersistedUnsentLogs(); - + TestLogPrefService pref_service; // Set the size threshold very low, to verify that it's honored. - log_manager.set_max_ongoing_log_store_size(1); + MetricsLogManager log_manager(&pref_service, 1); + log_manager.LoadPersistedUnsentLogs(); MetricsLogBase* log1 = new MetricsLogBase("id", 0, MetricsLogBase::INITIAL_STABILITY_LOG, "v"); @@ -297,16 +281,15 @@ TEST(MetricsLogManagerTest, LargeLogDiscarding) { // Only the ongoing log should be written out, due to the threshold. log_manager.PersistUnsentLogs(); - EXPECT_EQ(1U, serializer->TypeCount(MetricsLogBase::INITIAL_STABILITY_LOG)); - EXPECT_EQ(0U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); + EXPECT_EQ(1U, pref_service.TypeCount(MetricsLogBase::INITIAL_STABILITY_LOG)); + EXPECT_EQ(0U, pref_service.TypeCount(MetricsLogBase::ONGOING_LOG)); } TEST(MetricsLogManagerTest, ProvisionalStoreStandardFlow) { // Ensure that provisional store works, and discards the correct log. { - MetricsLogManager log_manager; - DummyLogSerializer* serializer = new DummyLogSerializer; - log_manager.set_log_serializer(serializer); + TestLogPrefService pref_service; + MetricsLogManager log_manager(&pref_service, 0); log_manager.LoadPersistedUnsentLogs(); MetricsLogBase* log1 = @@ -317,13 +300,15 @@ TEST(MetricsLogManagerTest, ProvisionalStoreStandardFlow) { log_manager.FinishCurrentLog(); log_manager.BeginLoggingWithLog(log2); log_manager.StageNextLogForUpload(); - log_manager.StoreStagedLogAsUnsent(MetricsLogManager::PROVISIONAL_STORE); + log_manager.StoreStagedLogAsUnsent( + metrics::PersistedLogs::PROVISIONAL_STORE); log_manager.FinishCurrentLog(); log_manager.DiscardLastProvisionalStore(); log_manager.PersistUnsentLogs(); - EXPECT_EQ(0U, serializer->TypeCount(MetricsLogBase::INITIAL_STABILITY_LOG)); - EXPECT_EQ(1U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); + EXPECT_EQ(0U, pref_service.TypeCount( + MetricsLogBase::INITIAL_STABILITY_LOG)); + EXPECT_EQ(1U, pref_service.TypeCount(MetricsLogBase::ONGOING_LOG)); } } @@ -331,9 +316,8 @@ TEST(MetricsLogManagerTest, ProvisionalStoreNoop) { // Ensure that trying to drop a sent log is a no-op, even if another log has // since been staged. { - MetricsLogManager log_manager; - DummyLogSerializer* serializer = new DummyLogSerializer; - log_manager.set_log_serializer(serializer); + TestLogPrefService pref_service; + MetricsLogManager log_manager(&pref_service, 0); log_manager.LoadPersistedUnsentLogs(); MetricsLogBase* log1 = @@ -343,24 +327,24 @@ TEST(MetricsLogManagerTest, ProvisionalStoreNoop) { log_manager.BeginLoggingWithLog(log1); log_manager.FinishCurrentLog(); log_manager.StageNextLogForUpload(); - log_manager.StoreStagedLogAsUnsent(MetricsLogManager::PROVISIONAL_STORE); + log_manager.StoreStagedLogAsUnsent( + metrics::PersistedLogs::PROVISIONAL_STORE); log_manager.StageNextLogForUpload(); log_manager.DiscardStagedLog(); log_manager.BeginLoggingWithLog(log2); log_manager.FinishCurrentLog(); log_manager.StageNextLogForUpload(); - log_manager.StoreStagedLogAsUnsent(MetricsLogManager::NORMAL_STORE); + log_manager.StoreStagedLogAsUnsent(metrics::PersistedLogs::NORMAL_STORE); log_manager.DiscardLastProvisionalStore(); log_manager.PersistUnsentLogs(); - EXPECT_EQ(1U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); + EXPECT_EQ(1U, pref_service.TypeCount(MetricsLogBase::ONGOING_LOG)); } // Ensure that trying to drop more than once is a no-op { - MetricsLogManager log_manager; - DummyLogSerializer* serializer = new DummyLogSerializer; - log_manager.set_log_serializer(serializer); + TestLogPrefService pref_service; + MetricsLogManager log_manager(&pref_service, 0); log_manager.LoadPersistedUnsentLogs(); MetricsLogBase* log1 = @@ -370,58 +354,18 @@ TEST(MetricsLogManagerTest, ProvisionalStoreNoop) { log_manager.BeginLoggingWithLog(log1); log_manager.FinishCurrentLog(); log_manager.StageNextLogForUpload(); - log_manager.StoreStagedLogAsUnsent(MetricsLogManager::NORMAL_STORE); + log_manager.StoreStagedLogAsUnsent(metrics::PersistedLogs::NORMAL_STORE); log_manager.BeginLoggingWithLog(log2); log_manager.FinishCurrentLog(); log_manager.StageNextLogForUpload(); - log_manager.StoreStagedLogAsUnsent(MetricsLogManager::PROVISIONAL_STORE); + log_manager.StoreStagedLogAsUnsent( + metrics::PersistedLogs::PROVISIONAL_STORE); log_manager.DiscardLastProvisionalStore(); log_manager.DiscardLastProvisionalStore(); log_manager.PersistUnsentLogs(); - EXPECT_EQ(1U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); + EXPECT_EQ(1U, pref_service.TypeCount(MetricsLogBase::ONGOING_LOG)); } } -TEST(MetricsLogManagerTest, SerializedLog) { - const char kFooText[] = "foo"; - const std::string foo_hash = base::SHA1HashString(kFooText); - const char kBarText[] = "bar"; - const std::string bar_hash = base::SHA1HashString(kBarText); - - MetricsLogManager::SerializedLog log; - EXPECT_TRUE(log.log_text().empty()); - EXPECT_TRUE(log.log_hash().empty()); - - std::string foo = kFooText; - log.SwapLogText(&foo); - EXPECT_TRUE(foo.empty()); - EXPECT_FALSE(log.IsEmpty()); - EXPECT_EQ(kFooText, log.log_text()); - EXPECT_EQ(foo_hash, log.log_hash()); - - std::string bar = kBarText; - log.SwapLogText(&bar); - EXPECT_EQ(kFooText, bar); - EXPECT_FALSE(log.IsEmpty()); - EXPECT_EQ(kBarText, log.log_text()); - EXPECT_EQ(bar_hash, log.log_hash()); - - log.Clear(); - EXPECT_TRUE(log.IsEmpty()); - EXPECT_TRUE(log.log_text().empty()); - EXPECT_TRUE(log.log_hash().empty()); - - MetricsLogManager::SerializedLog log2; - foo = kFooText; - log2.SwapLogText(&foo); - log.Swap(&log2); - EXPECT_FALSE(log.IsEmpty()); - EXPECT_EQ(kFooText, log.log_text()); - EXPECT_EQ(foo_hash, log.log_hash()); - EXPECT_TRUE(log2.IsEmpty()); - EXPECT_TRUE(log2.log_text().empty()); - EXPECT_TRUE(log2.log_hash().empty()); -} - } // namespace metrics diff --git a/components/metrics/metrics_pref_names.cc b/components/metrics/metrics_pref_names.cc new file mode 100644 index 0000000..92a1890 --- /dev/null +++ b/components/metrics/metrics_pref_names.cc @@ -0,0 +1,24 @@ +// Copyright 2014 The Chromium 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 "components/metrics/metrics_pref_names.h" + +namespace metrics { +namespace prefs { + +// Array of strings that are each UMA logs that were supposed to be sent in the +// first minute of a browser session. These logs include things like crash count +// info, etc. +const char kMetricsInitialLogs[] = + "user_experience_metrics.initial_logs_as_protobufs"; + +// Array of strings that are each UMA logs that were not sent because the +// browser terminated before these accumulated metrics could be sent. These +// logs typically include histograms and memory reports, as well as ongoing +// user activities. +const char kMetricsOngoingLogs[] = + "user_experience_metrics.ongoing_logs_as_protobufs"; + +} // namespace prefs +} // namespace metrics diff --git a/components/metrics/metrics_pref_names.h b/components/metrics/metrics_pref_names.h new file mode 100644 index 0000000..750d58d --- /dev/null +++ b/components/metrics/metrics_pref_names.h @@ -0,0 +1,19 @@ +// Copyright 2014 The Chromium 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 COMPONENTS_METRICS_METRICS_PREF_NAMES_H_ +#define COMPONENTS_METRICS_METRICS_PREF_NAMES_H_ + +namespace metrics { +namespace prefs { + +// Alphabetical list of preference names specific to the metrics +// component. Keep alphabetized, and document each in the .cc file. +extern const char kMetricsInitialLogs[]; +extern const char kMetricsOngoingLogs[]; + +} // namespace prefs +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_PREF_NAMES_H_ diff --git a/components/metrics/persisted_logs.cc b/components/metrics/persisted_logs.cc new file mode 100644 index 0000000..006f095 --- /dev/null +++ b/components/metrics/persisted_logs.cc @@ -0,0 +1,246 @@ +// Copyright 2014 The Chromium 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 "components/metrics/persisted_logs.h" + +#include <string> + +#include "base/base64.h" +#include "base/md5.h" +#include "base/metrics/histogram.h" +#include "base/prefs/pref_service.h" +#include "base/prefs/scoped_user_pref_update.h" +#include "base/sha1.h" +#include "base/timer/elapsed_timer.h" + +namespace metrics { + +namespace { + +// We append (2) more elements to persisted lists: the size of the list and a +// checksum of the elements. +const size_t kChecksumEntryCount = 2; + +PersistedLogs::LogReadStatus MakeRecallStatusHistogram( + PersistedLogs::LogReadStatus status) { + UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogRecallProtobufs", + status, PersistedLogs::END_RECALL_STATUS); + return status; +} + +} // namespace + +void PersistedLogs::LogHashPair::SwapLog(std::string* input) { + log.swap(*input); + if (!log.empty()) + hash = base::SHA1HashString(log); + else + hash.clear(); +} + +void PersistedLogs::LogHashPair::Swap(PersistedLogs::LogHashPair* input) { + log.swap(input->log); + hash.swap(input->hash); +} + +PersistedLogs::PersistedLogs(PrefService* local_state, + const char* pref_name, + size_t min_log_count, + size_t min_log_bytes, + size_t max_log_size) + : local_state_(local_state), + pref_name_(pref_name), + min_log_count_(min_log_count), + min_log_bytes_(min_log_bytes), + max_log_size_(max_log_size), + last_provisional_store_index_(-1) { + DCHECK(local_state_); + // One of the limit arguments must be non-zero. + DCHECK(min_log_count_ > 0 || min_log_bytes_ > 0); +} + +PersistedLogs::~PersistedLogs() {} + +void PersistedLogs::SerializeLogs() { + // Remove any logs that are over the serialization size limit. + if (max_log_size_) { + for (std::vector<LogHashPair>::iterator it = list_.begin(); + it != list_.end();) { + size_t log_size = it->log.length(); + if (log_size > max_log_size_) { + UMA_HISTOGRAM_COUNTS("UMA.Large Accumulated Log Not Persisted", + static_cast<int>(log_size)); + it = list_.erase(it); + } else { + ++it; + } + } + } + ListPrefUpdate update(local_state_, pref_name_); + WriteLogsToPrefList(update.Get()); +} + +PersistedLogs::LogReadStatus PersistedLogs::DeserializeLogs() { + const base::ListValue* unsent_logs = local_state_->GetList(pref_name_); + return ReadLogsFromPrefList(*unsent_logs); +} + +void PersistedLogs::StoreLog(std::string* input) { + list_.push_back(LogHashPair()); + list_.back().SwapLog(input); +} + +void PersistedLogs::StageLog() { + // CHECK, rather than DCHECK, because swap()ing with an empty list causes + // hard-to-identify crashes much later. + CHECK(!list_.empty()); + DCHECK(!has_staged_log()); + staged_log_.Swap(&list_.back()); + list_.pop_back(); + + // If the staged log was the last provisional store, clear that. + if (static_cast<size_t>(last_provisional_store_index_) == list_.size()) + last_provisional_store_index_ = -1; + DCHECK(has_staged_log()); +} + +void PersistedLogs::DiscardStagedLog() { + DCHECK(has_staged_log()); + staged_log_.log.clear(); +} + +void PersistedLogs::StoreStagedLogAsUnsent(StoreType store_type) { + list_.push_back(LogHashPair()); + list_.back().Swap(&staged_log_); + if (store_type == PROVISIONAL_STORE) + last_provisional_store_index_ = list_.size() - 1; +} + +void PersistedLogs::DiscardLastProvisionalStore() { + if (last_provisional_store_index_ == -1) + return; + DCHECK_LT(static_cast<size_t>(last_provisional_store_index_), list_.size()); + list_.erase(list_.begin() + last_provisional_store_index_); + last_provisional_store_index_ = -1; +} + +void PersistedLogs::WriteLogsToPrefList(base::ListValue* list_value) { + list_value->Clear(); + + // Leave the list completely empty if there are no storable values. + if (list_.empty()) + return; + + size_t start = 0; + // If there are too many logs, keep the most recent logs up to the length + // limit, and at least to the minimum number of bytes. + if (list_.size() > min_log_count_) { + start = list_.size(); + size_t bytes_used = 0; + std::vector<LogHashPair>::const_reverse_iterator end = list_.rend(); + for (std::vector<LogHashPair>::const_reverse_iterator it = list_.rbegin(); + it != end; ++it) { + size_t log_size = it->log.length(); + if (bytes_used >= min_log_bytes_ && + (list_.size() - start) >= min_log_count_) { + break; + } + bytes_used += log_size; + --start; + } + } + DCHECK_LT(start, list_.size()); + if (start >= list_.size()) + return; + + // Store size at the beginning of the list_value. + list_value->Append(base::Value::CreateIntegerValue(list_.size() - start)); + + base::MD5Context ctx; + base::MD5Init(&ctx); + std::string encoded_log; + for (std::vector<LogHashPair>::const_iterator it = list_.begin() + start; + it != list_.end(); ++it) { + // We encode the compressed log as Value::CreateStringValue() expects to + // take a valid UTF8 string. + base::Base64Encode(it->log, &encoded_log); + base::MD5Update(&ctx, encoded_log); + list_value->Append(base::Value::CreateStringValue(encoded_log)); + } + + // Append hash to the end of the list_value. + base::MD5Digest digest; + base::MD5Final(&digest, &ctx); + list_value->Append(base::Value::CreateStringValue( + base::MD5DigestToBase16(digest))); + // Minimum of 3 elements (size, data, hash). + DCHECK_GE(list_value->GetSize(), 3U); +} + +PersistedLogs::LogReadStatus PersistedLogs::ReadLogsFromPrefList( + const base::ListValue& list_value) { + if (list_value.GetSize() == 0) + return MakeRecallStatusHistogram(LIST_EMPTY); + if (list_value.GetSize() <= kChecksumEntryCount) + return MakeRecallStatusHistogram(LIST_SIZE_TOO_SMALL); + + // The size is stored at the beginning of the list_value. + int size; + bool valid = (*list_value.begin())->GetAsInteger(&size); + if (!valid) + return MakeRecallStatusHistogram(LIST_SIZE_MISSING); + // Account for checksum and size included in the list_value. + if (static_cast<size_t>(size) != list_value.GetSize() - kChecksumEntryCount) + return MakeRecallStatusHistogram(LIST_SIZE_CORRUPTION); + + // Allocate strings for all of the logs we are going to read in. + // Do this ahead of time so that we can decode the string values directly into + // the elements of |list_|, and thereby avoid making copies of the + // serialized logs, which can be fairly large. + DCHECK(list_.empty()); + list_.resize(size); + + base::MD5Context ctx; + base::MD5Init(&ctx); + std::string encoded_log; + size_t local_index = 0; + for (base::ListValue::const_iterator it = list_value.begin() + 1; + it != list_value.end() - 1; // Last element is the checksum. + ++it, ++local_index) { + bool valid = (*it)->GetAsString(&encoded_log); + if (!valid) { + list_.clear(); + return MakeRecallStatusHistogram(LOG_STRING_CORRUPTION); + } + + base::MD5Update(&ctx, encoded_log); + + std::string log_text; + if (!base::Base64Decode(encoded_log, &log_text)) { + list_.clear(); + return MakeRecallStatusHistogram(DECODE_FAIL); + } + + DCHECK_LT(local_index, list_.size()); + list_[local_index].SwapLog(&log_text); + } + + // Verify checksum. + base::MD5Digest digest; + base::MD5Final(&digest, &ctx); + std::string recovered_md5; + // We store the hash at the end of the list_value. + valid = (*(list_value.end() - 1))->GetAsString(&recovered_md5); + if (!valid) { + list_.clear(); + return MakeRecallStatusHistogram(CHECKSUM_STRING_CORRUPTION); + } + if (recovered_md5 != base::MD5DigestToBase16(digest)) { + list_.clear(); + return MakeRecallStatusHistogram(CHECKSUM_CORRUPTION); + } + return MakeRecallStatusHistogram(RECALL_SUCCESS); +} + +} // namespace metrics diff --git a/components/metrics/persisted_logs.h b/components/metrics/persisted_logs.h new file mode 100644 index 0000000..c8457e3 --- /dev/null +++ b/components/metrics/persisted_logs.h @@ -0,0 +1,164 @@ +// Copyright 2014 The Chromium 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 COMPONENTS_METRICS_PERSISTED_LOGS_H_ +#define COMPONENTS_METRICS_PERSISTED_LOGS_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/values.h" + +class PrefService; + +namespace metrics { + +// Maintains a list of unsent logs that are written and restored from disk. +class PersistedLogs { + public: + // Used to produce a histogram that keeps track of the status of recalling + // persisted per logs. + enum LogReadStatus { + RECALL_SUCCESS, // We were able to correctly recall a persisted log. + LIST_EMPTY, // Attempting to recall from an empty list. + LIST_SIZE_MISSING, // Failed to recover list size using GetAsInteger(). + LIST_SIZE_TOO_SMALL, // Too few elements in the list (less than 3). + LIST_SIZE_CORRUPTION, // List size is not as expected. + LOG_STRING_CORRUPTION, // Failed to recover log string using GetAsString(). + CHECKSUM_CORRUPTION, // Failed to verify checksum. + CHECKSUM_STRING_CORRUPTION, // Failed to recover checksum string using + // GetAsString(). + DECODE_FAIL, // Failed to decode log. + DEPRECATED_XML_PROTO_MISMATCH, // The XML and protobuf logs have + // inconsistent data. + END_RECALL_STATUS // Number of bins to use to create the histogram. + }; + + enum StoreType { + NORMAL_STORE, // A standard store operation. + PROVISIONAL_STORE, // A store operation that can be easily reverted later. + }; + + // Constructs a PersistedLogs that stores data in |local_state| under + // the preference |pref_name|. Calling code is responsible for ensuring that + // the lifetime of |local_state| is longer than the lifetime of PersistedLogs. + // When saving logs to disk, we will store either the first |min_log_count| + // logs, or at least |min_log_bytes| bytes of logs, whichever is more. + // If the optional max_log_size parameter is non-zero, all logs larger than + // that limit will be dropped before logs are written to disk. + PersistedLogs(PrefService* local_state, + const char* pref_name, + size_t min_log_count, + size_t min_log_bytes, + size_t max_log_size); + ~PersistedLogs(); + + // Write list to storage. + void SerializeLogs(); + + // Reads the list from the preference. + LogReadStatus DeserializeLogs(); + + // Adds a log to the list. |input| will be swapped with an empty string. + void StoreLog(std::string* input); + + // Stages the most recent log. The staged_log will remain the same even if + // additional logs are added. + void StageLog(); + + // Remove the staged log. + void DiscardStagedLog(); + + // Saves the staged log, then clears staged_log(). + // If |store_type| is PROVISIONAL_STORE, it can be dropped from storage with + // a later call to DiscardLastProvisionalStore (if it hasn't already been + // staged again). + // This is intended to be used when logs are being saved while an upload is in + // progress, in case the upload later succeeds. + // This can only be called if has_staged_log() is true. + void StoreStagedLogAsUnsent(StoreType store_type); + + // Discards the last log stored with StoreStagedLogAsUnsent with |store_type| + // set to PROVISIONAL_STORE, as long as it hasn't already been re-staged. If + // the log is no longer present, this is a no-op. + void DiscardLastProvisionalStore(); + + // True if a log has been staged. + bool has_staged_log() const { return !staged_log_.log.empty(); }; + + // Returns the element in the front of the list. + const std::string& staged_log() const { + DCHECK(has_staged_log()); + return staged_log_.log; + } + + // Returns the element in the front of the list. + const std::string& staged_log_hash() const { + DCHECK(has_staged_log()); + return staged_log_.hash; + } + + // The number of elements currently stored. + size_t size() const { return list_.size(); } + + // True if there are no stored logs. + bool empty() const { return list_.empty(); } + + private: + // Writes the list to the ListValue. + void WriteLogsToPrefList(base::ListValue* list); + + // Reads the list from the ListValue. + LogReadStatus ReadLogsFromPrefList(const base::ListValue& list); + + // A weak pointer to the PrefService object to read and write the preference + // from. Calling code should ensure this object continues to exist for the + // lifetime of the PersistedLogs object. + PrefService* local_state_; + + // The name of the preference this object stores logs in. + const char* pref_name_; + + // We will keep at least this |min_log_count_| logs or |min_log_bytes_| bytes + // of logs, whichever is greater, when writing to disk. These apply after + // skipping logs greater than |max_log_size_|. + const size_t min_log_count_; + const size_t min_log_bytes_; + + // Logs greater than this size will not be written to disk. + const size_t max_log_size_; + + struct LogHashPair { + // Raw log text, typically a serialized protobuf. + std::string log; + // The SHA1 hash of log, stored to catch errors from memory corruption. + std::string hash; + // Swap the content of input into log and update the hash. + void SwapLog(std::string* input); + // Swap both log and hash from another LogHashPair. + void Swap(LogHashPair* input); + }; + // A list of all of the stored logs, stored with SHA1 hashes to check for + // corruption while they are stored in memory. + std::vector<LogHashPair> list_; + + // The log staged for upload. + LogHashPair staged_log_; + + // The index and type of the last provisional store. If nothing has been + // provisionally stored, or the last provisional store has already been + // re-staged, the index will be -1; + // This is necessary because during an upload there are two logs (staged + // and current) and a client might store them in either order, so it's + // not necessarily the case that the provisional store is the last store. + int last_provisional_store_index_; + + DISALLOW_COPY_AND_ASSIGN(PersistedLogs); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_PERSISTED_LOGS_H_ diff --git a/components/metrics/persisted_logs_unittest.cc b/components/metrics/persisted_logs_unittest.cc new file mode 100644 index 0000000..97e9f41 --- /dev/null +++ b/components/metrics/persisted_logs_unittest.cc @@ -0,0 +1,415 @@ +// Copyright 2014 The Chromium 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 "base/base64.h" +#include "base/md5.h" +#include "base/prefs/pref_registry_simple.h" +#include "base/prefs/scoped_user_pref_update.h" +#include "base/prefs/testing_pref_service.h" +#include "base/sha1.h" +#include "base/values.h" +#include "components/metrics/persisted_logs.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +namespace { + +const char kTestPrefName[] = "TestPref"; +const size_t kLogCountLimit = 3; +const size_t kLogByteLimit = 1000; + + +class PersistedLogsTest : public testing::Test { + public: + PersistedLogsTest() { + prefs_.registry()->RegisterListPref(kTestPrefName); + } + + protected: + TestingPrefServiceSimple prefs_; + + private: + DISALLOW_COPY_AND_ASSIGN(PersistedLogsTest); +}; + +class TestPersistedLogs : public PersistedLogs { + public: + TestPersistedLogs(PrefService* service) + : PersistedLogs(service, kTestPrefName, kLogCountLimit, kLogByteLimit, 0) { + } + + // Make a copy of the string and store the copy. + void StoreLogCopy(std::string tmp) { + StoreLog(&tmp); + } + + // Stages and removes the next log, while testing it's value. + void ExpectNextLog(const std::string& expected_log) { + StageLog(); + EXPECT_EQ(staged_log(), expected_log); + DiscardStagedLog(); + } +}; + +} // namespace + +// Store and retrieve empty list_value. +TEST_F(PersistedLogsTest, EmptyLogList) { + TestPersistedLogs persisted_logs(&prefs_); + + persisted_logs.SerializeLogs(); + const base::ListValue* list_value = prefs_.GetList(kTestPrefName); + EXPECT_EQ(0U, list_value->GetSize()); + + TestPersistedLogs result_persisted_logs(&prefs_); + EXPECT_EQ(PersistedLogs::LIST_EMPTY, result_persisted_logs.DeserializeLogs()); + EXPECT_EQ(0U, result_persisted_logs.size()); +} + +// Store and retrieve a single log value. +TEST_F(PersistedLogsTest, SingleElementLogList) { + TestPersistedLogs persisted_logs(&prefs_); + + persisted_logs.StoreLogCopy("Hello world!"); + persisted_logs.SerializeLogs(); + + const base::ListValue* list_value = prefs_.GetList(kTestPrefName); + // |list_value| will now contain the following: + // [1, Base64Encode("Hello world!"), MD5("Hello world!")]. + ASSERT_EQ(3U, list_value->GetSize()); + + // Examine each element. + base::ListValue::const_iterator it = list_value->begin(); + int size = 0; + (*it)->GetAsInteger(&size); + EXPECT_EQ(1, size); + + ++it; + std::string str; + (*it)->GetAsString(&str); // Base64 encoded "Hello world!" string. + std::string encoded; + base::Base64Encode("Hello world!", &encoded); + EXPECT_TRUE(encoded == str); + + ++it; + (*it)->GetAsString(&str); // MD5 for encoded "Hello world!" string. + EXPECT_TRUE(base::MD5String(encoded) == str); + + ++it; + EXPECT_TRUE(it == list_value->end()); // Reached end of list_value. + + TestPersistedLogs result_persisted_logs(&prefs_); + EXPECT_EQ(PersistedLogs::RECALL_SUCCESS, + result_persisted_logs.DeserializeLogs()); + EXPECT_EQ(1U, result_persisted_logs.size()); +} + +// Store a set of logs over the length limit, but smaller than the min number of +// bytes. +TEST_F(PersistedLogsTest, LongButTinyLogList) { + TestPersistedLogs persisted_logs(&prefs_); + + size_t log_count = kLogCountLimit * 5; + for (size_t i = 0; i < log_count; ++i) + persisted_logs.StoreLogCopy("x"); + + persisted_logs.SerializeLogs(); + + TestPersistedLogs result_persisted_logs(&prefs_); + EXPECT_EQ(PersistedLogs::RECALL_SUCCESS, + result_persisted_logs.DeserializeLogs()); + EXPECT_EQ(persisted_logs.size(), result_persisted_logs.size()); + + result_persisted_logs.ExpectNextLog("x"); +} + +// Store a set of logs over the length limit, but that doesn't reach the minimum +// number of bytes until after passing the length limit. +TEST_F(PersistedLogsTest, LongButSmallLogList) { + TestPersistedLogs persisted_logs(&prefs_); + + // Make log_count logs each slightly larger than + // kLogByteLimit / (log_count - 2) + // so that the minimum is reached before the oldest (first) two logs. + size_t log_count = kLogCountLimit * 5; + size_t log_size = (kLogByteLimit / (log_count - 2)) + 2; + persisted_logs.StoreLogCopy("one"); + persisted_logs.StoreLogCopy("two"); + std::string first_kept = "First to keep"; + first_kept.resize(log_size, ' '); + persisted_logs.StoreLogCopy(first_kept); + std::string blank_log = std::string(log_size, ' '); + for (size_t i = persisted_logs.size(); i < log_count - 1; ++i) { + persisted_logs.StoreLogCopy(blank_log); + } + std::string last_kept = "Last to keep"; + last_kept.resize(log_size, ' '); + persisted_logs.StoreLogCopy(last_kept); + + persisted_logs.SerializeLogs(); + + TestPersistedLogs result_persisted_logs(&prefs_); + EXPECT_EQ(PersistedLogs::RECALL_SUCCESS, + result_persisted_logs.DeserializeLogs()); + EXPECT_EQ(persisted_logs.size() - 2, result_persisted_logs.size()); + + result_persisted_logs.ExpectNextLog(last_kept); + while (result_persisted_logs.size() > 1) { + result_persisted_logs.ExpectNextLog(blank_log); + } + result_persisted_logs.ExpectNextLog(first_kept); +} + +// Store a set of logs within the length limit, but well over the minimum +// number of bytes. +TEST_F(PersistedLogsTest, ShortButLargeLogList) { + TestPersistedLogs persisted_logs(&prefs_); + + // Make the total byte count about twice the minimum. + size_t log_count = kLogCountLimit; + size_t log_size = (kLogByteLimit / log_count) * 2; + std::string blank_log = std::string(log_size, ' '); + for (size_t i = 0; i < log_count; ++i) { + persisted_logs.StoreLogCopy(blank_log); + } + + persisted_logs.SerializeLogs(); + + TestPersistedLogs result_persisted_logs(&prefs_); + EXPECT_EQ(PersistedLogs::RECALL_SUCCESS, + result_persisted_logs.DeserializeLogs()); + EXPECT_EQ(persisted_logs.size(), result_persisted_logs.size()); +} + +// Store a set of logs over the length limit, and over the minimum number of +// bytes. +TEST_F(PersistedLogsTest, LongAndLargeLogList) { + TestPersistedLogs persisted_logs(&prefs_); + + // Include twice the max number of logs. + size_t log_count = kLogCountLimit * 2; + // Make the total byte count about four times the minimum. + size_t log_size = (kLogByteLimit / log_count) * 4; + std::string target_log = "First to keep"; + target_log.resize(log_size, ' '); + std::string blank_log = std::string(log_size, ' '); + for (size_t i = 0; i < log_count; ++i) { + if (i == log_count - kLogCountLimit) + persisted_logs.StoreLogCopy(target_log); + else + persisted_logs.StoreLogCopy(blank_log); + } + + persisted_logs.SerializeLogs(); + + TestPersistedLogs result_persisted_logs(&prefs_); + EXPECT_EQ(PersistedLogs::RECALL_SUCCESS, + result_persisted_logs.DeserializeLogs()); + EXPECT_EQ(kLogCountLimit, result_persisted_logs.size()); + + while (result_persisted_logs.size() > 1) { + result_persisted_logs.ExpectNextLog(blank_log); + } + result_persisted_logs.ExpectNextLog(target_log); +} + +// Induce LIST_SIZE_TOO_SMALL corruption +TEST_F(PersistedLogsTest, SmallRecoveredListSize) { + TestPersistedLogs persisted_logs(&prefs_); + + persisted_logs.StoreLogCopy("Hello world!"); + + persisted_logs.SerializeLogs(); + + { + ListPrefUpdate update(&prefs_, kTestPrefName); + base::ListValue* list_value = update.Get(); + EXPECT_EQ(3U, list_value->GetSize()); + + // Remove last element. + list_value->Remove(list_value->GetSize() - 1, NULL); + EXPECT_EQ(2U, list_value->GetSize()); + } + + TestPersistedLogs result_persisted_logs(&prefs_); + EXPECT_EQ(PersistedLogs::LIST_SIZE_TOO_SMALL, + result_persisted_logs.DeserializeLogs()); +} + +// Remove size from the stored list_value. +TEST_F(PersistedLogsTest, RemoveSizeFromLogList) { + TestPersistedLogs persisted_logs(&prefs_); + + persisted_logs.StoreLogCopy("one"); + persisted_logs.StoreLogCopy("two"); + EXPECT_EQ(2U, persisted_logs.size()); + + persisted_logs.SerializeLogs(); + + { + ListPrefUpdate update(&prefs_, kTestPrefName); + base::ListValue* list_value = update.Get(); + EXPECT_EQ(4U, list_value->GetSize()); + + list_value->Remove(0, NULL); // Delete size (1st element). + EXPECT_EQ(3U, list_value->GetSize()); + } + + TestPersistedLogs result_persisted_logs(&prefs_); + EXPECT_EQ(PersistedLogs::LIST_SIZE_MISSING, + result_persisted_logs.DeserializeLogs()); +} + +// Corrupt size of stored list_value. +TEST_F(PersistedLogsTest, CorruptSizeOfLogList) { + TestPersistedLogs persisted_logs(&prefs_); + + persisted_logs.StoreLogCopy("Hello world!"); + + persisted_logs.SerializeLogs(); + + { + ListPrefUpdate update(&prefs_, kTestPrefName); + base::ListValue* list_value = update.Get(); + EXPECT_EQ(3U, list_value->GetSize()); + + // Change list_value size from 1 to 2. + EXPECT_TRUE(list_value->Set(0, base::Value::CreateIntegerValue(2))); + EXPECT_EQ(3U, list_value->GetSize()); + } + + TestPersistedLogs result_persisted_logs(&prefs_); + EXPECT_EQ(PersistedLogs::LIST_SIZE_CORRUPTION, + result_persisted_logs.DeserializeLogs()); +} + +// Corrupt checksum of stored list_value. +TEST_F(PersistedLogsTest, CorruptChecksumOfLogList) { + TestPersistedLogs persisted_logs(&prefs_); + + persisted_logs.StoreLogCopy("Hello world!"); + + persisted_logs.SerializeLogs(); + + { + ListPrefUpdate update(&prefs_, kTestPrefName); + base::ListValue* list_value = update.Get(); + EXPECT_EQ(3U, list_value->GetSize()); + + // Fetch checksum (last element) and change it. + std::string checksum; + EXPECT_TRUE((*(list_value->end() - 1))->GetAsString(&checksum)); + checksum[0] = (checksum[0] == 'a') ? 'b' : 'a'; + EXPECT_TRUE(list_value->Set(2, base::Value::CreateStringValue(checksum))); + EXPECT_EQ(3U, list_value->GetSize()); + } + + TestPersistedLogs result_persisted_logs(&prefs_); + EXPECT_EQ(PersistedLogs::CHECKSUM_CORRUPTION, + result_persisted_logs.DeserializeLogs()); +} + +// Check that the store/stage/discard functions work as expected. +TEST_F(PersistedLogsTest, Staging) { + TestPersistedLogs persisted_logs(&prefs_); + std::string tmp; + + EXPECT_FALSE(persisted_logs.has_staged_log()); + persisted_logs.StoreLogCopy("one"); + EXPECT_FALSE(persisted_logs.has_staged_log()); + persisted_logs.StoreLogCopy("two"); + persisted_logs.StageLog(); + EXPECT_TRUE(persisted_logs.has_staged_log()); + EXPECT_EQ(persisted_logs.staged_log(), std::string("two")); + persisted_logs.StoreLogCopy("three"); + EXPECT_EQ(persisted_logs.staged_log(), std::string("two")); + EXPECT_EQ(persisted_logs.size(), 2U); + persisted_logs.DiscardStagedLog(); + EXPECT_FALSE(persisted_logs.has_staged_log()); + EXPECT_EQ(persisted_logs.size(), 2U); + persisted_logs.StageLog(); + EXPECT_EQ(persisted_logs.staged_log(), std::string("three")); + persisted_logs.DiscardStagedLog(); + persisted_logs.StageLog(); + EXPECT_EQ(persisted_logs.staged_log(), std::string("one")); + persisted_logs.DiscardStagedLog(); + EXPECT_FALSE(persisted_logs.has_staged_log()); + EXPECT_EQ(persisted_logs.size(), 0U); +} + +TEST_F(PersistedLogsTest, ProvisionalStoreStandardFlow) { + // Ensure that provisional store works, and discards the correct log. + TestPersistedLogs persisted_logs(&prefs_); + + persisted_logs.StoreLogCopy("one"); + persisted_logs.StageLog(); + persisted_logs.StoreStagedLogAsUnsent(PersistedLogs::PROVISIONAL_STORE); + persisted_logs.StoreLogCopy("two"); + persisted_logs.DiscardLastProvisionalStore(); + persisted_logs.SerializeLogs(); + + TestPersistedLogs result_persisted_logs(&prefs_); + EXPECT_EQ(PersistedLogs::RECALL_SUCCESS, + result_persisted_logs.DeserializeLogs()); + EXPECT_EQ(1U, result_persisted_logs.size()); + result_persisted_logs.ExpectNextLog("two"); +} + +TEST_F(PersistedLogsTest, ProvisionalStoreNoop1) { + // Ensure that trying to drop a sent log is a no-op, even if another log has + // since been staged. + TestPersistedLogs persisted_logs(&prefs_); + persisted_logs.DeserializeLogs(); + persisted_logs.StoreLogCopy("one"); + persisted_logs.StageLog(); + persisted_logs.StoreStagedLogAsUnsent(PersistedLogs::PROVISIONAL_STORE); + persisted_logs.StageLog(); + persisted_logs.DiscardStagedLog(); + persisted_logs.StoreLogCopy("two"); + persisted_logs.StageLog(); + persisted_logs.StoreStagedLogAsUnsent(PersistedLogs::NORMAL_STORE); + persisted_logs.DiscardLastProvisionalStore(); + persisted_logs.SerializeLogs(); + + TestPersistedLogs result_persisted_logs(&prefs_); + EXPECT_EQ(PersistedLogs::RECALL_SUCCESS, + result_persisted_logs.DeserializeLogs()); + EXPECT_EQ(1U, result_persisted_logs.size()); + result_persisted_logs.ExpectNextLog("two"); +} + +TEST_F(PersistedLogsTest, ProvisionalStoreNoop2) { + // Ensure that trying to drop more than once is a no-op + TestPersistedLogs persisted_logs(&prefs_); + persisted_logs.DeserializeLogs(); + persisted_logs.StoreLogCopy("one"); + persisted_logs.StageLog(); + persisted_logs.StoreStagedLogAsUnsent(PersistedLogs::NORMAL_STORE); + persisted_logs.StoreLogCopy("two"); + persisted_logs.StageLog(); + persisted_logs.StoreStagedLogAsUnsent(PersistedLogs::PROVISIONAL_STORE); + persisted_logs.DiscardLastProvisionalStore(); + persisted_logs.DiscardLastProvisionalStore(); + persisted_logs.SerializeLogs(); + + TestPersistedLogs result_persisted_logs(&prefs_); + EXPECT_EQ(PersistedLogs::RECALL_SUCCESS, + result_persisted_logs.DeserializeLogs()); + EXPECT_EQ(1U, result_persisted_logs.size()); + result_persisted_logs.ExpectNextLog("one"); +} + +TEST_F(PersistedLogsTest, Hashes) { + const char kFooText[] = "foo"; + const std::string foo_hash = base::SHA1HashString(kFooText); + TestingPrefServiceSimple prefs_; + prefs_.registry()->RegisterListPref(kTestPrefName); + TestPersistedLogs persisted_logs(&prefs_); + persisted_logs.StoreLogCopy(kFooText); + persisted_logs.StageLog(); + EXPECT_EQ(foo_hash, persisted_logs.staged_log_hash()); +} + +} // namespace metrics |