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
3 // found in the LICENSE file.
5 #include "base/metrics/field_trial.h"
7 #include "base/build_time.h"
8 #include "base/logging.h"
9 #include "base/rand_util.h"
10 #include "base/sha1.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/sys_byteorder.h"
20 // Created a time value based on |year|, |month| and |day_of_month| parameters.
21 Time
CreateTimeFromParams(int year
, int month
, int day_of_month
) {
22 DCHECK_GT(year
, 1970);
25 DCHECK_GT(day_of_month
, 0);
26 DCHECK_LT(day_of_month
, 32);
28 Time::Exploded exploded
;
30 exploded
.month
= month
;
31 exploded
.day_of_week
= 0; // Should be unused.
32 exploded
.day_of_month
= day_of_month
;
36 exploded
.millisecond
= 0;
38 return Time::FromLocalExploded(exploded
);
43 static const char kHistogramFieldTrialSeparator('_');
46 const int FieldTrial::kNotFinalized
= -1;
47 const int FieldTrial::kDefaultGroupNumber
= 0;
48 bool FieldTrial::enable_benchmarking_
= false;
50 const char FieldTrialList::kPersistentStringSeparator('/');
51 int FieldTrialList::kNoExpirationYear
= 0;
53 //------------------------------------------------------------------------------
54 // FieldTrial methods and members.
56 FieldTrial::FieldTrial(const std::string
& trial_name
,
57 const Probability total_probability
,
58 const std::string
& default_group_name
)
59 : trial_name_(trial_name
),
60 divisor_(total_probability
),
61 default_group_name_(default_group_name
),
62 random_(static_cast<Probability
>(divisor_
* RandDouble())),
63 accumulated_group_probability_(0),
64 next_group_number_(kDefaultGroupNumber
+ 1),
65 group_(kNotFinalized
),
66 enable_field_trial_(true),
68 group_reported_(false) {
69 DCHECK_GT(total_probability
, 0);
70 DCHECK(!trial_name_
.empty());
71 DCHECK(!default_group_name_
.empty());
74 FieldTrial::EntropyProvider::~EntropyProvider() {
77 void FieldTrial::UseOneTimeRandomization() {
78 UseOneTimeRandomizationWithCustomSeed(0);
81 void FieldTrial::UseOneTimeRandomizationWithCustomSeed(
82 uint32 randomization_seed
) {
83 // No need to specify randomization when the group choice was forced.
86 DCHECK_EQ(group_
, kNotFinalized
);
87 DCHECK_EQ(kDefaultGroupNumber
+ 1, next_group_number_
);
88 const EntropyProvider
* entropy_provider
=
89 FieldTrialList::GetEntropyProviderForOneTimeRandomization();
90 if (!entropy_provider
) {
96 random_
= static_cast<Probability
>(
97 divisor_
* entropy_provider
->GetEntropyForTrial(trial_name_
,
101 void FieldTrial::Disable() {
102 DCHECK(!group_reported_
);
103 enable_field_trial_
= false;
105 // In case we are disabled after initialization, we need to switch
106 // the trial to the default group.
107 if (group_
!= kNotFinalized
) {
108 // Only reset when not already the default group, because in case we were
109 // forced to the default group, the group number may not be
110 // kDefaultGroupNumber, so we should keep it as is.
111 if (group_name_
!= default_group_name_
)
112 SetGroupChoice(default_group_name_
, kDefaultGroupNumber
);
116 int FieldTrial::AppendGroup(const std::string
& name
,
117 Probability group_probability
) {
118 // When the group choice was previously forced, we only need to return the
119 // the id of the chosen group, and anything can be returned for the others.
121 DCHECK(!group_name_
.empty());
122 if (name
== group_name_
) {
123 // Note that while |group_| may be equal to |kDefaultGroupNumber| on the
124 // forced trial, it will not have the same value as the default group
125 // number returned from the non-forced |FactoryGetFieldTrial()| call,
126 // which takes care to ensure that this does not happen.
129 DCHECK_NE(next_group_number_
, group_
);
130 // We still return different numbers each time, in case some caller need
131 // them to be different.
132 return next_group_number_
++;
135 DCHECK_LE(group_probability
, divisor_
);
136 DCHECK_GE(group_probability
, 0);
138 if (enable_benchmarking_
|| !enable_field_trial_
)
139 group_probability
= 0;
141 accumulated_group_probability_
+= group_probability
;
143 DCHECK_LE(accumulated_group_probability_
, divisor_
);
144 if (group_
== kNotFinalized
&& accumulated_group_probability_
> random_
) {
145 // This is the group that crossed the random line, so we do the assignment.
146 SetGroupChoice(name
, next_group_number_
);
148 return next_group_number_
++;
151 int FieldTrial::group() {
152 FinalizeGroupChoice();
153 FieldTrialList::NotifyFieldTrialGroupSelection(this);
157 const std::string
& FieldTrial::group_name() {
158 // Call |group()| to ensure group gets assigned and observers are notified.
160 DCHECK(!group_name_
.empty());
165 std::string
FieldTrial::MakeName(const std::string
& name_prefix
,
166 const std::string
& trial_name
) {
167 std::string
big_string(name_prefix
);
168 big_string
.append(1, kHistogramFieldTrialSeparator
);
169 return big_string
.append(FieldTrialList::FindFullName(trial_name
));
173 void FieldTrial::EnableBenchmarking() {
174 DCHECK_EQ(0u, FieldTrialList::GetFieldTrialCount());
175 enable_benchmarking_
= true;
178 void FieldTrial::SetForced() {
179 // We might have been forced before (e.g., by CreateFieldTrial) and it's
180 // first come first served, e.g., command line switch has precedence.
184 // And we must finalize the group choice before we mark ourselves as forced.
185 FinalizeGroupChoice();
189 FieldTrial::~FieldTrial() {}
191 void FieldTrial::SetGroupChoice(const std::string
& group_name
, int number
) {
193 if (group_name
.empty())
194 StringAppendF(&group_name_
, "%d", group_
);
196 group_name_
= group_name
;
197 DVLOG(1) << "Field trial: " << trial_name_
<< " Group choice:" << group_name_
;
200 void FieldTrial::FinalizeGroupChoice() {
201 if (group_
!= kNotFinalized
)
203 accumulated_group_probability_
= divisor_
;
204 // Here it's OK to use |kDefaultGroupNumber| since we can't be forced and not
207 SetGroupChoice(default_group_name_
, kDefaultGroupNumber
);
210 bool FieldTrial::GetActiveGroup(ActiveGroup
* active_group
) const {
211 if (!group_reported_
|| !enable_field_trial_
)
213 DCHECK_NE(group_
, kNotFinalized
);
214 active_group
->trial_name
= trial_name_
;
215 active_group
->group_name
= group_name_
;
219 //------------------------------------------------------------------------------
220 // FieldTrialList methods and members.
223 FieldTrialList
* FieldTrialList::global_
= NULL
;
226 bool FieldTrialList::used_without_global_
= false;
228 FieldTrialList::Observer::~Observer() {
231 FieldTrialList::FieldTrialList(
232 const FieldTrial::EntropyProvider
* entropy_provider
)
233 : entropy_provider_(entropy_provider
),
234 observer_list_(new ObserverListThreadSafe
<FieldTrialList::Observer
>(
235 ObserverListBase
<FieldTrialList::Observer
>::NOTIFY_EXISTING_ONLY
)) {
237 DCHECK(!used_without_global_
);
240 Time two_years_from_build_time
= GetBuildTime() + TimeDelta::FromDays(730);
241 Time::Exploded exploded
;
242 two_years_from_build_time
.LocalExplode(&exploded
);
243 kNoExpirationYear
= exploded
.year
;
246 FieldTrialList::~FieldTrialList() {
247 AutoLock
auto_lock(lock_
);
248 while (!registered_
.empty()) {
249 RegistrationList::iterator it
= registered_
.begin();
250 it
->second
->Release();
251 registered_
.erase(it
->first
);
253 DCHECK_EQ(this, global_
);
258 FieldTrial
* FieldTrialList::FactoryGetFieldTrial(
259 const std::string
& name
,
260 FieldTrial::Probability total_probability
,
261 const std::string
& default_group_name
,
264 const int day_of_month
,
265 int* default_group_number
) {
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 CHECK(existing_trial
->forced_
);
272 // If the default group name differs between the existing forced trial
273 // and this trial, then use a different value for the default group number.
274 if (default_group_number
&&
275 default_group_name
!= existing_trial
->default_group_name()) {
276 // If the new default group number corresponds to the group that was
277 // chosen for the forced trial (which has been finalized when it was
278 // forced), then set the default group number to that.
279 if (default_group_name
== existing_trial
->group_name_internal()) {
280 *default_group_number
= existing_trial
->group_
;
282 // Otherwise, use |kNonConflictingGroupNumber| (-2) for the default
283 // group number, so that it does not conflict with the |AppendGroup()|
284 // result for the chosen group.
285 const int kNonConflictingGroupNumber
= -2;
287 kNonConflictingGroupNumber
!= FieldTrial::kDefaultGroupNumber
,
288 conflicting_default_group_number
);
290 kNonConflictingGroupNumber
!= FieldTrial::kNotFinalized
,
291 conflicting_default_group_number
);
292 *default_group_number
= kNonConflictingGroupNumber
;
295 return existing_trial
;
298 FieldTrial
* field_trial
=
299 new FieldTrial(name
, total_probability
, default_group_name
);
300 if (GetBuildTime() > CreateTimeFromParams(year
, month
, day_of_month
))
301 field_trial
->Disable();
302 FieldTrialList::Register(field_trial
);
307 FieldTrial
* FieldTrialList::Find(const std::string
& name
) {
310 AutoLock
auto_lock(global_
->lock_
);
311 return global_
->PreLockedFind(name
);
315 int FieldTrialList::FindValue(const std::string
& name
) {
316 FieldTrial
* field_trial
= Find(name
);
318 return field_trial
->group();
319 return FieldTrial::kNotFinalized
;
323 std::string
FieldTrialList::FindFullName(const std::string
& name
) {
324 FieldTrial
* field_trial
= Find(name
);
326 return field_trial
->group_name();
327 return std::string();
331 bool FieldTrialList::TrialExists(const std::string
& name
) {
332 return Find(name
) != NULL
;
336 void FieldTrialList::StatesToString(std::string
* output
) {
337 FieldTrial::ActiveGroups active_groups
;
338 GetActiveFieldTrialGroups(&active_groups
);
339 for (FieldTrial::ActiveGroups::const_iterator it
= active_groups
.begin();
340 it
!= active_groups
.end(); ++it
) {
341 DCHECK_EQ(std::string::npos
,
342 it
->trial_name
.find(kPersistentStringSeparator
));
343 DCHECK_EQ(std::string::npos
,
344 it
->group_name
.find(kPersistentStringSeparator
));
345 output
->append(it
->trial_name
);
346 output
->append(1, kPersistentStringSeparator
);
347 output
->append(it
->group_name
);
348 output
->append(1, kPersistentStringSeparator
);
353 void FieldTrialList::GetActiveFieldTrialGroups(
354 FieldTrial::ActiveGroups
* active_groups
) {
355 DCHECK(active_groups
->empty());
358 AutoLock
auto_lock(global_
->lock_
);
360 for (RegistrationList::iterator it
= global_
->registered_
.begin();
361 it
!= global_
->registered_
.end(); ++it
) {
362 FieldTrial::ActiveGroup active_group
;
363 if (it
->second
->GetActiveGroup(&active_group
))
364 active_groups
->push_back(active_group
);
369 bool FieldTrialList::CreateTrialsFromString(const std::string
& trials_string
,
370 FieldTrialActivationMode mode
) {
372 if (trials_string
.empty() || !global_
)
375 size_t next_item
= 0;
376 while (next_item
< trials_string
.length()) {
377 size_t name_end
= trials_string
.find(kPersistentStringSeparator
, next_item
);
378 if (name_end
== trials_string
.npos
|| next_item
== name_end
)
380 size_t group_name_end
= trials_string
.find(kPersistentStringSeparator
,
382 if (group_name_end
== trials_string
.npos
|| name_end
+ 1 == group_name_end
)
384 std::string
name(trials_string
, next_item
, name_end
- next_item
);
385 std::string
group_name(trials_string
, name_end
+ 1,
386 group_name_end
- name_end
- 1);
387 next_item
= group_name_end
+ 1;
389 FieldTrial
* trial
= CreateFieldTrial(name
, group_name
);
392 if (mode
== ACTIVATE_TRIALS
) {
393 // Call |group()| to mark the trial as "used" and notify observers, if
394 // any. This is useful to ensure that field trials created in child
395 // processes are properly reported in crash reports.
403 FieldTrial
* FieldTrialList::CreateFieldTrial(
404 const std::string
& name
,
405 const std::string
& group_name
) {
407 DCHECK_GE(name
.size(), 0u);
408 DCHECK_GE(group_name
.size(), 0u);
409 if (name
.empty() || group_name
.empty() || !global_
)
412 FieldTrial
* field_trial
= FieldTrialList::Find(name
);
414 // In single process mode, or when we force them from the command line,
415 // we may have already created the field trial.
416 if (field_trial
->group_name_internal() != group_name
)
420 const int kTotalProbability
= 100;
421 field_trial
= new FieldTrial(name
, kTotalProbability
, group_name
);
422 // Force the trial, which will also finalize the group choice.
423 field_trial
->SetForced();
424 FieldTrialList::Register(field_trial
);
429 void FieldTrialList::AddObserver(Observer
* observer
) {
432 global_
->observer_list_
->AddObserver(observer
);
436 void FieldTrialList::RemoveObserver(Observer
* observer
) {
439 global_
->observer_list_
->RemoveObserver(observer
);
443 void FieldTrialList::NotifyFieldTrialGroupSelection(FieldTrial
* field_trial
) {
448 AutoLock
auto_lock(global_
->lock_
);
449 if (field_trial
->group_reported_
)
451 field_trial
->group_reported_
= true;
454 if (!field_trial
->enable_field_trial_
)
457 global_
->observer_list_
->Notify(
458 &FieldTrialList::Observer::OnFieldTrialGroupFinalized
,
459 field_trial
->trial_name(),
460 field_trial
->group_name_internal());
464 size_t FieldTrialList::GetFieldTrialCount() {
467 AutoLock
auto_lock(global_
->lock_
);
468 return global_
->registered_
.size();
472 const FieldTrial::EntropyProvider
*
473 FieldTrialList::GetEntropyProviderForOneTimeRandomization() {
475 used_without_global_
= true;
479 return global_
->entropy_provider_
.get();
482 FieldTrial
* FieldTrialList::PreLockedFind(const std::string
& name
) {
483 RegistrationList::iterator it
= registered_
.find(name
);
484 if (registered_
.end() == it
)
490 void FieldTrialList::Register(FieldTrial
* trial
) {
492 used_without_global_
= true;
495 AutoLock
auto_lock(global_
->lock_
);
496 DCHECK(!global_
->PreLockedFind(trial
->trial_name()));
498 global_
->registered_
[trial
->trial_name()] = trial
;