| 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 SetGroupChoice(default_group_name_, kDefaultGroupNumber); |
| 95 } | 100 } |
| 96 } | 101 } |
| 97 | 102 |
| 98 int FieldTrial::AppendGroup(const std::string& name, | 103 int FieldTrial::AppendGroup(const std::string& name, |
| 99 Probability group_probability) { | 104 Probability group_probability) { |
| 105 // When the group choice was previously forced, we only need to return the |
| 106 // the id of the chosen group, and anything can be returned for the others. |
| 107 if (forced_) { |
| 108 DCHECK(!group_name_.empty()); |
| 109 if (name == group_name_) { |
| 110 return group_; |
| 111 } |
| 112 DCHECK_NE(next_group_number_, group_); |
| 113 // We still return different numbers each time, in case some caller need |
| 114 // them to be different. |
| 115 return next_group_number_++; |
| 116 } |
| 117 |
| 100 DCHECK_LE(group_probability, divisor_); | 118 DCHECK_LE(group_probability, divisor_); |
| 101 DCHECK_GE(group_probability, 0); | 119 DCHECK_GE(group_probability, 0); |
| 102 | 120 |
| 103 if (enable_benchmarking_ || !enable_field_trial_) | 121 if (enable_benchmarking_ || !enable_field_trial_) |
| 104 group_probability = 0; | 122 group_probability = 0; |
| 105 | 123 |
| 106 accumulated_group_probability_ += group_probability; | 124 accumulated_group_probability_ += group_probability; |
| 107 | 125 |
| 108 DCHECK_LE(accumulated_group_probability_, divisor_); | 126 DCHECK_LE(accumulated_group_probability_, divisor_); |
| 109 if (group_ == kNotFinalized && accumulated_group_probability_ > random_) { | 127 if (group_ == kNotFinalized && accumulated_group_probability_ > random_) { |
| 110 // This is the group that crossed the random line, so we do the assignment. | 128 // This is the group that crossed the random line, so we do the assignment. |
| 111 group_ = next_group_number_; | 129 SetGroupChoice(name, next_group_number_); |
| 112 if (name.empty()) | |
| 113 StringAppendF(&group_name_, "%d", group_); | |
| 114 else | |
| 115 group_name_ = name; | |
| 116 group_name_hash_ = HashName(group_name_); | |
| 117 FieldTrialList::NotifyFieldTrialGroupSelection(name_, group_name_); | 130 FieldTrialList::NotifyFieldTrialGroupSelection(name_, group_name_); |
| 118 } | 131 } |
| 119 return next_group_number_++; | 132 return next_group_number_++; |
| 120 } | 133 } |
| 121 | 134 |
| 122 int FieldTrial::group() { | 135 int FieldTrial::group() { |
| 123 if (group_ == kNotFinalized) { | 136 if (group_ == kNotFinalized) { |
| 124 accumulated_group_probability_ = divisor_; | 137 accumulated_group_probability_ = divisor_; |
| 125 group_ = kDefaultGroupNumber; | 138 // Here it's OK to use kDefaultGroupNumber |
| 126 group_name_ = default_group_name_; | 139 // since we can't be forced and not finalized. |
| 127 group_name_hash_ = HashName(group_name_); | 140 DCHECK(!forced_); |
| 141 SetGroupChoice(default_group_name_, kDefaultGroupNumber); |
| 128 FieldTrialList::NotifyFieldTrialGroupSelection(name_, group_name_); | 142 FieldTrialList::NotifyFieldTrialGroupSelection(name_, group_name_); |
| 129 } | 143 } |
| 130 return group_; | 144 return group_; |
| 131 } | 145 } |
| 132 | 146 |
| 133 std::string FieldTrial::group_name() { | 147 std::string FieldTrial::group_name() { |
| 134 group(); // call group() to make sure group assignment was done. | 148 group(); // call group() to make sure group assignment was done. |
| 135 DCHECK(!group_name_.empty()); | 149 DCHECK(!group_name_.empty()); |
| 136 return group_name_; | 150 return group_name_; |
| 137 } | 151 } |
| (...skipping 15 matching lines...) Expand all Loading... |
| 153 } | 167 } |
| 154 | 168 |
| 155 // static | 169 // static |
| 156 void FieldTrial::EnableBenchmarking() { | 170 void FieldTrial::EnableBenchmarking() { |
| 157 DCHECK_EQ(0u, FieldTrialList::GetFieldTrialCount()); | 171 DCHECK_EQ(0u, FieldTrialList::GetFieldTrialCount()); |
| 158 enable_benchmarking_ = true; | 172 enable_benchmarking_ = true; |
| 159 } | 173 } |
| 160 | 174 |
| 161 FieldTrial::~FieldTrial() {} | 175 FieldTrial::~FieldTrial() {} |
| 162 | 176 |
| 177 void FieldTrial::SetGroupChoice(const std::string& name, int number) { |
| 178 group_ = number; |
| 179 if (name.empty()) |
| 180 StringAppendF(&group_name_, "%d", group_); |
| 181 else |
| 182 group_name_ = name; |
| 183 group_name_hash_ = HashName(group_name_); |
| 184 } |
| 185 |
| 163 // static | 186 // static |
| 164 double FieldTrial::HashClientId(const std::string& client_id, | 187 double FieldTrial::HashClientId(const std::string& client_id, |
| 165 const std::string& trial_name) { | 188 const std::string& trial_name) { |
| 166 // SHA-1 is designed to produce a uniformly random spread in its output space, | 189 // SHA-1 is designed to produce a uniformly random spread in its output space, |
| 167 // even for nearly-identical inputs, so it helps massage whatever client_id | 190 // even for nearly-identical inputs, so it helps massage whatever client_id |
| 168 // and trial_name we get into something with a uniform distribution, which | 191 // and trial_name we get into something with a uniform distribution, which |
| 169 // is desirable so that we don't skew any part of the 0-100% spectrum. | 192 // is desirable so that we don't skew any part of the 0-100% spectrum. |
| 170 std::string input(client_id + trial_name); | 193 std::string input(client_id + trial_name); |
| 171 unsigned char sha1_hash[kSHA1Length]; | 194 unsigned char sha1_hash[kSHA1Length]; |
| 172 SHA1HashBytes(reinterpret_cast<const unsigned char*>(input.c_str()), | 195 SHA1HashBytes(reinterpret_cast<const unsigned char*>(input.c_str()), |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 227 while (!registered_.empty()) { | 250 while (!registered_.empty()) { |
| 228 RegistrationList::iterator it = registered_.begin(); | 251 RegistrationList::iterator it = registered_.begin(); |
| 229 it->second->Release(); | 252 it->second->Release(); |
| 230 registered_.erase(it->first); | 253 registered_.erase(it->first); |
| 231 } | 254 } |
| 232 DCHECK_EQ(this, global_); | 255 DCHECK_EQ(this, global_); |
| 233 global_ = NULL; | 256 global_ = NULL; |
| 234 } | 257 } |
| 235 | 258 |
| 236 // static | 259 // static |
| 237 void FieldTrialList::Register(FieldTrial* trial) { | 260 FieldTrial* FieldTrialList::FactoryGetFieldTrial( |
| 238 if (!global_) { | 261 const std::string& name, |
| 239 used_without_global_ = true; | 262 FieldTrial::Probability total_probability, |
| 240 return; | 263 const std::string& default_group_name, |
| 264 const int year, |
| 265 const int month, |
| 266 const int day_of_month, |
| 267 int* default_group_number) { |
| 268 if (default_group_number) |
| 269 *default_group_number = FieldTrial::kDefaultGroupNumber; |
| 270 // Check if the field trial has already been created in some other way. |
| 271 FieldTrial* existing_trial = Find(name); |
| 272 if (existing_trial) { |
| 273 CHECK(existing_trial->forced_); |
| 274 // If the field trial has already been forced, check whether it was forced |
| 275 // to the default group. Return the chosen group number, in that case.. |
| 276 if (default_group_number && |
| 277 default_group_name == existing_trial->default_group_name()) { |
| 278 *default_group_number = existing_trial->group(); |
| 279 } |
| 280 return existing_trial; |
| 241 } | 281 } |
| 242 AutoLock auto_lock(global_->lock_); | 282 |
| 243 DCHECK(!global_->PreLockedFind(trial->name())); | 283 FieldTrial* field_trial = new FieldTrial( |
| 244 trial->AddRef(); | 284 name, total_probability, default_group_name, year, month, day_of_month); |
| 245 global_->registered_[trial->name()] = trial; | 285 FieldTrialList::Register(field_trial); |
| 286 return field_trial; |
| 246 } | 287 } |
| 247 | 288 |
| 248 // static | 289 // static |
| 249 FieldTrial* FieldTrialList::Find(const std::string& name) { | 290 FieldTrial* FieldTrialList::Find(const std::string& name) { |
| 250 if (!global_) | 291 if (!global_) |
| 251 return NULL; | 292 return NULL; |
| 252 AutoLock auto_lock(global_->lock_); | 293 AutoLock auto_lock(global_->lock_); |
| 253 return global_->PreLockedFind(name); | 294 return global_->PreLockedFind(name); |
| 254 } | 295 } |
| 255 | 296 |
| (...skipping 20 matching lines...) Expand all Loading... |
| 276 | 317 |
| 277 // static | 318 // static |
| 278 void FieldTrialList::StatesToString(std::string* output) { | 319 void FieldTrialList::StatesToString(std::string* output) { |
| 279 DCHECK(output->empty()); | 320 DCHECK(output->empty()); |
| 280 if (!global_) | 321 if (!global_) |
| 281 return; | 322 return; |
| 282 AutoLock auto_lock(global_->lock_); | 323 AutoLock auto_lock(global_->lock_); |
| 283 | 324 |
| 284 for (RegistrationList::iterator it = global_->registered_.begin(); | 325 for (RegistrationList::iterator it = global_->registered_.begin(); |
| 285 it != global_->registered_.end(); ++it) { | 326 it != global_->registered_.end(); ++it) { |
| 286 const std::string name = it->first; | 327 const std::string& name = it->first; |
| 287 std::string group_name = it->second->group_name_internal(); | 328 std::string group_name = it->second->group_name_internal(); |
| 288 if (group_name.empty()) | 329 if (group_name.empty()) |
| 289 continue; // Should not include uninitialized trials. | 330 continue; // Should not include uninitialized trials. |
| 290 DCHECK_EQ(name.find(kPersistentStringSeparator), std::string::npos); | 331 DCHECK_EQ(name.find(kPersistentStringSeparator), std::string::npos); |
| 291 DCHECK_EQ(group_name.find(kPersistentStringSeparator), std::string::npos); | 332 DCHECK_EQ(group_name.find(kPersistentStringSeparator), std::string::npos); |
| 292 output->append(name); | 333 output->append(name); |
| 293 output->append(1, kPersistentStringSeparator); | 334 output->append(1, kPersistentStringSeparator); |
| 294 output->append(group_name); | 335 output->append(group_name); |
| 295 output->append(1, kPersistentStringSeparator); | 336 output->append(1, kPersistentStringSeparator); |
| 296 } | 337 } |
| 297 } | 338 } |
| 298 | 339 |
| 299 // static | 340 // static |
| 300 void FieldTrialList::GetFieldTrialNameGroupIds( | 341 void FieldTrialList::GetFieldTrialNameGroupIds( |
| 301 std::vector<FieldTrial::NameGroupId>* name_group_ids) { | 342 std::vector<FieldTrial::NameGroupId>* name_group_ids) { |
| 302 DCHECK(name_group_ids->empty()); | 343 DCHECK(name_group_ids->empty()); |
| 303 if (!global_) | 344 if (!global_) |
| 304 return; | 345 return; |
| 305 AutoLock auto_lock(global_->lock_); | 346 AutoLock auto_lock(global_->lock_); |
| 306 | 347 |
| 307 for (RegistrationList::iterator it = global_->registered_.begin(); | 348 for (RegistrationList::iterator it = global_->registered_.begin(); |
| 308 it != global_->registered_.end(); ++it) { | 349 it != global_->registered_.end(); ++it) { |
| 309 FieldTrial::NameGroupId name_group_id; | 350 FieldTrial::NameGroupId name_group_id; |
| 310 if (it->second->GetNameGroupId(&name_group_id)) | 351 if (it->second->GetNameGroupId(&name_group_id)) |
| 311 name_group_ids->push_back(name_group_id); | 352 name_group_ids->push_back(name_group_id); |
| 312 } | 353 } |
| 313 } | 354 } |
| 314 | 355 |
| 315 // static | 356 // static |
| 316 bool FieldTrialList::CreateTrialsInChildProcess( | 357 bool FieldTrialList::CreateTrialsFromString(const std::string& trials_string) { |
| 317 const std::string& parent_trials) { | |
| 318 DCHECK(global_); | 358 DCHECK(global_); |
| 319 if (parent_trials.empty() || !global_) | 359 if (trials_string.empty() || !global_) |
| 320 return true; | 360 return true; |
| 321 | 361 |
| 322 size_t next_item = 0; | 362 size_t next_item = 0; |
| 323 while (next_item < parent_trials.length()) { | 363 while (next_item < trials_string.length()) { |
| 324 size_t name_end = parent_trials.find(kPersistentStringSeparator, next_item); | 364 size_t name_end = trials_string.find(kPersistentStringSeparator, next_item); |
| 325 if (name_end == parent_trials.npos || next_item == name_end) | 365 if (name_end == trials_string.npos || next_item == name_end) |
| 326 return false; | 366 return false; |
| 327 size_t group_name_end = parent_trials.find(kPersistentStringSeparator, | 367 size_t group_name_end = trials_string.find(kPersistentStringSeparator, |
| 328 name_end + 1); | 368 name_end + 1); |
| 329 if (group_name_end == parent_trials.npos || name_end + 1 == group_name_end) | 369 if (group_name_end == trials_string.npos || name_end + 1 == group_name_end) |
| 330 return false; | 370 return false; |
| 331 std::string name(parent_trials, next_item, name_end - next_item); | 371 std::string name(trials_string, next_item, name_end - next_item); |
| 332 std::string group_name(parent_trials, name_end + 1, | 372 std::string group_name(trials_string, name_end + 1, |
| 333 group_name_end - name_end - 1); | 373 group_name_end - name_end - 1); |
| 334 next_item = group_name_end + 1; | 374 next_item = group_name_end + 1; |
| 335 | 375 |
| 336 if (!CreateFieldTrial(name, group_name)) | 376 if (!CreateFieldTrial(name, group_name)) |
| 337 return false; | 377 return false; |
| 338 } | 378 } |
| 339 return true; | 379 return true; |
| 340 } | 380 } |
| 341 | 381 |
| 342 // static | 382 // static |
| 343 FieldTrial* FieldTrialList::CreateFieldTrial( | 383 FieldTrial* FieldTrialList::CreateFieldTrial( |
| 344 const std::string& name, | 384 const std::string& name, |
| 345 const std::string& group_name) { | 385 const std::string& group_name) { |
| 346 DCHECK(global_); | 386 DCHECK(global_); |
| 347 DCHECK_GE(name.size(), 0u); | 387 DCHECK_GE(name.size(), 0u); |
| 348 DCHECK_GE(group_name.size(), 0u); | 388 DCHECK_GE(group_name.size(), 0u); |
| 349 if (name.empty() || group_name.empty() || !global_) | 389 if (name.empty() || group_name.empty() || !global_) |
| 350 return NULL; | 390 return NULL; |
| 351 | 391 |
| 352 FieldTrial *field_trial(FieldTrialList::Find(name)); | 392 FieldTrial* field_trial = FieldTrialList::Find(name); |
| 353 if (field_trial) { | 393 if (field_trial) { |
| 354 // In single process mode, we may have already created the field trial. | 394 // In single process mode, or when we force them from the command line, |
| 395 // we may have already created the field trial. |
| 355 if (field_trial->group_name_internal() != group_name) | 396 if (field_trial->group_name_internal() != group_name) |
| 356 return NULL; | 397 return NULL; |
| 357 return field_trial; | 398 return field_trial; |
| 358 } | 399 } |
| 359 const int kTotalProbability = 100; | 400 const int kTotalProbability = 100; |
| 360 field_trial = new FieldTrial(name, kTotalProbability, group_name, | 401 field_trial = new FieldTrial(name, kTotalProbability, group_name, |
| 361 kExpirationYearInFuture, 1, 1); | 402 kExpirationYearInFuture, 1, 1); |
| 403 // This is where we may assign a group number different from |
| 404 // kDefaultGroupNumber to the default group. |
| 362 field_trial->AppendGroup(group_name, kTotalProbability); | 405 field_trial->AppendGroup(group_name, kTotalProbability); |
| 406 field_trial->forced_ = true; |
| 407 FieldTrialList::Register(field_trial); |
| 363 return field_trial; | 408 return field_trial; |
| 364 } | 409 } |
| 365 | 410 |
| 366 // static | 411 // static |
| 367 void FieldTrialList::AddObserver(Observer* observer) { | 412 void FieldTrialList::AddObserver(Observer* observer) { |
| 368 if (!global_) | 413 if (!global_) |
| 369 return; | 414 return; |
| 370 DCHECK(global_); | 415 DCHECK(global_); |
| 371 global_->observer_list_.AddObserver(observer); | 416 global_->observer_list_.AddObserver(observer); |
| 372 } | 417 } |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 418 return global_->client_id_; | 463 return global_->client_id_; |
| 419 } | 464 } |
| 420 | 465 |
| 421 FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) { | 466 FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) { |
| 422 RegistrationList::iterator it = registered_.find(name); | 467 RegistrationList::iterator it = registered_.find(name); |
| 423 if (registered_.end() == it) | 468 if (registered_.end() == it) |
| 424 return NULL; | 469 return NULL; |
| 425 return it->second; | 470 return it->second; |
| 426 } | 471 } |
| 427 | 472 |
| 473 // static |
| 474 void FieldTrialList::Register(FieldTrial* trial) { |
| 475 if (!global_) { |
| 476 used_without_global_ = true; |
| 477 return; |
| 478 } |
| 479 AutoLock auto_lock(global_->lock_); |
| 480 DCHECK(!global_->PreLockedFind(trial->name())); |
| 481 trial->AddRef(); |
| 482 global_->registered_[trial->name()] = trial; |
| 483 } |
| 484 |
| 428 } // namespace base | 485 } // namespace base |
| OLD | NEW |