Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "base/metrics/field_trial.h" | 5 #include "base/metrics/field_trial.h" |
| 6 | 6 |
| 7 #include "base/build_time.h" | 7 #include "base/build_time.h" |
| 8 #include "base/logging.h" | 8 #include "base/logging.h" |
| 9 #include "base/rand_util.h" | 9 #include "base/rand_util.h" |
| 10 #include "base/sha1.h" | 10 #include "base/sha1.h" |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 46 const int FieldTrial::kNotFinalized = -1; | 46 const int FieldTrial::kNotFinalized = -1; |
| 47 const int FieldTrial::kDefaultGroupNumber = 0; | 47 const int FieldTrial::kDefaultGroupNumber = 0; |
| 48 bool FieldTrial::enable_benchmarking_ = false; | 48 bool FieldTrial::enable_benchmarking_ = false; |
| 49 | 49 |
| 50 const char FieldTrialList::kPersistentStringSeparator('/'); | 50 const char FieldTrialList::kPersistentStringSeparator('/'); |
| 51 int FieldTrialList::kExpirationYearInFuture = 0; | 51 int FieldTrialList::kExpirationYearInFuture = 0; |
| 52 | 52 |
| 53 //------------------------------------------------------------------------------ | 53 //------------------------------------------------------------------------------ |
| 54 // FieldTrial methods and members. | 54 // FieldTrial methods and members. |
| 55 | 55 |
| 56 FieldTrial::FieldTrial(const std::string& name, | 56 FieldTrial::FieldTrial(const EntropyProvider* entropy_provider, |
| 57 const std::string& name, | |
| 57 const Probability total_probability, | 58 const Probability total_probability, |
| 58 const std::string& default_group_name) | 59 const std::string& default_group_name) |
| 59 : name_(name), | 60 : entropy_provider_(entropy_provider), |
|
jar (doing other things)
2012/08/17 19:06:19
nit: Please indent the ":" by four spaces.
Alexei Svitkine (slow)
2012/08/20 15:57:40
Done.
| |
| 61 name_(name), | |
| 60 divisor_(total_probability), | 62 divisor_(total_probability), |
| 61 default_group_name_(default_group_name), | 63 default_group_name_(default_group_name), |
| 62 random_(static_cast<Probability>(divisor_ * RandDouble())), | 64 random_(static_cast<Probability>(divisor_ * RandDouble())), |
| 63 accumulated_group_probability_(0), | 65 accumulated_group_probability_(0), |
| 64 next_group_number_(kDefaultGroupNumber + 1), | 66 next_group_number_(kDefaultGroupNumber + 1), |
| 65 group_(kNotFinalized), | 67 group_(kNotFinalized), |
| 66 enable_field_trial_(true), | 68 enable_field_trial_(true), |
| 67 forced_(false) { | 69 forced_(false) { |
| 68 DCHECK_GT(total_probability, 0); | 70 DCHECK_GT(total_probability, 0); |
| 69 DCHECK(!name_.empty()); | 71 DCHECK(!name_.empty()); |
| 70 DCHECK(!default_group_name_.empty()); | 72 DCHECK(!default_group_name_.empty()); |
| 71 } | 73 } |
| 72 | 74 |
| 75 FieldTrial::EntropyProvider::~EntropyProvider() { | |
| 76 } | |
| 77 | |
| 73 void FieldTrial::UseOneTimeRandomization() { | 78 void FieldTrial::UseOneTimeRandomization() { |
| 74 // No need to specify randomization when the group choice was forced. | 79 // No need to specify randomization when the group choice was forced. |
| 75 if (forced_) | 80 if (forced_) |
| 76 return; | 81 return; |
| 77 DCHECK_EQ(group_, kNotFinalized); | 82 DCHECK_EQ(group_, kNotFinalized); |
| 78 DCHECK_EQ(kDefaultGroupNumber + 1, next_group_number_); | 83 DCHECK_EQ(kDefaultGroupNumber + 1, next_group_number_); |
| 79 if (!FieldTrialList::IsOneTimeRandomizationEnabled()) { | 84 if (!FieldTrialList::IsOneTimeRandomizationEnabled()) { |
|
jar (doing other things)
2012/08/17 19:06:19
This function, could, for example, return an entro
Alexei Svitkine (slow)
2012/08/20 15:57:40
Done.
By having the entropy provider passed in ex
| |
| 80 NOTREACHED(); | 85 NOTREACHED(); |
| 81 Disable(); | 86 Disable(); |
| 82 return; | 87 return; |
| 83 } | 88 } |
| 84 | 89 |
| 85 random_ = static_cast<Probability>( | 90 random_ = static_cast<Probability>( |
| 86 divisor_ * HashClientId(FieldTrialList::client_id(), name_)); | 91 divisor_ * entropy_provider_->GetEntropyForTrial(name_)); |
| 87 } | 92 } |
| 88 | 93 |
| 89 void FieldTrial::Disable() { | 94 void FieldTrial::Disable() { |
| 90 enable_field_trial_ = false; | 95 enable_field_trial_ = false; |
| 91 | 96 |
| 92 // In case we are disabled after initialization, we need to switch | 97 // In case we are disabled after initialization, we need to switch |
| 93 // the trial to the default group. | 98 // the trial to the default group. |
| 94 if (group_ != kNotFinalized) { | 99 if (group_ != kNotFinalized) { |
| 95 // Only reset when not already the default group, because in case we were | 100 // Only reset when not already the default group, because in case we were |
| 96 // forced to the default group, the group number may not be | 101 // forced to the default group, the group number may not be |
| (...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 191 | 196 |
| 192 void FieldTrial::SetGroupChoice(const std::string& name, int number) { | 197 void FieldTrial::SetGroupChoice(const std::string& name, int number) { |
| 193 group_ = number; | 198 group_ = number; |
| 194 if (name.empty()) | 199 if (name.empty()) |
| 195 StringAppendF(&group_name_, "%d", group_); | 200 StringAppendF(&group_name_, "%d", group_); |
| 196 else | 201 else |
| 197 group_name_ = name; | 202 group_name_ = name; |
| 198 DVLOG(1) << "Field trial: " << name_ << " Group choice:" << group_name_; | 203 DVLOG(1) << "Field trial: " << name_ << " Group choice:" << group_name_; |
| 199 } | 204 } |
| 200 | 205 |
| 201 // static | |
| 202 double FieldTrial::HashClientId(const std::string& client_id, | |
| 203 const std::string& trial_name) { | |
| 204 // SHA-1 is designed to produce a uniformly random spread in its output space, | |
| 205 // even for nearly-identical inputs, so it helps massage whatever client_id | |
| 206 // and trial_name we get into something with a uniform distribution, which | |
| 207 // is desirable so that we don't skew any part of the 0-100% spectrum. | |
| 208 std::string input(client_id + trial_name); | |
| 209 unsigned char sha1_hash[kSHA1Length]; | |
| 210 SHA1HashBytes(reinterpret_cast<const unsigned char*>(input.c_str()), | |
| 211 input.size(), | |
| 212 sha1_hash); | |
| 213 | |
| 214 COMPILE_ASSERT(sizeof(uint64) < sizeof(sha1_hash), need_more_data); | |
| 215 uint64 bits; | |
| 216 memcpy(&bits, sha1_hash, sizeof(bits)); | |
| 217 bits = base::ByteSwapToLE64(bits); | |
| 218 | |
| 219 return BitsToOpenEndedUnitInterval(bits); | |
| 220 } | |
| 221 | |
| 222 //------------------------------------------------------------------------------ | 206 //------------------------------------------------------------------------------ |
| 223 // FieldTrialList methods and members. | 207 // FieldTrialList methods and members. |
| 224 | 208 |
| 225 // static | 209 // static |
| 226 FieldTrialList* FieldTrialList::global_ = NULL; | 210 FieldTrialList* FieldTrialList::global_ = NULL; |
| 227 | 211 |
| 228 // static | 212 // static |
| 229 bool FieldTrialList::used_without_global_ = false; | 213 bool FieldTrialList::used_without_global_ = false; |
| 230 | 214 |
| 231 FieldTrialList::FieldTrialList(const std::string& client_id) | 215 FieldTrialList::Observer::~Observer() { |
| 216 } | |
| 217 | |
| 218 FieldTrialList::FieldTrialList(FieldTrial::EntropyProvider* entropy_provider) | |
| 232 : application_start_time_(TimeTicks::Now()), | 219 : application_start_time_(TimeTicks::Now()), |
| 233 client_id_(client_id), | 220 entropy_provider_(entropy_provider), |
| 234 observer_list_(new ObserverListThreadSafe<FieldTrialList::Observer>( | 221 observer_list_(new ObserverListThreadSafe<FieldTrialList::Observer>( |
| 235 ObserverListBase<FieldTrialList::Observer>::NOTIFY_EXISTING_ONLY)) { | 222 ObserverListBase<FieldTrialList::Observer>::NOTIFY_EXISTING_ONLY)) { |
| 236 DCHECK(!global_); | 223 DCHECK(!global_); |
| 237 DCHECK(!used_without_global_); | 224 DCHECK(!used_without_global_); |
| 238 global_ = this; | 225 global_ = this; |
| 239 | 226 |
| 240 Time::Exploded exploded; | 227 Time::Exploded exploded; |
| 241 Time two_years_from_now = | 228 Time two_years_from_now = |
| 242 Time::NowFromSystemTime() + TimeDelta::FromDays(730); | 229 Time::NowFromSystemTime() + TimeDelta::FromDays(730); |
| 243 two_years_from_now.LocalExplode(&exploded); | 230 two_years_from_now.LocalExplode(&exploded); |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 272 CHECK(existing_trial->forced_); | 259 CHECK(existing_trial->forced_); |
| 273 // If the field trial has already been forced, check whether it was forced | 260 // If the field trial has already been forced, check whether it was forced |
| 274 // to the default group. Return the chosen group number, in that case.. | 261 // to the default group. Return the chosen group number, in that case.. |
| 275 if (default_group_number && | 262 if (default_group_number && |
| 276 default_group_name == existing_trial->default_group_name()) { | 263 default_group_name == existing_trial->default_group_name()) { |
| 277 *default_group_number = existing_trial->group(); | 264 *default_group_number = existing_trial->group(); |
| 278 } | 265 } |
| 279 return existing_trial; | 266 return existing_trial; |
| 280 } | 267 } |
| 281 | 268 |
| 282 FieldTrial* field_trial = | 269 FieldTrial* field_trial = new FieldTrial(global_->entropy_provider_.get(), |
| 283 new FieldTrial(name, total_probability, default_group_name); | 270 name, total_probability, |
| 271 default_group_name); | |
| 284 if (GetBuildTime() > CreateTimeFromParams(year, month, day_of_month)) | 272 if (GetBuildTime() > CreateTimeFromParams(year, month, day_of_month)) |
| 285 field_trial->Disable(); | 273 field_trial->Disable(); |
| 286 FieldTrialList::Register(field_trial); | 274 FieldTrialList::Register(field_trial); |
| 287 return field_trial; | 275 return field_trial; |
| 288 } | 276 } |
| 289 | 277 |
| 290 // static | 278 // static |
| 291 FieldTrial* FieldTrialList::Find(const std::string& name) { | 279 FieldTrial* FieldTrialList::Find(const std::string& name) { |
| 292 if (!global_) | 280 if (!global_) |
| 293 return NULL; | 281 return NULL; |
| (...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 392 | 380 |
| 393 FieldTrial* field_trial = FieldTrialList::Find(name); | 381 FieldTrial* field_trial = FieldTrialList::Find(name); |
| 394 if (field_trial) { | 382 if (field_trial) { |
| 395 // In single process mode, or when we force them from the command line, | 383 // In single process mode, or when we force them from the command line, |
| 396 // we may have already created the field trial. | 384 // we may have already created the field trial. |
| 397 if (field_trial->group_name_internal() != group_name) | 385 if (field_trial->group_name_internal() != group_name) |
| 398 return NULL; | 386 return NULL; |
| 399 return field_trial; | 387 return field_trial; |
| 400 } | 388 } |
| 401 const int kTotalProbability = 100; | 389 const int kTotalProbability = 100; |
| 402 field_trial = new FieldTrial(name, kTotalProbability, group_name); | 390 field_trial = new FieldTrial(global_->entropy_provider_.get(), |
| 391 name, kTotalProbability, group_name); | |
| 403 // This is where we may assign a group number different from | 392 // This is where we may assign a group number different from |
| 404 // kDefaultGroupNumber to the default group. | 393 // kDefaultGroupNumber to the default group. |
| 405 field_trial->AppendGroup(group_name, kTotalProbability); | 394 field_trial->AppendGroup(group_name, kTotalProbability); |
| 406 field_trial->forced_ = true; | 395 field_trial->forced_ = true; |
| 407 FieldTrialList::Register(field_trial); | 396 FieldTrialList::Register(field_trial); |
| 408 return field_trial; | 397 return field_trial; |
| 409 } | 398 } |
| 410 | 399 |
| 411 // static | 400 // static |
| 412 void FieldTrialList::AddObserver(Observer* observer) { | 401 void FieldTrialList::AddObserver(Observer* observer) { |
| (...skipping 26 matching lines...) Expand all Loading... | |
| 439 | 428 |
| 440 // static | 429 // static |
| 441 size_t FieldTrialList::GetFieldTrialCount() { | 430 size_t FieldTrialList::GetFieldTrialCount() { |
| 442 if (!global_) | 431 if (!global_) |
| 443 return 0; | 432 return 0; |
| 444 AutoLock auto_lock(global_->lock_); | 433 AutoLock auto_lock(global_->lock_); |
| 445 return global_->registered_.size(); | 434 return global_->registered_.size(); |
| 446 } | 435 } |
| 447 | 436 |
| 448 // static | 437 // static |
| 449 bool FieldTrialList::IsOneTimeRandomizationEnabled() { | 438 bool FieldTrialList::IsOneTimeRandomizationEnabled() { |
|
jar (doing other things)
2012/08/17 19:06:19
You might change this to:
GetEntropyProviderForOne
Alexei Svitkine (slow)
2012/08/20 15:57:40
Done.
| |
| 450 if (!global_) { | 439 if (!global_) { |
| 451 used_without_global_ = true; | 440 used_without_global_ = true; |
| 452 return false; | 441 return false; |
| 453 } | 442 } |
| 454 | 443 |
| 455 return !global_->client_id_.empty(); | 444 return global_->entropy_provider_.get() != NULL; |
| 456 } | |
| 457 | |
| 458 // static | |
| 459 const std::string& FieldTrialList::client_id() { | |
| 460 DCHECK(global_); | |
| 461 if (!global_) | |
| 462 return EmptyString(); | |
| 463 | |
| 464 return global_->client_id_; | |
| 465 } | 445 } |
| 466 | 446 |
| 467 FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) { | 447 FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) { |
| 468 RegistrationList::iterator it = registered_.find(name); | 448 RegistrationList::iterator it = registered_.find(name); |
| 469 if (registered_.end() == it) | 449 if (registered_.end() == it) |
| 470 return NULL; | 450 return NULL; |
| 471 return it->second; | 451 return it->second; |
| 472 } | 452 } |
| 473 | 453 |
| 474 // static | 454 // static |
| 475 void FieldTrialList::Register(FieldTrial* trial) { | 455 void FieldTrialList::Register(FieldTrial* trial) { |
| 476 if (!global_) { | 456 if (!global_) { |
| 477 used_without_global_ = true; | 457 used_without_global_ = true; |
| 478 return; | 458 return; |
| 479 } | 459 } |
| 480 AutoLock auto_lock(global_->lock_); | 460 AutoLock auto_lock(global_->lock_); |
| 481 DCHECK(!global_->PreLockedFind(trial->name())); | 461 DCHECK(!global_->PreLockedFind(trial->name())); |
| 482 trial->AddRef(); | 462 trial->AddRef(); |
| 483 global_->registered_[trial->name()] = trial; | 463 global_->registered_[trial->name()] = trial; |
| 484 } | 464 } |
| 485 | 465 |
| 486 } // namespace base | 466 } // namespace base |
| OLD | NEW |