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 |