Home | History | Annotate | Download | only in rappor
      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 
     13 namespace {
     14 
     15 // The delay, in seconds, between uploading when there are queued logs to send.
     16 const 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.
     22 const double kBackoffMultiplier = 1.1;
     23 
     24 // The maximum backoff multiplier.
     25 const int kMaxBackoffIntervalSeconds = 60 * 60;
     26 
     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;
     30 
     31 enum DiscardReason {
     32   UPLOAD_SUCCESS,
     33   UPLOAD_REJECTED,
     34   QUEUE_OVERFLOW,
     35   NUM_DISCARD_REASONS
     36 };
     37 
     38 }  // namespace
     39 
     40 namespace rappor {
     41 
     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)) {
     51 }
     52 
     53 LogUploader::~LogUploader() {}
     54 
     55 void LogUploader::QueueLog(const std::string& log) {
     56   queued_logs_.push(log);
     57   if (!IsUploadScheduled() && !has_callback_pending_)
     58     StartScheduledUpload();
     59 }
     60 
     61 bool LogUploader::IsUploadScheduled() const {
     62   return upload_timer_.IsRunning();
     63 }
     64 
     65 void 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 
     73 void 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
     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()));
     93 
     94   base::TimeDelta max_interval =
     95       base::TimeDelta::FromSeconds(kMaxBackoffIntervalSeconds);
     96   return interval > max_interval ? max_interval : interval;
     97 }
     98 
     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());
    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 
    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_);
    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