• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
LogUploader(const GURL & server_url,const std::string & mime_type,net::URLRequestContextGetter * request_context)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 
~LogUploader()53 LogUploader::~LogUploader() {}
54 
QueueLog(const std::string & log)55 void LogUploader::QueueLog(const std::string& log) {
56   queued_logs_.push(log);
57   if (!IsUploadScheduled() && !has_callback_pending_)
58     StartScheduledUpload();
59 }
60 
IsUploadScheduled() const61 bool LogUploader::IsUploadScheduled() const {
62   return upload_timer_.IsRunning();
63 }
64 
ScheduleNextUpload(base::TimeDelta interval)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 
StartScheduledUpload()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
BackOffUploadInterval(base::TimeDelta interval)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 
OnURLFetchComplete(const net::URLFetcher * source)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 
OnUploadFinished(bool server_is_healthy,bool more_logs_remaining)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