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