summaryrefslogtreecommitdiffstats
path: root/base
diff options
context:
space:
mode:
authorjar@chromium.org <jar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-30 16:31:54 +0000
committerjar@chromium.org <jar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-30 16:31:54 +0000
commite695fbd6d62e9967d6256cd0667eca2fb2bb918d (patch)
tree58e196031e02447b31bd9a4182b8fb3f4d7ea81b /base
parent5c7c19d83d201fa23d5097cce108c033ece5c63b (diff)
downloadchromium_src-e695fbd6d62e9967d6256cd0667eca2fb2bb918d.zip
chromium_src-e695fbd6d62e9967d6256cd0667eca2fb2bb918d.tar.gz
chromium_src-e695fbd6d62e9967d6256cd0667eca2fb2bb918d.tar.bz2
Create A/B test of SDCH
To do this, I needed to add the feature that ALL FieldTrials that are established in the browser process are forwarded and established in the corresponding renderer processes. This then allows both DNS impact, as well as SDCH inmpact (and any other field tests) to be studied at the same time in a single binary. This checkin also establishes a pattern that when we're doing A/B tests via a histogram such as RequestToFinish, that we produce names for all groups, rather than leaving one group as the "default" or "empty postfix" group. This is critical for naming various sub-groups when a multitude of tests are taking place at the same time. BUG=15479 r=mbelshe Review URL: http://codereview.chromium.org/150087 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@19595 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base')
-rw-r--r--base/field_trial.cc94
-rw-r--r--base/field_trial.h52
-rw-r--r--base/field_trial_unittest.cc56
3 files changed, 132 insertions, 70 deletions
diff --git a/base/field_trial.cc b/base/field_trial.cc
index fc12f31..549822f 100644
--- a/base/field_trial.cc
+++ b/base/field_trial.cc
@@ -14,7 +14,10 @@ using base::Time;
const int FieldTrial::kNotParticipating = -1;
// static
-const char FieldTrial::kPersistentStringSeparator('/');
+const int FieldTrial::kAllRemainingProbability = -2;
+
+// static
+const char FieldTrialList::kPersistentStringSeparator('/');
//------------------------------------------------------------------------------
// FieldTrial methods and members.
@@ -53,40 +56,6 @@ std::string FieldTrial::MakeName(const std::string& name_prefix,
return big_string.append(FieldTrialList::FindFullName(trial_name));
}
-std::string FieldTrial::MakePersistentString() const {
- DCHECK_EQ(name_.find(kPersistentStringSeparator), std::string::npos);
- DCHECK_EQ(group_name_.find(kPersistentStringSeparator), std::string::npos);
-
- std::string persistent(name_);
- persistent = persistent.append(1, kPersistentStringSeparator);
- persistent = persistent.append(group_name_);
- return persistent;
-}
-
-// static
-FieldTrial* FieldTrial::RestorePersistentString(const std::string &persistent) {
- size_t split_point = persistent.find(kPersistentStringSeparator);
- if (std::string::npos == split_point)
- return NULL; // Bogus string.
- std::string new_name(persistent, 0, split_point);
- std::string new_group_name(persistent, split_point + 1);
- if (new_name.empty() || new_group_name.empty())
- return NULL; // Incomplete string.
-
- FieldTrial *field_trial;
- field_trial = FieldTrialList::Find(new_name);
- if (field_trial) {
- // In single process mode, we may have already created the field trial.
- if (field_trial->group_name_ != new_group_name)
- return NULL; // Conflicting group name :-(.
- } else {
- const int kTotalProbability = 100;
- field_trial = new FieldTrial(new_name, kTotalProbability);
- field_trial->AppendGroup(new_group_name, kTotalProbability);
- }
- return field_trial;
-}
-
//------------------------------------------------------------------------------
// FieldTrialList methods and members.
@@ -151,3 +120,58 @@ FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) {
return NULL;
return it->second;
}
+
+// static
+void FieldTrialList::StatesToString(std::string* output) {
+ if (!global_)
+ return;
+ DCHECK(output->empty());
+ for (RegistrationList::iterator it = global_->registered_.begin();
+ it != global_->registered_.end(); ++it) {
+ const std::string name = it->first;
+ const std::string group_name = it->second->group_name();
+ if (group_name.empty())
+ continue; // No definitive winner in this trial.
+ DCHECK_EQ(name.find(kPersistentStringSeparator), std::string::npos);
+ DCHECK_EQ(group_name.find(kPersistentStringSeparator), std::string::npos);
+ output->append(name);
+ output->append(1, kPersistentStringSeparator);
+ output->append(group_name);
+ output->append(1, kPersistentStringSeparator);
+ }
+}
+
+// static
+bool FieldTrialList::StringAugmentsState(const std::string& prior_state) {
+ DCHECK(global_);
+ if (prior_state.empty() || !global_)
+ return true;
+
+ size_t next_item = 0;
+ while (next_item < prior_state.length()) {
+ size_t name_end = prior_state.find(kPersistentStringSeparator, next_item);
+ if (name_end == prior_state.npos || next_item == name_end)
+ return false;
+ size_t group_name_end = prior_state.find(kPersistentStringSeparator,
+ name_end + 1);
+ if (group_name_end == prior_state.npos || name_end + 1 == group_name_end)
+ return false;
+ std::string name(prior_state, next_item, name_end - next_item);
+ std::string group_name(prior_state, name_end + 1,
+ 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() != group_name)
+ return false;
+ continue;
+ }
+ const int kTotalProbability = 100;
+ field_trial = new FieldTrial(name, kTotalProbability);
+ field_trial->AppendGroup(group_name, kTotalProbability);
+ }
+ return true;
+}
+
diff --git a/base/field_trial.h b/base/field_trial.h
index 39a5bc7..6d3c101 100644
--- a/base/field_trial.h
+++ b/base/field_trial.h
@@ -72,14 +72,16 @@
class FieldTrial : public base::RefCounted<FieldTrial> {
public:
- static const int kNotParticipating;
+ typedef int Probability; // Probability type for being selected in a trial.
- // Define a separator charactor to use when creating a persistent form of an
- // instance. This is intended for use as a command line argument, passed to a
- // second process to mimic our state (i.e., provide the same group name).
- static const char kPersistentStringSeparator; // Currently a slash.
+ // A return value to indicate that a given instance has not yet had a group
+ // assignment (and hence is not yet participating in the trial).
+ static const int kNotParticipating;
- typedef int Probability; // Use scaled up probability.
+ // Provide an easy way to assign all remaining probability to a group. Note
+ // that this will force an instance to participate, and make it illegal to
+ // attempt to probabalistically add any other groups to the trial.
+ static const Probability kAllRemainingProbability;
// The name is used to register the instance with the FieldTrialList class,
// and can be used to find the trial (only one trial can be present for each
@@ -111,18 +113,6 @@ class FieldTrial : public base::RefCounted<FieldTrial> {
static std::string MakeName(const std::string& name_prefix,
const std::string& trial_name);
- // Create a persistent representation of the instance that could be resurected
- // in another process. This allows randomization to be done in one process,
- // and secondary processes can by synchronized on the result.
- // The resulting string contains only the name, the trial name, and a "/"
- // separator.
- std::string MakePersistentString() const;
-
- // Using a string created by MakePersistentString(), construct a new instance
- // that has the same state as the original instance. Currently only the
- // group_name_ and name_ are restored.
- static FieldTrial* RestorePersistentString(const std::string &persistent);
-
private:
// The name of the field trial, as can be found via the FieldTrialList.
// This is empty of the trial is not in the experiment.
@@ -159,6 +149,11 @@ class FieldTrial : public base::RefCounted<FieldTrial> {
// Only one instance of this class exists.
class FieldTrialList {
public:
+ // Define a separator charactor to use when creating a persistent form of an
+ // instance. This is intended for use as a command line argument, passed to a
+ // second process to mimic our state (i.e., provide the same group name).
+ static const char kPersistentStringSeparator; // Currently a slash.
+
// This singleton holds the global list of registered FieldTrials.
FieldTrialList();
// Destructor Release()'s references to all registered FieldTrial instances.
@@ -176,6 +171,21 @@ class FieldTrialList {
static std::string FindFullName(const std::string& name);
+ // Create a persistent representation of all FieldTrial instances for
+ // resurrection in another process. This allows randomization to be done in
+ // one process, and secondary processes can by synchronized on the result.
+ // The resulting string contains only the names, the trial name, and a "/"
+ // separator.
+ static void StatesToString(std::string* output);
+
+ // 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.
+ static bool StringAugmentsState(const std::string& prior_state);
+
// 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
@@ -185,6 +195,7 @@ class FieldTrialList {
if (global_)
return global_->application_start_time_;
// For testing purposes only, or when we don't yet have a start time.
+ // TODO(jar): Switch to TimeTicks
return base::Time::Now();
}
@@ -192,10 +203,14 @@ class FieldTrialList {
// Helper function should be called only while holding lock_.
FieldTrial* PreLockedFind(const std::string& name);
+ // A map from FieldTrial names to the actual instances.
typedef std::map<std::string, FieldTrial*> RegistrationList;
static FieldTrialList* global_; // The singleton of this class.
+ // A helper value made availabel to users, that shows when the FieldTrialList
+ // was initialized. Note that this is a singleton instance, and hence is a
+ // good approximation to the start of the process.
base::Time application_start_time_;
// Lock for access to registered_.
@@ -206,3 +221,4 @@ class FieldTrialList {
};
#endif // BASE_FIELD_TRIAL_H_
+
diff --git a/base/field_trial_unittest.cc b/base/field_trial_unittest.cc
index f678b0b..59d8e15 100644
--- a/base/field_trial_unittest.cc
+++ b/base/field_trial_unittest.cc
@@ -116,42 +116,64 @@ TEST_F(FieldTrialTest, OneWinner) {
}
TEST_F(FieldTrialTest, Save) {
+ std::string save_string;
+
FieldTrial* trial = new FieldTrial("Some name", 10);
// There is no winner yet, so no textual group name is associated with trial.
EXPECT_EQ(trial->group_name(), "");
- EXPECT_EQ(trial->MakePersistentString(), "Some name/");
+ FieldTrialList::StatesToString(&save_string);
+ EXPECT_EQ(save_string, "");
+ save_string.clear();
// Create a winning group.
trial->AppendGroup("Winner", 10);
- EXPECT_EQ(trial->MakePersistentString(), "Some name/Winner");
+ FieldTrialList::StatesToString(&save_string);
+ EXPECT_EQ(save_string, "Some name/Winner/");
+ save_string.clear();
+
+ // Create a second trial and winning group.
+ FieldTrial* trial2 = new FieldTrial("xxx", 10);
+ trial2->AppendGroup("yyyy", 10);
+
+ FieldTrialList::StatesToString(&save_string);
+ // We assume names are alphabetized... though this is not critical.
+ EXPECT_EQ(save_string, "Some name/Winner/xxx/yyyy/");
}
TEST_F(FieldTrialTest, Restore) {
- FieldTrial* trial = FieldTrial::RestorePersistentString("Some name/winner");
- EXPECT_EQ(trial->group_name(), "winner");
- EXPECT_EQ(trial->name(), "Some name");
-}
+ EXPECT_EQ(NULL, FieldTrialList::Find("Some_name"));
+ EXPECT_EQ(NULL, FieldTrialList::Find("xxx"));
-TEST_F(FieldTrialTest, BogusRestore) {
- const FieldTrial *trial = FieldTrial::RestorePersistentString("MissingSlash");
- EXPECT_EQ(trial, static_cast<FieldTrial *>(NULL));
+ FieldTrialList::StringAugmentsState("Some_name/Winner/xxx/yyyy/");
+
+ FieldTrial* trial = FieldTrialList::Find("Some_name");
+ ASSERT_NE(static_cast<FieldTrial*>(NULL), trial);
+ EXPECT_EQ(trial->group_name(), "Winner");
+ EXPECT_EQ(trial->name(), "Some_name");
- trial = FieldTrial::RestorePersistentString("MissingGroupName/");
- EXPECT_EQ(trial, static_cast<FieldTrial *>(NULL));
+ trial = FieldTrialList::Find("xxx");
+ ASSERT_NE(static_cast<FieldTrial*>(NULL), trial);
+ EXPECT_EQ(trial->group_name(), "yyyy");
+ EXPECT_EQ(trial->name(), "xxx");
+}
- trial = FieldTrial::RestorePersistentString("/MissingName");
- EXPECT_EQ(trial, static_cast<FieldTrial *>(NULL));
+TEST_F(FieldTrialTest, BogusRestore) {
+ EXPECT_FALSE(FieldTrialList::StringAugmentsState("MissingSlash"));
+ EXPECT_FALSE(FieldTrialList::StringAugmentsState("MissingGroupName/"));
+ EXPECT_FALSE(FieldTrialList::StringAugmentsState("MissingFinalSlash/gname"));
+ EXPECT_FALSE(FieldTrialList::StringAugmentsState("/noname, only group/"));
}
TEST_F(FieldTrialTest, DuplicateRestore) {
FieldTrial* trial = new FieldTrial("Some name", 10);
trial->AppendGroup("Winner", 10);
- EXPECT_EQ(trial->MakePersistentString(), "Some name/Winner");
+ std::string save_string;
+ FieldTrialList::StatesToString(&save_string);
+ EXPECT_EQ("Some name/Winner/", save_string);
// It is OK if we redundantly specify a winner.
- EXPECT_EQ(trial, FieldTrial::RestorePersistentString("Some name/Winner"));
+ EXPECT_TRUE(FieldTrialList::StringAugmentsState(save_string));
// But it is an error to try to change to a different winner.
- EXPECT_EQ(FieldTrial::RestorePersistentString("Some name/Loser"),
- static_cast<FieldTrial *>(NULL));
+ EXPECT_FALSE(FieldTrialList::StringAugmentsState("Some name/Loser/"));
}