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
15namespace {
16
17const unsigned kInvalidCollectorIndex = static_cast<unsigned>(-1);
18
19const unsigned kDefaultMinimumUploadDelaySec = 60;
20const unsigned kDefaultMaximumUploadDelaySec = 300;
21const unsigned kDefaultUploadRetryIntervalSec = 60;
22
23const char* kMinimumUploadDelayFieldTrialName = "DomRel-MinimumUploadDelay";
24const char* kMaximumUploadDelayFieldTrialName = "DomRel-MaximumUploadDelay";
25const char* kUploadRetryIntervalFieldTrialName = "DomRel-UploadRetryInterval";
26
27unsigned 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
46namespace domain_reliability {
47
48// static
49DomainReliabilityScheduler::Params
50DomainReliabilityScheduler::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
66DomainReliabilityScheduler::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
82DomainReliabilityScheduler::~DomainReliabilityScheduler() {}
83
84void DomainReliabilityScheduler::OnBeaconAdded() {
85  if (!upload_pending_)
86    first_beacon_time_ = time_->NowTicks();
87  upload_pending_ = true;
88  MaybeScheduleUpload();
89}
90
91size_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
111void 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
147base::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
184DomainReliabilityScheduler::CollectorState::CollectorState() : failures(0) {}
185
186void 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
220void 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
250base::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