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_) { | |
Alexei Svitkine (slow)
2012/03/29 19:19:07
Hmm. If group_name_ == default_group_name_, then w
MAD
2012/04/02 18:31:15
Filed a bug for this since it's already broken wit
| |
99 group_ = kDefaultGroupNumber; | |
100 group_name_ = default_group_name_; | |
101 group_name_hash_ = HashName(group_name_); | |
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 { | |
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. | |
Alexei Svitkine (slow)
2012/03/29 19:19:07
Add a DCHECK(!forced_);
MAD
2012/04/02 18:31:15
Done.
| |
125 group_ = kDefaultGroupNumber; | 149 group_ = kDefaultGroupNumber; |
126 group_name_ = default_group_name_; | 150 group_name_ = default_group_name_; |
127 group_name_hash_ = HashName(group_name_); | 151 group_name_hash_ = HashName(group_name_); |
128 FieldTrialList::NotifyFieldTrialGroupSelection(name_, group_name_); | 152 FieldTrialList::NotifyFieldTrialGroupSelection(name_, group_name_); |
129 } | 153 } |
130 return group_; | 154 return group_; |
131 } | 155 } |
132 | 156 |
133 std::string FieldTrial::group_name() { | 157 std::string FieldTrial::group_name() { |
134 group(); // call group() to make sure group assignment was done. | 158 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()) { | 251 while (!registered_.empty()) { |
228 RegistrationList::iterator it = registered_.begin(); | 252 RegistrationList::iterator it = registered_.begin(); |
229 it->second->Release(); | 253 it->second->Release(); |
230 registered_.erase(it->first); | 254 registered_.erase(it->first); |
231 } | 255 } |
232 DCHECK_EQ(this, global_); | 256 DCHECK_EQ(this, global_); |
233 global_ = NULL; | 257 global_ = NULL; |
234 } | 258 } |
235 | 259 |
236 // static | 260 // static |
237 void FieldTrialList::Register(FieldTrial* trial) { | 261 FieldTrial* FieldTrialList::GetFieldTrialInstance( |
238 if (!global_) { | 262 const std::string& name, FieldTrial::Probability total_probability, |
239 used_without_global_ = true; | 263 const std::string& default_group_name, int* default_group_number, |
240 return; | 264 const int year, const int month, const int day_of_month) { |
265 // First check if the field trial has already been created in some other way. | |
266 FieldTrial* existing_trial = Find(name); | |
267 if (existing_trial) { | |
268 DCHECK(existing_trial->forced_); | |
269 if (default_group_number) { | |
270 // If the default group was forced, it won't have kDefaultGroupNumber | |
Alexei Svitkine (slow)
2012/03/29 19:19:07
I don't think this comment is clear. How about:
"
MAD
2012/04/02 18:31:15
Done.
| |
271 // as a group number, so we must return the chosen group number. | |
Alexei Svitkine (slow)
2012/03/29 19:19:07
Add a test for this logic.
MAD
2012/04/02 18:31:15
I already added one, but it was missing one expect
| |
272 if (default_group_name == existing_trial->default_group_name()) | |
273 *default_group_number = existing_trial->group(); | |
274 else | |
275 *default_group_number = FieldTrial::kDefaultGroupNumber; | |
276 } | |
277 return existing_trial; | |
241 } | 278 } |
242 AutoLock auto_lock(global_->lock_); | 279 |
243 DCHECK(!global_->PreLockedFind(trial->name())); | 280 FieldTrial* field_trial = new FieldTrial( |
244 trial->AddRef(); | 281 name, total_probability, default_group_name, year, month, day_of_month); |
245 global_->registered_[trial->name()] = trial; | 282 FieldTrialList::Register(field_trial); |
283 if (default_group_number) | |
284 *default_group_number = FieldTrial::kDefaultGroupNumber; | |
Alexei Svitkine (slow)
2012/03/29 19:19:07
Nit: Move this to the top of the function. Then, i
MAD
2012/04/02 18:31:15
Done.
| |
285 return field_trial; | |
246 } | 286 } |
247 | 287 |
248 // static | 288 // static |
249 FieldTrial* FieldTrialList::Find(const std::string& name) { | 289 FieldTrial* FieldTrialList::Find(const std::string& name) { |
250 if (!global_) | 290 if (!global_) |
251 return NULL; | 291 return NULL; |
252 AutoLock auto_lock(global_->lock_); | 292 AutoLock auto_lock(global_->lock_); |
253 return global_->PreLockedFind(name); | 293 return global_->PreLockedFind(name); |
254 } | 294 } |
255 | 295 |
(...skipping 20 matching lines...) Expand all Loading... | |
276 | 316 |
277 // static | 317 // static |
278 void FieldTrialList::StatesToString(std::string* output) { | 318 void FieldTrialList::StatesToString(std::string* output) { |
279 DCHECK(output->empty()); | 319 DCHECK(output->empty()); |
280 if (!global_) | 320 if (!global_) |
281 return; | 321 return; |
282 AutoLock auto_lock(global_->lock_); | 322 AutoLock auto_lock(global_->lock_); |
283 | 323 |
284 for (RegistrationList::iterator it = global_->registered_.begin(); | 324 for (RegistrationList::iterator it = global_->registered_.begin(); |
285 it != global_->registered_.end(); ++it) { | 325 it != global_->registered_.end(); ++it) { |
286 const std::string name = it->first; | 326 const std::string& name = it->first; |
287 std::string group_name = it->second->group_name_internal(); | 327 std::string group_name = it->second->group_name_internal(); |
288 if (group_name.empty()) | 328 if (group_name.empty()) |
289 continue; // Should not include uninitialized trials. | 329 continue; // Should not include uninitialized trials. |
290 DCHECK_EQ(name.find(kPersistentStringSeparator), std::string::npos); | 330 DCHECK_EQ(name.find(kPersistentStringSeparator), std::string::npos); |
291 DCHECK_EQ(group_name.find(kPersistentStringSeparator), std::string::npos); | 331 DCHECK_EQ(group_name.find(kPersistentStringSeparator), std::string::npos); |
292 output->append(name); | 332 output->append(name); |
293 output->append(1, kPersistentStringSeparator); | 333 output->append(1, kPersistentStringSeparator); |
294 output->append(group_name); | 334 output->append(group_name); |
295 output->append(1, kPersistentStringSeparator); | 335 output->append(1, kPersistentStringSeparator); |
296 } | 336 } |
297 } | 337 } |
298 | 338 |
299 // static | 339 // static |
300 void FieldTrialList::GetFieldTrialNameGroupIds( | 340 void FieldTrialList::GetFieldTrialNameGroupIds( |
301 std::vector<FieldTrial::NameGroupId>* name_group_ids) { | 341 std::vector<FieldTrial::NameGroupId>* name_group_ids) { |
302 DCHECK(name_group_ids->empty()); | 342 DCHECK(name_group_ids->empty()); |
303 if (!global_) | 343 if (!global_) |
304 return; | 344 return; |
305 AutoLock auto_lock(global_->lock_); | 345 AutoLock auto_lock(global_->lock_); |
306 | 346 |
307 for (RegistrationList::iterator it = global_->registered_.begin(); | 347 for (RegistrationList::iterator it = global_->registered_.begin(); |
308 it != global_->registered_.end(); ++it) { | 348 it != global_->registered_.end(); ++it) { |
309 FieldTrial::NameGroupId name_group_id; | 349 FieldTrial::NameGroupId name_group_id; |
310 if (it->second->GetNameGroupId(&name_group_id)) | 350 if (it->second->GetNameGroupId(&name_group_id)) |
311 name_group_ids->push_back(name_group_id); | 351 name_group_ids->push_back(name_group_id); |
312 } | 352 } |
313 } | 353 } |
314 | 354 |
315 // static | 355 // static |
316 bool FieldTrialList::CreateTrialsInChildProcess( | 356 bool FieldTrialList::CreateTrialsFromString(const std::string& trials_string) { |
317 const std::string& parent_trials) { | |
318 DCHECK(global_); | 357 DCHECK(global_); |
319 if (parent_trials.empty() || !global_) | 358 if (trials_string.empty() || !global_) |
320 return true; | 359 return true; |
321 | 360 |
322 size_t next_item = 0; | 361 size_t next_item = 0; |
323 while (next_item < parent_trials.length()) { | 362 while (next_item < trials_string.length()) { |
324 size_t name_end = parent_trials.find(kPersistentStringSeparator, next_item); | 363 size_t name_end = trials_string.find(kPersistentStringSeparator, next_item); |
325 if (name_end == parent_trials.npos || next_item == name_end) | 364 if (name_end == trials_string.npos || next_item == name_end) |
326 return false; | 365 return false; |
327 size_t group_name_end = parent_trials.find(kPersistentStringSeparator, | 366 size_t group_name_end = trials_string.find(kPersistentStringSeparator, |
328 name_end + 1); | 367 name_end + 1); |
Alexei Svitkine (slow)
2012/03/29 19:19:07
Nit: Align this with kPersistentStringSeparator, s
MAD
2012/04/02 18:31:15
Done.
| |
329 if (group_name_end == parent_trials.npos || name_end + 1 == group_name_end) | 368 if (group_name_end == trials_string.npos || name_end + 1 == group_name_end) |
330 return false; | 369 return false; |
331 std::string name(parent_trials, next_item, name_end - next_item); | 370 std::string name(trials_string, next_item, name_end - next_item); |
332 std::string group_name(parent_trials, name_end + 1, | 371 std::string group_name(trials_string, name_end + 1, |
333 group_name_end - name_end - 1); | 372 group_name_end - name_end - 1); |
Alexei Svitkine (slow)
2012/03/29 19:19:07
Nit: Align this with trials_string, so that there'
MAD
2012/04/02 18:31:15
Done.
| |
334 next_item = group_name_end + 1; | 373 next_item = group_name_end + 1; |
335 | 374 |
336 if (!CreateFieldTrial(name, group_name)) | 375 if (!CreateFieldTrial(name, group_name)) |
337 return false; | 376 return false; |
338 } | 377 } |
339 return true; | 378 return true; |
340 } | 379 } |
341 | 380 |
342 // static | 381 // static |
343 FieldTrial* FieldTrialList::CreateFieldTrial( | 382 FieldTrial* FieldTrialList::CreateFieldTrial( |
344 const std::string& name, | 383 const std::string& name, |
345 const std::string& group_name) { | 384 const std::string& group_name) { |
346 DCHECK(global_); | 385 DCHECK(global_); |
347 DCHECK_GE(name.size(), 0u); | 386 DCHECK_GE(name.size(), 0u); |
348 DCHECK_GE(group_name.size(), 0u); | 387 DCHECK_GE(group_name.size(), 0u); |
349 if (name.empty() || group_name.empty() || !global_) | 388 if (name.empty() || group_name.empty() || !global_) |
350 return NULL; | 389 return NULL; |
351 | 390 |
352 FieldTrial *field_trial(FieldTrialList::Find(name)); | 391 FieldTrial* field_trial = FieldTrialList::Find(name); |
353 if (field_trial) { | 392 if (field_trial) { |
354 // In single process mode, we may have already created the field trial. | 393 // In single process mode, or when we force them from the command line, |
394 // we may have already created the field trial. | |
355 if (field_trial->group_name_internal() != group_name) | 395 if (field_trial->group_name_internal() != group_name) |
356 return NULL; | 396 return NULL; |
357 return field_trial; | 397 return field_trial; |
358 } | 398 } |
359 const int kTotalProbability = 100; | 399 const int kTotalProbability = 100; |
360 field_trial = new FieldTrial(name, kTotalProbability, group_name, | 400 field_trial = new FieldTrial(name, kTotalProbability, group_name, |
361 kExpirationYearInFuture, 1, 1); | 401 kExpirationYearInFuture, 1, 1); |
402 // This is where we may assign a group number different from | |
403 // kDefaultGroupNumber to the default group. | |
362 field_trial->AppendGroup(group_name, kTotalProbability); | 404 field_trial->AppendGroup(group_name, kTotalProbability); |
405 field_trial->forced_ = true; | |
406 FieldTrialList::Register(field_trial); | |
363 return field_trial; | 407 return field_trial; |
364 } | 408 } |
365 | 409 |
366 // static | 410 // static |
367 void FieldTrialList::AddObserver(Observer* observer) { | 411 void FieldTrialList::AddObserver(Observer* observer) { |
368 if (!global_) | 412 if (!global_) |
369 return; | 413 return; |
370 DCHECK(global_); | 414 DCHECK(global_); |
371 global_->observer_list_.AddObserver(observer); | 415 global_->observer_list_.AddObserver(observer); |
372 } | 416 } |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
418 return global_->client_id_; | 462 return global_->client_id_; |
419 } | 463 } |
420 | 464 |
421 FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) { | 465 FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) { |
422 RegistrationList::iterator it = registered_.find(name); | 466 RegistrationList::iterator it = registered_.find(name); |
423 if (registered_.end() == it) | 467 if (registered_.end() == it) |
424 return NULL; | 468 return NULL; |
425 return it->second; | 469 return it->second; |
426 } | 470 } |
427 | 471 |
472 // static | |
473 void FieldTrialList::Register(FieldTrial* trial) { | |
474 if (!global_) { | |
475 used_without_global_ = true; | |
476 return; | |
477 } | |
478 AutoLock auto_lock(global_->lock_); | |
479 DCHECK(!global_->PreLockedFind(trial->name())); | |
480 trial->AddRef(); | |
481 global_->registered_[trial->name()] = trial; | |
482 } | |
483 | |
428 } // namespace base | 484 } // namespace base |
OLD | NEW |