Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(240)

Side by Side Diff: base/metrics/field_trial.cc

Issue 6883102: Add one-time randomization support for FieldTrial, and the ability to (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Update comments. Created 9 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 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/logging.h" 7 #include "base/logging.h"
8 #include "base/rand_util.h" 8 #include "base/rand_util.h"
9 #include "base/sha1.h"
10 #include "base/string_util.h"
9 #include "base/stringprintf.h" 11 #include "base/stringprintf.h"
10 #include "base/utf_string_conversions.h" 12 #include "base/utf_string_conversions.h"
11 13
12 namespace base { 14 namespace base {
13 15
14 // static 16 // static
15 const int FieldTrial::kNotFinalized = -1; 17 const int FieldTrial::kNotFinalized = -1;
16 18
17 // static 19 // static
18 const int FieldTrial::kDefaultGroupNumber = 0; 20 const int FieldTrial::kDefaultGroupNumber = 0;
(...skipping 15 matching lines...) Expand all
34 36
35 FieldTrial::FieldTrial(const std::string& name, 37 FieldTrial::FieldTrial(const std::string& name,
36 const Probability total_probability, 38 const Probability total_probability,
37 const std::string& default_group_name, 39 const std::string& default_group_name,
38 const int year, 40 const int year,
39 const int month, 41 const int month,
40 const int day_of_month) 42 const int day_of_month)
41 : name_(name), 43 : name_(name),
42 divisor_(total_probability), 44 divisor_(total_probability),
43 default_group_name_(default_group_name), 45 default_group_name_(default_group_name),
44 random_(static_cast<Probability>(divisor_ * base::RandDouble())), 46 random_(static_cast<Probability>(divisor_ * RandDouble())),
45 accumulated_group_probability_(0), 47 accumulated_group_probability_(0),
46 next_group_number_(kDefaultGroupNumber+1), 48 next_group_number_(kDefaultGroupNumber+1),
47 group_(kNotFinalized) { 49 group_(kNotFinalized),
50 enable_field_trial_(true) {
48 DCHECK_GT(total_probability, 0); 51 DCHECK_GT(total_probability, 0);
52 DCHECK(!name_.empty());
49 DCHECK(!default_group_name_.empty()); 53 DCHECK(!default_group_name_.empty());
50 FieldTrialList::Register(this); 54 FieldTrialList::Register(this);
51 55
52 DCHECK_GT(year, 1970); 56 DCHECK_GT(year, 1970);
53 DCHECK_GT(month, 0); 57 DCHECK_GT(month, 0);
54 DCHECK_LT(month, 13); 58 DCHECK_LT(month, 13);
55 DCHECK_GT(day_of_month, 0); 59 DCHECK_GT(day_of_month, 0);
56 DCHECK_LT(day_of_month, 32); 60 DCHECK_LT(day_of_month, 32);
57 61
58 base::Time::Exploded exploded; 62 Time::Exploded exploded;
59 exploded.year = year; 63 exploded.year = year;
60 exploded.month = month; 64 exploded.month = month;
61 exploded.day_of_week = 0; // Should be unused. 65 exploded.day_of_week = 0; // Should be unused.
62 exploded.day_of_month = day_of_month; 66 exploded.day_of_month = day_of_month;
63 exploded.hour = 0; 67 exploded.hour = 0;
64 exploded.minute = 0; 68 exploded.minute = 0;
65 exploded.second = 0; 69 exploded.second = 0;
66 exploded.millisecond = 0; 70 exploded.millisecond = 0;
67 71
68 base::Time expiration_time = Time::FromLocalExploded(exploded); 72 Time expiration_time = Time::FromLocalExploded(exploded);
69 disable_field_trial_ = (GetBuildTime() > expiration_time) ? true : false; 73 if (GetBuildTime() > expiration_time)
74 Disable();
75 }
76
77 void FieldTrial::UseOneTimeRandomization() {
78 DCHECK_EQ(group_, kNotFinalized);
jar (doing other things) 2011/05/03 00:17:47 Could you also add a DCHECK_EQ(0, next_group_numbe
Jói 2011/05/03 17:41:47 Done.
79 if (!FieldTrialList::IsOneTimeRandomizationEnabled()) {
80 NOTREACHED();
81 Disable();
82 return;
83 }
84
85 random_ = static_cast<Probability>(
86 divisor_ * HashClientId(FieldTrialList::client_id(), name_));
87 }
88
89 void FieldTrial::Disable() {
90 enable_field_trial_ = false;
91
92 // In case we are disabled after initialization, we need to switch
93 // the trial to the default group.
94 if (group_ != kNotFinalized) {
95 group_ = kDefaultGroupNumber;
96 group_name_ = default_group_name_;
97 }
70 } 98 }
71 99
72 int FieldTrial::AppendGroup(const std::string& name, 100 int FieldTrial::AppendGroup(const std::string& name,
73 Probability group_probability) { 101 Probability group_probability) {
74 DCHECK_LE(group_probability, divisor_); 102 DCHECK_LE(group_probability, divisor_);
75 DCHECK_GE(group_probability, 0); 103 DCHECK_GE(group_probability, 0);
76 104
77 if (enable_benchmarking_ || disable_field_trial_) 105 if (enable_benchmarking_ || !enable_field_trial_)
78 group_probability = 0; 106 group_probability = 0;
79 107
80 accumulated_group_probability_ += group_probability; 108 accumulated_group_probability_ += group_probability;
81 109
82 DCHECK_LE(accumulated_group_probability_, divisor_); 110 DCHECK_LE(accumulated_group_probability_, divisor_);
83 if (group_ == kNotFinalized && accumulated_group_probability_ > random_) { 111 if (group_ == kNotFinalized && accumulated_group_probability_ > random_) {
84 // This is the group that crossed the random line, so we do the assignment. 112 // This is the group that crossed the random line, so we do the assignment.
85 group_ = next_group_number_; 113 group_ = next_group_number_;
86 if (name.empty()) 114 if (name.empty())
87 base::StringAppendF(&group_name_, "%d", group_); 115 StringAppendF(&group_name_, "%d", group_);
88 else 116 else
89 group_name_ = name; 117 group_name_ = name;
90 FieldTrialList::NotifyFieldTrialGroupSelection(name_, group_name_); 118 FieldTrialList::NotifyFieldTrialGroupSelection(name_, group_name_);
91 } 119 }
92 return next_group_number_++; 120 return next_group_number_++;
93 } 121 }
94 122
95 int FieldTrial::group() { 123 int FieldTrial::group() {
96 if (group_ == kNotFinalized) { 124 if (group_ == kNotFinalized) {
97 accumulated_group_probability_ = divisor_; 125 accumulated_group_probability_ = divisor_;
98 group_ = kDefaultGroupNumber; 126 group_ = kDefaultGroupNumber;
99 group_name_ = default_group_name_; 127 group_name_ = default_group_name_;
100 FieldTrialList::NotifyFieldTrialGroupSelection(name_, group_name_); 128 FieldTrialList::NotifyFieldTrialGroupSelection(name_, group_name_);
101 } 129 }
102 return group_; 130 return group_;
103 } 131 }
104 132
105 std::string FieldTrial::group_name() { 133 std::string FieldTrial::group_name() {
106 group(); // call group() to make group assignment was done. 134 group(); // call group() to make group assignment was done.
jar (doing other things) 2011/05/03 00:17:47 nit (in old code): "make group assignment" --> "m
Jói 2011/05/03 17:41:47 Done.
135 DCHECK(!group_name_.empty());
107 return group_name_; 136 return group_name_;
108 } 137 }
109 138
110 // static 139 // static
111 std::string FieldTrial::MakeName(const std::string& name_prefix, 140 std::string FieldTrial::MakeName(const std::string& name_prefix,
112 const std::string& trial_name) { 141 const std::string& trial_name) {
113 std::string big_string(name_prefix); 142 std::string big_string(name_prefix);
114 big_string.append(1, kHistogramFieldTrialSeparator); 143 big_string.append(1, kHistogramFieldTrialSeparator);
115 return big_string.append(FieldTrialList::FindFullName(trial_name)); 144 return big_string.append(FieldTrialList::FindFullName(trial_name));
116 } 145 }
117 146
118 // static 147 // static
119 void FieldTrial::EnableBenchmarking() { 148 void FieldTrial::EnableBenchmarking() {
120 DCHECK_EQ(0u, FieldTrialList::GetFieldTrialCount()); 149 DCHECK_EQ(0u, FieldTrialList::GetFieldTrialCount());
121 enable_benchmarking_ = true; 150 enable_benchmarking_ = true;
122 } 151 }
123 152
124 FieldTrial::~FieldTrial() {} 153 FieldTrial::~FieldTrial() {}
125 154
126 // static 155 // static
127 Time FieldTrial::GetBuildTime() { 156 Time FieldTrial::GetBuildTime() {
128 Time integral_build_time; 157 Time integral_build_time;
129 const char* kDateTime = __DATE__ " " __TIME__; 158 const char* kDateTime = __DATE__ " " __TIME__;
130 bool result = Time::FromString(ASCIIToWide(kDateTime).c_str(), 159 bool result = Time::FromString(ASCIIToWide(kDateTime).c_str(),
131 &integral_build_time); 160 &integral_build_time);
132 DCHECK(result); 161 DCHECK(result);
133 return integral_build_time; 162 return integral_build_time;
134 } 163 }
135 164
165 // static
166 double FieldTrial::HashClientId(const std::string& client_id,
167 const std::string& trial_name) {
168 // SHA-1 is designed to produce a uniformly random spread in its output space,
169 // even for nearly-identical inputs, so it helps massage whatever client_id
170 // and trial_name we get into something with a uniform distribution, which
171 // is desirable so that we don't skew any part of the 0-100% spectrum.
172 std::string input(client_id + trial_name);
173 unsigned char sha1_hash[SHA1_LENGTH];
174 SHA1HashBytes(reinterpret_cast<const unsigned char*>(input.c_str()),
175 input.size(),
176 sha1_hash);
177
178 COMPILE_ASSERT(sizeof(uint64) < sizeof(sha1_hash), need_more_data);
179 uint64* bits = reinterpret_cast<uint64*>(&sha1_hash[0]);
180
181 return BitsToOpenEndedUnitInterval(*bits);
182 }
183
136 //------------------------------------------------------------------------------ 184 //------------------------------------------------------------------------------
137 // FieldTrialList methods and members. 185 // FieldTrialList methods and members.
138 186
139 // static 187 // static
140 FieldTrialList* FieldTrialList::global_ = NULL; 188 FieldTrialList* FieldTrialList::global_ = NULL;
141 189
142 // static 190 // static
143 bool FieldTrialList::register_without_global_ = false; 191 bool FieldTrialList::register_without_global_ = false;
144 192
145 FieldTrialList::FieldTrialList() 193 FieldTrialList::FieldTrialList(const std::string& client_id)
146 : application_start_time_(TimeTicks::Now()), 194 : application_start_time_(TimeTicks::Now()),
195 client_id_(client_id),
147 observer_list_(ObserverList<Observer>::NOTIFY_EXISTING_ONLY) { 196 observer_list_(ObserverList<Observer>::NOTIFY_EXISTING_ONLY) {
148 DCHECK(!global_); 197 DCHECK(!global_);
149 DCHECK(!register_without_global_); 198 DCHECK(!register_without_global_);
150 global_ = this; 199 global_ = this;
151 200
152 Time::Exploded exploded; 201 Time::Exploded exploded;
153 Time two_years_from_now = 202 Time two_years_from_now =
154 Time::NowFromSystemTime() + TimeDelta::FromDays(730); 203 Time::NowFromSystemTime() + TimeDelta::FromDays(730);
155 two_years_from_now.LocalExplode(&exploded); 204 two_years_from_now.LocalExplode(&exploded);
156 kExpirationYearInFuture = exploded.year; 205 kExpirationYearInFuture = exploded.year;
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
197 246
198 // static 247 // static
199 std::string FieldTrialList::FindFullName(const std::string& name) { 248 std::string FieldTrialList::FindFullName(const std::string& name) {
200 FieldTrial* field_trial = Find(name); 249 FieldTrial* field_trial = Find(name);
201 if (field_trial) 250 if (field_trial)
202 return field_trial->group_name(); 251 return field_trial->group_name();
203 return ""; 252 return "";
204 } 253 }
205 254
206 // static 255 // static
256 bool FieldTrialList::TrialExists(const std::string& name) {
257 return Find(name) != NULL;
258 }
259
260 // static
207 void FieldTrialList::StatesToString(std::string* output) { 261 void FieldTrialList::StatesToString(std::string* output) {
208 if (!global_) 262 if (!global_)
209 return; 263 return;
210 DCHECK(output->empty()); 264 DCHECK(output->empty());
211 AutoLock auto_lock(global_->lock_); 265 AutoLock auto_lock(global_->lock_);
266
267 // First, write just the client_id plus a separator.
jar (doing other things) 2011/05/03 00:17:47 Please add a DCHECK() comparable to line 279 to en
Jói 2011/05/03 17:41:47 Removed this code.
268 output->append(global_->client_id_);
269 output->append(1, kPersistentStringSeparator);
270
212 for (RegistrationList::iterator it = global_->registered_.begin(); 271 for (RegistrationList::iterator it = global_->registered_.begin();
213 it != global_->registered_.end(); ++it) { 272 it != global_->registered_.end(); ++it) {
214 const std::string name = it->first; 273 const std::string name = it->first;
215 std::string group_name = it->second->group_name_internal(); 274 std::string group_name = it->second->group_name_internal();
216 if (group_name.empty()) 275 if (group_name.empty())
217 // No definitive winner in this trial, use default_group_name as the 276 // No definitive winner in this trial, use default_group_name as the
218 // group_name. 277 // group_name.
219 group_name = it->second->default_group_name(); 278 group_name = it->second->default_group_name();
jar (doing other things) 2011/05/03 00:17:47 This is a bug (now... though not because of your c
Jói 2011/05/03 17:41:47 Switched to a continue.
220 DCHECK_EQ(name.find(kPersistentStringSeparator), std::string::npos); 279 DCHECK_EQ(name.find(kPersistentStringSeparator), std::string::npos);
221 DCHECK_EQ(group_name.find(kPersistentStringSeparator), std::string::npos); 280 DCHECK_EQ(group_name.find(kPersistentStringSeparator), std::string::npos);
222 output->append(name); 281 output->append(name);
223 output->append(1, kPersistentStringSeparator); 282 output->append(1, kPersistentStringSeparator);
224 output->append(group_name); 283 output->append(group_name);
225 output->append(1, kPersistentStringSeparator); 284 output->append(1, kPersistentStringSeparator);
226 } 285 }
227 } 286 }
228 287
229 // static 288 // static
230 bool FieldTrialList::CreateTrialsInChildProcess( 289 bool FieldTrialList::CreateTrialsInChildProcess(
231 const std::string& parent_trials) { 290 const std::string& parent_trials) {
232 DCHECK(global_); 291 DCHECK(global_);
233 if (parent_trials.empty() || !global_) 292 if (parent_trials.empty() || !global_)
234 return true; 293 return true;
235 294
236 size_t next_item = 0; 295 size_t next_item = 0;
296
297 // Get the client_id first, before the list of trials/groups.
298 size_t id_end = parent_trials.find(kPersistentStringSeparator, next_item);
299 // Must allow empty client_id, so we don't check for id_end == 0.
300 if (id_end == parent_trials.npos)
301 return false;
302 global_->client_id_ = std::string(parent_trials, 0, id_end);
303 next_item = id_end + 1;
304
237 while (next_item < parent_trials.length()) { 305 while (next_item < parent_trials.length()) {
238 size_t name_end = parent_trials.find(kPersistentStringSeparator, next_item); 306 size_t name_end = parent_trials.find(kPersistentStringSeparator, next_item);
239 if (name_end == parent_trials.npos || next_item == name_end) 307 if (name_end == parent_trials.npos || next_item == name_end)
240 return false; 308 return false;
241 size_t group_name_end = parent_trials.find(kPersistentStringSeparator, 309 size_t group_name_end = parent_trials.find(kPersistentStringSeparator,
242 name_end + 1); 310 name_end + 1);
243 if (group_name_end == parent_trials.npos || name_end + 1 == group_name_end) 311 if (group_name_end == parent_trials.npos || name_end + 1 == group_name_end)
244 return false; 312 return false;
245 std::string name(parent_trials, next_item, name_end - next_item); 313 std::string name(parent_trials, next_item, name_end - next_item);
246 std::string group_name(parent_trials, name_end + 1, 314 std::string group_name(parent_trials, name_end + 1,
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
306 } 374 }
307 375
308 // static 376 // static
309 size_t FieldTrialList::GetFieldTrialCount() { 377 size_t FieldTrialList::GetFieldTrialCount() {
310 if (!global_) 378 if (!global_)
311 return 0; 379 return 0;
312 AutoLock auto_lock(global_->lock_); 380 AutoLock auto_lock(global_->lock_);
313 return global_->registered_.size(); 381 return global_->registered_.size();
314 } 382 }
315 383
384 // static
385 bool FieldTrialList::IsOneTimeRandomizationEnabled() {
386 DCHECK(global_);
387 if (!global_)
388 return false;
389
390 return !global_->client_id_.empty();
391 }
392
393 // static
394 const std::string& FieldTrialList::client_id() {
395 DCHECK(global_);
396 if (!global_)
397 return EmptyString();
398
399 return global_->client_id_;
400 }
401
316 FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) { 402 FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) {
317 RegistrationList::iterator it = registered_.find(name); 403 RegistrationList::iterator it = registered_.find(name);
318 if (registered_.end() == it) 404 if (registered_.end() == it)
319 return NULL; 405 return NULL;
320 return it->second; 406 return it->second;
321 } 407 }
322 408
323 } // namespace base 409 } // namespace base
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698