1 // Copyright (c) 2006-2009 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
6 #include "base/field_trial.h"
7 #include "base/logging.h"
8 #include "base/rand_util.h"
9 #include "base/string_util.h"
10
11 using base::TimeTicks;
12
13 // static
14 const int FieldTrial::kNotParticipating = -1;
15
16 // static
17 const int FieldTrial::kAllRemainingProbability = -2;
18
19 // static
20 const char FieldTrialList::kPersistentStringSeparator('/');
21
22 //------------------------------------------------------------------------------
23 // FieldTrial methods and members.
24
FieldTrial(const std::string & name,const Probability total_probability)25 FieldTrial::FieldTrial(const std::string& name,
26 const Probability total_probability)
27 : name_(name),
28 divisor_(total_probability),
29 random_(static_cast<Probability>(divisor_ * base::RandDouble())),
30 accumulated_group_probability_(0),
31 next_group_number_(0),
32 group_(kNotParticipating) {
33 FieldTrialList::Register(this);
34 }
35
AppendGroup(const std::string & name,Probability group_probability)36 int FieldTrial::AppendGroup(const std::string& name,
37 Probability group_probability) {
38 DCHECK(group_probability <= divisor_);
39 DCHECK(group_probability >=0 ||
40 group_probability == kAllRemainingProbability);
41 if (group_probability == kAllRemainingProbability)
42 accumulated_group_probability_ = divisor_;
43 else
44 accumulated_group_probability_ += group_probability;
45 DCHECK(accumulated_group_probability_ <= divisor_);
46 if (group_ == kNotParticipating && accumulated_group_probability_ > random_) {
47 // This is the group that crossed the random line, so we do the assignment.
48 group_ = next_group_number_;
49 if (name.empty())
50 StringAppendF(&group_name_, "_%d", group_);
51 else
52 group_name_ = name;
53 }
54 return next_group_number_++;
55 }
56
57 // static
MakeName(const std::string & name_prefix,const std::string & trial_name)58 std::string FieldTrial::MakeName(const std::string& name_prefix,
59 const std::string& trial_name) {
60 std::string big_string(name_prefix);
61 return big_string.append(FieldTrialList::FindFullName(trial_name));
62 }
63
64 //------------------------------------------------------------------------------
65 // FieldTrialList methods and members.
66
67 // static
68 FieldTrialList* FieldTrialList::global_ = NULL;
69
70 // static
71 bool FieldTrialList::register_without_global_ = false;
72
FieldTrialList()73 FieldTrialList::FieldTrialList() : application_start_time_(TimeTicks::Now()) {
74 DCHECK(!global_);
75 DCHECK(!register_without_global_);
76 global_ = this;
77 }
78
~FieldTrialList()79 FieldTrialList::~FieldTrialList() {
80 AutoLock auto_lock(lock_);
81 while (!registered_.empty()) {
82 RegistrationList::iterator it = registered_.begin();
83 it->second->Release();
84 registered_.erase(it->first);
85 }
86 DCHECK(this == global_);
87 global_ = NULL;
88 }
89
90 // static
Register(FieldTrial * trial)91 void FieldTrialList::Register(FieldTrial* trial) {
92 if (!global_) {
93 register_without_global_ = true;
94 return;
95 }
96 AutoLock auto_lock(global_->lock_);
97 DCHECK(!global_->PreLockedFind(trial->name()));
98 trial->AddRef();
99 global_->registered_[trial->name()] = trial;
100 }
101
102 // static
FindValue(const std::string & name)103 int FieldTrialList::FindValue(const std::string& name) {
104 FieldTrial* field_trial = Find(name);
105 if (field_trial)
106 return field_trial->group();
107 return FieldTrial::kNotParticipating;
108 }
109
110 // static
FindFullName(const std::string & name)111 std::string FieldTrialList::FindFullName(const std::string& name) {
112 FieldTrial* field_trial = Find(name);
113 if (field_trial)
114 return field_trial->group_name();
115 return "";
116 }
117
118 // static
Find(const std::string & name)119 FieldTrial* FieldTrialList::Find(const std::string& name) {
120 if (!global_)
121 return NULL;
122 AutoLock auto_lock(global_->lock_);
123 return global_->PreLockedFind(name);
124 }
125
PreLockedFind(const std::string & name)126 FieldTrial* FieldTrialList::PreLockedFind(const std::string& name) {
127 RegistrationList::iterator it = registered_.find(name);
128 if (registered_.end() == it)
129 return NULL;
130 return it->second;
131 }
132
133 // static
StatesToString(std::string * output)134 void FieldTrialList::StatesToString(std::string* output) {
135 if (!global_)
136 return;
137 DCHECK(output->empty());
138 for (RegistrationList::iterator it = global_->registered_.begin();
139 it != global_->registered_.end(); ++it) {
140 const std::string name = it->first;
141 const std::string group_name = it->second->group_name();
142 if (group_name.empty())
143 continue; // No definitive winner in this trial.
144 DCHECK_EQ(name.find(kPersistentStringSeparator), std::string::npos);
145 DCHECK_EQ(group_name.find(kPersistentStringSeparator), std::string::npos);
146 output->append(name);
147 output->append(1, kPersistentStringSeparator);
148 output->append(group_name);
149 output->append(1, kPersistentStringSeparator);
150 }
151 }
152
153 // static
StringAugmentsState(const std::string & prior_state)154 bool FieldTrialList::StringAugmentsState(const std::string& prior_state) {
155 DCHECK(global_);
156 if (prior_state.empty() || !global_)
157 return true;
158
159 size_t next_item = 0;
160 while (next_item < prior_state.length()) {
161 size_t name_end = prior_state.find(kPersistentStringSeparator, next_item);
162 if (name_end == prior_state.npos || next_item == name_end)
163 return false;
164 size_t group_name_end = prior_state.find(kPersistentStringSeparator,
165 name_end + 1);
166 if (group_name_end == prior_state.npos || name_end + 1 == group_name_end)
167 return false;
168 std::string name(prior_state, next_item, name_end - next_item);
169 std::string group_name(prior_state, name_end + 1,
170 group_name_end - name_end - 1);
171 next_item = group_name_end + 1;
172
173 FieldTrial *field_trial(FieldTrialList::Find(name));
174 if (field_trial) {
175 // In single process mode, we may have already created the field trial.
176 if (field_trial->group_name() != group_name)
177 return false;
178 continue;
179 }
180 const int kTotalProbability = 100;
181 field_trial = new FieldTrial(name, kTotalProbability);
182 field_trial->AppendGroup(group_name, kTotalProbability);
183 }
184 return true;
185 }
186
187