[Metrics] Make MetricsStateManager take a callback param to check if UMA is enabled.
[chromium-blink-merge.git] / chrome / browser / captive_portal / captive_portal_service.cc
blobc76a9d420aaff2aeae550b253db5b9cc694a20ab
1 // Copyright (c) 2012 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.
5 #include "chrome/browser/captive_portal/captive_portal_service.h"
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/logging.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/metrics/histogram.h"
12 #include "base/prefs/pref_service.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/common/pref_names.h"
16 #include "components/captive_portal/captive_portal_types.h"
17 #include "content/public/browser/notification_service.h"
19 #if defined(OS_MACOSX)
20 #include "base/mac/mac_util.h"
21 #endif
23 #if defined(OS_WIN)
24 #include "base/win/windows_version.h"
25 #endif
27 using captive_portal::CaptivePortalResult;
29 namespace {
31 // Make sure this enum is in sync with CaptivePortalDetectionResult enum
32 // in histograms.xml. This enum is append-only, don't modify existing values.
33 enum CaptivePortalDetectionResult {
34 // There's a confirmed connection to the Internet.
35 DETECTION_RESULT_INTERNET_CONNECTED,
36 // Received a network or HTTP error, or a non-HTTP response.
37 DETECTION_RESULT_NO_RESPONSE,
38 // Encountered a captive portal with a non-HTTPS landing URL.
39 DETECTION_RESULT_BEHIND_CAPTIVE_PORTAL,
40 // Received a network or HTTP error with an HTTPS landing URL.
41 DETECTION_RESULT_NO_RESPONSE_HTTPS_LANDING_URL,
42 // Encountered a captive portal with an HTTPS landing URL.
43 DETECTION_RESULT_BEHIND_CAPTIVE_PORTAL_HTTPS_LANDING_URL,
44 DETECTION_RESULT_COUNT
47 // Records histograms relating to how often captive portal detection attempts
48 // ended with |result| in a row, and for how long |result| was the last result
49 // of a detection attempt. Recorded both on quit and on a new Result.
51 // |repeat_count| may be 0 if there were no captive portal checks during
52 // a session.
54 // |result_duration| is the time between when a captive portal check first
55 // returned |result| and when a check returned a different result, or when the
56 // CaptivePortalService was shut down.
57 void RecordRepeatHistograms(CaptivePortalResult result,
58 int repeat_count,
59 base::TimeDelta result_duration) {
60 // Histogram macros can't be used with variable names, since they cache
61 // pointers, so have to use the histogram functions directly.
63 // Record number of times the last result was received in a row.
64 base::HistogramBase* result_repeated_histogram =
65 base::Histogram::FactoryGet(
66 "CaptivePortal.ResultRepeated." + CaptivePortalResultToString(result),
67 1, // min
68 100, // max
69 100, // bucket_count
70 base::Histogram::kUmaTargetedHistogramFlag);
71 result_repeated_histogram->Add(repeat_count);
73 if (repeat_count == 0)
74 return;
76 // Time between first request that returned |result| and now.
77 base::HistogramBase* result_duration_histogram =
78 base::Histogram::FactoryTimeGet(
79 "CaptivePortal.ResultDuration." + CaptivePortalResultToString(result),
80 base::TimeDelta::FromSeconds(1), // min
81 base::TimeDelta::FromHours(1), // max
82 50, // bucket_count
83 base::Histogram::kUmaTargetedHistogramFlag);
84 result_duration_histogram->AddTime(result_duration);
87 int GetHistogramEntryForDetectionResult(
88 const captive_portal::CaptivePortalDetector::Results& results) {
89 bool is_https = results.landing_url.SchemeIs("https");
90 switch (results.result) {
91 case captive_portal::RESULT_INTERNET_CONNECTED:
92 return DETECTION_RESULT_INTERNET_CONNECTED;
93 case captive_portal::RESULT_NO_RESPONSE:
94 return is_https ?
95 DETECTION_RESULT_NO_RESPONSE_HTTPS_LANDING_URL :
96 DETECTION_RESULT_NO_RESPONSE;
97 case captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL:
98 return is_https ?
99 DETECTION_RESULT_BEHIND_CAPTIVE_PORTAL_HTTPS_LANDING_URL :
100 DETECTION_RESULT_BEHIND_CAPTIVE_PORTAL;
101 default:
102 NOTREACHED();
103 return -1;
107 bool ShouldDeferToNativeCaptivePortalDetection() {
108 // On Windows 8, defer to the native captive portal detection. OSX Lion and
109 // later also have captive portal detection, but experimentally, this code
110 // works in cases its does not.
112 // TODO(mmenke): Investigate how well Windows 8's captive portal detection
113 // works.
114 #if defined(OS_WIN)
115 return base::win::GetVersion() >= base::win::VERSION_WIN8;
116 #else
117 return false;
118 #endif
121 } // namespace
123 CaptivePortalService::TestingState CaptivePortalService::testing_state_ =
124 NOT_TESTING;
126 class CaptivePortalService::RecheckBackoffEntry : public net::BackoffEntry {
127 public:
128 explicit RecheckBackoffEntry(CaptivePortalService* captive_portal_service)
129 : net::BackoffEntry(
130 &captive_portal_service->recheck_policy().backoff_policy),
131 captive_portal_service_(captive_portal_service) {
134 private:
135 virtual base::TimeTicks ImplGetTimeNow() const OVERRIDE {
136 return captive_portal_service_->GetCurrentTimeTicks();
139 CaptivePortalService* captive_portal_service_;
141 DISALLOW_COPY_AND_ASSIGN(RecheckBackoffEntry);
144 CaptivePortalService::RecheckPolicy::RecheckPolicy()
145 : initial_backoff_no_portal_ms(600 * 1000),
146 initial_backoff_portal_ms(20 * 1000) {
147 // Receiving a new Result is considered a success. All subsequent requests
148 // that get the same Result are considered "failures", so a value of N
149 // means exponential backoff starts after getting a result N + 2 times:
150 // +1 for the initial success, and +1 because N failures are ignored.
152 // A value of 6 means to start backoff on the 7th failure, which is the 8th
153 // time the same result is received.
154 backoff_policy.num_errors_to_ignore = 6;
156 // It doesn't matter what this is initialized to. It will be overwritten
157 // after the first captive portal detection request.
158 backoff_policy.initial_delay_ms = initial_backoff_no_portal_ms;
160 backoff_policy.multiply_factor = 2.0;
161 backoff_policy.jitter_factor = 0.3;
162 backoff_policy.maximum_backoff_ms = 2 * 60 * 1000;
164 // -1 means the entry never expires. This doesn't really matter, as the
165 // service never checks for its expiration.
166 backoff_policy.entry_lifetime_ms = -1;
168 backoff_policy.always_use_initial_delay = true;
171 CaptivePortalService::CaptivePortalService(Profile* profile)
172 : profile_(profile),
173 state_(STATE_IDLE),
174 captive_portal_detector_(profile->GetRequestContext()),
175 enabled_(false),
176 last_detection_result_(captive_portal::RESULT_INTERNET_CONNECTED),
177 num_checks_with_same_result_(0),
178 test_url_(captive_portal::CaptivePortalDetector::kDefaultURL) {
179 // The order matters here:
180 // |resolve_errors_with_web_service_| must be initialized and |backoff_entry_|
181 // created before the call to UpdateEnabledState.
182 resolve_errors_with_web_service_.Init(
183 prefs::kAlternateErrorPagesEnabled,
184 profile_->GetPrefs(),
185 base::Bind(&CaptivePortalService::UpdateEnabledState,
186 base::Unretained(this)));
187 ResetBackoffEntry(last_detection_result_);
189 UpdateEnabledState();
192 CaptivePortalService::~CaptivePortalService() {
195 void CaptivePortalService::DetectCaptivePortal() {
196 DCHECK(CalledOnValidThread());
198 // If a request is pending or running, do nothing.
199 if (state_ == STATE_CHECKING_FOR_PORTAL || state_ == STATE_TIMER_RUNNING)
200 return;
202 base::TimeDelta time_until_next_check = backoff_entry_->GetTimeUntilRelease();
204 // Start asynchronously.
205 state_ = STATE_TIMER_RUNNING;
206 check_captive_portal_timer_.Start(
207 FROM_HERE,
208 time_until_next_check,
209 this,
210 &CaptivePortalService::DetectCaptivePortalInternal);
213 void CaptivePortalService::DetectCaptivePortalInternal() {
214 DCHECK(CalledOnValidThread());
215 DCHECK(state_ == STATE_TIMER_RUNNING || state_ == STATE_IDLE);
216 DCHECK(!TimerRunning());
218 state_ = STATE_CHECKING_FOR_PORTAL;
220 // When not enabled, just claim there's an Internet connection.
221 if (!enabled_) {
222 // Count this as a success, so the backoff entry won't apply exponential
223 // backoff, but will apply the standard delay.
224 backoff_entry_->InformOfRequest(true);
225 OnResult(captive_portal::RESULT_INTERNET_CONNECTED);
226 return;
229 captive_portal_detector_.DetectCaptivePortal(
230 test_url_, base::Bind(
231 &CaptivePortalService::OnPortalDetectionCompleted,
232 base::Unretained(this)));
235 void CaptivePortalService::OnPortalDetectionCompleted(
236 const captive_portal::CaptivePortalDetector::Results& results) {
237 DCHECK(CalledOnValidThread());
238 DCHECK_EQ(STATE_CHECKING_FOR_PORTAL, state_);
239 DCHECK(!TimerRunning());
240 DCHECK(enabled_);
242 CaptivePortalResult result = results.result;
243 const base::TimeDelta& retry_after_delta = results.retry_after_delta;
244 base::TimeTicks now = GetCurrentTimeTicks();
246 // Record histograms.
247 UMA_HISTOGRAM_ENUMERATION("CaptivePortal.DetectResult",
248 GetHistogramEntryForDetectionResult(results),
249 DETECTION_RESULT_COUNT);
251 // If this isn't the first captive portal result, record stats.
252 if (!last_check_time_.is_null()) {
253 UMA_HISTOGRAM_LONG_TIMES("CaptivePortal.TimeBetweenChecks",
254 now - last_check_time_);
256 if (last_detection_result_ != result) {
257 // If the last result was different from the result of the latest test,
258 // record histograms about the previous period over which the result was
259 // the same.
260 RecordRepeatHistograms(last_detection_result_,
261 num_checks_with_same_result_,
262 now - first_check_time_with_same_result_);
266 if (last_check_time_.is_null() || result != last_detection_result_) {
267 first_check_time_with_same_result_ = now;
268 num_checks_with_same_result_ = 1;
270 // Reset the backoff entry both to update the default time and clear
271 // previous failures.
272 ResetBackoffEntry(result);
274 backoff_entry_->SetCustomReleaseTime(now + retry_after_delta);
275 // The BackoffEntry is not informed of this request, so there's no delay
276 // before the next request. This allows for faster login when a captive
277 // portal is first detected. It can also help when moving between captive
278 // portals.
279 } else {
280 DCHECK_LE(1, num_checks_with_same_result_);
281 ++num_checks_with_same_result_;
283 // Requests that have the same Result as the last one are considered
284 // "failures", to trigger backoff.
285 backoff_entry_->SetCustomReleaseTime(now + retry_after_delta);
286 backoff_entry_->InformOfRequest(false);
289 last_check_time_ = now;
291 OnResult(result);
294 void CaptivePortalService::Shutdown() {
295 DCHECK(CalledOnValidThread());
296 if (enabled_) {
297 RecordRepeatHistograms(
298 last_detection_result_,
299 num_checks_with_same_result_,
300 GetCurrentTimeTicks() - first_check_time_with_same_result_);
304 void CaptivePortalService::OnResult(CaptivePortalResult result) {
305 DCHECK_EQ(STATE_CHECKING_FOR_PORTAL, state_);
306 state_ = STATE_IDLE;
308 Results results;
309 results.previous_result = last_detection_result_;
310 results.result = result;
311 last_detection_result_ = result;
313 content::NotificationService::current()->Notify(
314 chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT,
315 content::Source<Profile>(profile_),
316 content::Details<Results>(&results));
319 void CaptivePortalService::ResetBackoffEntry(CaptivePortalResult result) {
320 if (!enabled_ || result == captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL) {
321 // Use the shorter time when the captive portal service is not enabled, or
322 // behind a captive portal.
323 recheck_policy_.backoff_policy.initial_delay_ms =
324 recheck_policy_.initial_backoff_portal_ms;
325 } else {
326 recheck_policy_.backoff_policy.initial_delay_ms =
327 recheck_policy_.initial_backoff_no_portal_ms;
330 backoff_entry_.reset(new RecheckBackoffEntry(this));
333 void CaptivePortalService::UpdateEnabledState() {
334 DCHECK(CalledOnValidThread());
335 bool enabled_before = enabled_;
336 enabled_ = testing_state_ != DISABLED_FOR_TESTING &&
337 resolve_errors_with_web_service_.GetValue();
339 if (testing_state_ != SKIP_OS_CHECK_FOR_TESTING &&
340 ShouldDeferToNativeCaptivePortalDetection()) {
341 enabled_ = false;
344 if (enabled_before == enabled_)
345 return;
347 // Clear data used for histograms.
348 num_checks_with_same_result_ = 0;
349 first_check_time_with_same_result_ = base::TimeTicks();
350 last_check_time_ = base::TimeTicks();
352 ResetBackoffEntry(last_detection_result_);
354 if (state_ == STATE_CHECKING_FOR_PORTAL || state_ == STATE_TIMER_RUNNING) {
355 // If a captive portal check was running or pending, cancel check
356 // and the timer.
357 check_captive_portal_timer_.Stop();
358 captive_portal_detector_.Cancel();
359 state_ = STATE_IDLE;
361 // Since a captive portal request was queued or running, something may be
362 // expecting to receive a captive portal result.
363 DetectCaptivePortal();
367 base::TimeTicks CaptivePortalService::GetCurrentTimeTicks() const {
368 if (time_ticks_for_testing_.is_null())
369 return base::TimeTicks::Now();
370 else
371 return time_ticks_for_testing_;
374 bool CaptivePortalService::DetectionInProgress() const {
375 return state_ == STATE_CHECKING_FOR_PORTAL;
378 bool CaptivePortalService::TimerRunning() const {
379 return check_captive_portal_timer_.IsRunning();