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 13namespace { 14 15// The delay, in seconds, between uploading when there are queued logs to send. 16const 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. 22const double kBackoffMultiplier = 1.1; 23 24// The maximum backoff multiplier. 25const int kMaxBackoffIntervalSeconds = 60 * 60; 26 27// The maximum number of unsent logs we will keep. 28// TODO(holte): Limit based on log size instead. 29const size_t kMaxQueuedLogs = 10; 30 31enum DiscardReason { 32 UPLOAD_SUCCESS, 33 UPLOAD_REJECTED, 34 QUEUE_OVERFLOW, 35 NUM_DISCARD_REASONS 36}; 37 38} // namespace 39 40namespace rappor { 41 42LogUploader::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 53LogUploader::~LogUploader() {} 54 55void LogUploader::QueueLog(const std::string& log) { 56 queued_logs_.push(log); 57 if (!IsUploadScheduled() && !has_callback_pending_) 58 StartScheduledUpload(); 59} 60 61bool LogUploader::IsUploadScheduled() const { 62 return upload_timer_.IsRunning(); 63} 64 65void 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 73void 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 89base::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 99void 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 147void 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