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

Unified Diff: components/rappor/log_uploader.cc

Issue 49753002: RAPPOR implementation (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 10 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: components/rappor/log_uploader.cc
diff --git a/components/rappor/log_uploader.cc b/components/rappor/log_uploader.cc
new file mode 100644
index 0000000000000000000000000000000000000000..3b35e04878afe07fbbe75bc0abad070de25d44cd
--- /dev/null
+++ b/components/rappor/log_uploader.cc
@@ -0,0 +1,172 @@
+// 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/rappor/log_uploader.h"
+
+#include "base/metrics/histogram.h"
+#include "net/base/load_flags.h"
+
+using base::TimeDelta;
+
+namespace {
+
+// The delay, in seconds, between uploading when there are queued logs to send.
+const int kUnsentLogsIntervalSeconds = 3;
+
+// When uploading metrics to the server fails, we progressively wait longer and
+// longer before sending the next log. This backoff process helps reduce load
+// on a server that is having issues.
+// The following is the multiplier we use to expand that inter-log duration.
+const double kBackoffMultiplier = 1.1;
+
+// The maximum backoff multiplier.
+const int kMaxBackoffIntervalSeconds = 60 * 60;
+
+// The maximum number of unsent logs we will keep.
+// TODO(holte): Limit based on log size instead.
+const size_t kMaxQueuedLogs = 10;
+
+enum ResponseStatus {
+ UNKNOWN_FAILURE,
+ SUCCESS,
+ BAD_REQUEST, // Invalid syntax or log too large.
+ NO_RESPONSE,
+ NUM_RESPONSE_STATUSES
+};
+
+ResponseStatus ResponseCodeToStatus(int response_code) {
+ switch (response_code) {
+ case 200:
+ return SUCCESS;
+ case 400:
+ return BAD_REQUEST;
+ case net::URLFetcher::RESPONSE_CODE_INVALID:
+ return NO_RESPONSE;
+ default:
+ return UNKNOWN_FAILURE;
+ }
+}
+
+enum DiscardReason {
+ UPLOAD_SUCCESS,
+ UPLOAD_REJECTED,
+ QUEUE_OVERFLOW,
+ NUM_DISCARD_REASONS
+};
+
+} // namespace
+
+namespace rappor {
+
+LogUploader::LogUploader(const GURL& server_url,
+ const std::string& mime_type,
+ net::URLRequestContextGetter* request_context)
+ : server_url_(server_url),
+ mime_type_(mime_type),
+ request_context_(request_context),
+ has_callback_pending_(false),
+ upload_interval_(TimeDelta::FromSeconds(kUnsentLogsIntervalSeconds)) {
+}
+
+LogUploader::~LogUploader() {}
+
+void LogUploader::QueueLog(const std::string& log) {
+ queued_logs_.push(log);
+ if (!upload_timer_.IsRunning() && !has_callback_pending_)
+ StartScheduledUpload();
+}
+
+void LogUploader::ScheduleNextUpload() {
+ if (upload_timer_.IsRunning() || has_callback_pending_)
+ return;
+
+ upload_timer_.Start(
+ FROM_HERE, upload_interval_, this, &LogUploader::StartScheduledUpload);
+}
+
+void LogUploader::StartScheduledUpload() {
+ DCHECK(!has_callback_pending_);
+ has_callback_pending_ = true;
+ current_fetch_.reset(
+ net::URLFetcher::Create(server_url_, net::URLFetcher::POST, this));
+ current_fetch_->SetRequestContext(request_context_.get());
+ current_fetch_->SetUploadData(mime_type_, queued_logs_.front());
+
+ // We already drop cookies server-side, but we might as well strip them out
+ // client-side as well.
+ current_fetch_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES |
+ net::LOAD_DO_NOT_SEND_COOKIES);
+ current_fetch_->Start();
+}
+
+void LogUploader::OnURLFetchComplete(const net::URLFetcher* source) {
+ // We're not allowed to re-use the existing |URLFetcher|s, so free them here.
+ // Note however that |source| is aliased to the fetcher, so we should be
+ // careful not to delete it too early.
+ DCHECK_EQ(current_fetch_.get(), source);
+ scoped_ptr<net::URLFetcher> fetch(current_fetch_.Pass());
+
+ int response_code = source->GetResponseCode();
+
+ // Log a histogram to track response success vs. failure rates.
+ UMA_HISTOGRAM_ENUMERATION("Rappor.UploadResponseStatus",
+ ResponseCodeToStatus(response_code),
Alexei Svitkine (slow) 2014/02/06 17:47:20 I know this how the MetricsService does it, but I'
Steven Holte 2014/02/06 23:07:08 Done.
+ NUM_RESPONSE_STATUSES);
+
+ bool upload_succeeded = response_code == 200;
+
+ // Determine whether this log should be retransmitted.
+ bool discard_log = false;
+ if (upload_succeeded) {
+ UMA_HISTOGRAM_ENUMERATION("Rappor.DiscardReason",
Alexei Svitkine (slow) 2014/02/06 17:47:20 Make this a separate function in the anon namespac
Steven Holte 2014/02/06 23:07:08 Rewrote so the macros is only used once.
+ UPLOAD_SUCCESS,
+ NUM_DISCARD_REASONS);
+ discard_log = true;
+ } else if (response_code == 400) {
+ UMA_HISTOGRAM_ENUMERATION("Rappor.DiscardReason",
+ UPLOAD_REJECTED,
+ NUM_DISCARD_REASONS);
+ discard_log = true;
+ } else if (queued_logs_.size() > kMaxQueuedLogs) {
+ UMA_HISTOGRAM_ENUMERATION("Rappor.DiscardReason",
+ QUEUE_OVERFLOW,
+ NUM_DISCARD_REASONS);
+ discard_log = true;
+ }
+
+ if (discard_log)
+ queued_logs_.pop();
+
+ // Error 400 indicates a problem with the log, not with the server, so
+ // don't consider that a sign that the server is in trouble.
+ bool server_is_healthy = upload_succeeded || response_code == 400;
+ UploadFinished(server_is_healthy, !queued_logs_.empty());
+}
+
+void LogUploader::UploadFinished(bool server_is_healthy,
+ bool more_logs_remaining) {
+ DCHECK(has_callback_pending_);
+ has_callback_pending_ = false;
+ // If the server is having issues, back off. Otherwise, reset to default.
+ if (!server_is_healthy)
+ BackOffUploadInterval();
+ else
+ upload_interval_ = TimeDelta::FromSeconds(kUnsentLogsIntervalSeconds);
+
+ if (more_logs_remaining)
+ ScheduleNextUpload();
+}
+
+void LogUploader::BackOffUploadInterval() {
+ DCHECK_GT(kBackoffMultiplier, 1.0);
+ upload_interval_ = TimeDelta::FromMicroseconds(static_cast<int64>(
+ kBackoffMultiplier * upload_interval_.InMicroseconds()));
+
+ TimeDelta max_interval = TimeDelta::FromSeconds(kMaxBackoffIntervalSeconds);
+ // Clamp large/overflowed times to the maximum interval.
+ if (upload_interval_ > max_interval || upload_interval_.InSeconds() < 0)
+ upload_interval_ = max_interval;
+}
+
+} // namespace rappor

Powered by Google App Engine
This is Rietveld 408576698