summaryrefslogtreecommitdiffstats
path: root/base
diff options
context:
space:
mode:
authorjar@chromium.org <jar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-03-02 19:02:56 +0000
committerjar@chromium.org <jar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-03-02 19:02:56 +0000
commit9660b97b7869116645a92d342c52a857d0550ec2 (patch)
tree06e907b12fbd3d019283fef414422dfd4205e592 /base
parentd02e43a80f0fae032501e68d38bd562976b9d866 (diff)
downloadchromium_src-9660b97b7869116645a92d342c52a857d0550ec2.zip
chromium_src-9660b97b7869116645a92d342c52a857d0550ec2.tar.gz
chromium_src-9660b97b7869116645a92d342c52a857d0550ec2.tar.bz2
Renovate FieldTrial class to better bit with histogram usage.
r=mbelshe Review URL: http://codereview.chromium.org/28226 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@10714 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base')
-rw-r--r--base/field_trial.cc66
-rw-r--r--base/field_trial.h144
-rw-r--r--base/field_trial_unittest.cc73
3 files changed, 236 insertions, 47 deletions
diff --git a/base/field_trial.cc b/base/field_trial.cc
index c88ab65..94121d7 100644
--- a/base/field_trial.cc
+++ b/base/field_trial.cc
@@ -6,10 +6,48 @@
#include "base/field_trial.h"
#include "base/logging.h"
#include "base/rand_util.h"
+#include "base/string_util.h"
using base::Time;
//------------------------------------------------------------------------------
+// FieldTrial methods and members.
+
+FieldTrial::FieldTrial(const std::string& name,
+ const Probability total_probability)
+ : name_(name),
+ divisor_(total_probability),
+ random_(static_cast<Probability>(divisor_ * base::RandDouble())),
+ accumulated_group_probability_(0),
+ next_group_number_(0),
+ group_(kNotParticipating) {
+ FieldTrialList::Register(this);
+}
+
+int FieldTrial::AppendGroup(const std::string& name,
+ Probability group_probability) {
+ DCHECK(group_probability <= divisor_);
+ accumulated_group_probability_ += group_probability;
+ DCHECK(accumulated_group_probability_ <= divisor_);
+ if (group_ == kNotParticipating && accumulated_group_probability_ > random_) {
+ // This is the group that crossed the random line, so we do teh assignment.
+ group_ = next_group_number_;
+ if (name.empty())
+ StringAppendF(&group_name_, "_%d", group_);
+ else
+ group_name_ = name;
+ }
+ return next_group_number_++;
+}
+
+// static
+std::string FieldTrial::MakeName(const std::string& name_prefix,
+ const std::string& trial_name) {
+ std::string big_string(name_prefix);
+ return big_string.append(FieldTrialList::FindFullName(trial_name));
+}
+
+//------------------------------------------------------------------------------
// FieldTrialList methods and members.
// static
@@ -40,7 +78,23 @@ void FieldTrialList::Register(FieldTrial* trial) {
}
// static
-FieldTrial* FieldTrialList::Find(const std::wstring& name) {
+int FieldTrialList::FindValue(const std::string& name) {
+ FieldTrial* field_trial = Find(name);
+ if (field_trial)
+ return field_trial->group();
+ return FieldTrial::kNotParticipating;
+}
+
+// static
+std::string FieldTrialList::FindFullName(const std::string& name) {
+ FieldTrial* field_trial = Find(name);
+ if (field_trial)
+ return field_trial->group_name();
+ return "";
+}
+
+ // static
+FieldTrial* FieldTrialList::Find(const std::string& name) {
DCHECK(global_->CalledOnValidThread());
RegistrationList::iterator it = global_->registered_.find(name);
if (global_->registered_.end() == it)
@@ -48,13 +102,3 @@ FieldTrial* FieldTrialList::Find(const std::wstring& name) {
return it->second;
}
-//------------------------------------------------------------------------------
-// FieldTrial methods and members.
-
-FieldTrial::FieldTrial(const std::wstring& name, double probability)
- : name_(name) {
- double rand = base::RandDouble();
- DCHECK(rand >= 0.0 && rand < 1.0);
- boolean_value_ = rand < probability;
- FieldTrialList::Register(this);
-}
diff --git a/base/field_trial.h b/base/field_trial.h
index f392f88..312dbb0 100644
--- a/base/field_trial.h
+++ b/base/field_trial.h
@@ -6,15 +6,64 @@
// performed by actual users in the field (i.e., in a shipped or beta product).
// All code is called exclusively on the UI thread currently.
//
-// The simplest example is a test to see whether one of two options produces
-// "better" results across our user population. In that scenario, UMA data
-// is uploaded to show the test results, and this class manages the state of
-// each such test (state == which option was pseudo-randomly selected).
+// The simplest example is an experiment to see whether one of two options
+// produces "better" results across our user population. In that scenario, UMA
+// data is uploaded to aggregate the test results, and this FieldTrial class
+// manages the state of each such experiment (state == which option was
+// pseudo-randomly selected).
+//
// States are typically generated randomly, either based on a one time
-// randomization (reused during each run of the program), or by a startup
-// randomization (keeping that tests state constant across a run), or by
-// continuous randomization across a run.
-// Only startup randomization is implemented (thus far).
+// randomization (generated randomly once, and then persitently reused in the
+// client during each future run of the program), or by a startup randomization
+// (generated each time the application starts up, but held constant during the
+// duration of the process), or by continuous randomization across a run (where
+// the state can be recalculated again and again, many times during a process).
+// Only startup randomization is implemented thus far.
+
+//------------------------------------------------------------------------------
+// Example: Suppose we have an experiment involving memory, such as determining
+// the impact of memory model command line flags actual memory use.
+// We assume that we already have a histogram of memory usage, such as:
+
+// HISTOGRAM_COUNTS("Memory.RendererTotal", count);
+
+// Somewhere in main thread initialization code, we'd probably define an
+// instance of a FieldTrial, with code such as:
+
+// // Note, FieldTrials are reference counted, and persist automagically until
+// // process teardown, courtesy of their automatic registration in
+// // FieldTrialList.
+// trial = new FieldTrial("MemoryExperiment", 1000);
+// group1 = trial->AppendGroup("_high_mem", 20); // 2% this _high_mem group.
+// group2 = trial->AppendGroup("_low_mem", 20); // 2% this _low_mem group.
+// // Take action depending of which group we randomly land in.
+// switch (trial->group()) {
+// case group1:
+// ... do something
+// break;
+// case group2:
+// ....
+// break;
+// default:
+// ...
+// }
+
+// We then modify any histograms we wish to correlate with our experiment to
+// have slighly different names, depending on what group the trial instance
+// happened (randomly) to be assigned to:
+
+// HISTOGRAM_COUNTS(FieldTrial::MakeName("Memory.RendererTotal",
+// "MemoryExperiment"), count);
+
+// The above code will create 3 distinct histograms, with each run of the
+// application being assigned to of of teh three groups, and for each group, the
+// correspondingly named histogram will be populated:
+
+// Memory.RendererTotal // 96% of users still fill this histogram.
+// Memory.RendererTotal_high_mem // 2% of users will fill this histogram.
+// Memory.RendererTotal_low_mem // 2% of users will fill this histogram.
+
+//------------------------------------------------------------------------------
#ifndef BASE_FIELD_TRIAL_H_
#define BASE_FIELD_TRIAL_H_
@@ -26,27 +75,74 @@
#include "base/ref_counted.h"
#include "base/time.h"
+
class FieldTrial : public base::RefCounted<FieldTrial> {
public:
- // Constructor for a 2-state (boolean) trial.
+ static const int kNotParticipating = -1;
+
+ typedef int Probability; // Use scaled up probability.
+
// 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
- // name) using the Find() method.
- // The probability is a number in the range [0, 1], and is the likliehood that
- // the assigned boolean value will be true.
- FieldTrial(const std::wstring& name, double probability);
-
- // Return the selected boolean value.
- bool boolean_value() const { return boolean_value_; }
- std::wstring name() const { return name_; }
+ // name).
+ // Group probabilities that are later supplied must sum to less than or equal
+ // to the total_probability.
+ FieldTrial(const std::string& name, Probability total_probability);
+
+ // Establish the name and probability of the next group in this trial.
+ // Sometimes, based on construction randomization, this call may causes the
+ // provided group to be *THE* group selected for use in this instance.
+ int AppendGroup(const std::string& name, Probability group_probability);
+
+ // Return the name of the FieldTrial (excluding the group name).
+ std::string name() const { return name_; }
+
+ // Return the randomly selected group number that was assigned.
+ // Return kNotParticipating if the instance is not participating in the
+ // experiment.
+ int group() const { return group_; }
+
+ // If the field trial is not in an experiment, this returns the empty string.
+ // if the group's name is empty, a name of "_" concatenated with the group
+ // number is used as the group name.
+ std::string group_name() const { return group_name_; }
+
+ // Helper function for the most common use: as an argument to specifiy the
+ // name of a HISTOGRAM. Use the original histogram name as the name_prefix.
+ static std::string MakeName(const std::string& name_prefix,
+ const std::string& trial_name);
private:
- const std::wstring name_;
- bool boolean_value_;
+ // The name of the field trial, as can be found via the FieldTrialList.
+ // This is empty of the trial is not in the experiment.
+ const std::string name_;
+
+ // The maximu sum of all probabilities supplied, which corresponds to 100%.
+ // This is the scaling factor used to adjust supplied probabilities.
+ Probability divisor_;
+
+ // The randomly selected probability that is used to select a group (or have
+ // the instance not participate). It is the product of divisor_ and a random
+ // number between [0, 1).
+ Probability random_;
+
+ // Sum of the probabilities of all appended groups.
+ Probability accumulated_group_probability_;
+
+ int next_group_number_;
+
+ // The pseudo-randomly assigned group number.
+ // This is kNotParticipating if no group has been assigned.
+ int group_;
+
+ // A textual name for the randomly selected group, including the Trial name.
+ // If this Trial is not a member of an group, this string is empty.
+ std::string group_name_;
DISALLOW_COPY_AND_ASSIGN(FieldTrial);
};
+//------------------------------------------------------------------------------
// Class with a list of all active field trials. A trial is active if it has
// been registered, which includes evaluating its state based on its probaility.
// Only one instance of this class exists.
@@ -63,12 +159,16 @@ class FieldTrialList : NonThreadSafe {
// The Find() method can be used to test to see if a named Trial was already
// registered, or to retrieve a pointer to it from the global map.
- static FieldTrial* Find(const std::wstring& name);
+ static FieldTrial* Find(const std::string& name);
+
+ static int FindValue(const std::string& name);
+
+ static std::string FindFullName(const std::string& 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
- // data that is gathered before the application has reach sufficient
+ // data that is gathered before the application has reached sufficient
// stability (example: most DLL have loaded, etc.)
static base::Time application_start_time() {
if (global_)
@@ -78,7 +178,7 @@ class FieldTrialList : NonThreadSafe {
}
private:
- typedef std::map<std::wstring, FieldTrial*> RegistrationList;
+ typedef std::map<std::string, FieldTrial*> RegistrationList;
static FieldTrialList* global_; // The singleton of this class.
diff --git a/base/field_trial_unittest.cc b/base/field_trial_unittest.cc
index f7be7e0..8d9f2e4 100644
--- a/base/field_trial_unittest.cc
+++ b/base/field_trial_unittest.cc
@@ -7,6 +7,7 @@
#include "base/field_trial.h"
#include "base/logging.h"
+#include "base/string_util.h"
#include "testing/gtest/include/gtest/gtest.h"
class FieldTrialTest : public testing::Test {
@@ -20,17 +21,27 @@ class FieldTrialTest : public testing::Test {
// Test registration, and also check that destructors are called for trials
// (and that Purify doesn't catch us leaking).
TEST_F(FieldTrialTest, Registration) {
- const wchar_t* name1 = L"name 1 test";
- const wchar_t* name2 = L"name 2 test";
+ const char* name1 = "name 1 test";
+ const char* name2 = "name 2 test";
EXPECT_FALSE(FieldTrialList::Find(name1));
EXPECT_FALSE(FieldTrialList::Find(name2));
- FieldTrial* trial1 = new FieldTrial(name1, 0.7);
+ FieldTrial* trial1 = new FieldTrial(name1, 10);
+ EXPECT_EQ(trial1->group(), FieldTrial::kNotParticipating);
+ EXPECT_EQ(trial1->name(), name1);
+ EXPECT_EQ(trial1->group_name(), "");
+
+ trial1->AppendGroup("", 7);
EXPECT_EQ(trial1, FieldTrialList::Find(name1));
EXPECT_FALSE(FieldTrialList::Find(name2));
- FieldTrial* trial2 = new FieldTrial(name2, 0.7);
+ FieldTrial* trial2 = new FieldTrial(name2, 10);
+ EXPECT_EQ(trial2->group(), FieldTrial::kNotParticipating);
+ EXPECT_EQ(trial2->name(), name2);
+ EXPECT_EQ(trial2->group_name(), "");
+
+ trial2->AppendGroup("a first group", 7);
EXPECT_EQ(trial1, FieldTrialList::Find(name1));
EXPECT_EQ(trial2, FieldTrialList::Find(name2));
@@ -38,27 +49,37 @@ TEST_F(FieldTrialTest, Registration) {
}
TEST_F(FieldTrialTest, AbsoluteProbabilities) {
- wchar_t always_true[] = L" always true";
- wchar_t always_false[] = L" always false";
+ char always_true[] = " always true";
+ char always_false[] = " always false";
for (int i = 1; i < 250; ++i) {
// Try lots of names, by changing the first character of the name.
always_true[0] = i;
always_false[0] = i;
- FieldTrial* trial_true = new FieldTrial(always_true, 1.0);
- EXPECT_TRUE(trial_true->boolean_value());
- FieldTrial* trial_false = new FieldTrial(always_false, 0.0);
- EXPECT_FALSE(trial_false->boolean_value());
+
+ FieldTrial* trial_true = new FieldTrial(always_true, 10);
+ const std::string winner = "_TheWinner";
+ int winner_group = trial_true->AppendGroup(winner, 10);
+
+ EXPECT_EQ(trial_true->group(), winner_group);
+ EXPECT_EQ(trial_true->group_name(), winner);
+
+ FieldTrial* trial_false = new FieldTrial(always_false, 10);
+ int loser_group = trial_false->AppendGroup("ALoser", 0);
+
+ EXPECT_NE(trial_false->group(), loser_group);
}
}
-TEST_F(FieldTrialTest, MiddleProbabalities) {
- wchar_t name[] = L" same name";
+TEST_F(FieldTrialTest, MiddleProbabilities) {
+ char name[] = " same name";
bool false_event_seen = false;
bool true_event_seen = false;
for (int i = 1; i < 250; ++i) {
name[0] = i;
- FieldTrial* trial = new FieldTrial(name, 0.5);
- if (trial->boolean_value()) {
+ FieldTrial* trial = new FieldTrial(name, 10);
+ int might_win = trial->AppendGroup("MightWin", 5);
+
+ if (trial->group() == might_win) {
true_event_seen = true;
} else {
false_event_seen = true;
@@ -71,3 +92,27 @@ TEST_F(FieldTrialTest, MiddleProbabalities) {
EXPECT_TRUE(false_event_seen);
EXPECT_TRUE(true_event_seen);
}
+
+TEST_F(FieldTrialTest, OneWinner) {
+ char name[] = "Some name";
+ int group_count(10);
+
+ FieldTrial* trial = new FieldTrial(name, group_count);
+ int winner_index(-2);
+ std::string winner_name;
+
+ for (int i = 1; i <= group_count; ++i) {
+ int might_win = trial->AppendGroup("", 1);
+
+ if (trial->group() == might_win) {
+ EXPECT_EQ(winner_index, -2);
+ winner_index = might_win;
+ StringAppendF(&winner_name, "_%d", might_win);
+ EXPECT_EQ(winner_name, trial->group_name());
+ }
+ }
+ EXPECT_GE(winner_index, 0);
+ EXPECT_EQ(trial->group(), winner_index);
+ EXPECT_EQ(winner_name, trial->group_name());
+}
+