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" |
| 12 #include "base/string_util.h" | 12 #include "base/string_util.h" |
| 13 #include "base/utf_string_conversions.h" | 13 #include "base/utf_string_conversions.h" |
| 14 | 14 |
| 15 namespace base { | 15 namespace base { |
| 16 | 16 |
| 17 static const char kHistogramFieldTrialSeparator('_'); | 17 static const char kHistogramFieldTrialSeparator('_'); |
| 18 | 18 |
| 19 // statics | 19 // statics |
| 20 const int FieldTrial::kNotFinalized = -1; | 20 const int FieldTrial::kNotFinalized = -1; |
| 21 const int FieldTrial::kDefaultGroupNumber = 0; | 21 const int FieldTrial::kDefaultGroupNumber = 0; |
| 22 const uint32 FieldTrial::kReservedHashValue = 0; | 22 const uint32 FieldTrial::kReservedHashValue = 0; |
| 23 bool FieldTrial::enable_benchmarking_ = false; | 23 bool FieldTrial::enable_benchmarking_ = false; |
| 24 | 24 |
| 25 const char FieldTrialList::kPersistentStringSeparator('/'); | 25 const char FieldTrialList::kPersistentStringSeparator('/'); |
| 26 int FieldTrialList::kExpirationYearInFuture = 0; | 26 int FieldTrialList::kExpirationYearInFuture = 0; |
| 27 | 27 |
| 28 //------------------------------------------------------------------------------ | 28 //------------------------------------------------------------------------------ |
| 29 // FieldTrial methods and members. | 29 // FieldTrial methods and members. |
| 30 | 30 |
| 31 FieldTrial* FieldTrial::CreateInstance( | |
| 32 const std::string& name, Probability total_probability, | |
| 33 const std::string& default_group_name, const int year, const int month, | |
| 34 const int day_of_month) { | |
| 35 // First check if the field trial has already been created in some other ways. | |
|
Alexei Svitkine (slow)
2012/03/23 18:41:55
Nit: "some other ways" -> "some other way"
MAD
2012/03/23 20:20:12
Done.
| |
| 36 FieldTrial* existing_trial = FieldTrialList::Find(name); | |
| 37 if (existing_trial) { | |
| 38 return existing_trial; | |
| 39 } else { | |
|
Alexei Svitkine (slow)
2012/03/23 18:41:55
Nit: Since you're early returning, don't need an e
MAD
2012/03/23 20:20:12
Done.
| |
| 40 FieldTrial* new_trial = new FieldTrial( | |
| 41 name, total_probability, default_group_name, year, month, day_of_month); | |
| 42 FieldTrialList::Register(new_trial); | |
| 43 return new_trial; | |
| 44 } | |
| 45 } | |
| 46 | |
| 31 FieldTrial::FieldTrial(const std::string& name, | 47 FieldTrial::FieldTrial(const std::string& name, |
| 32 const Probability total_probability, | 48 const Probability total_probability, |
| 33 const std::string& default_group_name, | 49 const std::string& default_group_name, |
| 34 const int year, | 50 const int year, |
| 35 const int month, | 51 const int month, |
| 36 const int day_of_month) | 52 const int day_of_month) |
| 37 : name_(name), | 53 : name_(name), |
| 38 name_hash_(HashName(name)), | 54 name_hash_(HashName(name)), |
| 39 divisor_(total_probability), | 55 divisor_(total_probability), |
| 40 default_group_name_(default_group_name), | 56 default_group_name_(default_group_name), |
| 41 random_(static_cast<Probability>(divisor_ * RandDouble())), | 57 random_(static_cast<Probability>(divisor_ * RandDouble())), |
| 42 accumulated_group_probability_(0), | 58 accumulated_group_probability_(0), |
| 43 next_group_number_(kDefaultGroupNumber + 1), | 59 next_group_number_(kDefaultGroupNumber + 1), |
| 44 group_(kNotFinalized), | 60 group_(kNotFinalized), |
| 45 group_name_hash_(kReservedHashValue), | 61 group_name_hash_(kReservedHashValue), |
| 46 enable_field_trial_(true) { | 62 enable_field_trial_(true), |
| 63 forced_(false) { | |
| 47 DCHECK_GT(total_probability, 0); | 64 DCHECK_GT(total_probability, 0); |
| 48 DCHECK(!name_.empty()); | 65 DCHECK(!name_.empty()); |
| 49 DCHECK(!default_group_name_.empty()); | 66 DCHECK(!default_group_name_.empty()); |
| 50 FieldTrialList::Register(this); | |
| 51 | 67 |
| 52 DCHECK_GT(year, 1970); | 68 DCHECK_GT(year, 1970); |
| 53 DCHECK_GT(month, 0); | 69 DCHECK_GT(month, 0); |
| 54 DCHECK_LT(month, 13); | 70 DCHECK_LT(month, 13); |
| 55 DCHECK_GT(day_of_month, 0); | 71 DCHECK_GT(day_of_month, 0); |
| 56 DCHECK_LT(day_of_month, 32); | 72 DCHECK_LT(day_of_month, 32); |
| 57 | 73 |
| 58 Time::Exploded exploded; | 74 Time::Exploded exploded; |
| 59 exploded.year = year; | 75 exploded.year = year; |
| 60 exploded.month = month; | 76 exploded.month = month; |
| 61 exploded.day_of_week = 0; // Should be unused. | 77 exploded.day_of_week = 0; // Should be unused. |
| 62 exploded.day_of_month = day_of_month; | 78 exploded.day_of_month = day_of_month; |
| 63 exploded.hour = 0; | 79 exploded.hour = 0; |
| 64 exploded.minute = 0; | 80 exploded.minute = 0; |
| 65 exploded.second = 0; | 81 exploded.second = 0; |
| 66 exploded.millisecond = 0; | 82 exploded.millisecond = 0; |
| 67 | 83 |
| 68 Time expiration_time = Time::FromLocalExploded(exploded); | 84 Time expiration_time = Time::FromLocalExploded(exploded); |
| 69 if (GetBuildTime() > expiration_time) | 85 if (GetBuildTime() > expiration_time) |
| 70 Disable(); | 86 Disable(); |
| 71 } | 87 } |
| 72 | 88 |
| 73 void FieldTrial::UseOneTimeRandomization() { | 89 void FieldTrial::UseOneTimeRandomization() { |
| 90 if (forced_) | |
| 91 return; | |
| 74 DCHECK_EQ(group_, kNotFinalized); | 92 DCHECK_EQ(group_, kNotFinalized); |
| 75 DCHECK_EQ(kDefaultGroupNumber + 1, next_group_number_); | 93 DCHECK_EQ(kDefaultGroupNumber + 1, next_group_number_); |
| 76 if (!FieldTrialList::IsOneTimeRandomizationEnabled()) { | 94 if (!FieldTrialList::IsOneTimeRandomizationEnabled()) { |
| 77 NOTREACHED(); | 95 NOTREACHED(); |
| 78 Disable(); | 96 Disable(); |
| 79 return; | 97 return; |
| 80 } | 98 } |
| 81 | 99 |
| 82 random_ = static_cast<Probability>( | 100 random_ = static_cast<Probability>( |
| 83 divisor_ * HashClientId(FieldTrialList::client_id(), name_)); | 101 divisor_ * HashClientId(FieldTrialList::client_id(), name_)); |
| 84 } | 102 } |
| 85 | 103 |
| 86 void FieldTrial::Disable() { | 104 void FieldTrial::Disable() { |
| 87 enable_field_trial_ = false; | 105 enable_field_trial_ = false; |
| 88 | 106 |
| 89 // In case we are disabled after initialization, we need to switch | 107 // In case we are disabled after initialization, we need to switch |
| 90 // the trial to the default group. | 108 // the trial to the default group. |
| 91 if (group_ != kNotFinalized) { | 109 if (group_ != kNotFinalized) { |
| 92 group_ = kDefaultGroupNumber; | 110 group_ = kDefaultGroupNumber; |
| 93 group_name_ = default_group_name_; | 111 group_name_ = default_group_name_; |
| 94 group_name_hash_ = HashName(group_name_); | 112 group_name_hash_ = HashName(group_name_); |
| 95 } | 113 } |
| 96 } | 114 } |
| 97 | 115 |
| 98 int FieldTrial::AppendGroup(const std::string& name, | 116 int FieldTrial::AppendGroup(const std::string& name, |
| 99 Probability group_probability) { | 117 Probability group_probability) { |
| 118 if (forced_) { | |
| 119 if (name == group_name_) | |
| 120 return group_; | |
| 121 else | |
| 122 return group_ + 1; | |
| 123 } | |
| 124 | |
| 100 DCHECK_LE(group_probability, divisor_); | 125 DCHECK_LE(group_probability, divisor_); |
| 101 DCHECK_GE(group_probability, 0); | 126 DCHECK_GE(group_probability, 0); |
| 102 | 127 |
| 103 if (enable_benchmarking_ || !enable_field_trial_) | 128 if (enable_benchmarking_ || !enable_field_trial_) |
| 104 group_probability = 0; | 129 group_probability = 0; |
| 105 | 130 |
| 106 accumulated_group_probability_ += group_probability; | 131 accumulated_group_probability_ += group_probability; |
| 107 | 132 |
| 108 DCHECK_LE(accumulated_group_probability_, divisor_); | 133 DCHECK_LE(accumulated_group_probability_, divisor_); |
| 109 if (group_ == kNotFinalized && accumulated_group_probability_ > random_) { | 134 if (group_ == kNotFinalized && (accumulated_group_probability_ > random_)) { |
| 110 // This is the group that crossed the random line, so we do the assignment. | 135 // This is the group that crossed the random line, so we do the assignment. |
| 111 group_ = next_group_number_; | 136 group_ = next_group_number_; |
| 112 if (name.empty()) | 137 if (name.empty()) |
| 113 StringAppendF(&group_name_, "%d", group_); | 138 StringAppendF(&group_name_, "%d", group_); |
| 114 else | 139 else |
| 115 group_name_ = name; | 140 group_name_ = name; |
| 116 group_name_hash_ = HashName(group_name_); | 141 group_name_hash_ = HashName(group_name_); |
| 117 FieldTrialList::NotifyFieldTrialGroupSelection(name_, group_name_); | 142 FieldTrialList::NotifyFieldTrialGroupSelection(name_, group_name_); |
| 118 } | 143 } |
| 119 return next_group_number_++; | 144 return next_group_number_++; |
| (...skipping 107 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) { | |
| 238 if (!global_) { | |
| 239 used_without_global_ = true; | |
| 240 return; | |
| 241 } | |
| 242 AutoLock auto_lock(global_->lock_); | |
| 243 DCHECK(!global_->PreLockedFind(trial->name())); | |
| 244 trial->AddRef(); | |
| 245 global_->registered_[trial->name()] = trial; | |
| 246 } | |
| 247 | |
| 248 // static | |
| 249 FieldTrial* FieldTrialList::Find(const std::string& name) { | 262 FieldTrial* FieldTrialList::Find(const std::string& name) { |
| 250 if (!global_) | 263 if (!global_) |
| 251 return NULL; | 264 return NULL; |
| 252 AutoLock auto_lock(global_->lock_); | 265 AutoLock auto_lock(global_->lock_); |
| 253 return global_->PreLockedFind(name); | 266 return global_->PreLockedFind(name); |
| 254 } | 267 } |
| 255 | 268 |
| 256 // static | 269 // static |
| 257 int FieldTrialList::FindValue(const std::string& name) { | 270 int FieldTrialList::FindValue(const std::string& name) { |
| 258 FieldTrial* field_trial = Find(name); | 271 FieldTrial* field_trial = Find(name); |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 276 | 289 |
| 277 // static | 290 // static |
| 278 void FieldTrialList::StatesToString(std::string* output) { | 291 void FieldTrialList::StatesToString(std::string* output) { |
| 279 DCHECK(output->empty()); | 292 DCHECK(output->empty()); |
| 280 if (!global_) | 293 if (!global_) |
| 281 return; | 294 return; |
| 282 AutoLock auto_lock(global_->lock_); | 295 AutoLock auto_lock(global_->lock_); |
| 283 | 296 |
| 284 for (RegistrationList::iterator it = global_->registered_.begin(); | 297 for (RegistrationList::iterator it = global_->registered_.begin(); |
| 285 it != global_->registered_.end(); ++it) { | 298 it != global_->registered_.end(); ++it) { |
| 286 const std::string name = it->first; | 299 const std::string& name = it->first; |
| 287 std::string group_name = it->second->group_name_internal(); | 300 std::string group_name = it->second->group_name_internal(); |
| 288 if (group_name.empty()) | 301 if (group_name.empty()) |
| 289 continue; // Should not include uninitialized trials. | 302 continue; // Should not include uninitialized trials. |
| 290 DCHECK_EQ(name.find(kPersistentStringSeparator), std::string::npos); | 303 DCHECK_EQ(name.find(kPersistentStringSeparator), std::string::npos); |
| 291 DCHECK_EQ(group_name.find(kPersistentStringSeparator), std::string::npos); | 304 DCHECK_EQ(group_name.find(kPersistentStringSeparator), std::string::npos); |
| 292 output->append(name); | 305 output->append(name); |
| 293 output->append(1, kPersistentStringSeparator); | 306 output->append(1, kPersistentStringSeparator); |
| 294 output->append(group_name); | 307 output->append(group_name); |
| 295 output->append(1, kPersistentStringSeparator); | 308 output->append(1, kPersistentStringSeparator); |
| 296 } | 309 } |
| 297 } | 310 } |
| 298 | 311 |
| 299 // static | 312 // static |
| 300 void FieldTrialList::GetFieldTrialNameGroupIds( | 313 void FieldTrialList::GetFieldTrialNameGroupIds( |
| 301 std::vector<FieldTrial::NameGroupId>* name_group_ids) { | 314 std::vector<FieldTrial::NameGroupId>* name_group_ids) { |
| 302 DCHECK(name_group_ids->empty()); | 315 DCHECK(name_group_ids->empty()); |
| 303 if (!global_) | 316 if (!global_) |
| 304 return; | 317 return; |
| 305 AutoLock auto_lock(global_->lock_); | 318 AutoLock auto_lock(global_->lock_); |
| 306 | 319 |
| 307 for (RegistrationList::iterator it = global_->registered_.begin(); | 320 for (RegistrationList::iterator it = global_->registered_.begin(); |
| 308 it != global_->registered_.end(); ++it) { | 321 it != global_->registered_.end(); ++it) { |
| 309 FieldTrial::NameGroupId name_group_id; | 322 FieldTrial::NameGroupId name_group_id; |
| 310 if (it->second->GetNameGroupId(&name_group_id)) | 323 if (it->second->GetNameGroupId(&name_group_id)) |
| 311 name_group_ids->push_back(name_group_id); | 324 name_group_ids->push_back(name_group_id); |
| 312 } | 325 } |
| 313 } | 326 } |
| 314 | 327 |
| 315 // static | 328 // static |
| 316 bool FieldTrialList::CreateTrialsInChildProcess( | 329 bool FieldTrialList::CreateTrialsFromString(const std::string& trials_string) { |
| 317 const std::string& parent_trials) { | |
| 318 DCHECK(global_); | 330 DCHECK(global_); |
| 319 if (parent_trials.empty() || !global_) | 331 if (trials_string.empty() || !global_) |
| 320 return true; | 332 return true; |
| 321 | 333 |
| 322 size_t next_item = 0; | 334 size_t next_item = 0; |
| 323 while (next_item < parent_trials.length()) { | 335 while (next_item < trials_string.length()) { |
| 324 size_t name_end = parent_trials.find(kPersistentStringSeparator, next_item); | 336 size_t name_end = trials_string.find(kPersistentStringSeparator, next_item); |
| 325 if (name_end == parent_trials.npos || next_item == name_end) | 337 if (name_end == trials_string.npos || next_item == name_end) |
| 326 return false; | 338 return false; |
| 327 size_t group_name_end = parent_trials.find(kPersistentStringSeparator, | 339 size_t group_name_end = trials_string.find(kPersistentStringSeparator, |
| 328 name_end + 1); | 340 name_end + 1); |
| 329 if (group_name_end == parent_trials.npos || name_end + 1 == group_name_end) | 341 if (group_name_end == trials_string.npos || name_end + 1 == group_name_end) |
| 330 return false; | 342 return false; |
| 331 std::string name(parent_trials, next_item, name_end - next_item); | 343 std::string name(trials_string, next_item, name_end - next_item); |
| 332 std::string group_name(parent_trials, name_end + 1, | 344 std::string group_name(trials_string, name_end + 1, |
| 333 group_name_end - name_end - 1); | 345 group_name_end - name_end - 1); |
| 334 next_item = group_name_end + 1; | 346 next_item = group_name_end + 1; |
| 335 | 347 |
| 336 if (!CreateFieldTrial(name, group_name)) | 348 if (!CreateFieldTrial(name, group_name)) |
| 337 return false; | 349 return false; |
| 338 } | 350 } |
| 339 return true; | 351 return true; |
| 340 } | 352 } |
| 341 | 353 |
| 342 // static | 354 // static |
| 343 FieldTrial* FieldTrialList::CreateFieldTrial( | 355 FieldTrial* FieldTrialList::CreateFieldTrial( |
| 344 const std::string& name, | 356 const std::string& name, |
| 345 const std::string& group_name) { | 357 const std::string& group_name) { |
| 346 DCHECK(global_); | 358 DCHECK(global_); |
| 347 DCHECK_GE(name.size(), 0u); | 359 DCHECK_GE(name.size(), 0u); |
| 348 DCHECK_GE(group_name.size(), 0u); | 360 DCHECK_GE(group_name.size(), 0u); |
| 349 if (name.empty() || group_name.empty() || !global_) | 361 if (name.empty() || group_name.empty() || !global_) |
| 350 return NULL; | 362 return NULL; |
| 351 | 363 |
| 352 FieldTrial *field_trial(FieldTrialList::Find(name)); | 364 FieldTrial* field_trial(FieldTrialList::Find(name)); |
| 353 if (field_trial) { | 365 if (field_trial) { |
| 354 // In single process mode, we may have already created the field trial. | 366 // In single process mode, or when we force them from the command line, |
| 367 // we may have already created the field trial. | |
| 355 if (field_trial->group_name_internal() != group_name) | 368 if (field_trial->group_name_internal() != group_name) |
| 356 return NULL; | 369 return NULL; |
| 357 return field_trial; | 370 return field_trial; |
| 358 } | 371 } |
| 359 const int kTotalProbability = 100; | 372 const int kTotalProbability = 100; |
| 360 field_trial = new FieldTrial(name, kTotalProbability, group_name, | 373 field_trial = new FieldTrial(name, kTotalProbability, group_name, |
| 361 kExpirationYearInFuture, 1, 1); | 374 kExpirationYearInFuture, 1, 1); |
| 362 field_trial->AppendGroup(group_name, kTotalProbability); | 375 field_trial->AppendGroup(group_name, kTotalProbability); |
| 376 field_trial->forced_ = true; | |
| 377 FieldTrialList::Register(field_trial); | |
| 363 return field_trial; | 378 return field_trial; |
| 364 } | 379 } |
| 365 | 380 |
| 366 // static | 381 // static |
| 367 void FieldTrialList::AddObserver(Observer* observer) { | 382 void FieldTrialList::AddObserver(Observer* observer) { |
| 368 if (!global_) | 383 if (!global_) |
| 369 return; | 384 return; |
| 370 DCHECK(global_); | 385 DCHECK(global_); |
| 371 global_->observer_list_.AddObserver(observer); | 386 global_->observer_list_.AddObserver(observer); |
| 372 } | 387 } |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 418 return global_->client_id_; | 433 return global_->client_id_; |
| 419 } | 434 } |
| 420 | 435 |
| 421 FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) { | 436 FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) { |
| 422 RegistrationList::iterator it = registered_.find(name); | 437 RegistrationList::iterator it = registered_.find(name); |
| 423 if (registered_.end() == it) | 438 if (registered_.end() == it) |
| 424 return NULL; | 439 return NULL; |
| 425 return it->second; | 440 return it->second; |
| 426 } | 441 } |
| 427 | 442 |
| 443 // static | |
| 444 void FieldTrialList::Register(FieldTrial* trial) { | |
| 445 if (!global_) { | |
| 446 used_without_global_ = true; | |
| 447 return; | |
| 448 } | |
| 449 AutoLock auto_lock(global_->lock_); | |
| 450 DCHECK(!global_->PreLockedFind(trial->name())); | |
| 451 trial->AddRef(); | |
| 452 global_->registered_[trial->name()] = trial; | |
| 453 } | |
| 454 | |
| 428 } // namespace base | 455 } // namespace base |
| OLD | NEW |