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 10 using base::TimeDelta; 11 12 namespace { 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. 18 const int kInitialUploadIntervalSeconds = 15; 19 #else 20 const 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. 28 const int kUnsentLogsIntervalSeconds = 3; 29 #else 30 const int kUnsentLogsIntervalSeconds = 15; 31 #endif 32 33 // Standard interval between log uploads, in seconds. 34 #if defined(OS_ANDROID) || defined(OS_IOS) 35 const int kStandardUploadIntervalSeconds = 5 * 60; // Five minutes. 36 #else 37 const 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. 44 const double kBackoffMultiplier = 1.1; 45 46 // The maximum backoff multiplier. 47 const int kMaxBackoffMultiplier = 10; 48 49 enum InitSequence { 50 TIMER_FIRED_FIRST, 51 INIT_TASK_COMPLETED_FIRST, 52 INIT_SEQUENCE_ENUM_SIZE, 53 }; 54 55 void LogMetricsInitSequence(InitSequence sequence) { 56 UMA_HISTOGRAM_ENUMERATION("UMA.InitSequence", sequence, 57 INIT_SEQUENCE_ENUM_SIZE); 58 } 59 60 } // anonymous namespace 61 62 MetricsReportingScheduler::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 72 MetricsReportingScheduler::~MetricsReportingScheduler() {} 73 74 void MetricsReportingScheduler::Start() { 75 running_ = true; 76 ScheduleNextUpload(); 77 } 78 79 void 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. 86 void 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 97 void 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 116 void MetricsReportingScheduler::UploadCancelled() { 117 DCHECK(callback_pending_); 118 callback_pending_ = false; 119 if (running_) 120 ScheduleNextUpload(); 121 } 122 123 void MetricsReportingScheduler::SetUploadIntervalForTesting( 124 base::TimeDelta interval) { 125 upload_interval_ = interval; 126 } 127 128 void 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 140 void 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 149 void 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