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.
5 #include "components/rappor/log_uploader.h"
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"
15 // The delay, in seconds, between uploading when there are queued logs to send.
16 const int kUnsentLogsIntervalSeconds
= 3;
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;
24 // The maximum backoff multiplier.
25 const int kMaxBackoffIntervalSeconds
= 60 * 60;
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;
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
)) {
53 LogUploader::~LogUploader() {}
55 void LogUploader::QueueLog(const std::string
& log
) {
56 queued_logs_
.push(log
);
57 if (!IsUploadScheduled() && !has_callback_pending_
)
58 StartScheduledUpload();
61 bool LogUploader::IsUploadScheduled() const {
62 return upload_timer_
.IsRunning();
65 void LogUploader::ScheduleNextUpload(base::TimeDelta interval
) {
66 if (IsUploadScheduled() || has_callback_pending_
)
70 FROM_HERE
, interval
, this, &LogUploader::StartScheduledUpload
);
73 void LogUploader::StartScheduledUpload() {
74 DCHECK(!has_callback_pending_
);
75 has_callback_pending_
= true;
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());
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();
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()));
94 base::TimeDelta max_interval
=
95 base::TimeDelta::FromSeconds(kMaxBackoffIntervalSeconds
);
96 return interval
> max_interval
? max_interval
: interval
;
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());
106 const net::URLRequestStatus
& request_status
= source
->GetStatus();
108 const int response_code
= source
->GetResponseCode();
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
);
118 // Log a histogram to track response success vs. failure rates.
119 UMA_HISTOGRAM_SPARSE_SLOWLY("Rappor.UploadResponseCode", response_code
);
122 const bool upload_succeeded
= response_code
== 200;
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
;
134 if (reason
!= NUM_DISCARD_REASONS
) {
135 UMA_HISTOGRAM_ENUMERATION("Rappor.DiscardReason",
137 NUM_DISCARD_REASONS
);
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());
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_
);
155 upload_interval_
= base::TimeDelta::FromSeconds(kUnsentLogsIntervalSeconds
);
157 if (more_logs_remaining
)
158 ScheduleNextUpload(upload_interval_
);
161 } // namespace rappor