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(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), |
| 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 |
73 void FieldTrial::UseOneTimeRandomization() { | 75 void FieldTrial::UseOneTimeRandomization() { |
74 // No need to specify randomization when the group choice was forced. | 76 // No need to specify randomization when the group choice was forced. |
75 if (forced_) | 77 if (forced_) |
76 return; | 78 return; |
77 DCHECK_EQ(group_, kNotFinalized); | 79 DCHECK_EQ(group_, kNotFinalized); |
78 DCHECK_EQ(kDefaultGroupNumber + 1, next_group_number_); | 80 DCHECK_EQ(kDefaultGroupNumber + 1, next_group_number_); |
79 if (!FieldTrialList::IsOneTimeRandomizationEnabled()) { | 81 if (!FieldTrialList::IsOneTimeRandomizationEnabled()) { |
80 NOTREACHED(); | 82 NOTREACHED(); |
81 Disable(); | 83 Disable(); |
82 return; | 84 return; |
83 } | 85 } |
84 | 86 |
85 random_ = static_cast<Probability>( | 87 random_ = static_cast<Probability>( |
86 divisor_ * HashClientId(FieldTrialList::client_id(), name_)); | 88 divisor_ * entropy_provider_->GetEntropyForTrial(name_)); |
87 } | 89 } |
88 | 90 |
89 void FieldTrial::Disable() { | 91 void FieldTrial::Disable() { |
90 enable_field_trial_ = false; | 92 enable_field_trial_ = false; |
91 | 93 |
92 // In case we are disabled after initialization, we need to switch | 94 // In case we are disabled after initialization, we need to switch |
93 // the trial to the default group. | 95 // the trial to the default group. |
94 if (group_ != kNotFinalized) { | 96 if (group_ != kNotFinalized) { |
95 // Only reset when not already the default group, because in case we were | 97 // 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 | 98 // 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 | 193 |
192 void FieldTrial::SetGroupChoice(const std::string& name, int number) { | 194 void FieldTrial::SetGroupChoice(const std::string& name, int number) { |
193 group_ = number; | 195 group_ = number; |
194 if (name.empty()) | 196 if (name.empty()) |
195 StringAppendF(&group_name_, "%d", group_); | 197 StringAppendF(&group_name_, "%d", group_); |
196 else | 198 else |
197 group_name_ = name; | 199 group_name_ = name; |
198 DVLOG(1) << "Field trial: " << name_ << " Group choice:" << group_name_; | 200 DVLOG(1) << "Field trial: " << name_ << " Group choice:" << group_name_; |
199 } | 201 } |
200 | 202 |
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 //------------------------------------------------------------------------------ | 203 //------------------------------------------------------------------------------ |
223 // FieldTrialList methods and members. | 204 // FieldTrialList methods and members. |
224 | 205 |
225 // static | 206 // static |
226 FieldTrialList* FieldTrialList::global_ = NULL; | 207 FieldTrialList* FieldTrialList::global_ = NULL; |
227 | 208 |
228 // static | 209 // static |
229 bool FieldTrialList::used_without_global_ = false; | 210 bool FieldTrialList::used_without_global_ = false; |
230 | 211 |
231 FieldTrialList::FieldTrialList(const std::string& client_id) | 212 FieldTrialList::FieldTrialList(FieldTrial::EntropyProvider* entropy_provider) |
232 : application_start_time_(TimeTicks::Now()), | 213 : application_start_time_(TimeTicks::Now()), |
233 client_id_(client_id), | 214 entropy_provider_(entropy_provider), |
234 observer_list_(new ObserverListThreadSafe<FieldTrialList::Observer>( | 215 observer_list_(new ObserverListThreadSafe<FieldTrialList::Observer>( |
235 ObserverListBase<FieldTrialList::Observer>::NOTIFY_EXISTING_ONLY)) { | 216 ObserverListBase<FieldTrialList::Observer>::NOTIFY_EXISTING_ONLY)) { |
236 DCHECK(!global_); | 217 DCHECK(!global_); |
237 DCHECK(!used_without_global_); | 218 DCHECK(!used_without_global_); |
238 global_ = this; | 219 global_ = this; |
239 | 220 |
240 Time::Exploded exploded; | 221 Time::Exploded exploded; |
241 Time two_years_from_now = | 222 Time two_years_from_now = |
242 Time::NowFromSystemTime() + TimeDelta::FromDays(730); | 223 Time::NowFromSystemTime() + TimeDelta::FromDays(730); |
243 two_years_from_now.LocalExplode(&exploded); | 224 two_years_from_now.LocalExplode(&exploded); |
(...skipping 28 matching lines...) Expand all Loading... |
272 CHECK(existing_trial->forced_); | 253 CHECK(existing_trial->forced_); |
273 // If the field trial has already been forced, check whether it was forced | 254 // 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.. | 255 // to the default group. Return the chosen group number, in that case.. |
275 if (default_group_number && | 256 if (default_group_number && |
276 default_group_name == existing_trial->default_group_name()) { | 257 default_group_name == existing_trial->default_group_name()) { |
277 *default_group_number = existing_trial->group(); | 258 *default_group_number = existing_trial->group(); |
278 } | 259 } |
279 return existing_trial; | 260 return existing_trial; |
280 } | 261 } |
281 | 262 |
282 FieldTrial* field_trial = | 263 FieldTrial* field_trial = new FieldTrial(global_->entropy_provider_.get(), |
283 new FieldTrial(name, total_probability, default_group_name); | 264 name, total_probability, |
| 265 default_group_name); |
284 if (GetBuildTime() > CreateTimeFromParams(year, month, day_of_month)) | 266 if (GetBuildTime() > CreateTimeFromParams(year, month, day_of_month)) |
285 field_trial->Disable(); | 267 field_trial->Disable(); |
286 FieldTrialList::Register(field_trial); | 268 FieldTrialList::Register(field_trial); |
287 return field_trial; | 269 return field_trial; |
288 } | 270 } |
289 | 271 |
290 // static | 272 // static |
291 FieldTrial* FieldTrialList::Find(const std::string& name) { | 273 FieldTrial* FieldTrialList::Find(const std::string& name) { |
292 if (!global_) | 274 if (!global_) |
293 return NULL; | 275 return NULL; |
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
392 | 374 |
393 FieldTrial* field_trial = FieldTrialList::Find(name); | 375 FieldTrial* field_trial = FieldTrialList::Find(name); |
394 if (field_trial) { | 376 if (field_trial) { |
395 // In single process mode, or when we force them from the command line, | 377 // In single process mode, or when we force them from the command line, |
396 // we may have already created the field trial. | 378 // we may have already created the field trial. |
397 if (field_trial->group_name_internal() != group_name) | 379 if (field_trial->group_name_internal() != group_name) |
398 return NULL; | 380 return NULL; |
399 return field_trial; | 381 return field_trial; |
400 } | 382 } |
401 const int kTotalProbability = 100; | 383 const int kTotalProbability = 100; |
402 field_trial = new FieldTrial(name, kTotalProbability, group_name); | 384 field_trial = new FieldTrial(global_->entropy_provider_.get(), |
| 385 name, kTotalProbability, group_name); |
403 // This is where we may assign a group number different from | 386 // This is where we may assign a group number different from |
404 // kDefaultGroupNumber to the default group. | 387 // kDefaultGroupNumber to the default group. |
405 field_trial->AppendGroup(group_name, kTotalProbability); | 388 field_trial->AppendGroup(group_name, kTotalProbability); |
406 field_trial->forced_ = true; | 389 field_trial->forced_ = true; |
407 FieldTrialList::Register(field_trial); | 390 FieldTrialList::Register(field_trial); |
408 return field_trial; | 391 return field_trial; |
409 } | 392 } |
410 | 393 |
411 // static | 394 // static |
412 void FieldTrialList::AddObserver(Observer* observer) { | 395 void FieldTrialList::AddObserver(Observer* observer) { |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
445 return global_->registered_.size(); | 428 return global_->registered_.size(); |
446 } | 429 } |
447 | 430 |
448 // static | 431 // static |
449 bool FieldTrialList::IsOneTimeRandomizationEnabled() { | 432 bool FieldTrialList::IsOneTimeRandomizationEnabled() { |
450 if (!global_) { | 433 if (!global_) { |
451 used_without_global_ = true; | 434 used_without_global_ = true; |
452 return false; | 435 return false; |
453 } | 436 } |
454 | 437 |
455 return !global_->client_id_.empty(); | 438 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 } | 439 } |
466 | 440 |
467 FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) { | 441 FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) { |
468 RegistrationList::iterator it = registered_.find(name); | 442 RegistrationList::iterator it = registered_.find(name); |
469 if (registered_.end() == it) | 443 if (registered_.end() == it) |
470 return NULL; | 444 return NULL; |
471 return it->second; | 445 return it->second; |
472 } | 446 } |
473 | 447 |
474 // static | 448 // static |
475 void FieldTrialList::Register(FieldTrial* trial) { | 449 void FieldTrialList::Register(FieldTrial* trial) { |
476 if (!global_) { | 450 if (!global_) { |
477 used_without_global_ = true; | 451 used_without_global_ = true; |
478 return; | 452 return; |
479 } | 453 } |
480 AutoLock auto_lock(global_->lock_); | 454 AutoLock auto_lock(global_->lock_); |
481 DCHECK(!global_->PreLockedFind(trial->name())); | 455 DCHECK(!global_->PreLockedFind(trial->name())); |
482 trial->AddRef(); | 456 trial->AddRef(); |
483 global_->registered_[trial->name()] = trial; | 457 global_->registered_[trial->name()] = trial; |
484 } | 458 } |
485 | 459 |
486 } // namespace base | 460 } // namespace base |
OLD | NEW |