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
GetUnsignedFieldTrialValueOrDefault(std::string field_trial_name,unsigned default_value)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
GetFromFieldTrialsOrDefaults()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
DomainReliabilityScheduler(MockableTime * time,size_t num_collectors,const Params & params,const ScheduleUploadCallback & callback)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
~DomainReliabilityScheduler()80 DomainReliabilityScheduler::~DomainReliabilityScheduler() {}
81
OnBeaconAdded()82 void DomainReliabilityScheduler::OnBeaconAdded() {
83 if (!upload_pending_)
84 first_beacon_time_ = time_->NowTicks();
85 upload_pending_ = true;
86 MaybeScheduleUpload();
87 }
88
OnUploadStart()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
OnUploadComplete(bool success)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
CollectorState()137 DomainReliabilityScheduler::CollectorState::CollectorState() : failures(0) {}
138
MaybeScheduleUpload()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
GetNextUploadTimeAndCollector(base::TimeTicks now,base::TimeTicks * upload_time_out,size_t * collector_index_out)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
GetUploadRetryInterval(unsigned failures)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