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 <stddef.h>
8 #include <stdint.h>
9 #include <utility>
10
11 #include "base/bind.h"
12 #include "base/metrics/histogram_functions.h"
13 #include "base/metrics/histogram_macros.h"
14 #include "net/base/load_flags.h"
15 #include "net/base/net_errors.h"
16 #include "net/traffic_annotation/network_traffic_annotation.h"
17 #include "services/network/public/cpp/resource_request.h"
18 #include "services/network/public/cpp/shared_url_loader_factory.h"
19 #include "services/network/public/cpp/simple_url_loader.h"
20
21 namespace {
22
23 // The delay, in seconds, between uploading when there are queued logs to send.
24 const int kUnsentLogsIntervalSeconds = 3;
25
26 // When uploading metrics to the server fails, we progressively wait longer and
27 // longer before sending the next log. This backoff process helps reduce load
28 // on a server that is having issues.
29 // The following is the multiplier we use to expand that inter-log duration.
30 const double kBackoffMultiplier = 1.1;
31
32 // The maximum backoff multiplier.
33 const int kMaxBackoffIntervalSeconds = 60 * 60;
34
35 // The maximum number of unsent logs we will keep.
36 // TODO(holte): Limit based on log size instead.
37 const size_t kMaxQueuedLogs = 10;
38
39 enum DiscardReason {
40 UPLOAD_SUCCESS,
41 UPLOAD_REJECTED,
42 QUEUE_OVERFLOW,
43 NUM_DISCARD_REASONS
44 };
45
RecordDiscardReason(DiscardReason reason)46 void RecordDiscardReason(DiscardReason reason) {
47 UMA_HISTOGRAM_ENUMERATION("Rappor.DiscardReason",
48 reason,
49 NUM_DISCARD_REASONS);
50 }
51
52 } // namespace
53
54 namespace rappor {
55
LogUploader(const GURL & server_url,const std::string & mime_type,scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)56 LogUploader::LogUploader(
57 const GURL& server_url,
58 const std::string& mime_type,
59 scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
60 : server_url_(server_url),
61 mime_type_(mime_type),
62 url_loader_factory_(std::move(url_loader_factory)),
63 is_running_(false),
64 has_callback_pending_(false),
65 upload_interval_(
66 base::TimeDelta::FromSeconds(kUnsentLogsIntervalSeconds)) {}
67
~LogUploader()68 LogUploader::~LogUploader() {}
69
Start()70 void LogUploader::Start() {
71 is_running_ = true;
72 StartScheduledUpload();
73 }
74
Stop()75 void LogUploader::Stop() {
76 is_running_ = false;
77 // Rather than interrupting the current upload, just let it finish/fail and
78 // then inhibit any retry attempts.
79 }
80
QueueLog(const std::string & log)81 void LogUploader::QueueLog(const std::string& log) {
82 queued_logs_.push(log);
83 // Don't drop logs yet if an upload is in progress. They will be dropped
84 // when it finishes.
85 if (!has_callback_pending_)
86 DropExcessLogs();
87 StartScheduledUpload();
88 }
89
DropExcessLogs()90 void LogUploader::DropExcessLogs() {
91 while (queued_logs_.size() > kMaxQueuedLogs) {
92 DVLOG(2) << "Dropping excess log.";
93 RecordDiscardReason(QUEUE_OVERFLOW);
94 queued_logs_.pop();
95 }
96 }
97
IsUploadScheduled() const98 bool LogUploader::IsUploadScheduled() const {
99 return upload_timer_.IsRunning();
100 }
101
ScheduleNextUpload(base::TimeDelta interval)102 void LogUploader::ScheduleNextUpload(base::TimeDelta interval) {
103 upload_timer_.Start(
104 FROM_HERE, interval, this, &LogUploader::StartScheduledUpload);
105 }
106
CanStartUpload() const107 bool LogUploader::CanStartUpload() const {
108 return is_running_ &&
109 !queued_logs_.empty() &&
110 !IsUploadScheduled() &&
111 !has_callback_pending_;
112 }
113
StartScheduledUpload()114 void LogUploader::StartScheduledUpload() {
115 if (!CanStartUpload())
116 return;
117 DVLOG(2) << "Upload to " << server_url_.spec() << " starting.";
118 has_callback_pending_ = true;
119 net::NetworkTrafficAnnotationTag traffic_annotation =
120 net::DefineNetworkTrafficAnnotation("rappor_report", R"(
121 semantics {
122 sender: "RAPPOR"
123 description:
124 "This service sends RAPPOR anonymous usage statistics to Google."
125 trigger:
126 "Reports are automatically generated on startup and at intervals "
127 "while Chromium is running."
128 data: "A protocol buffer with RAPPOR anonymous usage statistics."
129 destination: GOOGLE_OWNED_SERVICE
130 }
131 policy {
132 cookies_allowed: NO
133 setting:
134 "Users can enable or disable this feature by stopping "
135 "'Automatically send usage statistics and crash reports to Google'"
136 "in Chromium's settings under Advanced Settings, Privacy. The "
137 "feature is enabled by default."
138 chrome_policy {
139 MetricsReportingEnabled {
140 policy_options {mode: MANDATORY}
141 MetricsReportingEnabled: false
142 }
143 }
144 })");
145
146 auto resource_request = std::make_unique<network::ResourceRequest>();
147 resource_request->url = server_url_;
148 // We already drop cookies server-side, but we might as well strip them out
149 // client-side as well.
150 resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
151 resource_request->method = "POST";
152 simple_url_loader_ = network::SimpleURLLoader::Create(
153 std::move(resource_request), traffic_annotation);
154 simple_url_loader_->AttachStringForUpload(queued_logs_.front(), mime_type_);
155 // TODO re-add data use measurement once SimpleURLLoader supports it.
156 // ID=data_use_measurement::DataUseUserData::RAPPOR
157 simple_url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
158 url_loader_factory_.get(),
159 base::BindOnce(&LogUploader::OnSimpleLoaderComplete,
160 base::Unretained(this)));
161 }
162
163 // static
164 base::TimeDelta LogUploader::BackOffUploadInterval(base::TimeDelta interval) {
165 DCHECK_GT(kBackoffMultiplier, 1.0);
166 interval = base::TimeDelta::FromMicroseconds(
167 static_cast<int64_t>(kBackoffMultiplier * interval.InMicroseconds()));
168
169 base::TimeDelta max_interval =
170 base::TimeDelta::FromSeconds(kMaxBackoffIntervalSeconds);
171 return interval > max_interval ? max_interval : interval;
172 }
173
174 void LogUploader::OnSimpleLoaderComplete(
175 std::unique_ptr<std::string> response_body) {
176 int response_code = -1;
177 if (simple_url_loader_->ResponseInfo() &&
178 simple_url_loader_->ResponseInfo()->headers) {
179 response_code =
180 simple_url_loader_->ResponseInfo()->headers->response_code();
181 }
182 DVLOG(2) << "Upload fetch complete response code: " << response_code;
183
184 int net_error = simple_url_loader_->NetError();
185 if (net_error != net::OK && (response_code == -1 || response_code == 200)) {
186 base::UmaHistogramSparse("Rappor.FailedUploadErrorCode", -net_error);
187 DVLOG(1) << "Rappor server upload failed with error: " << net_error << ": "
188 << net::ErrorToString(net_error);
189 } else {
190 // Log a histogram to track response success vs. failure rates.
191 base::UmaHistogramSparse("Rappor.UploadResponseCode", response_code);
192 }
193
194 const bool upload_succeeded = !!response_body;
195
196 // Determine whether this log should be retransmitted.
197 DiscardReason reason = NUM_DISCARD_REASONS;
198 if (upload_succeeded) {
199 reason = UPLOAD_SUCCESS;
200 } else if (response_code == 400) {
201 reason = UPLOAD_REJECTED;
202 }
203
204 if (reason != NUM_DISCARD_REASONS) {
205 DVLOG(2) << "Log discarded.";
206 RecordDiscardReason(reason);
207 queued_logs_.pop();
208 }
209
210 DropExcessLogs();
211
212 // Error 400 indicates a problem with the log, not with the server, so
213 // don't consider that a sign that the server is in trouble.
214 const bool server_is_healthy = upload_succeeded || response_code == 400;
215 OnUploadFinished(server_is_healthy);
216 }
217
OnUploadFinished(bool server_is_healthy)218 void LogUploader::OnUploadFinished(bool server_is_healthy) {
219 DCHECK(has_callback_pending_);
220 has_callback_pending_ = false;
221 // If the server is having issues, back off. Otherwise, reset to default.
222 if (!server_is_healthy)
223 upload_interval_ = BackOffUploadInterval(upload_interval_);
224 else
225 upload_interval_ = base::TimeDelta::FromSeconds(kUnsentLogsIntervalSeconds);
226
227 if (CanStartUpload())
228 ScheduleNextUpload(upload_interval_);
229 }
230
231 } // namespace rappor
232