1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "components/rappor/log_uploader.h" 6 7 #include "base/metrics/histogram.h" 8 #include "base/metrics/sparse_histogram.h" 9 #include "net/base/load_flags.h" 10 #include "net/base/net_errors.h" 11 #include "net/url_request/url_fetcher.h" 12 13 namespace { 14 15 // The delay, in seconds, between uploading when there are queued logs to send. 16 const int kUnsentLogsIntervalSeconds = 3; 17 18 // When uploading metrics to the server fails, we progressively wait longer and 19 // longer before sending the next log. This backoff process helps reduce load 20 // on a server that is having issues. 21 // The following is the multiplier we use to expand that inter-log duration. 22 const double kBackoffMultiplier = 1.1; 23 24 // The maximum backoff multiplier. 25 const int kMaxBackoffIntervalSeconds = 60 * 60; 26 27 // The maximum number of unsent logs we will keep. 28 // TODO(holte): Limit based on log size instead. 29 const size_t kMaxQueuedLogs = 10; 30 31 enum DiscardReason { 32 UPLOAD_SUCCESS, 33 UPLOAD_REJECTED, 34 QUEUE_OVERFLOW, 35 NUM_DISCARD_REASONS 36 }; 37 38 } // namespace 39 40 namespace rappor { 41 42 LogUploader::LogUploader(const GURL& server_url, 43 const std::string& mime_type, 44 net::URLRequestContextGetter* request_context) 45 : server_url_(server_url), 46 mime_type_(mime_type), 47 request_context_(request_context), 48 has_callback_pending_(false), 49 upload_interval_(base::TimeDelta::FromSeconds( 50 kUnsentLogsIntervalSeconds)) { 51 } 52 53 LogUploader::~LogUploader() {} 54 55 void LogUploader::QueueLog(const std::string& log) { 56 queued_logs_.push(log); 57 if (!IsUploadScheduled() && !has_callback_pending_) 58 StartScheduledUpload(); 59 } 60 61 bool LogUploader::IsUploadScheduled() const { 62 return upload_timer_.IsRunning(); 63 } 64 65 void LogUploader::ScheduleNextUpload(base::TimeDelta interval) { 66 if (IsUploadScheduled() || has_callback_pending_) 67 return; 68 69 upload_timer_.Start( 70 FROM_HERE, interval, this, &LogUploader::StartScheduledUpload); 71 } 72 73 void LogUploader::StartScheduledUpload() { 74 DCHECK(!has_callback_pending_); 75 has_callback_pending_ = true; 76 current_fetch_.reset( 77 net::URLFetcher::Create(server_url_, net::URLFetcher::POST, this)); 78 current_fetch_->SetRequestContext(request_context_.get()); 79 current_fetch_->SetUploadData(mime_type_, queued_logs_.front()); 80 81 // We already drop cookies server-side, but we might as well strip them out 82 // client-side as well. 83 current_fetch_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES | 84 net::LOAD_DO_NOT_SEND_COOKIES); 85 current_fetch_->Start(); 86 } 87 88 // static 89 base::TimeDelta LogUploader::BackOffUploadInterval(base::TimeDelta interval) { 90 DCHECK_GT(kBackoffMultiplier, 1.0); 91 interval = base::TimeDelta::FromMicroseconds(static_cast<int64>( 92 kBackoffMultiplier * interval.InMicroseconds())); 93 94 base::TimeDelta max_interval = 95 base::TimeDelta::FromSeconds(kMaxBackoffIntervalSeconds); 96 return interval > max_interval ? max_interval : interval; 97 } 98 99 void LogUploader::OnURLFetchComplete(const net::URLFetcher* source) { 100 // We're not allowed to re-use the existing |URLFetcher|s, so free them here. 101 // Note however that |source| is aliased to the fetcher, so we should be 102 // careful not to delete it too early. 103 DCHECK_EQ(current_fetch_.get(), source); 104 scoped_ptr<net::URLFetcher> fetch(current_fetch_.Pass()); 105 106 const net::URLRequestStatus& request_status = source->GetStatus(); 107 108 const int response_code = source->GetResponseCode(); 109 110 if (request_status.status() != net::URLRequestStatus::SUCCESS) { 111 UMA_HISTOGRAM_SPARSE_SLOWLY("Rappor.FailedUploadErrorCode", 112 -request_status.error()); 113 DVLOG(1) << "Rappor server upload failed with error: " 114 << request_status.error() << ": " 115 << net::ErrorToString(request_status.error()); 116 DCHECK_EQ(-1, response_code); 117 } else { 118 // Log a histogram to track response success vs. failure rates. 119 UMA_HISTOGRAM_SPARSE_SLOWLY("Rappor.UploadResponseCode", response_code); 120 } 121 122 const bool upload_succeeded = response_code == 200; 123 124 // Determine whether this log should be retransmitted. 125 DiscardReason reason = NUM_DISCARD_REASONS; 126 if (upload_succeeded) { 127 reason = UPLOAD_SUCCESS; 128 } else if (response_code == 400) { 129 reason = UPLOAD_REJECTED; 130 } else if (queued_logs_.size() > kMaxQueuedLogs) { 131 reason = QUEUE_OVERFLOW; 132 } 133 134 if (reason != NUM_DISCARD_REASONS) { 135 UMA_HISTOGRAM_ENUMERATION("Rappor.DiscardReason", 136 reason, 137 NUM_DISCARD_REASONS); 138 queued_logs_.pop(); 139 } 140 141 // Error 400 indicates a problem with the log, not with the server, so 142 // don't consider that a sign that the server is in trouble. 143 const bool server_is_healthy = upload_succeeded || response_code == 400; 144 OnUploadFinished(server_is_healthy, !queued_logs_.empty()); 145 } 146 147 void LogUploader::OnUploadFinished(bool server_is_healthy, 148 bool more_logs_remaining) { 149 DCHECK(has_callback_pending_); 150 has_callback_pending_ = false; 151 // If the server is having issues, back off. Otherwise, reset to default. 152 if (!server_is_healthy) 153 upload_interval_ = BackOffUploadInterval(upload_interval_); 154 else 155 upload_interval_ = base::TimeDelta::FromSeconds(kUnsentLogsIntervalSeconds); 156 157 if (more_logs_remaining) 158 ScheduleNextUpload(upload_interval_); 159 } 160 161 } // namespace rappor 162