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/metrics/metrics_reporting_scheduler.h"
6
7#include "base/compiler_specific.h"
8#include "base/metrics/histogram.h"
9
10using base::TimeDelta;
11
12namespace metrics {
13
14namespace {
15
16// The delay, in seconds, after startup before sending the first log message.
17#if defined(OS_ANDROID) || defined(OS_IOS)
18// Sessions are more likely to be short on a mobile device, so handle the
19// initial log quickly.
20const int kInitialUploadIntervalSeconds = 15;
21#else
22const int kInitialUploadIntervalSeconds = 60;
23#endif
24
25// The delay, in seconds, between uploading when there are queued logs from
26// previous sessions to send.
27#if defined(OS_ANDROID) || defined(OS_IOS)
28// Sending in a burst is better on a mobile device, since keeping the radio on
29// is very expensive.
30const int kUnsentLogsIntervalSeconds = 3;
31#else
32const int kUnsentLogsIntervalSeconds = 15;
33#endif
34
35// Standard interval between log uploads, in seconds.
36#if defined(OS_ANDROID) || defined(OS_IOS)
37const int kStandardUploadIntervalSeconds = 5 * 60;  // Five minutes.
38#else
39const int kStandardUploadIntervalSeconds = 30 * 60;  // Thirty minutes.
40#endif
41
42// When uploading metrics to the server fails, we progressively wait longer and
43// longer before sending the next log. This backoff process helps reduce load
44// on a server that is having issues.
45// The following is the multiplier we use to expand that inter-log duration.
46const double kBackoffMultiplier = 1.1;
47
48// The maximum backoff multiplier.
49const int kMaxBackoffMultiplier = 10;
50
51enum InitSequence {
52  TIMER_FIRED_FIRST,
53  INIT_TASK_COMPLETED_FIRST,
54  INIT_SEQUENCE_ENUM_SIZE,
55};
56
57void LogMetricsInitSequence(InitSequence sequence) {
58  UMA_HISTOGRAM_ENUMERATION("UMA.InitSequence", sequence,
59                            INIT_SEQUENCE_ENUM_SIZE);
60}
61
62}  // anonymous namespace
63
64MetricsReportingScheduler::MetricsReportingScheduler(
65    const base::Closure& upload_callback)
66    : upload_callback_(upload_callback),
67      upload_interval_(TimeDelta::FromSeconds(kInitialUploadIntervalSeconds)),
68      running_(false),
69      callback_pending_(false),
70      init_task_complete_(false),
71      waiting_for_init_task_complete_(false) {
72}
73
74MetricsReportingScheduler::~MetricsReportingScheduler() {}
75
76void MetricsReportingScheduler::Start() {
77  running_ = true;
78  ScheduleNextUpload();
79}
80
81void MetricsReportingScheduler::Stop() {
82  running_ = false;
83  if (upload_timer_.IsRunning())
84    upload_timer_.Stop();
85}
86
87// Callback from MetricsService when the startup init task has completed.
88void MetricsReportingScheduler::InitTaskComplete() {
89  DCHECK(!init_task_complete_);
90  init_task_complete_ = true;
91  if (waiting_for_init_task_complete_) {
92    waiting_for_init_task_complete_ = false;
93    TriggerUpload();
94  } else {
95    LogMetricsInitSequence(INIT_TASK_COMPLETED_FIRST);
96  }
97}
98
99void MetricsReportingScheduler::UploadFinished(bool server_is_healthy,
100                                               bool more_logs_remaining) {
101  DCHECK(callback_pending_);
102  callback_pending_ = false;
103  // If the server is having issues, back off. Otherwise, reset to default
104  // (unless there are more logs to send, in which case the next upload should
105  // happen sooner).
106  if (!server_is_healthy) {
107    BackOffUploadInterval();
108  } else if (more_logs_remaining) {
109    upload_interval_ = TimeDelta::FromSeconds(kUnsentLogsIntervalSeconds);
110  } else {
111    upload_interval_ = TimeDelta::FromSeconds(kStandardUploadIntervalSeconds);
112  }
113
114  if (running_)
115    ScheduleNextUpload();
116}
117
118void MetricsReportingScheduler::UploadCancelled() {
119  DCHECK(callback_pending_);
120  callback_pending_ = false;
121  if (running_)
122    ScheduleNextUpload();
123}
124
125void MetricsReportingScheduler::SetUploadIntervalForTesting(
126    base::TimeDelta interval) {
127  upload_interval_ = interval;
128}
129
130void MetricsReportingScheduler::TriggerUpload() {
131  // If the timer fired before the init task has completed, don't trigger the
132  // upload yet - wait for the init task to complete and do it then.
133  if (!init_task_complete_) {
134    LogMetricsInitSequence(TIMER_FIRED_FIRST);
135    waiting_for_init_task_complete_ = true;
136    return;
137  }
138  callback_pending_ = true;
139  upload_callback_.Run();
140}
141
142void MetricsReportingScheduler::ScheduleNextUpload() {
143  DCHECK(running_);
144  if (upload_timer_.IsRunning() || callback_pending_)
145    return;
146
147  upload_timer_.Start(FROM_HERE, upload_interval_, this,
148                      &MetricsReportingScheduler::TriggerUpload);
149}
150
151void MetricsReportingScheduler::BackOffUploadInterval() {
152  DCHECK_GT(kBackoffMultiplier, 1.0);
153  upload_interval_ = TimeDelta::FromMicroseconds(
154      static_cast<int64>(kBackoffMultiplier *
155                         upload_interval_.InMicroseconds()));
156
157  TimeDelta max_interval = kMaxBackoffMultiplier *
158      TimeDelta::FromSeconds(kStandardUploadIntervalSeconds);
159  if (upload_interval_ > max_interval || upload_interval_.InSeconds() < 0) {
160    upload_interval_ = max_interval;
161  }
162}
163
164}  // namespace metrics
165