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/metrics/histogram.h"
10 #include "base/rand_util.h"
11 #include "base/sha1.h"
12 #include "base/stringprintf.h"
13 #include "base/string_util.h"
14 #include "base/sys_byteorder.h"
15 #include "base/utf_string_conversions.h"
21 // Created a time value based on |year|, |month| and |day_of_month| parameters.
22 Time
CreateTimeFromParams(int year
, int month
, int day_of_month
) {
23 DCHECK_GT(year
, 1970);
26 DCHECK_GT(day_of_month
, 0);
27 DCHECK_LT(day_of_month
, 32);
29 Time::Exploded exploded
;
31 exploded
.month
= month
;
32 exploded
.day_of_week
= 0; // Should be unused.
33 exploded
.day_of_month
= day_of_month
;
37 exploded
.millisecond
= 0;
39 return Time::FromLocalExploded(exploded
);
44 static const char kHistogramFieldTrialSeparator('_');
47 const int FieldTrial::kNotFinalized
= -1;
48 const int FieldTrial::kDefaultGroupNumber
= 0;
49 bool FieldTrial::enable_benchmarking_
= false;
51 const char FieldTrialList::kPersistentStringSeparator('/');
52 int FieldTrialList::kExpirationYearInFuture
= 0;
54 //------------------------------------------------------------------------------
55 // FieldTrial methods and members.
57 FieldTrial::FieldTrial(const std::string
& trial_name
,
58 const Probability total_probability
,
59 const std::string
& default_group_name
)
60 : trial_name_(trial_name
),
61 divisor_(total_probability
),
62 default_group_name_(default_group_name
),
63 random_(static_cast<Probability
>(divisor_
* RandDouble())),
64 accumulated_group_probability_(0),
65 next_group_number_(kDefaultGroupNumber
+ 1),
66 group_(kNotFinalized
),
67 enable_field_trial_(true),
69 group_reported_(false) {
70 DCHECK_GT(total_probability
, 0);
71 DCHECK(!trial_name_
.empty());
72 DCHECK(!default_group_name_
.empty());
75 FieldTrial::EntropyProvider::~EntropyProvider() {
78 void FieldTrial::UseOneTimeRandomization() {
79 // No need to specify randomization when the group choice was forced.
82 DCHECK_EQ(group_
, kNotFinalized
);
83 DCHECK_EQ(kDefaultGroupNumber
+ 1, next_group_number_
);
84 const EntropyProvider
* entropy_provider
=
85 FieldTrialList::GetEntropyProviderForOneTimeRandomization();
86 if (!entropy_provider
) {
88 // TODO(stevet): Remove this temporary histogram when logging
89 // investigations are complete.
90 UMA_HISTOGRAM_BOOLEAN("Variations.DisabledNoEntropyProvider", true);
95 random_
= static_cast<Probability
>(
96 divisor_
* entropy_provider
->GetEntropyForTrial(trial_name_
));
99 void FieldTrial::Disable() {
100 DCHECK(!group_reported_
);
101 enable_field_trial_
= false;
103 // In case we are disabled after initialization, we need to switch
104 // the trial to the default group.
105 if (group_
!= kNotFinalized
) {
106 // Only reset when not already the default group, because in case we were
107 // forced to the default group, the group number may not be
108 // kDefaultGroupNumber, so we should keep it as is.
109 if (group_name_
!= default_group_name_
)
110 SetGroupChoice(default_group_name_
, kDefaultGroupNumber
);
114 int FieldTrial::AppendGroup(const std::string
& name
,
115 Probability group_probability
) {
116 // When the group choice was previously forced, we only need to return the
117 // the id of the chosen group, and anything can be returned for the others.
119 DCHECK(!group_name_
.empty());
120 if (name
== group_name_
) {
123 DCHECK_NE(next_group_number_
, group_
);
124 // We still return different numbers each time, in case some caller need
125 // them to be different.
126 return next_group_number_
++;
129 DCHECK_LE(group_probability
, divisor_
);
130 DCHECK_GE(group_probability
, 0);
132 if (enable_benchmarking_
|| !enable_field_trial_
)
133 group_probability
= 0;
135 accumulated_group_probability_
+= group_probability
;
137 DCHECK_LE(accumulated_group_probability_
, divisor_
);
138 if (group_
== kNotFinalized
&& accumulated_group_probability_
> random_
) {
139 // This is the group that crossed the random line, so we do the assignment.
140 SetGroupChoice(name
, next_group_number_
);
142 return next_group_number_
++;
145 int FieldTrial::group() {
146 FinalizeGroupChoice();
147 FieldTrialList::NotifyFieldTrialGroupSelection(this);
151 const std::string
& FieldTrial::group_name() {
152 // Call |group()| to ensure group gets assigned and observers are notified.
154 DCHECK(!group_name_
.empty());
159 std::string
FieldTrial::MakeName(const std::string
& name_prefix
,
160 const std::string
& trial_name
) {
161 std::string
big_string(name_prefix
);
162 big_string
.append(1, kHistogramFieldTrialSeparator
);
163 return big_string
.append(FieldTrialList::FindFullName(trial_name
));
167 void FieldTrial::EnableBenchmarking() {
168 DCHECK_EQ(0u, FieldTrialList::GetFieldTrialCount());
169 enable_benchmarking_
= true;
172 void FieldTrial::SetForced() {
173 // We might have been forced before (e.g., by CreateFieldTrial) and it's
174 // first come first served, e.g., command line switch has precedence.
178 // And we must finalize the group choice before we mark ourselves as forced.
179 FinalizeGroupChoice();
183 FieldTrial::~FieldTrial() {}
185 void FieldTrial::SetGroupChoice(const std::string
& group_name
, int number
) {
187 if (group_name
.empty())
188 StringAppendF(&group_name_
, "%d", group_
);
190 group_name_
= group_name
;
191 DVLOG(1) << "Field trial: " << trial_name_
<< " Group choice:" << group_name_
;
194 void FieldTrial::FinalizeGroupChoice() {
195 if (group_
!= kNotFinalized
)
197 accumulated_group_probability_
= divisor_
;
198 // Here it's OK to use |kDefaultGroupNumber| since we can't be forced and not
201 SetGroupChoice(default_group_name_
, kDefaultGroupNumber
);
204 bool FieldTrial::GetActiveGroup(ActiveGroup
* active_group
) const {
205 if (!group_reported_
|| !enable_field_trial_
) {
206 // TODO(asvitkine): Temporary histogram. Remove this once it is not needed.
207 if (trial_name_
== "UMA-Uniformity-Trial-1-Percent") {
208 const int kGroupNotReported
= 1;
209 const int kTrialDisabled
= 2;
211 if (!group_reported_
)
212 value
|= kGroupNotReported
;
213 if (!enable_field_trial_
)
214 value
|= kTrialDisabled
;
215 UMA_HISTOGRAM_ENUMERATION("Variations.UniformityTrialGroupNotActive",
220 DCHECK_NE(group_
, kNotFinalized
);
221 active_group
->trial_name
= trial_name_
;
222 active_group
->group_name
= group_name_
;
226 //------------------------------------------------------------------------------
227 // FieldTrialList methods and members.
230 FieldTrialList
* FieldTrialList::global_
= NULL
;
233 bool FieldTrialList::used_without_global_
= false;
235 FieldTrialList::Observer::~Observer() {
238 FieldTrialList::FieldTrialList(
239 const FieldTrial::EntropyProvider
* entropy_provider
)
240 : entropy_provider_(entropy_provider
),
241 observer_list_(new ObserverListThreadSafe
<FieldTrialList::Observer
>(
242 ObserverListBase
<FieldTrialList::Observer
>::NOTIFY_EXISTING_ONLY
)) {
244 DCHECK(!used_without_global_
);
247 Time::Exploded exploded
;
248 Time two_years_from_now
=
249 Time::NowFromSystemTime() + TimeDelta::FromDays(730);
250 two_years_from_now
.LocalExplode(&exploded
);
251 kExpirationYearInFuture
= exploded
.year
;
254 FieldTrialList::~FieldTrialList() {
255 AutoLock
auto_lock(lock_
);
256 while (!registered_
.empty()) {
257 RegistrationList::iterator it
= registered_
.begin();
258 it
->second
->Release();
259 registered_
.erase(it
->first
);
261 DCHECK_EQ(this, global_
);
266 FieldTrial
* FieldTrialList::FactoryGetFieldTrial(
267 const std::string
& name
,
268 FieldTrial::Probability total_probability
,
269 const std::string
& default_group_name
,
272 const int day_of_month
,
273 int* default_group_number
) {
274 if (default_group_number
)
275 *default_group_number
= FieldTrial::kDefaultGroupNumber
;
276 // Check if the field trial has already been created in some other way.
277 FieldTrial
* existing_trial
= Find(name
);
278 if (existing_trial
) {
279 CHECK(existing_trial
->forced_
);
280 // If the field trial has already been forced, check whether it was forced
281 // to the default group. Return the chosen group number, in that case..
282 if (default_group_number
&&
283 default_group_name
== existing_trial
->default_group_name()) {
284 *default_group_number
= existing_trial
->group();
286 return existing_trial
;
289 FieldTrial
* field_trial
=
290 new FieldTrial(name
, total_probability
, default_group_name
);
291 if (GetBuildTime() > CreateTimeFromParams(year
, month
, day_of_month
)) {
292 // TODO(asvitkine): Temporary histogram. Remove this once it is not needed.
293 if (name
== "UMA-Uniformity-Trial-1-Percent")
294 UMA_HISTOGRAM_BOOLEAN("Variations.UniformityTrialExpired", true);
295 field_trial
->Disable();
297 FieldTrialList::Register(field_trial
);
302 FieldTrial
* FieldTrialList::Find(const std::string
& name
) {
305 AutoLock
auto_lock(global_
->lock_
);
306 return global_
->PreLockedFind(name
);
310 int FieldTrialList::FindValue(const std::string
& name
) {
311 FieldTrial
* field_trial
= Find(name
);
313 return field_trial
->group();
314 return FieldTrial::kNotFinalized
;
318 std::string
FieldTrialList::FindFullName(const std::string
& name
) {
319 FieldTrial
* field_trial
= Find(name
);
321 return field_trial
->group_name();
326 bool FieldTrialList::TrialExists(const std::string
& name
) {
327 return Find(name
) != NULL
;
331 void FieldTrialList::StatesToString(std::string
* output
) {
332 FieldTrial::ActiveGroups active_groups
;
333 GetActiveFieldTrialGroups(&active_groups
);
334 for (FieldTrial::ActiveGroups::const_iterator it
= active_groups
.begin();
335 it
!= active_groups
.end(); ++it
) {
336 DCHECK_EQ(std::string::npos
,
337 it
->trial_name
.find(kPersistentStringSeparator
));
338 DCHECK_EQ(std::string::npos
,
339 it
->group_name
.find(kPersistentStringSeparator
));
340 output
->append(it
->trial_name
);
341 output
->append(1, kPersistentStringSeparator
);
342 output
->append(it
->group_name
);
343 output
->append(1, kPersistentStringSeparator
);
348 void FieldTrialList::GetActiveFieldTrialGroups(
349 FieldTrial::ActiveGroups
* active_groups
) {
350 DCHECK(active_groups
->empty());
353 AutoLock
auto_lock(global_
->lock_
);
355 for (RegistrationList::iterator it
= global_
->registered_
.begin();
356 it
!= global_
->registered_
.end(); ++it
) {
357 FieldTrial::ActiveGroup active_group
;
358 if (it
->second
->GetActiveGroup(&active_group
))
359 active_groups
->push_back(active_group
);
364 bool FieldTrialList::CreateTrialsFromString(const std::string
& trials_string
) {
366 if (trials_string
.empty() || !global_
)
369 size_t next_item
= 0;
370 while (next_item
< trials_string
.length()) {
371 size_t name_end
= trials_string
.find(kPersistentStringSeparator
, next_item
);
372 if (name_end
== trials_string
.npos
|| next_item
== name_end
)
374 size_t group_name_end
= trials_string
.find(kPersistentStringSeparator
,
376 if (group_name_end
== trials_string
.npos
|| name_end
+ 1 == group_name_end
)
378 std::string
name(trials_string
, next_item
, name_end
- next_item
);
379 std::string
group_name(trials_string
, name_end
+ 1,
380 group_name_end
- name_end
- 1);
381 next_item
= group_name_end
+ 1;
383 FieldTrial
* trial
= CreateFieldTrial(name
, group_name
);
386 // Call |group()| to mark the trial as "used" and notify observers, if any.
387 // This is needed to ensure the trial is properly reported in child process
395 FieldTrial
* FieldTrialList::CreateFieldTrial(
396 const std::string
& name
,
397 const std::string
& group_name
) {
399 DCHECK_GE(name
.size(), 0u);
400 DCHECK_GE(group_name
.size(), 0u);
401 if (name
.empty() || group_name
.empty() || !global_
)
404 FieldTrial
* field_trial
= FieldTrialList::Find(name
);
406 // In single process mode, or when we force them from the command line,
407 // we may have already created the field trial.
408 if (field_trial
->group_name_internal() != group_name
)
412 const int kTotalProbability
= 100;
413 field_trial
= new FieldTrial(name
, kTotalProbability
, group_name
);
414 // This is where we may assign a group number different from
415 // kDefaultGroupNumber to the default group.
416 field_trial
->AppendGroup(group_name
, kTotalProbability
);
417 field_trial
->forced_
= true;
418 FieldTrialList::Register(field_trial
);
423 void FieldTrialList::AddObserver(Observer
* observer
) {
426 global_
->observer_list_
->AddObserver(observer
);
430 void FieldTrialList::RemoveObserver(Observer
* observer
) {
433 global_
->observer_list_
->RemoveObserver(observer
);
437 void FieldTrialList::NotifyFieldTrialGroupSelection(FieldTrial
* field_trial
) {
442 AutoLock
auto_lock(global_
->lock_
);
443 if (field_trial
->group_reported_
)
445 field_trial
->group_reported_
= true;
448 if (!field_trial
->enable_field_trial_
)
451 global_
->observer_list_
->Notify(
452 &FieldTrialList::Observer::OnFieldTrialGroupFinalized
,
453 field_trial
->trial_name(),
454 field_trial
->group_name_internal());
458 size_t FieldTrialList::GetFieldTrialCount() {
461 AutoLock
auto_lock(global_
->lock_
);
462 return global_
->registered_
.size();
466 const FieldTrial::EntropyProvider
*
467 FieldTrialList::GetEntropyProviderForOneTimeRandomization() {
469 used_without_global_
= true;
473 return global_
->entropy_provider_
.get();
476 FieldTrial
* FieldTrialList::PreLockedFind(const std::string
& name
) {
477 RegistrationList::iterator it
= registered_
.find(name
);
478 if (registered_
.end() == it
)
484 void FieldTrialList::Register(FieldTrial
* trial
) {
486 used_without_global_
= true;
489 AutoLock
auto_lock(global_
->lock_
);
490 DCHECK(!global_
->PreLockedFind(trial
->trial_name()));
492 global_
->registered_
[trial
->trial_name()] = trial
;