Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(29)

Unified Diff: base/metrics/field_trial.cc

Issue 6883102: Add one-time randomization support for FieldTrial, and the ability to (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Update comments. Created 9 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: base/metrics/field_trial.cc
diff --git a/base/metrics/field_trial.cc b/base/metrics/field_trial.cc
index daeb36929e0e2a9b03607c6804a6e5b118bbea12..ab87c48d27f1a2c8b91bafe46afe4b93310d89df 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"
@@ -41,11 +43,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);
@@ -55,7 +59,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.
@@ -65,8 +69,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);
jar (doing other things) 2011/05/03 00:17:47 Could you also add a DCHECK_EQ(0, next_group_numbe
Jói 2011/05/03 17:41:47 Done.
+ 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,
@@ -74,7 +102,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;
@@ -84,7 +112,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;
FieldTrialList::NotifyFieldTrialGroupSelection(name_, group_name_);
@@ -104,6 +132,7 @@ int FieldTrial::group() {
std::string FieldTrial::group_name() {
group(); // call group() to make group assignment was done.
jar (doing other things) 2011/05/03 00:17:47 nit (in old code): "make group assignment" --> "m
Jói 2011/05/03 17:41:47 Done.
+ DCHECK(!group_name_.empty());
return group_name_;
}
@@ -133,6 +162,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.
@@ -142,8 +190,9 @@ FieldTrialList* FieldTrialList::global_ = NULL;
// static
bool FieldTrialList::register_without_global_ = false;
-FieldTrialList::FieldTrialList()
+FieldTrialList::FieldTrialList(const std::string& client_id)
: application_start_time_(TimeTicks::Now()),
+ client_id_(client_id),
observer_list_(ObserverList<Observer>::NOTIFY_EXISTING_ONLY) {
DCHECK(!global_);
DCHECK(!register_without_global_);
@@ -204,11 +253,21 @@ 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;
DCHECK(output->empty());
AutoLock auto_lock(global_->lock_);
+
+ // First, write just the client_id plus a separator.
jar (doing other things) 2011/05/03 00:17:47 Please add a DCHECK() comparable to line 279 to en
Jói 2011/05/03 17:41:47 Removed this code.
+ output->append(global_->client_id_);
+ output->append(1, kPersistentStringSeparator);
+
for (RegistrationList::iterator it = global_->registered_.begin();
it != global_->registered_.end(); ++it) {
const std::string name = it->first;
@@ -234,6 +293,15 @@ bool FieldTrialList::CreateTrialsInChildProcess(
return true;
size_t next_item = 0;
+
+ // Get the client_id first, before the list of trials/groups.
+ size_t id_end = parent_trials.find(kPersistentStringSeparator, next_item);
+ // Must allow empty client_id, so we don't check for id_end == 0.
+ if (id_end == parent_trials.npos)
+ return false;
+ global_->client_id_ = std::string(parent_trials, 0, id_end);
+ next_item = id_end + 1;
+
while (next_item < parent_trials.length()) {
size_t name_end = parent_trials.find(kPersistentStringSeparator, next_item);
if (name_end == parent_trials.npos || next_item == name_end)
@@ -313,6 +381,24 @@ size_t FieldTrialList::GetFieldTrialCount() {
return global_->registered_.size();
}
+// 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)

Powered by Google App Engine
This is Rietveld 408576698