Home | History | Annotate | Download | only in domain_reliability
      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/domain_reliability/scheduler.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/metrics/field_trial.h"
     10 #include "base/strings/string_number_conversions.h"
     11 #include "components/domain_reliability/config.h"
     12 #include "components/domain_reliability/util.h"
     13 
     14 namespace {
     15 
     16 const unsigned kInvalidCollectorIndex = -1;
     17 
     18 const unsigned kDefaultMinimumUploadDelaySec = 60;
     19 const unsigned kDefaultMaximumUploadDelaySec = 300;
     20 const unsigned kDefaultUploadRetryIntervalSec = 60;
     21 
     22 const char* kMinimumUploadDelayFieldTrialName = "DomRel-MinimumUploadDelay";
     23 const char* kMaximumUploadDelayFieldTrialName = "DomRel-MaximumUploadDelay";
     24 const char* kUploadRetryIntervalFieldTrialName = "DomRel-UploadRetryInterval";
     25 
     26 unsigned GetUnsignedFieldTrialValueOrDefault(std::string field_trial_name,
     27                                              unsigned default_value) {
     28   if (!base::FieldTrialList::TrialExists(field_trial_name))
     29     return default_value;
     30 
     31   std::string group_name = base::FieldTrialList::FindFullName(field_trial_name);
     32   unsigned value;
     33   if (!base::StringToUint(group_name, &value)) {
     34     LOG(ERROR) << "Expected unsigned integer for field trial "
     35                << field_trial_name << " group name, but got \"" << group_name
     36                << "\".";
     37     return default_value;
     38   }
     39 
     40   return value;
     41 }
     42 
     43 }  // namespace
     44 
     45 namespace domain_reliability {
     46 
     47 // static
     48 DomainReliabilityScheduler::Params
     49 DomainReliabilityScheduler::Params::GetFromFieldTrialsOrDefaults() {
     50   DomainReliabilityScheduler::Params params;
     51 
     52   params.minimum_upload_delay =
     53       base::TimeDelta::FromSeconds(GetUnsignedFieldTrialValueOrDefault(
     54           kMinimumUploadDelayFieldTrialName, kDefaultMinimumUploadDelaySec));
     55   params.maximum_upload_delay =
     56       base::TimeDelta::FromSeconds(GetUnsignedFieldTrialValueOrDefault(
     57           kMaximumUploadDelayFieldTrialName, kDefaultMaximumUploadDelaySec));
     58   params.upload_retry_interval =
     59       base::TimeDelta::FromSeconds(GetUnsignedFieldTrialValueOrDefault(
     60           kUploadRetryIntervalFieldTrialName, kDefaultUploadRetryIntervalSec));
     61 
     62   return params;
     63 }
     64 
     65 DomainReliabilityScheduler::DomainReliabilityScheduler(
     66     MockableTime* time,
     67     size_t num_collectors,
     68     const Params& params,
     69     const ScheduleUploadCallback& callback)
     70     : time_(time),
     71       collectors_(num_collectors),
     72       params_(params),
     73       callback_(callback),
     74       upload_pending_(false),
     75       upload_scheduled_(false),
     76       upload_running_(false),
     77       collector_index_(kInvalidCollectorIndex) {
     78 }
     79 
     80 DomainReliabilityScheduler::~DomainReliabilityScheduler() {}
     81 
     82 void DomainReliabilityScheduler::OnBeaconAdded() {
     83   if (!upload_pending_)
     84     first_beacon_time_ = time_->NowTicks();
     85   upload_pending_ = true;
     86   MaybeScheduleUpload();
     87 }
     88 
     89 size_t DomainReliabilityScheduler::OnUploadStart() {
     90   DCHECK(upload_scheduled_);
     91   DCHECK_EQ(kInvalidCollectorIndex, collector_index_);
     92   upload_pending_ = false;
     93   upload_scheduled_ = false;
     94   upload_running_ = true;
     95 
     96   base::TimeTicks now = time_->NowTicks();
     97   base::TimeTicks min_upload_time;
     98   GetNextUploadTimeAndCollector(now, &min_upload_time, &collector_index_);
     99   DCHECK(min_upload_time <= now);
    100 
    101   VLOG(1) << "Starting upload to collector " << collector_index_ << ".";
    102 
    103   return collector_index_;
    104 }
    105 
    106 void DomainReliabilityScheduler::OnUploadComplete(bool success) {
    107   DCHECK(upload_running_);
    108   DCHECK_NE(kInvalidCollectorIndex, collector_index_);
    109   upload_running_ = false;
    110 
    111   VLOG(1) << "Upload to collector " << collector_index_
    112           << (success ? " succeeded." : " failed.");
    113 
    114   CollectorState* collector = &collectors_[collector_index_];
    115   collector_index_ = kInvalidCollectorIndex;
    116 
    117   if (success) {
    118     collector->failures = 0;
    119   } else {
    120     // Restore upload_pending_ and first_beacon_time_ to pre-upload state,
    121     // since upload failed.
    122     upload_pending_ = true;
    123     first_beacon_time_ = old_first_beacon_time_;
    124 
    125     ++collector->failures;
    126   }
    127 
    128   base::TimeDelta retry_interval = GetUploadRetryInterval(collector->failures);
    129   collector->next_upload = time_->NowTicks() + retry_interval;
    130 
    131   VLOG(1) << "Next upload to collector at least "
    132           << retry_interval.InSeconds() << " seconds from now.";
    133 
    134   MaybeScheduleUpload();
    135 }
    136 
    137 DomainReliabilityScheduler::CollectorState::CollectorState() : failures(0) {}
    138 
    139 void DomainReliabilityScheduler::MaybeScheduleUpload() {
    140   if (!upload_pending_ || upload_scheduled_ || upload_running_)
    141     return;
    142 
    143   upload_scheduled_ = true;
    144   old_first_beacon_time_ = first_beacon_time_;
    145 
    146   base::TimeTicks now = time_->NowTicks();
    147 
    148   base::TimeTicks min_by_deadline, max_by_deadline;
    149   min_by_deadline = first_beacon_time_ + params_.minimum_upload_delay;
    150   max_by_deadline = first_beacon_time_ + params_.maximum_upload_delay;
    151   DCHECK(min_by_deadline <= max_by_deadline);
    152 
    153   base::TimeTicks min_by_backoff;
    154   size_t collector_index;
    155   GetNextUploadTimeAndCollector(now, &min_by_backoff, &collector_index);
    156 
    157   base::TimeDelta min_delay = std::max(min_by_deadline, min_by_backoff) - now;
    158   base::TimeDelta max_delay = std::max(max_by_deadline, min_by_backoff) - now;
    159 
    160   VLOG(1) << "Scheduling upload for between " << min_delay.InSeconds()
    161           << " and " << max_delay.InSeconds() << " seconds from now.";
    162 
    163   callback_.Run(min_delay, max_delay);
    164 }
    165 
    166 // TODO(ttuttle): Add min and max interval to config, use that instead.
    167 
    168 // TODO(ttuttle): Cap min and max intervals received from config.
    169 
    170 void DomainReliabilityScheduler::GetNextUploadTimeAndCollector(
    171     base::TimeTicks now,
    172     base::TimeTicks* upload_time_out,
    173     size_t* collector_index_out) {
    174   DCHECK(upload_time_out);
    175   DCHECK(collector_index_out);
    176 
    177   base::TimeTicks min_time;
    178   size_t min_index = kInvalidCollectorIndex;
    179 
    180   for (size_t i = 0; i < collectors_.size(); ++i) {
    181     CollectorState* collector = &collectors_[i];
    182     // If a collector is usable, use the first one in the list.
    183     if (collector->failures == 0 || collector->next_upload <= now) {
    184       min_time = now;
    185       min_index = i;
    186       break;
    187     // If not, keep track of which will be usable soonest:
    188     } else if (min_index == kInvalidCollectorIndex ||
    189         collector->next_upload < min_time) {
    190       min_time = collector->next_upload;
    191       min_index = i;
    192     }
    193   }
    194 
    195   DCHECK_NE(kInvalidCollectorIndex, min_index);
    196   *upload_time_out = min_time;
    197   *collector_index_out = min_index;
    198 }
    199 
    200 base::TimeDelta DomainReliabilityScheduler::GetUploadRetryInterval(
    201     unsigned failures) {
    202   if (failures == 0)
    203     return base::TimeDelta::FromSeconds(0);
    204   else {
    205     // Don't back off more than 64x the original delay.
    206     if (failures > 7)
    207       failures = 7;
    208     return params_.upload_retry_interval * (1 << (failures - 1));
    209   }
    210 }
    211 
    212 }  // namespace domain_reliability
    213