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