Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2011 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" |
| 11 #include "base/stringprintf.h" | 11 #include "base/stringprintf.h" |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 36 const int day_of_month) | 36 const int day_of_month) |
| 37 : name_(name), | 37 : name_(name), |
| 38 name_hash_(HashName(name)), | 38 name_hash_(HashName(name)), |
| 39 divisor_(total_probability), | 39 divisor_(total_probability), |
| 40 default_group_name_(default_group_name), | 40 default_group_name_(default_group_name), |
| 41 random_(static_cast<Probability>(divisor_ * RandDouble())), | 41 random_(static_cast<Probability>(divisor_ * RandDouble())), |
| 42 accumulated_group_probability_(0), | 42 accumulated_group_probability_(0), |
| 43 next_group_number_(kDefaultGroupNumber + 1), | 43 next_group_number_(kDefaultGroupNumber + 1), |
| 44 group_(kNotFinalized), | 44 group_(kNotFinalized), |
| 45 group_name_hash_(kReservedHashValue), | 45 group_name_hash_(kReservedHashValue), |
| 46 enable_field_trial_(true) { | 46 enable_field_trial_(true), |
| 47 forced_(false) { | |
| 47 DCHECK_GT(total_probability, 0); | 48 DCHECK_GT(total_probability, 0); |
| 48 DCHECK(!name_.empty()); | 49 DCHECK(!name_.empty()); |
| 49 DCHECK(!default_group_name_.empty()); | 50 DCHECK(!default_group_name_.empty()); |
| 50 FieldTrialList::Register(this); | |
| 51 | 51 |
| 52 DCHECK_GT(year, 1970); | 52 DCHECK_GT(year, 1970); |
| 53 DCHECK_GT(month, 0); | 53 DCHECK_GT(month, 0); |
| 54 DCHECK_LT(month, 13); | 54 DCHECK_LT(month, 13); |
| 55 DCHECK_GT(day_of_month, 0); | 55 DCHECK_GT(day_of_month, 0); |
| 56 DCHECK_LT(day_of_month, 32); | 56 DCHECK_LT(day_of_month, 32); |
| 57 | 57 |
| 58 Time::Exploded exploded; | 58 Time::Exploded exploded; |
| 59 exploded.year = year; | 59 exploded.year = year; |
| 60 exploded.month = month; | 60 exploded.month = month; |
| 61 exploded.day_of_week = 0; // Should be unused. | 61 exploded.day_of_week = 0; // Should be unused. |
| 62 exploded.day_of_month = day_of_month; | 62 exploded.day_of_month = day_of_month; |
| 63 exploded.hour = 0; | 63 exploded.hour = 0; |
| 64 exploded.minute = 0; | 64 exploded.minute = 0; |
| 65 exploded.second = 0; | 65 exploded.second = 0; |
| 66 exploded.millisecond = 0; | 66 exploded.millisecond = 0; |
| 67 | 67 |
| 68 Time expiration_time = Time::FromLocalExploded(exploded); | 68 Time expiration_time = Time::FromLocalExploded(exploded); |
| 69 if (GetBuildTime() > expiration_time) | 69 if (GetBuildTime() > expiration_time) |
| 70 Disable(); | 70 Disable(); |
| 71 } | 71 } |
| 72 | 72 |
| 73 void FieldTrial::UseOneTimeRandomization() { | 73 void FieldTrial::UseOneTimeRandomization() { |
| 74 // No need to specify randomization when the group choice was forced. | |
| 75 if (forced_) | |
| 76 return; | |
| 74 DCHECK_EQ(group_, kNotFinalized); | 77 DCHECK_EQ(group_, kNotFinalized); |
| 75 DCHECK_EQ(kDefaultGroupNumber + 1, next_group_number_); | 78 DCHECK_EQ(kDefaultGroupNumber + 1, next_group_number_); |
| 76 if (!FieldTrialList::IsOneTimeRandomizationEnabled()) { | 79 if (!FieldTrialList::IsOneTimeRandomizationEnabled()) { |
| 77 NOTREACHED(); | 80 NOTREACHED(); |
| 78 Disable(); | 81 Disable(); |
| 79 return; | 82 return; |
| 80 } | 83 } |
| 81 | 84 |
| 82 random_ = static_cast<Probability>( | 85 random_ = static_cast<Probability>( |
| 83 divisor_ * HashClientId(FieldTrialList::client_id(), name_)); | 86 divisor_ * HashClientId(FieldTrialList::client_id(), name_)); |
| 84 } | 87 } |
| 85 | 88 |
| 86 void FieldTrial::Disable() { | 89 void FieldTrial::Disable() { |
| 87 enable_field_trial_ = false; | 90 enable_field_trial_ = false; |
| 88 | 91 |
| 89 // In case we are disabled after initialization, we need to switch | 92 // In case we are disabled after initialization, we need to switch |
| 90 // the trial to the default group. | 93 // the trial to the default group. |
| 91 if (group_ != kNotFinalized) { | 94 if (group_ != kNotFinalized) { |
| 92 group_ = kDefaultGroupNumber; | 95 // Only reset when not already the default group, because in case we were |
| 93 group_name_ = default_group_name_; | 96 // forced to the default group, the group number may not be |
| 94 group_name_hash_ = HashName(group_name_); | 97 // kDefaultGroupNumber, so we should keep it as is. |
| 98 if (group_name_ != default_group_name_) { | |
| 99 group_ = kDefaultGroupNumber; | |
| 100 group_name_ = default_group_name_; | |
| 101 group_name_hash_ = HashName(group_name_); | |
|
jar (doing other things)
2012/04/06 01:41:29
I'm starting to see this parallel initialization m
MAD
2012/04/11 02:38:08
Done.
| |
| 102 } | |
| 95 } | 103 } |
| 96 } | 104 } |
| 97 | 105 |
| 98 int FieldTrial::AppendGroup(const std::string& name, | 106 int FieldTrial::AppendGroup(const std::string& name, |
| 99 Probability group_probability) { | 107 Probability group_probability) { |
| 108 // When the group choice was previously forced, we only need to return the | |
| 109 // the id of the chosen group, and anything can be returned for the others. | |
| 110 if (forced_) { | |
| 111 DCHECK(!group_name_.empty()); | |
| 112 if (name == group_name_) { | |
| 113 return group_; | |
| 114 } else { | |
|
jar (doing other things)
2012/04/06 01:41:29
nit: else not needed since we returned.
MAD
2012/04/11 02:38:08
Done.
| |
| 115 DCHECK_NE(next_group_number_, group_); | |
| 116 // We still return different numbers each time, in case some caller need | |
| 117 // them to be different. | |
| 118 return next_group_number_++; | |
| 119 } | |
| 120 } | |
| 121 | |
| 100 DCHECK_LE(group_probability, divisor_); | 122 DCHECK_LE(group_probability, divisor_); |
| 101 DCHECK_GE(group_probability, 0); | 123 DCHECK_GE(group_probability, 0); |
| 102 | 124 |
| 103 if (enable_benchmarking_ || !enable_field_trial_) | 125 if (enable_benchmarking_ || !enable_field_trial_) |
| 104 group_probability = 0; | 126 group_probability = 0; |
| 105 | 127 |
| 106 accumulated_group_probability_ += group_probability; | 128 accumulated_group_probability_ += group_probability; |
| 107 | 129 |
| 108 DCHECK_LE(accumulated_group_probability_, divisor_); | 130 DCHECK_LE(accumulated_group_probability_, divisor_); |
| 109 if (group_ == kNotFinalized && accumulated_group_probability_ > random_) { | 131 if (group_ == kNotFinalized && accumulated_group_probability_ > random_) { |
| 110 // This is the group that crossed the random line, so we do the assignment. | 132 // This is the group that crossed the random line, so we do the assignment. |
| 111 group_ = next_group_number_; | 133 group_ = next_group_number_; |
| 112 if (name.empty()) | 134 if (name.empty()) |
| 113 StringAppendF(&group_name_, "%d", group_); | 135 StringAppendF(&group_name_, "%d", group_); |
| 114 else | 136 else |
| 115 group_name_ = name; | 137 group_name_ = name; |
| 116 group_name_hash_ = HashName(group_name_); | 138 group_name_hash_ = HashName(group_name_); |
| 117 FieldTrialList::NotifyFieldTrialGroupSelection(name_, group_name_); | 139 FieldTrialList::NotifyFieldTrialGroupSelection(name_, group_name_); |
| 118 } | 140 } |
| 119 return next_group_number_++; | 141 return next_group_number_++; |
| 120 } | 142 } |
| 121 | 143 |
| 122 int FieldTrial::group() { | 144 int FieldTrial::group() { |
| 123 if (group_ == kNotFinalized) { | 145 if (group_ == kNotFinalized) { |
| 124 accumulated_group_probability_ = divisor_; | 146 accumulated_group_probability_ = divisor_; |
| 147 // Here is't OK to use kDefaultGroupNumber | |
| 148 // since we can't be forced and not finalized. | |
| 149 DCHECK(!forced_); | |
| 125 group_ = kDefaultGroupNumber; | 150 group_ = kDefaultGroupNumber; |
| 126 group_name_ = default_group_name_; | 151 group_name_ = default_group_name_; |
| 127 group_name_hash_ = HashName(group_name_); | 152 group_name_hash_ = HashName(group_name_); |
| 128 FieldTrialList::NotifyFieldTrialGroupSelection(name_, group_name_); | 153 FieldTrialList::NotifyFieldTrialGroupSelection(name_, group_name_); |
| 129 } | 154 } |
| 130 return group_; | 155 return group_; |
| 131 } | 156 } |
| 132 | 157 |
| 133 std::string FieldTrial::group_name() { | 158 std::string FieldTrial::group_name() { |
| 134 group(); // call group() to make sure group assignment was done. | 159 group(); // call group() to make sure group assignment was done. |
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 227 while (!registered_.empty()) { | 252 while (!registered_.empty()) { |
| 228 RegistrationList::iterator it = registered_.begin(); | 253 RegistrationList::iterator it = registered_.begin(); |
| 229 it->second->Release(); | 254 it->second->Release(); |
| 230 registered_.erase(it->first); | 255 registered_.erase(it->first); |
| 231 } | 256 } |
| 232 DCHECK_EQ(this, global_); | 257 DCHECK_EQ(this, global_); |
| 233 global_ = NULL; | 258 global_ = NULL; |
| 234 } | 259 } |
| 235 | 260 |
| 236 // static | 261 // static |
| 237 void FieldTrialList::Register(FieldTrial* trial) { | 262 FieldTrial* FieldTrialList::GetFieldTrialInstance( |
| 238 if (!global_) { | 263 const std::string& name, FieldTrial::Probability total_probability, |
|
jar (doing other things)
2012/04/06 01:41:29
nit: one parameter per line.
MAD
2012/04/11 02:38:08
Done.
| |
| 239 used_without_global_ = true; | 264 const std::string& default_group_name, int* default_group_number, |
| 240 return; | 265 const int year, const int month, const int day_of_month) { |
| 266 if (default_group_number) | |
| 267 *default_group_number = FieldTrial::kDefaultGroupNumber; | |
| 268 // Check if the field trial has already been created in some other way. | |
| 269 FieldTrial* existing_trial = Find(name); | |
| 270 if (existing_trial) { | |
| 271 DCHECK(existing_trial->forced_); | |
|
jar (doing other things)
2012/04/06 01:41:29
This is tempting to make a CHECK.
If a user tries
MAD
2012/04/11 02:38:08
Done.
Though it doesn't change much currently sinc
| |
| 272 // If the field trial has already been forced, check whether it was forced | |
| 273 // to the default group. Return the chosen group number, in that case.. | |
| 274 if (default_group_number && | |
| 275 default_group_name == existing_trial->default_group_name()) { | |
| 276 *default_group_number = existing_trial->group(); | |
| 277 } | |
| 278 return existing_trial; | |
| 241 } | 279 } |
| 242 AutoLock auto_lock(global_->lock_); | 280 |
| 243 DCHECK(!global_->PreLockedFind(trial->name())); | 281 FieldTrial* field_trial = new FieldTrial( |
| 244 trial->AddRef(); | 282 name, total_probability, default_group_name, year, month, day_of_month); |
| 245 global_->registered_[trial->name()] = trial; | 283 FieldTrialList::Register(field_trial); |
| 284 return field_trial; | |
| 246 } | 285 } |
| 247 | 286 |
| 248 // static | 287 // static |
| 249 FieldTrial* FieldTrialList::Find(const std::string& name) { | 288 FieldTrial* FieldTrialList::Find(const std::string& name) { |
| 250 if (!global_) | 289 if (!global_) |
| 251 return NULL; | 290 return NULL; |
| 252 AutoLock auto_lock(global_->lock_); | 291 AutoLock auto_lock(global_->lock_); |
| 253 return global_->PreLockedFind(name); | 292 return global_->PreLockedFind(name); |
| 254 } | 293 } |
| 255 | 294 |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 276 | 315 |
| 277 // static | 316 // static |
| 278 void FieldTrialList::StatesToString(std::string* output) { | 317 void FieldTrialList::StatesToString(std::string* output) { |
| 279 DCHECK(output->empty()); | 318 DCHECK(output->empty()); |
| 280 if (!global_) | 319 if (!global_) |
| 281 return; | 320 return; |
| 282 AutoLock auto_lock(global_->lock_); | 321 AutoLock auto_lock(global_->lock_); |
| 283 | 322 |
| 284 for (RegistrationList::iterator it = global_->registered_.begin(); | 323 for (RegistrationList::iterator it = global_->registered_.begin(); |
| 285 it != global_->registered_.end(); ++it) { | 324 it != global_->registered_.end(); ++it) { |
| 286 const std::string name = it->first; | 325 const std::string& name = it->first; |
| 287 std::string group_name = it->second->group_name_internal(); | 326 std::string group_name = it->second->group_name_internal(); |
| 288 if (group_name.empty()) | 327 if (group_name.empty()) |
| 289 continue; // Should not include uninitialized trials. | 328 continue; // Should not include uninitialized trials. |
| 290 DCHECK_EQ(name.find(kPersistentStringSeparator), std::string::npos); | 329 DCHECK_EQ(name.find(kPersistentStringSeparator), std::string::npos); |
| 291 DCHECK_EQ(group_name.find(kPersistentStringSeparator), std::string::npos); | 330 DCHECK_EQ(group_name.find(kPersistentStringSeparator), std::string::npos); |
| 292 output->append(name); | 331 output->append(name); |
| 293 output->append(1, kPersistentStringSeparator); | 332 output->append(1, kPersistentStringSeparator); |
| 294 output->append(group_name); | 333 output->append(group_name); |
| 295 output->append(1, kPersistentStringSeparator); | 334 output->append(1, kPersistentStringSeparator); |
| 296 } | 335 } |
| 297 } | 336 } |
| 298 | 337 |
| 299 // static | 338 // static |
| 300 void FieldTrialList::GetFieldTrialNameGroupIds( | 339 void FieldTrialList::GetFieldTrialNameGroupIds( |
| 301 std::vector<FieldTrial::NameGroupId>* name_group_ids) { | 340 std::vector<FieldTrial::NameGroupId>* name_group_ids) { |
| 302 DCHECK(name_group_ids->empty()); | 341 DCHECK(name_group_ids->empty()); |
| 303 if (!global_) | 342 if (!global_) |
| 304 return; | 343 return; |
| 305 AutoLock auto_lock(global_->lock_); | 344 AutoLock auto_lock(global_->lock_); |
| 306 | 345 |
| 307 for (RegistrationList::iterator it = global_->registered_.begin(); | 346 for (RegistrationList::iterator it = global_->registered_.begin(); |
| 308 it != global_->registered_.end(); ++it) { | 347 it != global_->registered_.end(); ++it) { |
| 309 FieldTrial::NameGroupId name_group_id; | 348 FieldTrial::NameGroupId name_group_id; |
| 310 if (it->second->GetNameGroupId(&name_group_id)) | 349 if (it->second->GetNameGroupId(&name_group_id)) |
| 311 name_group_ids->push_back(name_group_id); | 350 name_group_ids->push_back(name_group_id); |
| 312 } | 351 } |
| 313 } | 352 } |
| 314 | 353 |
| 315 // static | 354 // static |
| 316 bool FieldTrialList::CreateTrialsInChildProcess( | 355 bool FieldTrialList::CreateTrialsFromString(const std::string& trials_string) { |
| 317 const std::string& parent_trials) { | |
| 318 DCHECK(global_); | 356 DCHECK(global_); |
| 319 if (parent_trials.empty() || !global_) | 357 if (trials_string.empty() || !global_) |
| 320 return true; | 358 return true; |
| 321 | 359 |
| 322 size_t next_item = 0; | 360 size_t next_item = 0; |
| 323 while (next_item < parent_trials.length()) { | 361 while (next_item < trials_string.length()) { |
| 324 size_t name_end = parent_trials.find(kPersistentStringSeparator, next_item); | 362 size_t name_end = trials_string.find(kPersistentStringSeparator, next_item); |
| 325 if (name_end == parent_trials.npos || next_item == name_end) | 363 if (name_end == trials_string.npos || next_item == name_end) |
| 326 return false; | 364 return false; |
| 327 size_t group_name_end = parent_trials.find(kPersistentStringSeparator, | 365 size_t group_name_end = trials_string.find(kPersistentStringSeparator, |
| 328 name_end + 1); | 366 name_end + 1); |
| 329 if (group_name_end == parent_trials.npos || name_end + 1 == group_name_end) | 367 if (group_name_end == trials_string.npos || name_end + 1 == group_name_end) |
| 330 return false; | 368 return false; |
| 331 std::string name(parent_trials, next_item, name_end - next_item); | 369 std::string name(trials_string, next_item, name_end - next_item); |
| 332 std::string group_name(parent_trials, name_end + 1, | 370 std::string group_name(trials_string, name_end + 1, |
| 333 group_name_end - name_end - 1); | 371 group_name_end - name_end - 1); |
| 334 next_item = group_name_end + 1; | 372 next_item = group_name_end + 1; |
| 335 | 373 |
| 336 if (!CreateFieldTrial(name, group_name)) | 374 if (!CreateFieldTrial(name, group_name)) |
| 337 return false; | 375 return false; |
| 338 } | 376 } |
| 339 return true; | 377 return true; |
| 340 } | 378 } |
| 341 | 379 |
| 342 // static | 380 // static |
| 343 FieldTrial* FieldTrialList::CreateFieldTrial( | 381 FieldTrial* FieldTrialList::CreateFieldTrial( |
| 344 const std::string& name, | 382 const std::string& name, |
| 345 const std::string& group_name) { | 383 const std::string& group_name) { |
| 346 DCHECK(global_); | 384 DCHECK(global_); |
| 347 DCHECK_GE(name.size(), 0u); | 385 DCHECK_GE(name.size(), 0u); |
| 348 DCHECK_GE(group_name.size(), 0u); | 386 DCHECK_GE(group_name.size(), 0u); |
| 349 if (name.empty() || group_name.empty() || !global_) | 387 if (name.empty() || group_name.empty() || !global_) |
| 350 return NULL; | 388 return NULL; |
| 351 | 389 |
| 352 FieldTrial *field_trial(FieldTrialList::Find(name)); | 390 FieldTrial* field_trial = FieldTrialList::Find(name); |
| 353 if (field_trial) { | 391 if (field_trial) { |
| 354 // In single process mode, we may have already created the field trial. | 392 // In single process mode, or when we force them from the command line, |
| 393 // we may have already created the field trial. | |
| 355 if (field_trial->group_name_internal() != group_name) | 394 if (field_trial->group_name_internal() != group_name) |
| 356 return NULL; | 395 return NULL; |
| 357 return field_trial; | 396 return field_trial; |
| 358 } | 397 } |
| 359 const int kTotalProbability = 100; | 398 const int kTotalProbability = 100; |
| 360 field_trial = new FieldTrial(name, kTotalProbability, group_name, | 399 field_trial = new FieldTrial(name, kTotalProbability, group_name, |
| 361 kExpirationYearInFuture, 1, 1); | 400 kExpirationYearInFuture, 1, 1); |
| 401 // This is where we may assign a group number different from | |
| 402 // kDefaultGroupNumber to the default group. | |
| 362 field_trial->AppendGroup(group_name, kTotalProbability); | 403 field_trial->AppendGroup(group_name, kTotalProbability); |
| 404 field_trial->forced_ = true; | |
| 405 FieldTrialList::Register(field_trial); | |
| 363 return field_trial; | 406 return field_trial; |
| 364 } | 407 } |
| 365 | 408 |
| 366 // static | 409 // static |
| 367 void FieldTrialList::AddObserver(Observer* observer) { | 410 void FieldTrialList::AddObserver(Observer* observer) { |
| 368 if (!global_) | 411 if (!global_) |
| 369 return; | 412 return; |
| 370 DCHECK(global_); | 413 DCHECK(global_); |
| 371 global_->observer_list_.AddObserver(observer); | 414 global_->observer_list_.AddObserver(observer); |
| 372 } | 415 } |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 418 return global_->client_id_; | 461 return global_->client_id_; |
| 419 } | 462 } |
| 420 | 463 |
| 421 FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) { | 464 FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) { |
| 422 RegistrationList::iterator it = registered_.find(name); | 465 RegistrationList::iterator it = registered_.find(name); |
| 423 if (registered_.end() == it) | 466 if (registered_.end() == it) |
| 424 return NULL; | 467 return NULL; |
| 425 return it->second; | 468 return it->second; |
| 426 } | 469 } |
| 427 | 470 |
| 471 // static | |
| 472 void FieldTrialList::Register(FieldTrial* trial) { | |
| 473 if (!global_) { | |
| 474 used_without_global_ = true; | |
| 475 return; | |
| 476 } | |
| 477 AutoLock auto_lock(global_->lock_); | |
| 478 DCHECK(!global_->PreLockedFind(trial->name())); | |
| 479 trial->AddRef(); | |
| 480 global_->registered_[trial->name()] = trial; | |
| 481 } | |
| 482 | |
| 428 } // namespace base | 483 } // namespace base |
| OLD | NEW |