Index: base/metrics/field_trial.cc |
diff --git a/base/metrics/field_trial.cc b/base/metrics/field_trial.cc |
index ad2e5a0ccb118be812ecef9e998904f3c19e6d8a..eee0bdd75d04dd13af956022bf91089ef393320e 100644 |
--- a/base/metrics/field_trial.cc |
+++ b/base/metrics/field_trial.cc |
@@ -6,6 +6,8 @@ |
#include "base/logging.h" |
#include "base/rand_util.h" |
+#include "base/sha1.h" |
+#include "base/string_util.h" |
#include "base/stringprintf.h" |
#include "base/utf_string_conversions.h" |
@@ -37,11 +39,13 @@ FieldTrial::FieldTrial(const std::string& name, |
: name_(name), |
divisor_(total_probability), |
default_group_name_(default_group_name), |
- random_(static_cast<Probability>(divisor_ * base::RandDouble())), |
+ random_(static_cast<Probability>(divisor_ * RandDouble())), |
accumulated_group_probability_(0), |
next_group_number_(kDefaultGroupNumber+1), |
- group_(kNotFinalized) { |
+ group_(kNotFinalized), |
+ enable_field_trial_(true) { |
DCHECK_GT(total_probability, 0); |
+ DCHECK(!name_.empty()); |
DCHECK(!default_group_name_.empty()); |
FieldTrialList::Register(this); |
@@ -51,7 +55,7 @@ FieldTrial::FieldTrial(const std::string& name, |
DCHECK_GT(day_of_month, 0); |
DCHECK_LT(day_of_month, 32); |
- base::Time::Exploded exploded; |
+ Time::Exploded exploded; |
exploded.year = year; |
exploded.month = month; |
exploded.day_of_week = 0; // Should be unused. |
@@ -61,8 +65,32 @@ FieldTrial::FieldTrial(const std::string& name, |
exploded.second = 0; |
exploded.millisecond = 0; |
- base::Time expiration_time = Time::FromLocalExploded(exploded); |
- disable_field_trial_ = (GetBuildTime() > expiration_time) ? true : false; |
+ Time expiration_time = Time::FromLocalExploded(exploded); |
+ if (GetBuildTime() > expiration_time) |
+ Disable(); |
+} |
+ |
+void FieldTrial::UseOneTimeRandomization() { |
+ DCHECK_EQ(group_, kNotFinalized); |
+ if (!FieldTrialList::IsOneTimeRandomizationEnabled()) { |
+ NOTREACHED(); |
+ Disable(); |
+ return; |
+ } |
+ |
+ random_ = static_cast<Probability>( |
+ divisor_ * HashClientId(FieldTrialList::client_id(), name_)); |
+} |
+ |
+void FieldTrial::Disable() { |
+ enable_field_trial_ = false; |
+ |
+ // In case we are disabled after initialization, we need to switch |
+ // the trial to the default group. |
+ if (group_ != kNotFinalized) { |
+ group_ = kDefaultGroupNumber; |
+ group_name_ = default_group_name_; |
+ } |
} |
int FieldTrial::AppendGroup(const std::string& name, |
@@ -70,7 +98,7 @@ int FieldTrial::AppendGroup(const std::string& name, |
DCHECK_LE(group_probability, divisor_); |
DCHECK_GE(group_probability, 0); |
- if (enable_benchmarking_ || disable_field_trial_) |
+ if (enable_benchmarking_ || !enable_field_trial_) |
group_probability = 0; |
accumulated_group_probability_ += group_probability; |
@@ -80,7 +108,7 @@ int FieldTrial::AppendGroup(const std::string& name, |
// This is the group that crossed the random line, so we do the assignment. |
group_ = next_group_number_; |
if (name.empty()) |
- base::StringAppendF(&group_name_, "%d", group_); |
+ StringAppendF(&group_name_, "%d", group_); |
else |
group_name_ = name; |
} |
@@ -98,6 +126,7 @@ int FieldTrial::group() { |
std::string FieldTrial::group_name() { |
group(); // call group() to make group assignment was done. |
+ DCHECK(!group_name_.empty()); |
return group_name_; |
} |
@@ -127,6 +156,25 @@ Time FieldTrial::GetBuildTime() { |
return integral_build_time; |
} |
+// static |
+double FieldTrial::HashClientId(const std::string& client_id, |
+ const std::string& trial_name) { |
+ // SHA-1 is designed to produce a uniformly random spread in its output space, |
+ // even for nearly-identical inputs, so it helps massage whatever client_id |
+ // and trial_name we get into something with a uniform distribution, which |
+ // is desirable so that we don't skew any part of the 0-100% spectrum. |
+ std::string input(client_id + trial_name); |
+ unsigned char sha1_hash[SHA1_LENGTH]; |
+ SHA1HashBytes(reinterpret_cast<const unsigned char*>(input.c_str()), |
+ input.size(), |
+ sha1_hash); |
+ |
+ COMPILE_ASSERT(sizeof(uint64) < sizeof(sha1_hash), need_more_data); |
+ uint64* bits = reinterpret_cast<uint64*>(&sha1_hash[0]); |
+ |
+ return BitsToOpenEndedUnitInterval(*bits); |
+} |
+ |
//------------------------------------------------------------------------------ |
// FieldTrialList methods and members. |
@@ -190,6 +238,11 @@ std::string FieldTrialList::FindFullName(const std::string& name) { |
} |
// static |
+bool FieldTrialList::TrialExists(const std::string& name) { |
+ return Find(name) != NULL; |
+} |
+ |
+// static |
void FieldTrialList::StatesToString(std::string* output) { |
if (!global_) |
return; |
@@ -263,6 +316,36 @@ size_t FieldTrialList::GetFieldTrialCount() { |
return global_->registered_.size(); |
} |
+// static |
+void FieldTrialList::EnableOneTimeRandomization(const std::string& client_id) { |
+ DCHECK(global_); |
+ if (!global_) |
+ return; |
+ |
+ DCHECK(!global_->IsOneTimeRandomizationEnabled()); |
+ DCHECK(!client_id.empty()); |
+ |
+ global_->client_id_ = client_id; |
+} |
+ |
+// static |
+bool FieldTrialList::IsOneTimeRandomizationEnabled() { |
+ DCHECK(global_); |
+ if (!global_) |
+ return false; |
+ |
+ return !global_->client_id_.empty(); |
+} |
+ |
+// static |
+const std::string& FieldTrialList::client_id() { |
+ DCHECK(global_); |
+ if (!global_) |
+ return EmptyString(); |
+ |
+ return global_->client_id_; |
+} |
+ |
FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) { |
RegistrationList::iterator it = registered_.find(name); |
if (registered_.end() == it) |