diff options
author | rtenneti@chromium.org <rtenneti@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-29 05:42:22 +0000 |
---|---|---|
committer | rtenneti@chromium.org <rtenneti@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-04-29 05:42:22 +0000 |
commit | 63a8ba1e1fb71156beff23b4a26828dbc387c734 (patch) | |
tree | e69b6e1f24c5d7d1446bc6d5596518f44399bc67 /base | |
parent | dbf00c96e956033c1ea9b7542fa0d9c5ab1e66ac (diff) | |
download | chromium_src-63a8ba1e1fb71156beff23b4a26828dbc387c734.zip chromium_src-63a8ba1e1fb71156beff23b4a26828dbc387c734.tar.gz chromium_src-63a8ba1e1fb71156beff23b4a26828dbc387c734.tar.bz2 |
Field trials are currently implemented (commonly) using a static variable that is set
once, the first time it is necessary to decide if there is an experiment by a given
name active.
With this change the field-test system can "push" a group that is selected for
the given field trial (field test) if/when an experiment does arrive.
This change implements a simple IPC notification of the result of a FieldTrial
setting being sent to any previously started renderers.
BUG=16494
TEST=field trial tests
R=jar
Review URL: http://codereview.chromium.org/6883029
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@83488 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base')
-rw-r--r-- | base/metrics/field_trial.cc | 88 | ||||
-rw-r--r-- | base/metrics/field_trial.h | 47 | ||||
-rw-r--r-- | base/metrics/field_trial_unittest.cc | 28 |
3 files changed, 140 insertions, 23 deletions
diff --git a/base/metrics/field_trial.cc b/base/metrics/field_trial.cc index ad2e5a0..daeb369 100644 --- a/base/metrics/field_trial.cc +++ b/base/metrics/field_trial.cc @@ -25,6 +25,10 @@ const char FieldTrialList::kPersistentStringSeparator('/'); static const char kHistogramFieldTrialSeparator('_'); +// static +int FieldTrialList::kExpirationYearInFuture = 0; + + //------------------------------------------------------------------------------ // FieldTrial methods and members. @@ -83,6 +87,7 @@ int FieldTrial::AppendGroup(const std::string& name, base::StringAppendF(&group_name_, "%d", group_); else group_name_ = name; + FieldTrialList::NotifyFieldTrialGroupSelection(name_, group_name_); } return next_group_number_++; } @@ -92,6 +97,7 @@ int FieldTrial::group() { accumulated_group_probability_ = divisor_; group_ = kDefaultGroupNumber; group_name_ = default_group_name_; + FieldTrialList::NotifyFieldTrialGroupSelection(name_, group_name_); } return group_; } @@ -136,10 +142,18 @@ FieldTrialList* FieldTrialList::global_ = NULL; // static bool FieldTrialList::register_without_global_ = false; -FieldTrialList::FieldTrialList() : application_start_time_(TimeTicks::Now()) { +FieldTrialList::FieldTrialList() + : application_start_time_(TimeTicks::Now()), + observer_list_(ObserverList<Observer>::NOTIFY_EXISTING_ONLY) { DCHECK(!global_); DCHECK(!register_without_global_); global_ = this; + + Time::Exploded exploded; + Time two_years_from_now = + Time::NowFromSystemTime() + TimeDelta::FromDays(730); + two_years_from_now.LocalExplode(&exploded); + kExpirationYearInFuture = exploded.year; } FieldTrialList::~FieldTrialList() { @@ -219,12 +233,6 @@ bool FieldTrialList::CreateTrialsInChildProcess( if (parent_trials.empty() || !global_) return true; - Time::Exploded exploded; - Time two_years_from_now = - Time::NowFromSystemTime() + TimeDelta::FromDays(730); - two_years_from_now.LocalExplode(&exploded); - const int kTwoYearsFromNow = exploded.year; - size_t next_item = 0; while (next_item < parent_trials.length()) { size_t name_end = parent_trials.find(kPersistentStringSeparator, next_item); @@ -239,23 +247,65 @@ bool FieldTrialList::CreateTrialsInChildProcess( group_name_end - name_end - 1); next_item = group_name_end + 1; - FieldTrial *field_trial(FieldTrialList::Find(name)); - if (field_trial) { - // In single process mode, we may have already created the field trial. - if ((field_trial->group_name_internal() != group_name) && - (field_trial->default_group_name() != group_name)) - return false; - continue; - } - const int kTotalProbability = 100; - field_trial = new FieldTrial(name, kTotalProbability, group_name, - kTwoYearsFromNow, 1, 1); - field_trial->AppendGroup(group_name, kTotalProbability); + if (!CreateFieldTrial(name, group_name)) + return false; } return true; } // static +FieldTrial* FieldTrialList::CreateFieldTrial( + const std::string& name, + const std::string& group_name) { + DCHECK(global_); + DCHECK_GE(name.size(), 0u); + DCHECK_GE(group_name.size(), 0u); + if (name.empty() || group_name.empty() || !global_) + return NULL; + + FieldTrial *field_trial(FieldTrialList::Find(name)); + if (field_trial) { + // In single process mode, we may have already created the field trial. + if (field_trial->group_name_internal() != group_name) + return NULL; + return field_trial; + } + const int kTotalProbability = 100; + field_trial = new FieldTrial(name, kTotalProbability, group_name, + kExpirationYearInFuture, 1, 1); + field_trial->AppendGroup(group_name, kTotalProbability); + return field_trial; +} + +// static +void FieldTrialList::AddObserver(Observer* observer) { + if (!global_) + return; + DCHECK(global_); + global_->observer_list_.AddObserver(observer); +} + +// static +void FieldTrialList::RemoveObserver(Observer* observer) { + if (!global_) + return; + DCHECK(global_); + global_->observer_list_.RemoveObserver(observer); +} + +// static +void FieldTrialList::NotifyFieldTrialGroupSelection( + const std::string& name, + const std::string& group_name) { + if (!global_) + return; + DCHECK(global_); + FOR_EACH_OBSERVER(Observer, + global_->observer_list_, + OnFieldTrialGroupFinalized(name, group_name)); +} + +// static size_t FieldTrialList::GetFieldTrialCount() { if (!global_) return 0; diff --git a/base/metrics/field_trial.h b/base/metrics/field_trial.h index dcfd391..b8ab0c5 100644 --- a/base/metrics/field_trial.h +++ b/base/metrics/field_trial.h @@ -81,6 +81,7 @@ #include "base/base_api.h" #include "base/gtest_prod_util.h" #include "base/memory/ref_counted.h" +#include "base/observer_list.h" #include "base/synchronization/lock.h" #include "base/time.h" @@ -218,6 +219,20 @@ class BASE_API FieldTrialList { // second process to mimic our state (i.e., provide the same group name). static const char kPersistentStringSeparator; // Currently a slash. + // Define expiration year in future. It is initialized to two years from Now. + static int kExpirationYearInFuture; + + // Observer is notified when a FieldTrial's group is selected. + class Observer { + public: + // Notify observers when FieldTrials's group is selected. + virtual void OnFieldTrialGroupFinalized(const std::string& trial_name, + const std::string& group_name) = 0; + + protected: + virtual ~Observer() {} + }; + // This singleton holds the global list of registered FieldTrials. FieldTrialList(); // Destructor Release()'s references to all registered FieldTrial instances. @@ -245,11 +260,34 @@ class BASE_API FieldTrialList { // Use a previously generated state string (re: StatesToString()) augment the // current list of field tests to include the supplied tests, and using a 100% // probability for each test, force them to have the same group string. This - // is commonly used in a sub-process, to carry randomly selected state in a - // parent process into this sub-process. - // Currently only the group_name_ and name_ are restored. + // is commonly used in a non-browser process, to carry randomly selected state + // in a browser process into this non-browser process. This method calls + // CreateFieldTrial to create the FieldTrial in the non-browser process. + // Currently only the group_name_ and name_ are restored. static bool CreateTrialsInChildProcess(const std::string& prior_trials); + // Create a FieldTrial with the given |name| and using 100% probability for + // the FieldTrial, force FieldTrial to have the same group string as + // |group_name|. This is commonly used in a non-browser process, to carry + // randomly selected state in a browser process into this non-browser process. + // Currently only the group_name_ and name_ are set in the FieldTrial. It + // returns NULL if there is a FieldTrial that is already registered with the + // same |name| but has different finalized group string (|group_name|). + static FieldTrial* CreateFieldTrial(const std::string& name, + const std::string& group_name); + + // Add an observer to be notified when a field trial is irrevocably committed + // to being part of some specific field_group (and hence the group_name is + // also finalized for that field_trial). + static void AddObserver(Observer* observer); + + // Remove an observer. + static void RemoveObserver(Observer* observer); + + // Notify all observers that a group is finalized for the named Trial. + static void NotifyFieldTrialGroupSelection(const std::string& name, + const std::string& group_name); + // The time of construction of the global map is recorded in a static variable // and is commonly used by experiments to identify the time since the start // of the application. In some experiments it may be useful to discount @@ -288,6 +326,9 @@ class BASE_API FieldTrialList { base::Lock lock_; RegistrationList registered_; + // List of observers to be notified when a group is selected for a FieldTrial. + ObserverList<Observer> observer_list_; + DISALLOW_COPY_AND_ASSIGN(FieldTrialList); }; diff --git a/base/metrics/field_trial_unittest.cc b/base/metrics/field_trial_unittest.cc index 0af6d60..3357085 100644 --- a/base/metrics/field_trial_unittest.cc +++ b/base/metrics/field_trial_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -287,6 +287,32 @@ TEST_F(FieldTrialTest, DuplicateRestore) { EXPECT_FALSE(FieldTrialList::CreateTrialsInChildProcess("Some name/Loser/")); } +TEST_F(FieldTrialTest, CreateFieldTrial) { + EXPECT_TRUE(FieldTrialList::Find("Some_name") == NULL); + + FieldTrialList::CreateFieldTrial("Some_name", "Winner"); + + FieldTrial* trial = FieldTrialList::Find("Some_name"); + ASSERT_NE(static_cast<FieldTrial*>(NULL), trial); + EXPECT_EQ("Winner", trial->group_name()); + EXPECT_EQ("Some_name", trial->name()); +} + +TEST_F(FieldTrialTest, DuplicateFieldTrial) { + FieldTrial* trial = + new FieldTrial( + "Some_name", 10, "Default some name", next_year_, 12, 31); + trial->AppendGroup("Winner", 10); + + // It is OK if we redundantly specify a winner. + FieldTrial* trial1 = FieldTrialList::CreateFieldTrial("Some_name", "Winner"); + EXPECT_TRUE(trial1 != NULL); + + // But it is an error to try to change to a different winner. + FieldTrial* trial2 = FieldTrialList::CreateFieldTrial("Some_name", "Loser"); + EXPECT_TRUE(trial2 == NULL); +} + TEST_F(FieldTrialTest, MakeName) { FieldTrial* trial = new FieldTrial("Field Trial", 10, "Winner", next_year_, 12, 31); |