summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjwd@chromium.org <jwd@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-05-17 15:36:21 +0000
committerjwd@chromium.org <jwd@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-05-17 15:36:21 +0000
commit54e26c1249af1c3165587938d9edb0daf4ce4c21 (patch)
tree375d6077421f84755f4f7cd2dad2993daf110dae
parent2e2216e43424120be34fe6cab1136eddecbeb4a0 (diff)
downloadchromium_src-54e26c1249af1c3165587938d9edb0daf4ce4c21.zip
chromium_src-54e26c1249af1c3165587938d9edb0daf4ce4c21.tar.gz
chromium_src-54e26c1249af1c3165587938d9edb0daf4ce4c21.tar.bz2
Restoring the chrome variatioons client, with a fix for browser_test failures caused by it.
The CLs that this is restoring are http://codereview.chromium.org/10343007 and http://codereview.chromium.org/10381021. BUG=121695 TEST= Review URL: https://chromiumcodereview.appspot.com/10392007 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@137662 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/browser_process.h2
-rw-r--r--chrome/browser/browser_process_impl.cc21
-rw-r--r--chrome/browser/browser_process_impl.h3
-rw-r--r--chrome/browser/chrome_browser_main.cc19
-rw-r--r--chrome/browser/metrics/proto/study.proto96
-rw-r--r--chrome/browser/metrics/proto/trials_seed.proto22
-rw-r--r--chrome/browser/metrics/variations_service.cc279
-rw-r--r--chrome/browser/metrics/variations_service.h91
-rw-r--r--chrome/browser/metrics/variations_service_unittest.cc210
-rw-r--r--chrome/browser/prefs/browser_prefs.cc2
-rw-r--r--chrome/chrome_browser.gypi17
-rw-r--r--chrome/common/pref_names.cc3
-rw-r--r--chrome/common/pref_names.h2
-rw-r--r--chrome/test/base/testing_browser_process.cc4
-rw-r--r--chrome/test/base/testing_browser_process.h1
15 files changed, 762 insertions, 10 deletions
diff --git a/chrome/browser/browser_process.h b/chrome/browser/browser_process.h
index c54e1be..a8a2415 100644
--- a/chrome/browser/browser_process.h
+++ b/chrome/browser/browser_process.h
@@ -35,6 +35,7 @@ class SafeBrowsingService;
class StatusTray;
class TabCloseableStateWatcher;
class ThumbnailGenerator;
+class VariationsService;
class WatchDogThread;
#if defined(OS_CHROMEOS)
@@ -92,6 +93,7 @@ class BrowserProcess {
virtual PrefService* local_state() = 0;
virtual ui::Clipboard* clipboard() = 0;
virtual net::URLRequestContextGetter* system_request_context() = 0;
+ virtual VariationsService* variations_service() = 0;
#if defined(OS_CHROMEOS)
// Returns the out-of-memory priority manager.
diff --git a/chrome/browser/browser_process_impl.cc b/chrome/browser/browser_process_impl.cc
index e3a7bb5..ef593ea 100644
--- a/chrome/browser/browser_process_impl.cc
+++ b/chrome/browser/browser_process_impl.cc
@@ -36,6 +36,7 @@
#include "chrome/browser/io_thread.h"
#include "chrome/browser/metrics/metrics_service.h"
#include "chrome/browser/metrics/thread_watcher.h"
+#include "chrome/browser/metrics/variations_service.h"
#include "chrome/browser/net/chrome_net_log.h"
#include "chrome/browser/net/crl_set_fetcher.h"
#include "chrome/browser/net/sdch_dictionary_fetcher.h"
@@ -185,13 +186,14 @@ void BrowserProcessImpl::StartTearDown() {
BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
base::Bind(&SdchDictionaryFetcher::Shutdown));
- // We need to destroy the MetricsService, IntranetRedirectDetector, and
- // SafeBrowsing ClientSideDetectionService (owned by the SafeBrowsingService)
- // before the io_thread_ gets destroyed, since their destructors can call the
- // URLFetcher destructor, which does a PostDelayedTask operation on the IO
- // thread. (The IO thread will handle that URLFetcher operation before going
- // away.)
+ // We need to destroy the MetricsService, VariationsService,
+ // IntranetRedirectDetector, and SafeBrowsing ClientSideDetectionService
+ // (owned by the SafeBrowsingService) before the io_thread_ gets destroyed,
+ // since their destructors can call the URLFetcher destructor, which does a
+ // PostDelayedTask operation on the IO thread. (The IO thread will handle that
+ // URLFetcher operation before going away.)
metrics_service_.reset();
+ variations_service_.reset();
intranet_redirect_detector_.reset();
#if defined(ENABLE_SAFE_BROWSING)
if (safe_browsing_service_.get()) {
@@ -395,6 +397,13 @@ net::URLRequestContextGetter* BrowserProcessImpl::system_request_context() {
return io_thread()->system_url_request_context_getter();
}
+VariationsService* BrowserProcessImpl::variations_service() {
+ DCHECK(CalledOnValidThread());
+ if (!variations_service_.get())
+ variations_service_.reset(new VariationsService());
+ return variations_service_.get();
+}
+
#if defined(OS_CHROMEOS)
chromeos::OomPriorityManager* BrowserProcessImpl::oom_priority_manager() {
DCHECK(CalledOnValidThread());
diff --git a/chrome/browser/browser_process_impl.h b/chrome/browser/browser_process_impl.h
index 16685e4..425cecc 100644
--- a/chrome/browser/browser_process_impl.h
+++ b/chrome/browser/browser_process_impl.h
@@ -68,6 +68,7 @@ class BrowserProcessImpl : public BrowserProcess,
virtual PrefService* local_state() OVERRIDE;
virtual ui::Clipboard* clipboard() OVERRIDE;
virtual net::URLRequestContextGetter* system_request_context() OVERRIDE;
+ virtual VariationsService* variations_service() OVERRIDE;
#if defined(OS_CHROMEOS)
virtual chromeos::OomPriorityManager* oom_priority_manager() OVERRIDE;
#endif // defined(OS_CHROMEOS)
@@ -180,6 +181,8 @@ class BrowserProcessImpl : public BrowserProcess,
scoped_ptr<ui::Clipboard> clipboard_;
+ scoped_ptr<VariationsService> variations_service_;
+
// Manager for desktop notification UI.
bool created_notification_ui_manager_;
scoped_ptr<NotificationUIManager> notification_ui_manager_;
diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_browser_main.cc
index 32cf5ed..c068a56 100644
--- a/chrome/browser/chrome_browser_main.cc
+++ b/chrome/browser/chrome_browser_main.cc
@@ -55,6 +55,7 @@
#include "chrome/browser/metrics/metrics_service.h"
#include "chrome/browser/metrics/thread_watcher.h"
#include "chrome/browser/metrics/tracking_synchronizer.h"
+#include "chrome/browser/metrics/variations_service.h"
#include "chrome/browser/nacl_host/nacl_process_host.h"
#include "chrome/browser/net/chrome_net_log.h"
#include "chrome/browser/net/predictor.h"
@@ -584,6 +585,10 @@ void ChromeBrowserMainParts::SetupMetricsAndFieldTrials() {
}
#endif // NDEBUG
+ VariationsService* variations_service =
+ browser_process_->variations_service();
+ variations_service->CreateTrialsFromSeed(browser_process_->local_state());
+
SetupFieldTrials(metrics->recording_active(),
local_state_->IsManagedPreference(
prefs::kMaxConnectionsPerProxy));
@@ -1848,10 +1853,16 @@ int ChromeBrowserMainParts::PreMainMessageLoopRunImpl() {
// http://crosbug.com/17687
#if !defined(OS_CHROMEOS)
// If we're running tests (ui_task is non-null), then we don't want to
- // call FetchLanguageListFromTranslateServer
- if (parameters().ui_task == NULL && translate_manager_ != NULL) {
- translate_manager_->FetchLanguageListFromTranslateServer(
- profile_->GetPrefs());
+ // call FetchLanguageListFromTranslateServer or
+ // StartFetchingVariationsSeed.
+ if (parameters().ui_task == NULL) {
+ // Request new variations seed information from server.
+ browser_process_->variations_service()->StartFetchingVariationsSeed();
+
+ if (translate_manager_ != NULL) {
+ translate_manager_->FetchLanguageListFromTranslateServer(
+ profile_->GetPrefs());
+ }
}
#endif
diff --git a/chrome/browser/metrics/proto/study.proto b/chrome/browser/metrics/proto/study.proto
new file mode 100644
index 0000000..f8924b6
--- /dev/null
+++ b/chrome/browser/metrics/proto/study.proto
@@ -0,0 +1,96 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package chrome_variations;
+
+// This defines the Protocol Buffer representation of a Chrome Variations study
+// as sent to clients of the Variations server.
+//
+// Next tag: 10
+message Study {
+ // The name of the study. Should not contain spaces or special characters.
+ // Ex: "my_study"
+ required string name = 1;
+
+ // The start date of the study in Unix time format. (Seconds since midnight
+ // January 1, 1970 UTC). See: http://en.wikipedia.org/wiki/Unix_time
+ // Ex: 1330893974 (corresponds to 2012-03-04 20:46:14Z)
+ optional int64 start_date = 2;
+
+ // The expiry date of the study in Unix time format. (Seconds since midnight
+ // January 1, 1970 UTC). See: http://en.wikipedia.org/wiki/Unix_time
+ // Ex: 1330893974 (corresponds to 2012-03-04 20:46:14Z)
+ optional int64 expiry_date = 3;
+
+ // The minimum Chrome version for this study, allowing a trailing '*'
+ // character for pattern matching. Inclusive. (To check for a match, iterate
+ // over each component checking >= until a * or end of string is reached.)
+ // Optional - if not specified, there is no minimum version.
+ // Ex: "17.0.963.46", "17.0.963.*", "17.*"
+ optional string min_version = 4;
+
+ // The maximum Chrome version for this study; same formatting as |min_version|
+ // above. Inclusive. (To check for a match, iterate over each component
+ // checking <= until a * or end of string is reached.)
+ // Optional - if not specified, there is no maximum version.
+ // Ex: "19.*"
+ optional string max_version = 5;
+
+ // Possible Chrome release channels.
+ // See: http://dev.chromium.org/getting-involved/dev-channel
+ enum Channel {
+ CANARY = 0;
+ DEV = 1;
+ BETA = 2;
+ STABLE = 3;
+ }
+
+ // List of channels that will receive this study. If omitted, the study
+ // applies to all channels.
+ // Ex: [BETA, STABLE]
+ repeated Channel channel = 6;
+
+ // Consistency setting for a study.
+ enum Consistency {
+ SESSION = 0; // Can't change within a session.
+ PERMANENT = 1; // Can't change for a given user.
+ }
+
+ // Consistency setting for this study. Optional - defaults to SESSION.
+ // Ex: PERMANENT
+ optional Consistency consistency = 7 [default = SESSION];
+
+ // Name of the experiment that gets the default experience. This experiment
+ // must be included in the list below.
+ // Ex: "default"
+ optional string default_experiment_name = 8;
+
+ // An experiment within the study.
+ //
+ // Next tag: 4
+ message Experiment {
+ // The name of the experiment within the study.
+ // Ex: "bucketA"
+ required string name = 1;
+ // The cut of the total probability taken for this group (the x in x / N,
+ // where N is the sum of all x’s).
+ // Ex: "50"
+ required uint32 probability_weight = 2;
+ // Optional id used to uniquely identify this experiment.
+ optional uint64 experiment_id = 3;
+ }
+
+ // List of experiments in this study. This list should include the default /
+ // control group.
+ //
+ // For example, to specify that 99% of users get the default behavior, while
+ // 0.5% of users get experience "A" and 0.5% of users get experience "B",
+ // specify the values below.
+ // Ex: { "default": 990, "A": 5, "B": 5 }
+ repeated Experiment experiment = 9;
+}
diff --git a/chrome/browser/metrics/proto/trials_seed.proto b/chrome/browser/metrics/proto/trials_seed.proto
new file mode 100644
index 0000000..9e0249a
--- /dev/null
+++ b/chrome/browser/metrics/proto/trials_seed.proto
@@ -0,0 +1,22 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package chrome_variations;
+
+import "study.proto";
+
+// The TrialsSeed is a protobuf response from the server that contains the list
+// of studies and a serial number to uniquely identify its contents. The
+// serial number allows the client to easily determine if the list of
+// experiments has changed from the previous TrialsSeed seen by the client.
+//
+// Next tag: 3
+message TrialsSeed {
+ optional string serial_number = 1;
+ repeated Study study = 2;
+}
diff --git a/chrome/browser/metrics/variations_service.cc b/chrome/browser/metrics/variations_service.cc
new file mode 100644
index 0000000..8b9866f
--- /dev/null
+++ b/chrome/browser/metrics/variations_service.cc
@@ -0,0 +1,279 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/metrics/variations_service.h"
+
+#include "base/base64.h"
+#include "base/build_time.h"
+#include "base/version.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/field_trial.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/metrics/proto/trials_seed.pb.h"
+#include "chrome/browser/prefs/pref_service.h"
+#include "chrome/common/pref_names.h"
+#include "content/public/common/url_fetcher.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/load_flags.h"
+#include "net/base/network_change_notifier.h"
+#include "net/url_request/url_request_status.h"
+
+namespace {
+
+// Default server of Variations seed info.
+const char kDefaultVariationsServer[] =
+ "https://clients4.google.com/chrome-variations/seed";
+const int kMaxRetrySeedFetch = 5;
+
+// Maps chrome_variations::Study_Channel enum values to corresponding
+// chrome::VersionInfo::Channel enum values.
+chrome::VersionInfo::Channel ConvertStudyChannelToVersionChannel(
+ chrome_variations::Study_Channel study_channel) {
+ switch (study_channel) {
+ case chrome_variations::Study_Channel_CANARY:
+ return chrome::VersionInfo::CHANNEL_CANARY;
+ case chrome_variations::Study_Channel_DEV:
+ return chrome::VersionInfo::CHANNEL_DEV;
+ case chrome_variations::Study_Channel_BETA:
+ return chrome::VersionInfo::CHANNEL_BETA;
+ case chrome_variations::Study_Channel_STABLE:
+ return chrome::VersionInfo::CHANNEL_STABLE;
+ }
+ // All enum values of |study_channel| were handled above.
+ NOTREACHED();
+ return chrome::VersionInfo::CHANNEL_UNKNOWN;
+}
+
+} // namespace
+
+VariationsService::VariationsService() {}
+VariationsService::~VariationsService() {}
+
+bool VariationsService::CreateTrialsFromSeed(PrefService* local_prefs) {
+ chrome_variations::TrialsSeed seed;
+ if (!LoadTrialsSeedFromPref(local_prefs, &seed))
+ return false;
+
+ for (int i = 0; i < seed.study_size(); ++i)
+ CreateTrialFromStudy(seed.study(i));
+
+ return true;
+}
+
+void VariationsService::StartFetchingVariationsSeed() {
+ if (net::NetworkChangeNotifier::IsOffline())
+ return;
+
+ pending_seed_request_.reset(content::URLFetcher::Create(
+ GURL(kDefaultVariationsServer), net::URLFetcher::GET, this));
+ pending_seed_request_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SAVE_COOKIES);
+ pending_seed_request_->SetRequestContext(
+ g_browser_process->system_request_context());
+ pending_seed_request_->SetMaxRetries(kMaxRetrySeedFetch);
+ pending_seed_request_->Start();
+}
+
+void VariationsService::OnURLFetchComplete(const net::URLFetcher* source) {
+ DCHECK_EQ(pending_seed_request_.get(), source);
+ // When we're done handling the request, the fetcher will be deleted.
+ scoped_ptr<const content::URLFetcher> request(
+ pending_seed_request_.release());
+ if (request->GetStatus().status() != net::URLRequestStatus::SUCCESS ||
+ request->GetResponseCode() != 200)
+ return;
+
+ std::string seed_data;
+ request->GetResponseAsString(&seed_data);
+
+ StoreSeedData(seed_data, g_browser_process->local_state());
+}
+
+// static
+void VariationsService::RegisterPrefs(PrefService* prefs) {
+ prefs->RegisterStringPref(prefs::kVariationsSeed, std::string());
+}
+
+void VariationsService::StoreSeedData(const std::string& seed_data,
+ PrefService* local_prefs) {
+ // Only store the seed data if it parses correctly.
+ chrome_variations::TrialsSeed seed;
+ if (!seed.ParseFromString(seed_data)) {
+ VLOG(1) << "Variations Seed data from server is not in valid proto format, "
+ << "rejecting the seed.";
+ return;
+ }
+ std::string base64_seed_data;
+ if (!base::Base64Encode(seed_data, &base64_seed_data)) {
+ VLOG(1) << "Variations Seed data from server fails Base64Encode, rejecting "
+ << "the seed.";
+ return;
+ }
+ local_prefs->SetString(prefs::kVariationsSeed, base64_seed_data);
+}
+
+// static
+bool VariationsService::ShouldAddStudy(const chrome_variations::Study& study) {
+ const chrome::VersionInfo current_version_info;
+ if (!current_version_info.is_valid())
+ return false;
+
+ if (!CheckStudyChannel(study, chrome::VersionInfo::GetChannel())) {
+ DVLOG(1) << "Filtered out study " << study.name() << " due to version.";
+ return false;
+ }
+
+ if (!CheckStudyVersion(study, current_version_info.Version())) {
+ DVLOG(1) << "Filtered out study " << study.name() << " due to version.";
+ return false;
+ }
+
+ // Use build time and not system time to match what is done in field_trial.cc.
+ if (!CheckStudyDate(study, base::GetBuildTime())) {
+ DVLOG(1) << "Filtered out study " << study.name() << " due to date.";
+ return false;
+ }
+
+ DVLOG(1) << "Kept study " << study.name() << ".";
+ return true;
+}
+
+// static
+bool VariationsService::CheckStudyChannel(
+ const chrome_variations::Study& study,
+ chrome::VersionInfo::Channel channel) {
+ if (study.channel_size() == 0) {
+ // An empty channel list matches all channels.
+ return true;
+ }
+
+ for (int i = 0; i < study.channel_size(); ++i) {
+ if (ConvertStudyChannelToVersionChannel(study.channel(i)) == channel)
+ return true;
+ }
+ return false;
+}
+
+// static
+bool VariationsService::CheckStudyVersion(const chrome_variations::Study& study,
+ const std::string& version_string) {
+ const Version current_version(version_string);
+ if (!current_version.IsValid()) {
+ DCHECK(false);
+ return false;
+ }
+
+ if (study.has_min_version()) {
+ const Version min_version(study.min_version());
+ if (!min_version.IsValid())
+ return false;
+ if (current_version.CompareTo(min_version) < 0)
+ return false;
+ }
+
+ if (study.has_max_version()) {
+ const Version max_version(study.max_version());
+ if (!max_version.IsValid())
+ return false;
+ if (current_version.CompareTo(max_version) > 0)
+ return false;
+ }
+
+ return true;
+}
+
+// static
+bool VariationsService::CheckStudyDate(const chrome_variations::Study& study,
+ const base::Time& date_time) {
+ const base::Time epoch = base::Time::UnixEpoch();
+
+ if (study.has_start_date()) {
+ const base::Time start_date =
+ epoch + base::TimeDelta::FromSeconds(study.start_date());
+ if (date_time < start_date)
+ return false;
+ }
+
+ if (study.has_expiry_date()) {
+ const base::Time expiry_date =
+ epoch + base::TimeDelta::FromSeconds(study.expiry_date());
+ if (date_time >= expiry_date)
+ return false;
+ }
+
+ return true;
+}
+
+bool VariationsService::LoadTrialsSeedFromPref(
+ PrefService* local_prefs,
+ chrome_variations::TrialsSeed* seed) {
+ std::string base64_seed_data = local_prefs->GetString(prefs::kVariationsSeed);
+ std::string seed_data;
+
+ // If the decode process fails, assume the pref value is corrupt, and clear
+ // it.
+ if (!base::Base64Decode(base64_seed_data, &seed_data) ||
+ !seed->ParseFromString(seed_data)) {
+ VLOG(1) << "Variations Seed data in local pref is corrupt, clearing the "
+ << "pref.";
+ local_prefs->ClearPref(prefs::kVariationsSeed);
+ return false;
+ }
+ return true;
+}
+
+void VariationsService::CreateTrialFromStudy(
+ const chrome_variations::Study& study) {
+ if (!ShouldAddStudy(study))
+ return;
+
+ // At the moment, a missing default_experiment_name makes the study invalid.
+ if (!study.has_default_experiment_name()) {
+ DVLOG(1) << study.name() << " has no default experiment defined.";
+ return;
+ }
+
+ const std::string& default_group_name = study.default_experiment_name();
+ base::FieldTrial::Probability divisor = 0;
+
+ bool found_default_group = false;
+ for (int i = 0; i < study.experiment_size(); ++i) {
+ divisor += study.experiment(i).probability_weight();
+ if (study.experiment(i).name() == default_group_name)
+ found_default_group = true;
+ }
+ if (!found_default_group) {
+ DVLOG(1) << study.name() << " is missing default experiment in it's "
+ << "experiment list";
+ // The default group was not found in the list of groups. This study is not
+ // valid.
+ return;
+ }
+
+ const base::Time epoch = base::Time::UnixEpoch();
+ const base::Time expiry_date =
+ epoch + base::TimeDelta::FromSeconds(study.expiry_date());
+ base::Time::Exploded exploded_end_date;
+ expiry_date.UTCExplode(&exploded_end_date);
+
+ scoped_refptr<base::FieldTrial> trial(
+ base::FieldTrialList::FactoryGetFieldTrial(
+ study.name(), divisor, default_group_name, exploded_end_date.year,
+ exploded_end_date.month, exploded_end_date.day_of_month, NULL));
+
+ if (study.has_consistency() &&
+ study.consistency() == chrome_variations::Study_Consistency_PERMANENT) {
+ trial->UseOneTimeRandomization();
+ }
+
+ for (int i = 0; i < study.experiment_size(); ++i) {
+ if (study.experiment(i).name() != default_group_name) {
+ trial->AppendGroup(study.experiment(i).name(),
+ study.experiment(i).probability_weight());
+ }
+ }
+
+ // TODO(jwd): Add experiment_id association code.
+ trial->SetForced();
+}
diff --git a/chrome/browser/metrics/variations_service.h b/chrome/browser/metrics/variations_service.h
new file mode 100644
index 0000000..76e1cb1
--- /dev/null
+++ b/chrome/browser/metrics/variations_service.h
@@ -0,0 +1,91 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_METRICS_VARIATIONS_SERVICE_H_
+#define CHROME_BROWSER_METRICS_VARIATIONS_SERVICE_H_
+#pragma once
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/time.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/browser/metrics/proto/study.pb.h"
+#include "chrome/browser/metrics/proto/trials_seed.pb.h"
+#include "chrome/common/chrome_version_info.h"
+#include "content/public/common/url_fetcher_delegate.h"
+
+class PrefService;
+
+// Used to setup field trials based on stored variations seed data, and fetch
+// new seed data from the variations server.
+class VariationsService : public content::URLFetcherDelegate {
+ public:
+ VariationsService();
+ virtual ~VariationsService();
+
+ // Creates field trials based on Variations Seed loaded from local prefs. If
+ // there is a problem loading the seed data, all trials specified by the seed
+ // may not be created.
+ bool CreateTrialsFromSeed(PrefService* local_prefs);
+
+ // Starts the fetching process, where |OnURLFetchComplete| is called with the
+ // response.
+ void StartFetchingVariationsSeed();
+
+ // content::URLFetcherDelegate implementation:
+ virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
+
+ // Register Variations related prefs in Local State.
+ static void RegisterPrefs(PrefService* prefs);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(VariationsServiceTest, CheckStudyChannel);
+ FRIEND_TEST_ALL_PREFIXES(VariationsServiceTest, CheckStudyVersion);
+ FRIEND_TEST_ALL_PREFIXES(VariationsServiceTest, CheckStudyVersionWildcards);
+ FRIEND_TEST_ALL_PREFIXES(VariationsServiceTest, CheckStudyDate);
+
+ // Store the given seed data to the given local prefs. Note that |seed_data|
+ // is assumed to be the raw serialized protobuf data stored in a string. It
+ // will be Base64Encoded for storage. If the string is invalid or the encoding
+ // fails, the |local_prefs| is left as is.
+ void StoreSeedData(const std::string& seed_data, PrefService* local_prefs);
+
+ // Returns whether |study| should be added to the local field trials list
+ // according to its restriction parameters.
+ static bool ShouldAddStudy(const chrome_variations::Study& study);
+
+ // Checks whether |study| is applicable for the given |channel|.
+ static bool CheckStudyChannel(const chrome_variations::Study& study,
+ chrome::VersionInfo::Channel channel);
+
+ // Checks whether |study| is applicable for the given version string.
+ static bool CheckStudyVersion(const chrome_variations::Study& study,
+ const std::string& version_string);
+
+ // Checks whether |study| is applicable for the given date/time.
+ static bool CheckStudyDate(const chrome_variations::Study& study,
+ const base::Time& date_time);
+
+ // Loads the Variations seed data from the given local prefs into |seed|. If
+ // there is a problem with loading, the pref value is cleared and false is
+ // returned. If successful, |seed| will contain the loaded data and true is
+ // returned.
+ bool LoadTrialsSeedFromPref(PrefService* local_prefs,
+ chrome_variations::TrialsSeed* seed);
+
+ void CreateTrialFromStudy(const chrome_variations::Study& study);
+
+ // Contains the current seed request. Will only have a value while a request
+ // is pending, and will be reset by |OnURLFetchComplete|.
+ scoped_ptr<content::URLFetcher> pending_seed_request_;
+
+ // The variations seed data being used for this session.
+ // TODO(jwd): This should be removed. When the seed data is loaded, it will be
+ // used immediately so it won't need to be stored.
+ chrome_variations::TrialsSeed variations_seed_;
+};
+
+#endif // CHROME_BROWSER_METRICS_VARIATIONS_SERVICE_H_
diff --git a/chrome/browser/metrics/variations_service_unittest.cc b/chrome/browser/metrics/variations_service_unittest.cc
new file mode 100644
index 0000000..67f2c19f
--- /dev/null
+++ b/chrome/browser/metrics/variations_service_unittest.cc
@@ -0,0 +1,210 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/metrics/proto/study.pb.h"
+#include "chrome/browser/metrics/variations_service.h"
+#include "chrome/common/chrome_version_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// Converts |time| to chrome_variations::Study proto format.
+int64 TimeToProtoTime(const base::Time& time) {
+ return (time - base::Time::UnixEpoch()).InSeconds();
+}
+
+} // namespace
+
+TEST(VariationsServiceTest, CheckStudyChannel) {
+ const chrome::VersionInfo::Channel channels[] = {
+ chrome::VersionInfo::CHANNEL_CANARY,
+ chrome::VersionInfo::CHANNEL_DEV,
+ chrome::VersionInfo::CHANNEL_BETA,
+ chrome::VersionInfo::CHANNEL_STABLE,
+ };
+ const chrome_variations::Study_Channel study_channels[] = {
+ chrome_variations::Study_Channel_CANARY,
+ chrome_variations::Study_Channel_DEV,
+ chrome_variations::Study_Channel_BETA,
+ chrome_variations::Study_Channel_STABLE,
+ };
+ ASSERT_EQ(arraysize(channels), arraysize(study_channels));
+ bool channel_added[arraysize(channels)] = { 0 };
+
+ chrome_variations::Study study;
+
+ // Check in the forwarded order. The loop cond is <= arraysize(study_channels)
+ // instead of < so that the result of adding the last channel gets checked.
+ for (size_t i = 0; i <= arraysize(study_channels); ++i) {
+ for (size_t j = 0; j < arraysize(channels); ++j) {
+ const bool expected = channel_added[j] || study.channel_size() == 0;
+ const bool result = VariationsService::CheckStudyChannel(study,
+ channels[j]);
+ EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!";
+ }
+
+ if (i < arraysize(study_channels))
+ {
+ study.add_channel(study_channels[i]);
+ channel_added[i] = true;
+ }
+ }
+
+ // Do the same check in the reverse order.
+ study.clear_channel();
+ memset(&channel_added, 0, sizeof(channel_added));
+ for (size_t i = 0; i <= arraysize(study_channels); ++i) {
+ for (size_t j = 0; j < arraysize(channels); ++j) {
+ const bool expected = channel_added[j] || study.channel_size() == 0;
+ const bool result = VariationsService::CheckStudyChannel(study,
+ channels[j]);
+ EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!";
+ }
+
+ if (i < arraysize(study_channels))
+ {
+ const int index = arraysize(study_channels) - i - 1;
+ study.add_channel(study_channels[index]);
+ channel_added[index] = true;
+ }
+ }
+}
+
+TEST(VariationsServiceTest, CheckStudyVersion) {
+ const struct {
+ const char* min_version;
+ const char* version;
+ bool expected_result;
+ } min_test_cases[] = {
+ { "1.2.2", "1.2.3", true },
+ { "1.2.3", "1.2.3", true },
+ { "1.2.4", "1.2.3", false },
+ { "1.3.2", "1.2.3", false },
+ { "2.1.2", "1.2.3", false },
+ { "0.3.4", "1.2.3", true },
+ };
+
+ const struct {
+ const char* max_version;
+ const char* version;
+ bool expected_result;
+ } max_test_cases[] = {
+ { "1.2.2", "1.2.3", false },
+ { "1.2.3", "1.2.3", true },
+ { "1.2.4", "1.2.3", true },
+ { "2.1.1", "1.2.3", true },
+ { "2.1.1", "2.3.4", false },
+ };
+
+ chrome_variations::Study study;
+
+ // Min/max version not set should result in true.
+ EXPECT_TRUE(VariationsService::CheckStudyVersion(study, "1.2.3"));
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(min_test_cases); ++i) {
+ study.set_min_version(min_test_cases[i].min_version);
+ const bool result =
+ VariationsService::CheckStudyVersion(study, min_test_cases[i].version);
+ EXPECT_EQ(min_test_cases[i].expected_result, result) <<
+ "Case " << i << " failed!";
+ }
+ study.clear_min_version();
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(max_test_cases); ++i) {
+ study.set_max_version(max_test_cases[i].max_version);
+ const bool result =
+ VariationsService::CheckStudyVersion(study, max_test_cases[i].version);
+ EXPECT_EQ(max_test_cases[i].expected_result, result) <<
+ "Case " << i << " failed!";
+ }
+
+ // Check intersection semantics.
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(min_test_cases); ++i) {
+ for (size_t j = 0; j < ARRAYSIZE_UNSAFE(max_test_cases); ++j) {
+ study.set_min_version(min_test_cases[i].min_version);
+ study.set_max_version(max_test_cases[j].max_version);
+
+ if (!min_test_cases[i].expected_result) {
+ const bool result =
+ VariationsService::CheckStudyVersion(study,
+ min_test_cases[i].version);
+ EXPECT_FALSE(result) << "Case " << i << "," << j << " failed!";
+ }
+
+ if (!max_test_cases[j].expected_result) {
+ const bool result =
+ VariationsService::CheckStudyVersion(study,
+ max_test_cases[j].version);
+ EXPECT_FALSE(result) << "Case " << i << "," << j << " failed!";
+ }
+ }
+ }
+}
+
+// The current client logic does not handle version number strings containing
+// wildcards. Check that any such values received from the server result in the
+// study being disqualified.
+TEST(VariationsServiceTest, CheckStudyVersionWildcards) {
+ chrome_variations::Study study;
+
+ study.set_min_version("1.0.*");
+ EXPECT_FALSE(VariationsService::CheckStudyVersion(study, "1.2.3"));
+
+ study.clear_min_version();
+ study.set_max_version("2.0.*");
+ EXPECT_FALSE(VariationsService::CheckStudyVersion(study, "1.2.3"));
+}
+
+TEST(VariationsServiceTest, CheckStudyDate) {
+ const base::Time now = base::Time::Now();
+ const base::TimeDelta delta = base::TimeDelta::FromHours(1);
+ const struct {
+ const base::Time start_date;
+ bool expected_result;
+ } start_test_cases[] = {
+ { now - delta, true },
+ { now, true },
+ { now + delta, false },
+ };
+ const struct {
+ const base::Time expiry_date;
+ bool expected_result;
+ } expiry_test_cases[] = {
+ { now - delta, false },
+ { now, false },
+ { now + delta, true },
+ };
+
+ chrome_variations::Study study;
+
+ // Start/expiry date not set should result in true.
+ EXPECT_TRUE(VariationsService::CheckStudyDate(study, now));
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(start_test_cases); ++i) {
+ study.set_start_date(TimeToProtoTime(start_test_cases[i].start_date));
+ const bool result = VariationsService::CheckStudyDate(study, now);
+ EXPECT_EQ(start_test_cases[i].expected_result, result)
+ << "Case " << i << " failed!";
+ }
+ study.clear_start_date();
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(expiry_test_cases); ++i) {
+ study.set_expiry_date(TimeToProtoTime(expiry_test_cases[i].expiry_date));
+ const bool result = VariationsService::CheckStudyDate(study, now);
+ EXPECT_EQ(expiry_test_cases[i].expected_result, result)
+ << "Case " << i << " failed!";
+ }
+
+ // Check intersection semantics.
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(start_test_cases); ++i) {
+ for (size_t j = 0; j < ARRAYSIZE_UNSAFE(expiry_test_cases); ++j) {
+ study.set_start_date(TimeToProtoTime(start_test_cases[i].start_date));
+ study.set_expiry_date(TimeToProtoTime(expiry_test_cases[j].expiry_date));
+ const bool expected = start_test_cases[i].expected_result &&
+ expiry_test_cases[j].expected_result;
+ const bool result = VariationsService::CheckStudyDate(study, now);
+ EXPECT_EQ(expected, result) << "Case " << i << "," << j << " failed!";
+ }
+ }
+}
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index f6609a4..eefc855 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -29,6 +29,7 @@
#include "chrome/browser/managed_mode.h"
#include "chrome/browser/metrics/metrics_log.h"
#include "chrome/browser/metrics/metrics_service.h"
+#include "chrome/browser/metrics/variations_service.h"
#include "chrome/browser/net/http_server_properties_manager.h"
#include "chrome/browser/net/net_pref_observer.h"
#include "chrome/browser/net/predictor.h"
@@ -121,6 +122,7 @@ void RegisterLocalState(PrefService* local_state) {
ProfileInfoCache::RegisterPrefs(local_state);
ProfileManager::RegisterPrefs(local_state);
SSLConfigServiceManager::RegisterPrefs(local_state);
+ VariationsService::RegisterPrefs(local_state);
WebCacheManager::RegisterPrefs(local_state);
#if defined(ENABLE_CONFIGURATION_POLICY)
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index fbff207..85b35fc 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -30,6 +30,7 @@
'safe_browsing_report_proto',
'feedback_proto',
'gdata_proto',
+ 'variations_seed_proto',
'../build/temp_gyp/googleurl.gyp:googleurl',
'../content/content.gyp:content_browser',
'../crypto/crypto.gyp:crypto',
@@ -1363,6 +1364,8 @@
'browser/metrics/tracking_synchronizer.cc',
'browser/metrics/tracking_synchronizer.h',
'browser/metrics/tracking_synchronizer_observer.h',
+ 'browser/metrics/variations_service.cc',
+ 'browser/metrics/variations_service.h',
'browser/native_window_notification_source.h',
'browser/net/chrome_cookie_notification_details.h',
'browser/net/chrome_fraudulent_certificate_reporter.cc',
@@ -5058,5 +5061,19 @@
},
'includes': [ '../build/protoc.gypi' ]
},
+ {
+ # Protobuf compiler / generator for Chrome Variations seed.
+ 'target_name': 'variations_seed_proto',
+ 'type': 'static_library',
+ 'sources': [
+ 'browser/metrics/proto/trials_seed.proto',
+ 'browser/metrics/proto/study.proto',
+ ],
+ 'variables': {
+ 'proto_in_dir': 'browser/metrics/proto',
+ 'proto_out_dir': 'chrome/browser/metrics/proto',
+ },
+ 'includes': [ '../build/protoc.gypi' ]
+ },
],
}
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index f564a87..b876f21d 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -1028,6 +1028,9 @@ const char kMetricsOngoingLogsXml[] =
const char kMetricsOngoingLogsProto[] =
"user_experience_metrics.ongoing_logs_as_protobufs";
+// String serialized form of variations seed protobuf.
+const char kVariationsSeed[] = "variations_seed";
+
// Where profile specific metrics are placed.
const char kProfileMetrics[] = "user_experience_metrics.profiles";
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 10ab3f7..b355990 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -364,6 +364,8 @@ extern const char kMetricsInitialLogsProto[];
extern const char kMetricsOngoingLogsXml[];
extern const char kMetricsOngoingLogsProto[];
+extern const char kVariationsSeed[];
+
extern const char kProfileLastUsed[];
extern const char kProfilesLastActive[];
extern const char kProfilesNumCreated[];
diff --git a/chrome/test/base/testing_browser_process.cc b/chrome/test/base/testing_browser_process.cc
index 37cbc48..8cb8a78 100644
--- a/chrome/test/base/testing_browser_process.cc
+++ b/chrome/test/base/testing_browser_process.cc
@@ -62,6 +62,10 @@ PrefService* TestingBrowserProcess::local_state() {
return local_state_;
}
+VariationsService* TestingBrowserProcess::variations_service() {
+ return NULL;
+}
+
policy::BrowserPolicyConnector*
TestingBrowserProcess::browser_policy_connector() {
#if defined(ENABLE_CONFIGURATION_POLICY)
diff --git a/chrome/test/base/testing_browser_process.h b/chrome/test/base/testing_browser_process.h
index 87110ca..260ceec 100644
--- a/chrome/test/base/testing_browser_process.h
+++ b/chrome/test/base/testing_browser_process.h
@@ -55,6 +55,7 @@ class TestingBrowserProcess : public BrowserProcess {
virtual WatchDogThread* watchdog_thread() OVERRIDE;
virtual ProfileManager* profile_manager() OVERRIDE;
virtual PrefService* local_state() OVERRIDE;
+ virtual VariationsService* variations_service() OVERRIDE;
virtual policy::BrowserPolicyConnector* browser_policy_connector() OVERRIDE;
virtual policy::PolicyService* policy_service() OVERRIDE;
virtual IconManager* icon_manager() OVERRIDE;