Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3171)

Unified Diff: chrome/browser/metrics/metrics_service.cc

Issue 2936005: Compress and checksum pending logs that are going to be persisted. ... (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 10 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/metrics/metrics_service.cc
===================================================================
--- chrome/browser/metrics/metrics_service.cc (revision 52082)
+++ chrome/browser/metrics/metrics_service.cc (working copy)
@@ -19,9 +19,8 @@
// transmission. Transmission includes submitting a compressed log as data in a
// URL-post, and retransmitting (or retaining at process termination) if the
// attempted transmission failed. Retention across process terminations is done
-// using the the PrefServices facilities. The format for the retained
-// logs (ones that never got transmitted) is always the uncompressed textual
-// representation.
+// using the the PrefServices facilities. The retained logs (the ones that never
+// got transmitted) are compressed and base64-encoded before being persisted.
//
// Logs fall into one of two categories: "initial logs," and "ongoing logs."
// There is at most one initial log sent for each complete run of the chromium
@@ -164,12 +163,8 @@
#include <objbase.h>
#endif
-#if defined(USE_SYSTEM_LIBBZ2)
-#include <bzlib.h>
-#else
-#include "third_party/bzip2/bzlib.h"
-#endif
-
+#include "base/base64.h"
+#include "base/md5.h"
#include "base/thread.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
#include "chrome/browser/browser_list.h"
@@ -205,6 +200,21 @@
static const char kUnknownHardwareClass[] = "unknown";
#endif
+namespace {
+MetricsService::LogRecallStatus MakeRecallStatusHistogram(
+ MetricsService::LogRecallStatus status) {
+ UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogRecall", status,
+ MetricsService::END_RECALL_STATUS);
+ return status;
+}
+
+// TODO(ziadh): Remove this when done with experiment.
+void MakeStoreStatusHistogram(MetricsService::LogStoreStatus status) {
+ UMA_HISTOGRAM_ENUMERATION("PrefService.PersistentLogStore", status,
+ MetricsService::END_STORE_STATUS);
+}
+} // namespace
+
using base::Time;
using base::TimeDelta;
@@ -270,7 +280,11 @@
// ongoing_log_ at startup).
static const size_t kMaxOngoingLogsPersisted = 8;
+// We append (2) more elements to persisted lists: the size of the list and a
+// checksum of the elements.
+static const size_t kChecksumEntryCount = 2;
+
// Handles asynchronous fetching of memory details.
// Will run the provided task after finished.
class MetricsMemoryDetails : public MemoryDetails {
@@ -883,7 +897,7 @@
PreparePendingLogText();
if (state_ == INITIAL_LOG_READY) {
// We may race here, and send second copy of initial log later.
- unsent_initial_logs_.push_back(pending_log_text_);
+ unsent_initial_logs_.push_back(compressed_log_);
state_ = SEND_OLD_INITIAL_LOGS;
} else {
// TODO(jar): Verify correctness in other states, including sending unsent
@@ -905,14 +919,13 @@
// log. It wasn't supposed to be uploaded anyway.
if (!server_permits_upload_)
return;
-
- if (pending_log_text_.length() >
+ if (compressed_log_.length() >
static_cast<size_t>(kUploadLogAvoidRetransmitSize)) {
UMA_HISTOGRAM_COUNTS("UMA.Large Accumulated Log Not Persisted",
- static_cast<int>(pending_log_text_.length()));
+ static_cast<int>(compressed_log_.length()));
return;
}
- unsent_ongoing_logs_.push_back(pending_log_text_);
+ unsent_ongoing_logs_.push_back(compressed_log_);
}
//------------------------------------------------------------------------------
@@ -1078,7 +1091,7 @@
case SEND_OLD_INITIAL_LOGS:
if (!unsent_initial_logs_.empty()) {
- pending_log_text_ = unsent_initial_logs_.back();
+ compressed_log_ = unsent_initial_logs_.back();
break;
}
state_ = SENDING_OLD_LOGS;
@@ -1086,7 +1099,7 @@
case SENDING_OLD_LOGS:
if (!unsent_ongoing_logs_.empty()) {
- pending_log_text_ = unsent_ongoing_logs_.back();
+ compressed_log_ = unsent_ongoing_logs_.back();
break;
}
state_ = SENDING_CURRENT_LOGS;
@@ -1145,30 +1158,114 @@
pending_log_ = log;
}
+// static
+MetricsService::LogRecallStatus MetricsService::RecallUnsentLogsHelper(
+ const ListValue& list,
+ std::vector<std::string>* local_list) {
+ DCHECK(local_list->empty());
+ 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);
+
+ MD5Context ctx;
+ MD5Init(&ctx);
+ std::string encoded_log;
+ std::string decoded_log;
+ for (ListValue::const_iterator it = list.begin() + 1;
+ it != list.end() - 1; ++it) { // Last element is the checksum.
+ valid = (*it)->GetAsString(&encoded_log);
+ if (!valid) {
+ local_list->clear();
+ return MakeRecallStatusHistogram(LOG_STRING_CORRUPTION);
+ }
+
+ MD5Update(&ctx, encoded_log.data(), encoded_log.length());
+
+ if (!base::Base64Decode(encoded_log, &decoded_log)) {
+ local_list->clear();
+ return MakeRecallStatusHistogram(DECODE_FAIL);
+ }
+ local_list->push_back(decoded_log);
+ }
+
+ // Verify checksum.
+ MD5Digest digest;
+ 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 != MD5DigestToBase16(digest)) {
+ local_list->clear();
+ return MakeRecallStatusHistogram(CHECKSUM_CORRUPTION);
+ }
+ return MakeRecallStatusHistogram(RECALL_SUCCESS);
+}
void MetricsService::RecallUnsentLogs() {
- DCHECK(unsent_initial_logs_.empty());
- DCHECK(unsent_ongoing_logs_.empty());
-
PrefService* local_state = g_browser_process->local_state();
DCHECK(local_state);
ListValue* unsent_initial_logs = local_state->GetMutableList(
prefs::kMetricsInitialLogs);
- for (ListValue::const_iterator it = unsent_initial_logs->begin();
- it != unsent_initial_logs->end(); ++it) {
- std::string log;
- (*it)->GetAsString(&log);
- unsent_initial_logs_.push_back(log);
- }
+ RecallUnsentLogsHelper(*unsent_initial_logs, &unsent_initial_logs_);
ListValue* unsent_ongoing_logs = local_state->GetMutableList(
prefs::kMetricsOngoingLogs);
- for (ListValue::const_iterator it = unsent_ongoing_logs->begin();
- it != unsent_ongoing_logs->end(); ++it) {
- std::string log;
- (*it)->GetAsString(&log);
- unsent_ongoing_logs_.push_back(log);
+ RecallUnsentLogsHelper(*unsent_ongoing_logs, &unsent_ongoing_logs_);
+}
+
+// static
+void MetricsService::StoreUnsentLogsHelper(
+ const std::vector<std::string>& local_list,
+ const size_t kMaxLocalListSize,
+ ListValue* list) {
+ list->Clear();
+ size_t start = 0;
+ if (local_list.size() > kMaxLocalListSize)
+ start = local_list.size() - kMaxLocalListSize;
+ DCHECK(start <= local_list.size());
+ if (local_list.size() == start)
+ return;
+
+ // Store size at the beginning of the list.
+ list->Append(Value::CreateIntegerValue(local_list.size() - start));
+
+ MD5Context ctx;
+ MD5Init(&ctx);
+ std::string encoded_log;
+ for (std::vector<std::string>::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.
+ if (!base::Base64Encode(*it, &encoded_log)) {
+ MakeStoreStatusHistogram(ENCODE_FAIL);
+ list->Clear();
+ return;
+ }
+ MD5Update(&ctx, encoded_log.data(), encoded_log.length());
+ list->Append(Value::CreateStringValue(encoded_log));
}
+
+ // Append hash to the end of the list.
+ MD5Digest digest;
+ MD5Final(&digest, &ctx);
+ list->Append(Value::CreateStringValue(MD5DigestToBase16(digest)));
+ DCHECK(list->GetSize() >= 3); // Minimum of 3 elements (size, data, hash).
}
void MetricsService::StoreUnsentLogs() {
@@ -1180,59 +1277,51 @@
ListValue* unsent_initial_logs = local_state->GetMutableList(
prefs::kMetricsInitialLogs);
- unsent_initial_logs->Clear();
- size_t start = 0;
- if (unsent_initial_logs_.size() > kMaxInitialLogsPersisted)
- start = unsent_initial_logs_.size() - kMaxInitialLogsPersisted;
- for (size_t i = start; i < unsent_initial_logs_.size(); ++i)
- unsent_initial_logs->Append(
- Value::CreateStringValue(unsent_initial_logs_[i]));
+ StoreUnsentLogsHelper(unsent_initial_logs_, kMaxInitialLogsPersisted,
+ unsent_initial_logs);
ListValue* unsent_ongoing_logs = local_state->GetMutableList(
prefs::kMetricsOngoingLogs);
- unsent_ongoing_logs->Clear();
- start = 0;
- if (unsent_ongoing_logs_.size() > kMaxOngoingLogsPersisted)
- start = unsent_ongoing_logs_.size() - kMaxOngoingLogsPersisted;
-
- for (size_t i = start; i < unsent_ongoing_logs_.size(); ++i)
- unsent_ongoing_logs->Append(
- Value::CreateStringValue(unsent_ongoing_logs_[i]));
+ StoreUnsentLogsHelper(unsent_ongoing_logs_, kMaxOngoingLogsPersisted,
+ unsent_ongoing_logs);
}
void MetricsService::PreparePendingLogText() {
DCHECK(pending_log());
- if (!pending_log_text_.empty())
+ if (!compressed_log_.empty())
return;
int text_size = pending_log_->GetEncodedLogSize();
- // Leave room for the NUL terminator.
- pending_log_->GetEncodedLog(WriteInto(&pending_log_text_, text_size + 1),
+ std::string pending_log_text;
+ // Leave room for the NULL terminator.
+ pending_log_->GetEncodedLog(WriteInto(&pending_log_text, text_size + 1),
text_size);
+
+ if (Bzip2Compress(pending_log_text, &compressed_log_)) {
+ // Allow security conscious users to see all metrics logs that we send.
+ LOG(INFO) << "COMPRESSED FOLLOWING METRICS LOG: " << pending_log_text;
+ } else {
+ LOG(DFATAL) << "Failed to compress log for transmission.";
+ // We can't discard the logs as other caller functions expect that
+ // |compressed_log_| not be empty. We can detect this failure at the server
+ // after we transmit.
+ compressed_log_ = "Unable to compress!";
+ MakeStoreStatusHistogram(COMPRESS_FAIL);
+ return;
+ }
}
void MetricsService::PrepareFetchWithPendingLog() {
DCHECK(pending_log());
DCHECK(!current_fetch_.get());
PreparePendingLogText();
- DCHECK(!pending_log_text_.empty());
+ DCHECK(!compressed_log_.empty());
- // Allow security conscious users to see all metrics logs that we send.
- LOG(INFO) << "METRICS LOG: " << pending_log_text_;
-
- std::string compressed_log;
- if (!Bzip2Compress(pending_log_text_, &compressed_log)) {
- LOG(DFATAL) << "Failed to compress log for transmission.";
- DiscardPendingLog();
- StartLogTransmissionTimer(); // Maybe we'll do better on next log :-/.
- return;
- }
-
current_fetch_.reset(new URLFetcher(GURL(WideToUTF16(server_url_)),
URLFetcher::POST,
this));
current_fetch_->set_request_context(Profile::GetDefaultRequestContext());
- current_fetch_->set_upload_data(kMetricsType, compressed_log);
+ current_fetch_->set_upload_data(kMetricsType, compressed_log_);
}
static const char* StatusToString(const URLRequestStatus& status) {
@@ -1277,10 +1366,10 @@
bool discard_log = false;
if (response_code != 200 &&
- pending_log_text_.length() >
- static_cast<size_t>(kUploadLogAvoidRetransmitSize)) {
+ (compressed_log_.length() >
+ static_cast<size_t>(kUploadLogAvoidRetransmitSize))) {
UMA_HISTOGRAM_COUNTS("UMA.Large Rejected Log was Discarded",
- static_cast<int>(pending_log_text_.length()));
+ static_cast<int>(compressed_log_.length()));
discard_log = true;
} else if (response_code == 400) {
// Bad syntax. Retransmission won't work.
@@ -1361,7 +1450,7 @@
LOG(INFO) << "METRICS: transmission retry being scheduled in " <<
interlog_duration_.InSeconds() << " seconds for " <<
- pending_log_text_;
+ compressed_log_;
}
}
@@ -1378,7 +1467,6 @@
return;
}
xmlDocPtr doc = xmlReadMemory(data.c_str(), data_size, "", NULL, 0);
- DCHECK(doc);
// If the document is malformed, we just use the settings that were there.
if (!doc) {
LOG(INFO) << "METRICS: reading xml from server response data failed";

Powered by Google App Engine
This is Rietveld 408576698