Skip to content

Commit

Permalink
Make field trial randomization type a constructor param.
Browse files Browse the repository at this point in the history
Changes the Field Trial API to require users to pass ONE_TIME_RANDOMIZED
or SESSION_RANDOMIZED at the time the field trial is created.

This removes a source of human errors, where developers would sometimes
previously forget to call UseOneTimeRandomization() in their CLs.

It's a small startup performance win, since one time randomized trials will
no longer call RandDouble() that will be then replaced by a value from the
entropy provider. (This was showing up in profiles when I was measuring
metrics startup perf on mobile.)

BUG=262971
TEST=Existing unit tests.
TBR=isherman@chromium.org, thakis@chromium.org

Review URL: https://codereview.chromium.org/20777005

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@214352 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
asvitkine@chromium.org committed Jul 30, 2013
1 parent b554d46 commit ebcf69f
Show file tree
Hide file tree
Showing 21 changed files with 295 additions and 318 deletions.
66 changes: 35 additions & 31 deletions base/metrics/field_trial.cc
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,12 @@ int FieldTrialList::kNoExpirationYear = 0;

FieldTrial::FieldTrial(const std::string& trial_name,
const Probability total_probability,
const std::string& default_group_name)
const std::string& default_group_name,
double entropy_value)
: trial_name_(trial_name),
divisor_(total_probability),
default_group_name_(default_group_name),
random_(static_cast<Probability>(divisor_ * RandDouble())),
random_(static_cast<Probability>(divisor_ * entropy_value)),
accumulated_group_probability_(0),
next_group_number_(kDefaultGroupNumber + 1),
group_(kNotFinalized),
Expand All @@ -74,30 +75,6 @@ FieldTrial::FieldTrial(const std::string& trial_name,
FieldTrial::EntropyProvider::~EntropyProvider() {
}

void FieldTrial::UseOneTimeRandomization() {
UseOneTimeRandomizationWithCustomSeed(0);
}

void FieldTrial::UseOneTimeRandomizationWithCustomSeed(
uint32 randomization_seed) {
// No need to specify randomization when the group choice was forced.
if (forced_)
return;
DCHECK_EQ(group_, kNotFinalized);
DCHECK_EQ(kDefaultGroupNumber + 1, next_group_number_);
const EntropyProvider* entropy_provider =
FieldTrialList::GetEntropyProviderForOneTimeRandomization();
if (!entropy_provider) {
NOTREACHED();
Disable();
return;
}

random_ = static_cast<Probability>(
divisor_ * entropy_provider->GetEntropyForTrial(trial_name_,
randomization_seed));
}

void FieldTrial::Disable() {
DCHECK(!group_reported_);
enable_field_trial_ = false;
Expand Down Expand Up @@ -256,17 +233,34 @@ FieldTrialList::~FieldTrialList() {

// static
FieldTrial* FieldTrialList::FactoryGetFieldTrial(
const std::string& name,
const std::string& trial_name,
FieldTrial::Probability total_probability,
const std::string& default_group_name,
const int year,
const int month,
const int day_of_month,
FieldTrial::RandomizationType randomization_type,
int* default_group_number) {
return FactoryGetFieldTrialWithRandomizationSeed(
trial_name, total_probability, default_group_name,
year, month, day_of_month, randomization_type, 0, default_group_number);
}

// static
FieldTrial* FieldTrialList::FactoryGetFieldTrialWithRandomizationSeed(
const std::string& trial_name,
FieldTrial::Probability total_probability,
const std::string& default_group_name,
const int year,
const int month,
const int day_of_month,
FieldTrial::RandomizationType randomization_type,
uint32 randomization_seed,
int* default_group_number) {
if (default_group_number)
*default_group_number = FieldTrial::kDefaultGroupNumber;
// Check if the field trial has already been created in some other way.
FieldTrial* existing_trial = Find(name);
FieldTrial* existing_trial = Find(trial_name);
if (existing_trial) {
CHECK(existing_trial->forced_);
// If the default group name differs between the existing forced trial
Expand Down Expand Up @@ -295,8 +289,18 @@ FieldTrial* FieldTrialList::FactoryGetFieldTrial(
return existing_trial;
}

FieldTrial* field_trial =
new FieldTrial(name, total_probability, default_group_name);
double entropy_value;
if (randomization_type == FieldTrial::ONE_TIME_RANDOMIZED) {
entropy_value = GetEntropyProviderForOneTimeRandomization()->
GetEntropyForTrial(trial_name, randomization_seed);
} else {
DCHECK_EQ(FieldTrial::SESSION_RANDOMIZED, randomization_type);
DCHECK_EQ(0U, randomization_seed);
entropy_value = RandDouble();
}

FieldTrial* field_trial = new FieldTrial(trial_name, total_probability,
default_group_name, entropy_value);
if (GetBuildTime() > CreateTimeFromParams(year, month, day_of_month))
field_trial->Disable();
FieldTrialList::Register(field_trial);
Expand Down Expand Up @@ -418,7 +422,7 @@ FieldTrial* FieldTrialList::CreateFieldTrial(
return field_trial;
}
const int kTotalProbability = 100;
field_trial = new FieldTrial(name, kTotalProbability, group_name);
field_trial = new FieldTrial(name, kTotalProbability, group_name, 0);
// Force the trial, which will also finalize the group choice.
field_trial->SetForced();
FieldTrialList::Register(field_trial);
Expand Down
75 changes: 45 additions & 30 deletions base/metrics/field_trial.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@
// States are typically generated randomly, either based on a one time
// randomization (which will yield the same results, in terms of selecting
// the client for a field trial or not, for every run of the program on a
// given machine), or by a startup randomization (generated each time the
// given machine), or by a session 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).
// Continuous randomization is not yet implemented.
// process).

//------------------------------------------------------------------------------
// Example: Suppose we have an experiment involving memory, such as determining
Expand All @@ -37,9 +35,10 @@
// // Note: This field trial will run in Chrome instances compiled through
// // 8 July, 2015, and after that all instances will be in "StandardMem".
// scoped_refptr<base::FieldTrial> trial(
// base::FieldTrialList::FactoryGetFieldTrial("MemoryExperiment", 1000,
// "StandardMem", 2015, 7, 8,
// NULL));
// base::FieldTrialList::FactoryGetFieldTrial(
// "MemoryExperiment", 1000, "StandardMem", 2015, 7, 8,
// base::FieldTrial::ONE_TIME_RANDOMIZED, NULL));
//
// const int high_mem_group =
// trial->AppendGroup("HighMem", 20); // 2% in HighMem group.
// const int low_mem_group =
Expand Down Expand Up @@ -95,6 +94,17 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> {
public:
typedef int Probability; // Probability type for being selected in a trial.

// Specifies the persistence of the field trial group choice.
enum RandomizationType {
// One time randomized trials will persist the group choice between
// restarts, which is recommended for most trials, especially those that
// change user visible behavior.
ONE_TIME_RANDOMIZED,
// Session randomized trials will roll the dice to select a group on every
// process restart.
SESSION_RANDOMIZED,
};

// EntropyProvider is an interface for providing entropy for one-time
// randomized (persistent) field trials.
class BASE_EXPORT EntropyProvider {
Expand Down Expand Up @@ -122,21 +132,6 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> {
// assignment (and hence is not yet participating in the trial).
static const int kNotFinalized;

// Changes the field trial to use one-time randomization, i.e. produce the
// same result for the current trial on every run of this client. Must be
// called right after construction, before any groups are added.
void UseOneTimeRandomization();

// Changes the field trial to use one-time randomization, i.e. produce the
// same result for the current trial on every run of this client, with a
// custom randomization seed for the trial (instead of a hash of the trial
// name, which is used otherwise). The |randomization_seed| value should never
// be the same for two trials, else this would result in correlated group
// assignments. Note: Using a custom randomization seed is only supported by
// the PermutedEntropyProvider (which is used when UMA is not enabled). Must
// be called right after construction, before any groups are added.
void UseOneTimeRandomizationWithCustomSeed(uint32 randomization_seed);

// Disables this trial, meaning it always determines the default group
// has been selected. May be called immediately after construction, or
// at any time after initialization (should not be interleaved with
Expand Down Expand Up @@ -201,7 +196,6 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> {
FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, HashClientId);
FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, HashClientIdIsUniform);
FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, NameGroupIds);
FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, UseOneTimeRandomization);
FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, SetForcedTurnFeatureOff);
FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, SetForcedTurnFeatureOn);
FRIEND_TEST_ALL_PREFIXES(FieldTrialTest, SetForcedChangeDefault_Default);
Expand All @@ -216,9 +210,12 @@ class BASE_EXPORT FieldTrial : public RefCounted<FieldTrial> {
// consumers don't use it by mistake in cases where the group was forced.
static const int kDefaultGroupNumber;

// Creates a field trial with the specified parameters. Group assignment will
// be done based on |entropy_value|, which must have a range of [0, 1).
FieldTrial(const std::string& name,
Probability total_probability,
const std::string& default_group_name);
const std::string& default_group_name,
double entropy_value);
virtual ~FieldTrial();

// Return the default group name of the FieldTrial.
Expand Down Expand Up @@ -350,15 +347,33 @@ class BASE_EXPORT FieldTrialList {
// then the field trial reverts to the 'default' group.
//
// Use this static method to get a startup-randomized FieldTrial or a
// previously created forced FieldTrial. If you want a one-time randomized
// trial, call UseOneTimeRandomization() right after creation.
// previously created forced FieldTrial.
static FieldTrial* FactoryGetFieldTrial(
const std::string& trial_name,
FieldTrial::Probability total_probability,
const std::string& default_group_name,
const int year,
const int month,
const int day_of_month,
FieldTrial::RandomizationType randomization_type,
int* default_group_number);

// Same as FactoryGetFieldTrial(), but allows specifying a custom seed to be
// used on one-time randomized field trials (instead of a hash of the trial
// name, which is used otherwise or if |randomization_seed| has value 0). The
// |randomization_seed| value (other than 0) should never be the same for two
// trials, else this would result in correlated group assignments.
// Note: Using a custom randomization seed is only supported by the
// PermutedEntropyProvider (which is used when UMA is not enabled).
static FieldTrial* FactoryGetFieldTrialWithRandomizationSeed(
const std::string& trial_name,
FieldTrial::Probability total_probability,
const std::string& default_group_name,
const int year,
const int month,
const int day_of_month,
FieldTrial::RandomizationType randomization_type,
uint32 randomization_seed,
int* default_group_number);

// The Find() method can be used to test to see if a named Trial was already
Expand Down Expand Up @@ -427,15 +442,15 @@ class BASE_EXPORT FieldTrialList {
// Return the number of active field trials.
static size_t GetFieldTrialCount();

private:
// A map from FieldTrial names to the actual instances.
typedef std::map<std::string, FieldTrial*> RegistrationList;

// If one-time randomization is enabled, returns a weak pointer to the
// corresponding EntropyProvider. Otherwise, returns NULL.
static const FieldTrial::EntropyProvider*
GetEntropyProviderForOneTimeRandomization();

private:
// A map from FieldTrial names to the actual instances.
typedef std::map<std::string, FieldTrial*> RegistrationList;

// Helper function should be called only while holding lock_.
FieldTrial* PreLockedFind(const std::string& name);

Expand Down
Loading

0 comments on commit ebcf69f

Please sign in to comment.