diff options
author | jwd@chromium.org <jwd@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-05-17 15:36:21 +0000 |
---|---|---|
committer | jwd@chromium.org <jwd@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-05-17 15:36:21 +0000 |
commit | 54e26c1249af1c3165587938d9edb0daf4ce4c21 (patch) | |
tree | 375d6077421f84755f4f7cd2dad2993daf110dae | |
parent | 2e2216e43424120be34fe6cab1136eddecbeb4a0 (diff) | |
download | chromium_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.h | 2 | ||||
-rw-r--r-- | chrome/browser/browser_process_impl.cc | 21 | ||||
-rw-r--r-- | chrome/browser/browser_process_impl.h | 3 | ||||
-rw-r--r-- | chrome/browser/chrome_browser_main.cc | 19 | ||||
-rw-r--r-- | chrome/browser/metrics/proto/study.proto | 96 | ||||
-rw-r--r-- | chrome/browser/metrics/proto/trials_seed.proto | 22 | ||||
-rw-r--r-- | chrome/browser/metrics/variations_service.cc | 279 | ||||
-rw-r--r-- | chrome/browser/metrics/variations_service.h | 91 | ||||
-rw-r--r-- | chrome/browser/metrics/variations_service_unittest.cc | 210 | ||||
-rw-r--r-- | chrome/browser/prefs/browser_prefs.cc | 2 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 17 | ||||
-rw-r--r-- | chrome/common/pref_names.cc | 3 | ||||
-rw-r--r-- | chrome/common/pref_names.h | 2 | ||||
-rw-r--r-- | chrome/test/base/testing_browser_process.cc | 4 | ||||
-rw-r--r-- | chrome/test/base/testing_browser_process.h | 1 |
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; |