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