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