variations_service.cc revision 558790d6acca3451cf3a6b497803a5f07d0bec58
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.
4
5#include "chrome/browser/metrics/variations/variations_service.h"
6
7#include <set>
8
9#include "base/base64.h"
10#include "base/build_time.h"
11#include "base/command_line.h"
12#include "base/memory/scoped_ptr.h"
13#include "base/metrics/histogram.h"
14#include "base/metrics/sparse_histogram.h"
15#include "base/prefs/pref_registry_simple.h"
16#include "base/prefs/pref_service.h"
17#include "base/sha1.h"
18#include "base/strings/string_number_conversions.h"
19#include "base/version.h"
20#include "chrome/browser/browser_process.h"
21#include "chrome/browser/metrics/proto/trials_seed.pb.h"
22#include "chrome/browser/metrics/variations/variations_seed_processor.h"
23#include "chrome/browser/net/network_time_tracker.h"
24#include "chrome/common/chrome_switches.h"
25#include "chrome/common/metrics/variations/variations_util.h"
26#include "chrome/common/pref_names.h"
27#include "content/public/browser/browser_thread.h"
28#include "content/public/common/url_fetcher.h"
29#include "net/base/load_flags.h"
30#include "net/base/net_errors.h"
31#include "net/base/network_change_notifier.h"
32#include "net/base/url_util.h"
33#include "net/http/http_response_headers.h"
34#include "net/http/http_status_code.h"
35#include "net/http/http_util.h"
36#include "net/url_request/url_fetcher.h"
37#include "net/url_request/url_request_status.h"
38#include "url/gurl.h"
39
40#if defined(OS_CHROMEOS)
41#include "chrome/browser/chromeos/settings/cros_settings.h"
42#endif
43
44namespace chrome_variations {
45
46namespace {
47
48// Default server of Variations seed info.
49const char kDefaultVariationsServerURL[] =
50    "https://clients4.google.com/chrome-variations/seed";
51const int kMaxRetrySeedFetch = 5;
52
53// TODO(mad): To be removed when we stop updating the NetworkTimeTracker.
54// For the HTTP date headers, the resolution of the server time is 1 second.
55const int64 kServerTimeResolutionMs = 1000;
56
57// Wrapper around channel checking, used to enable channel mocking for
58// testing. If the current browser channel is not UNKNOWN, this will return
59// that channel value. Otherwise, if the fake channel flag is provided, this
60// will return the fake channel. Failing that, this will return the UNKNOWN
61// channel.
62chrome::VersionInfo::Channel GetChannelForVariations() {
63  chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
64  if (channel != chrome::VersionInfo::CHANNEL_UNKNOWN)
65    return channel;
66  std::string forced_channel =
67      CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
68          switches::kFakeVariationsChannel);
69  if (forced_channel == "stable")
70    channel = chrome::VersionInfo::CHANNEL_STABLE;
71  else if (forced_channel == "beta")
72    channel = chrome::VersionInfo::CHANNEL_BETA;
73  else if (forced_channel == "dev")
74    channel = chrome::VersionInfo::CHANNEL_DEV;
75  else if (forced_channel == "canary")
76    channel = chrome::VersionInfo::CHANNEL_CANARY;
77  else
78    DVLOG(1) << "Invalid channel provided: " << forced_channel;
79  return channel;
80}
81
82// Returns a string that will be used for the value of the 'osname' URL param
83// to the variations server.
84std::string GetPlatformString() {
85#if defined(OS_WIN)
86  return "win";
87#elif defined(OS_IOS)
88  return "ios";
89#elif defined(OS_MACOSX)
90  return "mac";
91#elif defined(OS_CHROMEOS)
92  return "chromeos";
93#elif defined(OS_ANDROID)
94  return "android";
95#elif defined(OS_LINUX) || defined(OS_BSD) || defined(OS_SOLARIS)
96  // Default BSD and SOLARIS to Linux to not break those builds, although these
97  // platforms are not officially supported by Chrome.
98  return "linux";
99#else
100#error Unknown platform
101#endif
102}
103
104// Converts |date_time| in Study date format to base::Time.
105base::Time ConvertStudyDateToBaseTime(int64 date_time) {
106  return base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(date_time);
107}
108
109// Gets the restrict parameter from |local_state| or from Chrome OS settings in
110// the case of that platform.
111std::string GetRestrictParameterPref(PrefService* local_state) {
112  std::string parameter;
113#if defined(OS_CHROMEOS)
114  chromeos::CrosSettings::Get()->GetString(
115      chromeos::kVariationsRestrictParameter, &parameter);
116#else
117  if (local_state)
118    parameter = local_state->GetString(prefs::kVariationsRestrictParameter);
119#endif
120  return parameter;
121}
122
123// Computes a hash of the serialized variations seed data.
124std::string HashSeed(const std::string& seed_data) {
125  const std::string sha1 = base::SHA1HashString(seed_data);
126  return base::HexEncode(sha1.data(), sha1.size());
127}
128
129enum ResourceRequestsAllowedState {
130  RESOURCE_REQUESTS_ALLOWED,
131  RESOURCE_REQUESTS_NOT_ALLOWED,
132  RESOURCE_REQUESTS_ALLOWED_NOTIFIED,
133  RESOURCE_REQUESTS_ALLOWED_ENUM_SIZE,
134};
135
136// Records UMA histogram with the current resource requests allowed state.
137void RecordRequestsAllowedHistogram(ResourceRequestsAllowedState state) {
138  UMA_HISTOGRAM_ENUMERATION("Variations.ResourceRequestsAllowed", state,
139                            RESOURCE_REQUESTS_ALLOWED_ENUM_SIZE);
140}
141
142enum VariationSeedEmptyState {
143  VARIATIONS_SEED_NOT_EMPTY,
144  VARIATIONS_SEED_EMPTY,
145  VARIATIONS_SEED_CORRUPT,
146  VARIATIONS_SEED_EMPTY_ENUM_SIZE,
147};
148
149void RecordVariationSeedEmptyHistogram(VariationSeedEmptyState state) {
150  UMA_HISTOGRAM_ENUMERATION("Variations.SeedEmpty", state,
151                            VARIATIONS_SEED_EMPTY_ENUM_SIZE);
152}
153
154}  // namespace
155
156VariationsService::VariationsService(PrefService* local_state)
157    : local_state_(local_state),
158      variations_server_url_(GetVariationsServerURL(local_state)),
159      create_trials_from_seed_called_(false),
160      initial_request_completed_(false),
161      resource_request_allowed_notifier_(
162          new ResourceRequestAllowedNotifier) {
163  resource_request_allowed_notifier_->Init(this);
164}
165
166VariationsService::VariationsService(ResourceRequestAllowedNotifier* notifier,
167                                     PrefService* local_state)
168    : local_state_(local_state),
169      variations_server_url_(GetVariationsServerURL(NULL)),
170      create_trials_from_seed_called_(false),
171      initial_request_completed_(false),
172      resource_request_allowed_notifier_(notifier) {
173  resource_request_allowed_notifier_->Init(this);
174}
175
176VariationsService::~VariationsService() {
177}
178
179bool VariationsService::CreateTrialsFromSeed() {
180  create_trials_from_seed_called_ = true;
181
182  TrialsSeed seed;
183  if (!LoadTrialsSeedFromPref(&seed))
184    return false;
185
186  const int64 date_value = local_state_->GetInt64(prefs::kVariationsSeedDate);
187  const base::Time seed_date = base::Time::FromInternalValue(date_value);
188  const base::Time build_time = base::GetBuildTime();
189  // Use the build time for date checks if either the seed date is invalid or
190  // the build time is newer than the seed date.
191  base::Time reference_date = seed_date;
192  if (seed_date.is_null() || seed_date < build_time)
193    reference_date = build_time;
194
195  const chrome::VersionInfo current_version_info;
196  if (!current_version_info.is_valid())
197    return false;
198
199  VariationsSeedProcessor().CreateTrialsFromSeed(
200      seed, g_browser_process->GetApplicationLocale(), reference_date,
201      current_version_info, GetChannelForVariations());
202
203  // Log the "freshness" of the seed that was just used. The freshness is the
204  // time between the last successful seed download and now.
205  const int64 last_fetch_time_internal =
206      local_state_->GetInt64(prefs::kVariationsLastFetchTime);
207  if (last_fetch_time_internal) {
208    const base::Time now = base::Time::Now();
209    const base::TimeDelta delta =
210        now - base::Time::FromInternalValue(last_fetch_time_internal);
211    // Log the value in number of minutes.
212    UMA_HISTOGRAM_CUSTOM_COUNTS("Variations.SeedFreshness", delta.InMinutes(),
213        1, base::TimeDelta::FromDays(30).InMinutes(), 50);
214  }
215
216  return true;
217}
218
219void VariationsService::StartRepeatedVariationsSeedFetch() {
220  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
221
222  // Check that |CreateTrialsFromSeed| was called, which is necessary to
223  // retrieve the serial number that will be sent to the server.
224  DCHECK(create_trials_from_seed_called_);
225
226  DCHECK(!request_scheduler_.get());
227  // Note that the act of instantiating the scheduler will start the fetch, if
228  // the scheduler deems appropriate. Using Unretained is fine here since the
229  // lifespan of request_scheduler_ is guaranteed to be shorter than that of
230  // this service.
231  request_scheduler_.reset(VariationsRequestScheduler::Create(
232      base::Bind(&VariationsService::FetchVariationsSeed,
233          base::Unretained(this)), local_state_));
234  request_scheduler_->Start();
235}
236
237// static
238GURL VariationsService::GetVariationsServerURL(PrefService* local_state) {
239  std::string server_url_string(CommandLine::ForCurrentProcess()->
240      GetSwitchValueASCII(switches::kVariationsServerURL));
241  if (server_url_string.empty())
242    server_url_string = kDefaultVariationsServerURL;
243  GURL server_url = GURL(server_url_string);
244
245  const std::string restrict_param = GetRestrictParameterPref(local_state);
246  if (!restrict_param.empty()) {
247    server_url = net::AppendOrReplaceQueryParameter(server_url,
248                                                    "restrict",
249                                                    restrict_param);
250  }
251
252  server_url = net::AppendOrReplaceQueryParameter(server_url, "osname",
253                                                  GetPlatformString());
254
255  DCHECK(server_url.is_valid());
256  return server_url;
257}
258
259#if defined(OS_WIN)
260void VariationsService::StartGoogleUpdateRegistrySync() {
261  registry_syncer_.RequestRegistrySync();
262}
263#endif
264
265void VariationsService::SetCreateTrialsFromSeedCalledForTesting(bool called) {
266  create_trials_from_seed_called_ = called;
267}
268
269// static
270std::string VariationsService::GetDefaultVariationsServerURLForTesting() {
271  return kDefaultVariationsServerURL;
272}
273
274// static
275void VariationsService::RegisterPrefs(PrefRegistrySimple* registry) {
276  registry->RegisterStringPref(prefs::kVariationsSeed, std::string());
277  registry->RegisterStringPref(prefs::kVariationsSeedHash, std::string());
278  registry->RegisterInt64Pref(prefs::kVariationsSeedDate,
279                              base::Time().ToInternalValue());
280  registry->RegisterInt64Pref(prefs::kVariationsLastFetchTime, 0);
281  registry->RegisterStringPref(prefs::kVariationsRestrictParameter,
282                               std::string());
283}
284
285// static
286VariationsService* VariationsService::Create(PrefService* local_state) {
287#if !defined(GOOGLE_CHROME_BUILD)
288  // Unless the URL was provided, unsupported builds should return NULL to
289  // indicate that the service should not be used.
290  if (!CommandLine::ForCurrentProcess()->HasSwitch(
291          switches::kVariationsServerURL)) {
292    DVLOG(1) << "Not creating VariationsService in unofficial build without --"
293             << switches::kVariationsServerURL << " specified.";
294    return NULL;
295  }
296#endif
297  return new VariationsService(local_state);
298}
299
300void VariationsService::DoActualFetch() {
301  pending_seed_request_.reset(net::URLFetcher::Create(
302      0, variations_server_url_, net::URLFetcher::GET, this));
303  pending_seed_request_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
304                                      net::LOAD_DO_NOT_SAVE_COOKIES);
305  pending_seed_request_->SetRequestContext(
306      g_browser_process->system_request_context());
307  pending_seed_request_->SetMaxRetriesOn5xx(kMaxRetrySeedFetch);
308  if (!variations_serial_number_.empty()) {
309    pending_seed_request_->AddExtraRequestHeader("If-Match:" +
310                                                 variations_serial_number_);
311  }
312  pending_seed_request_->Start();
313
314  const base::TimeTicks now = base::TimeTicks::Now();
315  base::TimeDelta time_since_last_fetch;
316  // Record a time delta of 0 (default value) if there was no previous fetch.
317  if (!last_request_started_time_.is_null())
318    time_since_last_fetch = now - last_request_started_time_;
319  UMA_HISTOGRAM_CUSTOM_COUNTS("Variations.TimeSinceLastFetchAttempt",
320                              time_since_last_fetch.InMinutes(), 0,
321                              base::TimeDelta::FromDays(7).InMinutes(), 50);
322  last_request_started_time_ = now;
323}
324
325void VariationsService::FetchVariationsSeed() {
326  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
327
328  if (!resource_request_allowed_notifier_->ResourceRequestsAllowed()) {
329    RecordRequestsAllowedHistogram(RESOURCE_REQUESTS_NOT_ALLOWED);
330    DVLOG(1) << "Resource requests were not allowed. Waiting for notification.";
331    return;
332  }
333
334  RecordRequestsAllowedHistogram(RESOURCE_REQUESTS_ALLOWED);
335  DoActualFetch();
336}
337
338void VariationsService::OnURLFetchComplete(const net::URLFetcher* source) {
339  DCHECK_EQ(pending_seed_request_.get(), source);
340
341  const bool is_first_request = !initial_request_completed_;
342  initial_request_completed_ = true;
343
344  // The fetcher will be deleted when the request is handled.
345  scoped_ptr<const net::URLFetcher> request(pending_seed_request_.release());
346  const net::URLRequestStatus& request_status = request->GetStatus();
347  if (request_status.status() != net::URLRequestStatus::SUCCESS) {
348    UMA_HISTOGRAM_SPARSE_SLOWLY("Variations.FailedRequestErrorCode",
349                                -request_status.error());
350    DVLOG(1) << "Variations server request failed with error: "
351             << request_status.error() << ": "
352             << net::ErrorToString(request_status.error());
353    // It's common for the very first fetch attempt to fail (e.g. the network
354    // may not yet be available). In such a case, try again soon, rather than
355    // waiting the full time interval.
356    if (is_first_request)
357      request_scheduler_->ScheduleFetchShortly();
358    return;
359  }
360
361  // Log the response code.
362  const int response_code = request->GetResponseCode();
363  UMA_HISTOGRAM_SPARSE_SLOWLY("Variations.SeedFetchResponseCode",
364                              response_code);
365
366  const base::TimeDelta latency =
367      base::TimeTicks::Now() - last_request_started_time_;
368
369  base::Time response_date;
370  if (response_code == net::HTTP_OK ||
371      response_code == net::HTTP_NOT_MODIFIED) {
372    bool success = request->GetResponseHeaders()->GetDateValue(&response_date);
373    DCHECK(success || response_date.is_null());
374
375    if (!response_date.is_null()) {
376      NetworkTimeTracker::BuildNotifierUpdateCallback().Run(
377          response_date,
378          base::TimeDelta::FromMilliseconds(kServerTimeResolutionMs),
379          latency);
380    }
381  }
382
383  if (response_code != net::HTTP_OK) {
384    DVLOG(1) << "Variations server request returned non-HTTP_OK response code: "
385             << response_code;
386    if (response_code == net::HTTP_NOT_MODIFIED) {
387      UMA_HISTOGRAM_MEDIUM_TIMES("Variations.FetchNotModifiedLatency", latency);
388      RecordLastFetchTime();
389    } else {
390      UMA_HISTOGRAM_MEDIUM_TIMES("Variations.FetchOtherLatency", latency);
391    }
392    return;
393  }
394  UMA_HISTOGRAM_MEDIUM_TIMES("Variations.FetchSuccessLatency", latency);
395
396  std::string seed_data;
397  bool success = request->GetResponseAsString(&seed_data);
398  DCHECK(success);
399
400  StoreSeedData(seed_data, response_date);
401}
402
403void VariationsService::OnResourceRequestsAllowed() {
404  // Note that this only attempts to fetch the seed at most once per period
405  // (kSeedFetchPeriodHours). This works because
406  // |resource_request_allowed_notifier_| only calls this method if an
407  // attempt was made earlier that fails (which implies that the period had
408  // elapsed). After a successful attempt is made, the notifier will know not
409  // to call this method again until another failed attempt occurs.
410  RecordRequestsAllowedHistogram(RESOURCE_REQUESTS_ALLOWED_NOTIFIED);
411  DVLOG(1) << "Retrying fetch.";
412  DoActualFetch();
413
414  // This service must have created a scheduler in order for this to be called.
415  DCHECK(request_scheduler_.get());
416  request_scheduler_->Reset();
417}
418
419bool VariationsService::StoreSeedData(const std::string& seed_data,
420                                      const base::Time& seed_date) {
421  if (seed_data.empty()) {
422    VLOG(1) << "Variations Seed data from server is empty, rejecting the seed.";
423    return false;
424  }
425
426  // Only store the seed data if it parses correctly.
427  TrialsSeed seed;
428  if (!seed.ParseFromString(seed_data)) {
429    VLOG(1) << "Variations Seed data from server is not in valid proto format, "
430            << "rejecting the seed.";
431    return false;
432  }
433
434  std::string base64_seed_data;
435  if (!base::Base64Encode(seed_data, &base64_seed_data)) {
436    VLOG(1) << "Variations Seed data from server fails Base64Encode, rejecting "
437            << "the seed.";
438    return false;
439  }
440
441  local_state_->SetString(prefs::kVariationsSeed, base64_seed_data);
442  local_state_->SetString(prefs::kVariationsSeedHash, HashSeed(seed_data));
443  local_state_->SetInt64(prefs::kVariationsSeedDate,
444                         seed_date.ToInternalValue());
445  variations_serial_number_ = seed.serial_number();
446
447  RecordLastFetchTime();
448
449  return true;
450}
451
452bool VariationsService::LoadTrialsSeedFromPref(TrialsSeed* seed) {
453  const std::string base64_seed_data =
454      local_state_->GetString(prefs::kVariationsSeed);
455  if (base64_seed_data.empty()) {
456    RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_EMPTY);
457    return false;
458  }
459
460  const std::string hash_from_pref =
461      local_state_->GetString(prefs::kVariationsSeedHash);
462  // If the decode process fails, assume the pref value is corrupt and clear it.
463  std::string seed_data;
464  if (!base::Base64Decode(base64_seed_data, &seed_data) ||
465      (!hash_from_pref.empty() && HashSeed(seed_data) != hash_from_pref) ||
466      !seed->ParseFromString(seed_data)) {
467    VLOG(1) << "Variations seed data in local pref is corrupt, clearing the "
468            << "pref.";
469    local_state_->ClearPref(prefs::kVariationsSeed);
470    local_state_->ClearPref(prefs::kVariationsSeedDate);
471    local_state_->ClearPref(prefs::kVariationsSeedHash);
472    RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_CORRUPT);
473    return false;
474  }
475  variations_serial_number_ = seed->serial_number();
476  RecordVariationSeedEmptyHistogram(VARIATIONS_SEED_NOT_EMPTY);
477  return true;
478}
479
480void VariationsService::RecordLastFetchTime() {
481  // local_state_ is NULL in tests, so check it first.
482  if (local_state_) {
483    local_state_->SetInt64(prefs::kVariationsLastFetchTime,
484                           base::Time::Now().ToInternalValue());
485  }
486}
487
488}  // namespace chrome_variations
489