1 // Copyright (c) 2010 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.
4
5 #include "base/metrics/field_trial.h"
6
7 #include "base/logging.h"
8 #include "base/rand_util.h"
9 #include "base/stringprintf.h"
10 #include "base/utf_string_conversions.h"
11
12 namespace base {
13
14 // static
15 const int FieldTrial::kNotFinalized = -1;
16
17 // static
18 const int FieldTrial::kDefaultGroupNumber = 0;
19
20 // static
21 bool FieldTrial::enable_benchmarking_ = false;
22
23 // static
24 const char FieldTrialList::kPersistentStringSeparator('/');
25
26 static const char kHistogramFieldTrialSeparator('_');
27
28 //------------------------------------------------------------------------------
29 // FieldTrial methods and members.
30
FieldTrial(const std::string & name,const Probability total_probability,const std::string & default_group_name,const int year,const int month,const int day_of_month)31 FieldTrial::FieldTrial(const std::string& name,
32 const Probability total_probability,
33 const std::string& default_group_name,
34 const int year,
35 const int month,
36 const int day_of_month)
37 : name_(name),
38 divisor_(total_probability),
39 default_group_name_(default_group_name),
40 random_(static_cast<Probability>(divisor_ * base::RandDouble())),
41 accumulated_group_probability_(0),
42 next_group_number_(kDefaultGroupNumber+1),
43 group_(kNotFinalized) {
44 DCHECK_GT(total_probability, 0);
45 DCHECK(!default_group_name_.empty());
46 FieldTrialList::Register(this);
47
48 DCHECK_GT(year, 1970);
49 DCHECK_GT(month, 0);
50 DCHECK_LT(month, 13);
51 DCHECK_GT(day_of_month, 0);
52 DCHECK_LT(day_of_month, 32);
53
54 base::Time::Exploded exploded;
55 exploded.year = year;
56 exploded.month = month;
57 exploded.day_of_week = 0; // Should be unused.
58 exploded.day_of_month = day_of_month;
59 exploded.hour = 0;
60 exploded.minute = 0;
61 exploded.second = 0;
62 exploded.millisecond = 0;
63
64 base::Time expiration_time = Time::FromLocalExploded(exploded);
65 disable_field_trial_ = (GetBuildTime() > expiration_time) ? true : false;
66 }
67
AppendGroup(const std::string & name,Probability group_probability)68 int FieldTrial::AppendGroup(const std::string& name,
69 Probability group_probability) {
70 DCHECK(group_probability <= divisor_);
71 DCHECK_GE(group_probability, 0);
72
73 if (enable_benchmarking_ || disable_field_trial_)
74 group_probability = 0;
75
76 accumulated_group_probability_ += group_probability;
77
78 DCHECK(accumulated_group_probability_ <= divisor_);
79 if (group_ == kNotFinalized && accumulated_group_probability_ > random_) {
80 // This is the group that crossed the random line, so we do the assignment.
81 group_ = next_group_number_;
82 if (name.empty())
83 base::StringAppendF(&group_name_, "%d", group_);
84 else
85 group_name_ = name;
86 }
87 return next_group_number_++;
88 }
89
group()90 int FieldTrial::group() {
91 if (group_ == kNotFinalized) {
92 accumulated_group_probability_ = divisor_;
93 group_ = kDefaultGroupNumber;
94 group_name_ = default_group_name_;
95 }
96 return group_;
97 }
98
group_name()99 std::string FieldTrial::group_name() {
100 group(); // call group() to make group assignment was done.
101 return group_name_;
102 }
103
104 // static
MakeName(const std::string & name_prefix,const std::string & trial_name)105 std::string FieldTrial::MakeName(const std::string& name_prefix,
106 const std::string& trial_name) {
107 std::string big_string(name_prefix);
108 big_string.append(1, kHistogramFieldTrialSeparator);
109 return big_string.append(FieldTrialList::FindFullName(trial_name));
110 }
111
112 // static
EnableBenchmarking()113 void FieldTrial::EnableBenchmarking() {
114 DCHECK_EQ(0u, FieldTrialList::GetFieldTrialCount());
115 enable_benchmarking_ = true;
116 }
117
~FieldTrial()118 FieldTrial::~FieldTrial() {}
119
120 // static
GetBuildTime()121 Time FieldTrial::GetBuildTime() {
122 Time integral_build_time;
123 const char* kDateTime = __DATE__ " " __TIME__;
124 bool result = Time::FromString(ASCIIToWide(kDateTime).c_str(),
125 &integral_build_time);
126 DCHECK(result);
127 return integral_build_time;
128 }
129
130 //------------------------------------------------------------------------------
131 // FieldTrialList methods and members.
132
133 // static
134 FieldTrialList* FieldTrialList::global_ = NULL;
135
136 // static
137 bool FieldTrialList::register_without_global_ = false;
138
FieldTrialList()139 FieldTrialList::FieldTrialList() : application_start_time_(TimeTicks::Now()) {
140 DCHECK(!global_);
141 DCHECK(!register_without_global_);
142 global_ = this;
143 }
144
~FieldTrialList()145 FieldTrialList::~FieldTrialList() {
146 AutoLock auto_lock(lock_);
147 while (!registered_.empty()) {
148 RegistrationList::iterator it = registered_.begin();
149 it->second->Release();
150 registered_.erase(it->first);
151 }
152 DCHECK(this == global_);
153 global_ = NULL;
154 }
155
156 // static
Register(FieldTrial * trial)157 void FieldTrialList::Register(FieldTrial* trial) {
158 if (!global_) {
159 register_without_global_ = true;
160 return;
161 }
162 AutoLock auto_lock(global_->lock_);
163 DCHECK(!global_->PreLockedFind(trial->name()));
164 trial->AddRef();
165 global_->registered_[trial->name()] = trial;
166 }
167
168 // static
Find(const std::string & name)169 FieldTrial* FieldTrialList::Find(const std::string& name) {
170 if (!global_)
171 return NULL;
172 AutoLock auto_lock(global_->lock_);
173 return global_->PreLockedFind(name);
174 }
175
176 // static
FindValue(const std::string & name)177 int FieldTrialList::FindValue(const std::string& name) {
178 FieldTrial* field_trial = Find(name);
179 if (field_trial)
180 return field_trial->group();
181 return FieldTrial::kNotFinalized;
182 }
183
184 // static
FindFullName(const std::string & name)185 std::string FieldTrialList::FindFullName(const std::string& name) {
186 FieldTrial* field_trial = Find(name);
187 if (field_trial)
188 return field_trial->group_name();
189 return "";
190 }
191
192 // static
StatesToString(std::string * output)193 void FieldTrialList::StatesToString(std::string* output) {
194 if (!global_)
195 return;
196 DCHECK(output->empty());
197 AutoLock auto_lock(global_->lock_);
198 for (RegistrationList::iterator it = global_->registered_.begin();
199 it != global_->registered_.end(); ++it) {
200 const std::string name = it->first;
201 std::string group_name = it->second->group_name_internal();
202 if (group_name.empty())
203 // No definitive winner in this trial, use default_group_name as the
204 // group_name.
205 group_name = it->second->default_group_name();
206 DCHECK_EQ(name.find(kPersistentStringSeparator), std::string::npos);
207 DCHECK_EQ(group_name.find(kPersistentStringSeparator), std::string::npos);
208 output->append(name);
209 output->append(1, kPersistentStringSeparator);
210 output->append(group_name);
211 output->append(1, kPersistentStringSeparator);
212 }
213 }
214
215 // static
CreateTrialsInChildProcess(const std::string & parent_trials)216 bool FieldTrialList::CreateTrialsInChildProcess(
217 const std::string& parent_trials) {
218 DCHECK(global_);
219 if (parent_trials.empty() || !global_)
220 return true;
221
222 Time::Exploded exploded;
223 Time two_years_from_now =
224 Time::NowFromSystemTime() + TimeDelta::FromDays(730);
225 two_years_from_now.LocalExplode(&exploded);
226 const int kTwoYearsFromNow = exploded.year;
227
228 size_t next_item = 0;
229 while (next_item < parent_trials.length()) {
230 size_t name_end = parent_trials.find(kPersistentStringSeparator, next_item);
231 if (name_end == parent_trials.npos || next_item == name_end)
232 return false;
233 size_t group_name_end = parent_trials.find(kPersistentStringSeparator,
234 name_end + 1);
235 if (group_name_end == parent_trials.npos || name_end + 1 == group_name_end)
236 return false;
237 std::string name(parent_trials, next_item, name_end - next_item);
238 std::string group_name(parent_trials, name_end + 1,
239 group_name_end - name_end - 1);
240 next_item = group_name_end + 1;
241
242 FieldTrial *field_trial(FieldTrialList::Find(name));
243 if (field_trial) {
244 // In single process mode, we may have already created the field trial.
245 if ((field_trial->group_name_internal() != group_name) &&
246 (field_trial->default_group_name() != group_name))
247 return false;
248 continue;
249 }
250 const int kTotalProbability = 100;
251 field_trial = new FieldTrial(name, kTotalProbability, group_name,
252 kTwoYearsFromNow, 1, 1);
253 field_trial->AppendGroup(group_name, kTotalProbability);
254 }
255 return true;
256 }
257
258 // static
GetFieldTrialCount()259 size_t FieldTrialList::GetFieldTrialCount() {
260 if (!global_)
261 return 0;
262 AutoLock auto_lock(global_->lock_);
263 return global_->registered_.size();
264 }
265
PreLockedFind(const std::string & name)266 FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) {
267 RegistrationList::iterator it = registered_.find(name);
268 if (registered_.end() == it)
269 return NULL;
270 return it->second;
271 }
272
273 } // namespace base
274