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
13namespace {
14
15// The delay, in seconds, between uploading when there are queued logs to send.
16const 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.
22const double kBackoffMultiplier = 1.1;
23
24// The maximum backoff multiplier.
25const int kMaxBackoffIntervalSeconds = 60 * 60;
26
27// The maximum number of unsent logs we will keep.
28// TODO(holte): Limit based on log size instead.
29const size_t kMaxQueuedLogs = 10;
30
31enum DiscardReason {
32  UPLOAD_SUCCESS,
33  UPLOAD_REJECTED,
34  QUEUE_OVERFLOW,
35  NUM_DISCARD_REASONS
36};
37
38}  // namespace
39
40namespace rappor {
41
42LogUploader::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
53LogUploader::~LogUploader() {}
54
55void LogUploader::QueueLog(const std::string& log) {
56  queued_logs_.push(log);
57  if (!IsUploadScheduled() && !has_callback_pending_)
58    StartScheduledUpload();
59}
60
61bool LogUploader::IsUploadScheduled() const {
62  return upload_timer_.IsRunning();
63}
64
65void 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
73void 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
89base::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
99void 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
147void 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