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