blob: 7f2f0f796a122c9e0150f480d7f53987fcc385e1 [file] [log] [blame]
[email protected]2a172e42014-02-21 04:06:101// 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
avif57136c12015-12-25 23:27:457#include <stddef.h>
8#include <stdint.h>
dcheng51ace48a2015-12-26 22:45:179#include <utility>
avif57136c12015-12-25 23:27:4510
asvitkine454600f2015-06-16 16:34:5011#include "base/metrics/histogram_macros.h"
[email protected]2a172e42014-02-21 04:06:1012#include "base/metrics/sparse_histogram.h"
amohammadkhanf76ae112015-09-14 17:34:4313#include "components/data_use_measurement/core/data_use_user_data.h"
[email protected]2a172e42014-02-21 04:06:1014#include "net/base/load_flags.h"
[email protected]f30f4832014-05-07 15:29:5015#include "net/base/net_errors.h"
[email protected]2a172e42014-02-21 04:06:1016#include "net/url_request/url_fetcher.h"
17
18namespace {
19
20// The delay, in seconds, between uploading when there are queued logs to send.
21const int kUnsentLogsIntervalSeconds = 3;
22
23// When uploading metrics to the server fails, we progressively wait longer and
24// longer before sending the next log. This backoff process helps reduce load
25// on a server that is having issues.
26// The following is the multiplier we use to expand that inter-log duration.
27const double kBackoffMultiplier = 1.1;
28
29// The maximum backoff multiplier.
30const int kMaxBackoffIntervalSeconds = 60 * 60;
31
32// The maximum number of unsent logs we will keep.
33// TODO(holte): Limit based on log size instead.
34const size_t kMaxQueuedLogs = 10;
35
36enum DiscardReason {
37 UPLOAD_SUCCESS,
38 UPLOAD_REJECTED,
39 QUEUE_OVERFLOW,
40 NUM_DISCARD_REASONS
41};
42
holte5a7ed7c2015-01-09 23:52:4643void RecordDiscardReason(DiscardReason reason) {
44 UMA_HISTOGRAM_ENUMERATION("Rappor.DiscardReason",
45 reason,
46 NUM_DISCARD_REASONS);
47}
48
[email protected]2a172e42014-02-21 04:06:1049} // namespace
50
51namespace rappor {
52
53LogUploader::LogUploader(const GURL& server_url,
54 const std::string& mime_type,
55 net::URLRequestContextGetter* request_context)
56 : server_url_(server_url),
57 mime_type_(mime_type),
58 request_context_(request_context),
holte5a7ed7c2015-01-09 23:52:4659 is_running_(false),
[email protected]2a172e42014-02-21 04:06:1060 has_callback_pending_(false),
61 upload_interval_(base::TimeDelta::FromSeconds(
62 kUnsentLogsIntervalSeconds)) {
63}
64
65LogUploader::~LogUploader() {}
66
holte5a7ed7c2015-01-09 23:52:4667void LogUploader::Start() {
68 is_running_ = true;
69 StartScheduledUpload();
70}
71
72void LogUploader::Stop() {
73 is_running_ = false;
74 // Rather than interrupting the current upload, just let it finish/fail and
75 // then inhibit any retry attempts.
76}
77
[email protected]2a172e42014-02-21 04:06:1078void LogUploader::QueueLog(const std::string& log) {
79 queued_logs_.push(log);
holte5a7ed7c2015-01-09 23:52:4680 // Don't drop logs yet if an upload is in progress. They will be dropped
81 // when it finishes.
82 if (!has_callback_pending_)
83 DropExcessLogs();
84 StartScheduledUpload();
85}
86
87void LogUploader::DropExcessLogs() {
88 while (queued_logs_.size() > kMaxQueuedLogs) {
89 DVLOG(2) << "Dropping excess log.";
90 RecordDiscardReason(QUEUE_OVERFLOW);
91 queued_logs_.pop();
92 }
[email protected]2a172e42014-02-21 04:06:1093}
94
95bool LogUploader::IsUploadScheduled() const {
96 return upload_timer_.IsRunning();
97}
98
99void LogUploader::ScheduleNextUpload(base::TimeDelta interval) {
[email protected]2a172e42014-02-21 04:06:10100 upload_timer_.Start(
101 FROM_HERE, interval, this, &LogUploader::StartScheduledUpload);
102}
103
holte5a7ed7c2015-01-09 23:52:46104bool LogUploader::CanStartUpload() const {
105 return is_running_ &&
106 !queued_logs_.empty() &&
107 !IsUploadScheduled() &&
108 !has_callback_pending_;
109}
110
[email protected]2a172e42014-02-21 04:06:10111void LogUploader::StartScheduledUpload() {
holte5a7ed7c2015-01-09 23:52:46112 if (!CanStartUpload())
113 return;
114 DVLOG(2) << "Upload to " << server_url_.spec() << " starting.";
[email protected]2a172e42014-02-21 04:06:10115 has_callback_pending_ = true;
dtapuskadafcf892015-05-01 13:58:25116 current_fetch_ =
117 net::URLFetcher::Create(server_url_, net::URLFetcher::POST, this);
amohammadkhanf76ae112015-09-14 17:34:43118 data_use_measurement::DataUseUserData::AttachToFetcher(
119 current_fetch_.get(), data_use_measurement::DataUseUserData::RAPPOR);
[email protected]2a172e42014-02-21 04:06:10120 current_fetch_->SetRequestContext(request_context_.get());
121 current_fetch_->SetUploadData(mime_type_, queued_logs_.front());
122
123 // We already drop cookies server-side, but we might as well strip them out
124 // client-side as well.
125 current_fetch_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES |
126 net::LOAD_DO_NOT_SEND_COOKIES);
127 current_fetch_->Start();
128}
129
130// static
131base::TimeDelta LogUploader::BackOffUploadInterval(base::TimeDelta interval) {
132 DCHECK_GT(kBackoffMultiplier, 1.0);
avif57136c12015-12-25 23:27:45133 interval = base::TimeDelta::FromMicroseconds(
134 static_cast<int64_t>(kBackoffMultiplier * interval.InMicroseconds()));
[email protected]2a172e42014-02-21 04:06:10135
136 base::TimeDelta max_interval =
137 base::TimeDelta::FromSeconds(kMaxBackoffIntervalSeconds);
138 return interval > max_interval ? max_interval : interval;
139}
140
141void LogUploader::OnURLFetchComplete(const net::URLFetcher* source) {
142 // We're not allowed to re-use the existing |URLFetcher|s, so free them here.
143 // Note however that |source| is aliased to the fetcher, so we should be
144 // careful not to delete it too early.
145 DCHECK_EQ(current_fetch_.get(), source);
dcheng82beb4f2016-04-26 00:35:02146 std::unique_ptr<net::URLFetcher> fetch(std::move(current_fetch_));
[email protected]2a172e42014-02-21 04:06:10147
[email protected]f30f4832014-05-07 15:29:50148 const net::URLRequestStatus& request_status = source->GetStatus();
149
[email protected]ccb49262014-03-26 04:10:17150 const int response_code = source->GetResponseCode();
holte5a7ed7c2015-01-09 23:52:46151 DVLOG(2) << "Upload fetch complete response code: " << response_code;
[email protected]2a172e42014-02-21 04:06:10152
[email protected]f30f4832014-05-07 15:29:50153 if (request_status.status() != net::URLRequestStatus::SUCCESS) {
154 UMA_HISTOGRAM_SPARSE_SLOWLY("Rappor.FailedUploadErrorCode",
155 -request_status.error());
156 DVLOG(1) << "Rappor server upload failed with error: "
157 << request_status.error() << ": "
158 << net::ErrorToString(request_status.error());
159 DCHECK_EQ(-1, response_code);
160 } else {
161 // Log a histogram to track response success vs. failure rates.
162 UMA_HISTOGRAM_SPARSE_SLOWLY("Rappor.UploadResponseCode", response_code);
163 }
[email protected]2a172e42014-02-21 04:06:10164
[email protected]ccb49262014-03-26 04:10:17165 const bool upload_succeeded = response_code == 200;
[email protected]2a172e42014-02-21 04:06:10166
167 // Determine whether this log should be retransmitted.
168 DiscardReason reason = NUM_DISCARD_REASONS;
169 if (upload_succeeded) {
170 reason = UPLOAD_SUCCESS;
171 } else if (response_code == 400) {
172 reason = UPLOAD_REJECTED;
[email protected]2a172e42014-02-21 04:06:10173 }
174
175 if (reason != NUM_DISCARD_REASONS) {
holte5a7ed7c2015-01-09 23:52:46176 DVLOG(2) << "Log discarded.";
177 RecordDiscardReason(reason);
[email protected]2a172e42014-02-21 04:06:10178 queued_logs_.pop();
179 }
180
holte5a7ed7c2015-01-09 23:52:46181 DropExcessLogs();
182
[email protected]2a172e42014-02-21 04:06:10183 // Error 400 indicates a problem with the log, not with the server, so
184 // don't consider that a sign that the server is in trouble.
[email protected]ccb49262014-03-26 04:10:17185 const bool server_is_healthy = upload_succeeded || response_code == 400;
holte5a7ed7c2015-01-09 23:52:46186 OnUploadFinished(server_is_healthy);
[email protected]2a172e42014-02-21 04:06:10187}
188
holte5a7ed7c2015-01-09 23:52:46189void LogUploader::OnUploadFinished(bool server_is_healthy) {
[email protected]2a172e42014-02-21 04:06:10190 DCHECK(has_callback_pending_);
191 has_callback_pending_ = false;
192 // If the server is having issues, back off. Otherwise, reset to default.
193 if (!server_is_healthy)
194 upload_interval_ = BackOffUploadInterval(upload_interval_);
195 else
196 upload_interval_ = base::TimeDelta::FromSeconds(kUnsentLogsIntervalSeconds);
197
holte5a7ed7c2015-01-09 23:52:46198 if (CanStartUpload())
[email protected]2a172e42014-02-21 04:06:10199 ScheduleNextUpload(upload_interval_);
200}
201
202} // namespace rappor