diff options
Diffstat (limited to 'base')
-rw-r--r-- | base/SConscript | 2 | ||||
-rw-r--r-- | base/build/base.vcproj | 16 | ||||
-rw-r--r-- | base/build/base_unittests.vcproj | 4 | ||||
-rw-r--r-- | base/field_trial.cc | 62 | ||||
-rw-r--r-- | base/field_trial.h | 92 | ||||
-rw-r--r-- | base/field_trial_unittest.cc | 74 |
6 files changed, 246 insertions, 4 deletions
diff --git a/base/SConscript b/base/SConscript index ef462d9..82d652e 100644 --- a/base/SConscript +++ b/base/SConscript @@ -35,6 +35,7 @@ input_files = [ 'bzip2_error_handler.cc', 'command_line.cc', 'debug_util.cc', + 'field_trial.cc', 'file_path.cc', 'file_util.cc', 'histogram.cc', @@ -257,6 +258,7 @@ test_files = [ 'clipboard_unittest.cc', 'command_line_unittest.cc', 'condition_variable_unittest.cc', + 'field_trial_unittest.cc', 'file_path_unittest.cc', 'file_util_unittest.cc', 'hmac_unittest.cc', diff --git a/base/build/base.vcproj b/base/build/base.vcproj index 76a08e9..0350c33 100644 --- a/base/build/base.vcproj +++ b/base/build/base.vcproj @@ -286,6 +286,14 @@ > </File> <File + RelativePath="..\field_trial.cc" + > + </File> + <File + RelativePath="..\field_trial.h" + > + </File> + <File RelativePath="..\file_path.cc" > </File> @@ -538,10 +546,6 @@ > </File> <File - RelativePath="..\process_win.cc" - > - </File> - <File RelativePath="..\process.h" > </File> @@ -554,6 +558,10 @@ > </File> <File + RelativePath="..\process_win.cc" + > + </File> + <File RelativePath="..\third_party\nspr\prtime.cc" > </File> diff --git a/base/build/base_unittests.vcproj b/base/build/base_unittests.vcproj index 7df06c9..cf91226 100644 --- a/base/build/base_unittests.vcproj +++ b/base/build/base_unittests.vcproj @@ -188,6 +188,10 @@ > </File> <File + RelativePath="..\field_trial_unittest.cc" + > + </File> + <File RelativePath="..\file_path_unittest.cc" > </File> diff --git a/base/field_trial.cc b/base/field_trial.cc new file mode 100644 index 0000000..103e539 --- /dev/null +++ b/base/field_trial.cc @@ -0,0 +1,62 @@ +// Copyright (c) 2006-2008 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 "base/field_trial.h" +#include "base/logging.h" +#include "base/rand_util.h" + +//------------------------------------------------------------------------------ +// FieldTrialList methods and members. + +// static +FieldTrialList* FieldTrialList::global_ = NULL; + +// static +int FieldTrialList::constructor_count_ = 0; + +FieldTrialList::FieldTrialList() + : application_start_time_(Time::Now()) { + DCHECK(!constructor_count_); + ++constructor_count_; + global_ = this; +} + +FieldTrialList::~FieldTrialList() { + while (!registered_.empty()) { + RegistrationList::iterator it = registered_.begin(); + it->second->Release(); + registered_.erase(it->first); + } + DCHECK(this == global_); + global_ = NULL; +} + +// static +void FieldTrialList::Register(FieldTrial* trial) { + DCHECK(global_->CalledOnValidThread()); + DCHECK(!Find(trial->name())); + trial->AddRef(); + global_->registered_[trial->name()] = trial; +} + +// static +FieldTrial* FieldTrialList::Find(const std::wstring& name) { + DCHECK(global_->CalledOnValidThread()); + RegistrationList::iterator it = global_->registered_.find(name); + if (global_->registered_.end() == it) + return NULL; + 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 new file mode 100644 index 0000000..e70ab1a --- /dev/null +++ b/base/field_trial.h @@ -0,0 +1,92 @@ +// Copyright (c) 2006-2008 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. + +// FieldTrial is a class for handling details of statistical experiments +// 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). +// 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). + +#ifndef BASE_FIELD_TRIAL_H_ +#define BASE_FIELD_TRIAL_H_ + +#include <map> +#include <string> + +#include "base/non_thread_safe.h" +#include "base/ref_counted.h" +#include "base/time.h" + +class FieldTrial : public base::RefCounted<FieldTrial> { + public: + // Constructor for a 2-state (boolean) trial. + // 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_; } + + private: + const std::wstring name_; + bool boolean_value_; + + 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. +class FieldTrialList : NonThreadSafe { + public: + // This singleton holds the global list of registered FieldTrials. + FieldTrialList(); + // Destructor Release()'s references to all registered FieldTrial instances. + ~FieldTrialList(); + + // Register() stores a pointer to the given trial in a global map. + // This method also AddRef's the indicated trial. + static void Register(FieldTrial* trial); + + // 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); + + // 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 + // stability (example: most DLL have loaded, etc.) + static Time application_start_time() { + return global_->application_start_time_; + } + + private: + typedef std::map<std::wstring, FieldTrial*> RegistrationList; + + friend class FieldTrialTest; + static void ResetConstructorCountForTestingOnly() { constructor_count_ = 0; } + + static FieldTrialList* global_; // The singleton of this class. + static int constructor_count_; // Prevent having more than one. + + Time application_start_time_; + RegistrationList registered_; + + DISALLOW_COPY_AND_ASSIGN(FieldTrialList); +}; + +#endif // BASE_FIELD_TRIAL_H_ diff --git a/base/field_trial_unittest.cc b/base/field_trial_unittest.cc new file mode 100644 index 0000000..3d8c0e2 --- /dev/null +++ b/base/field_trial_unittest.cc @@ -0,0 +1,74 @@ +// Copyright (c) 2006-2008 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. + +// Test of FieldTrial class + +#include "base/field_trial.h" + +#include "base/logging.h" +#include "testing/gtest/include/gtest/gtest.h" + +class FieldTrialTest : public testing::Test { + public: + FieldTrialTest() : trial_list_() { } + ~FieldTrialTest() { FieldTrialList::ResetConstructorCountForTestingOnly(); } + + private: + FieldTrialList trial_list_; +}; + +// 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"; + EXPECT_FALSE(FieldTrialList::Find(name1)); + EXPECT_FALSE(FieldTrialList::Find(name2)); + + FieldTrial* trial1 = new FieldTrial(name1, 0.7); + + EXPECT_EQ(trial1, FieldTrialList::Find(name1)); + EXPECT_FALSE(FieldTrialList::Find(name2)); + + FieldTrial* trial2 = new FieldTrial(name2, 0.7); + + EXPECT_EQ(trial1, FieldTrialList::Find(name1)); + EXPECT_EQ(trial2, FieldTrialList::Find(name2)); + // Note: FieldTrialList should delete the objects at shutdown. +} + +TEST_F(FieldTrialTest, AbsoluteProbabilities) { + wchar_t always_true[] = L" always true"; + wchar_t always_false[] = L" 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()); + } +} + +TEST_F(FieldTrialTest, MiddleProbabalities) { + wchar_t name[] = L" 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()) { + true_event_seen = true; + } else { + false_event_seen = true; + } + if (false_event_seen && true_event_seen) + return; // Successful test!!! + } + // Very surprising to get here. Probability should be around 1 in 2 ** 250. + // One of the following will fail. + EXPECT_TRUE(false_event_seen); + EXPECT_TRUE(true_event_seen); +} |