1 // Copyright 2013 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 "components/variations/variations_associated_data.h"
6
7 #include <map>
8 #include <utility>
9 #include <vector>
10
11 #include "base/memory/singleton.h"
12 #include "components/variations/metrics_util.h"
13
14 namespace chrome_variations {
15
16 namespace {
17
18 // The internal singleton accessor for the map, used to keep it thread-safe.
19 class GroupMapAccessor {
20 public:
21 typedef std::map<ActiveGroupId, VariationID, ActiveGroupIdCompare>
22 GroupToIDMap;
23
24 // Retrieve the singleton.
GetInstance()25 static GroupMapAccessor* GetInstance() {
26 return Singleton<GroupMapAccessor>::get();
27 }
28
29 // Note that this normally only sets the ID for a group the first time, unless
30 // |force| is set to true, in which case it will always override it.
AssociateID(IDCollectionKey key,const ActiveGroupId & group_identifier,const VariationID id,const bool force)31 void AssociateID(IDCollectionKey key,
32 const ActiveGroupId& group_identifier,
33 const VariationID id,
34 const bool force) {
35 #if !defined(NDEBUG)
36 // Validate that all collections with this |group_identifier| have the same
37 // associated ID.
38 DCHECK_EQ(2, ID_COLLECTION_COUNT);
39 IDCollectionKey other_key = GOOGLE_WEB_PROPERTIES;
40 if (key == GOOGLE_WEB_PROPERTIES)
41 other_key = GOOGLE_UPDATE_SERVICE;
42 VariationID other_id = GetID(other_key, group_identifier);
43 DCHECK(other_id == EMPTY_ID || other_id == id);
44 #endif
45
46 base::AutoLock scoped_lock(lock_);
47
48 GroupToIDMap* group_to_id_map = GetGroupToIDMap(key);
49 if (force ||
50 group_to_id_map->find(group_identifier) == group_to_id_map->end())
51 (*group_to_id_map)[group_identifier] = id;
52 }
53
GetID(IDCollectionKey key,const ActiveGroupId & group_identifier)54 VariationID GetID(IDCollectionKey key,
55 const ActiveGroupId& group_identifier) {
56 base::AutoLock scoped_lock(lock_);
57 GroupToIDMap* group_to_id_map = GetGroupToIDMap(key);
58 GroupToIDMap::const_iterator it = group_to_id_map->find(group_identifier);
59 if (it == group_to_id_map->end())
60 return EMPTY_ID;
61 return it->second;
62 }
63
ClearAllMapsForTesting()64 void ClearAllMapsForTesting() {
65 base::AutoLock scoped_lock(lock_);
66
67 for (int i = 0; i < ID_COLLECTION_COUNT; ++i) {
68 GroupToIDMap* map = GetGroupToIDMap(static_cast<IDCollectionKey>(i));
69 DCHECK(map);
70 map->clear();
71 }
72 }
73
74 private:
75 friend struct DefaultSingletonTraits<GroupMapAccessor>;
76
77 // Retrieves the GroupToIDMap for |key|.
GetGroupToIDMap(IDCollectionKey key)78 GroupToIDMap* GetGroupToIDMap(IDCollectionKey key) {
79 return &group_to_id_maps_[key];
80 }
81
GroupMapAccessor()82 GroupMapAccessor() {
83 group_to_id_maps_.resize(ID_COLLECTION_COUNT);
84 }
~GroupMapAccessor()85 ~GroupMapAccessor() {}
86
87 base::Lock lock_;
88 std::vector<GroupToIDMap> group_to_id_maps_;
89
90 DISALLOW_COPY_AND_ASSIGN(GroupMapAccessor);
91 };
92
93 // Singleton helper class that keeps track of the parameters of all variations
94 // and ensures access to these is thread-safe.
95 class VariationsParamAssociator {
96 public:
97 typedef std::pair<std::string, std::string> VariationKey;
98 typedef std::map<std::string, std::string> VariationParams;
99
100 // Retrieve the singleton.
GetInstance()101 static VariationsParamAssociator* GetInstance() {
102 return Singleton<VariationsParamAssociator>::get();
103 }
104
AssociateVariationParams(const std::string & trial_name,const std::string & group_name,const VariationParams & params)105 bool AssociateVariationParams(const std::string& trial_name,
106 const std::string& group_name,
107 const VariationParams& params) {
108 base::AutoLock scoped_lock(lock_);
109
110 if (IsFieldTrialActive(trial_name))
111 return false;
112
113 const VariationKey key(trial_name, group_name);
114 if (ContainsKey(variation_params_, key))
115 return false;
116
117 variation_params_[key] = params;
118 return true;
119 }
120
GetVariationParams(const std::string & trial_name,VariationParams * params)121 bool GetVariationParams(const std::string& trial_name,
122 VariationParams* params) {
123 base::AutoLock scoped_lock(lock_);
124
125 const std::string group_name =
126 base::FieldTrialList::FindFullName(trial_name);
127 const VariationKey key(trial_name, group_name);
128 if (!ContainsKey(variation_params_, key))
129 return false;
130
131 *params = variation_params_[key];
132 return true;
133 }
134
ClearAllParamsForTesting()135 void ClearAllParamsForTesting() {
136 base::AutoLock scoped_lock(lock_);
137 variation_params_.clear();
138 }
139
140 private:
141 friend struct DefaultSingletonTraits<VariationsParamAssociator>;
142
VariationsParamAssociator()143 VariationsParamAssociator() {}
~VariationsParamAssociator()144 ~VariationsParamAssociator() {}
145
146 // Tests whether a field trial is active (i.e. group() has been called on it).
147 // TODO(asvitkine): Expose this as an API on base::FieldTrial.
IsFieldTrialActive(const std::string & trial_name)148 bool IsFieldTrialActive(const std::string& trial_name) {
149 base::FieldTrial::ActiveGroups active_groups;
150 base::FieldTrialList::GetActiveFieldTrialGroups(&active_groups);
151 for (size_t i = 0; i < active_groups.size(); ++i) {
152 if (active_groups[i].trial_name == trial_name)
153 return true;
154 }
155 return false;
156 }
157
158 base::Lock lock_;
159 std::map<VariationKey, VariationParams> variation_params_;
160
161 DISALLOW_COPY_AND_ASSIGN(VariationsParamAssociator);
162 };
163
164 } // namespace
165
MakeActiveGroupId(const std::string & trial_name,const std::string & group_name)166 ActiveGroupId MakeActiveGroupId(const std::string& trial_name,
167 const std::string& group_name) {
168 ActiveGroupId id;
169 id.name = metrics::HashName(trial_name);
170 id.group = metrics::HashName(group_name);
171 return id;
172 }
173
AssociateGoogleVariationID(IDCollectionKey key,const std::string & trial_name,const std::string & group_name,VariationID id)174 void AssociateGoogleVariationID(IDCollectionKey key,
175 const std::string& trial_name,
176 const std::string& group_name,
177 VariationID id) {
178 GroupMapAccessor::GetInstance()->AssociateID(
179 key, MakeActiveGroupId(trial_name, group_name), id, false);
180 }
181
AssociateGoogleVariationIDForce(IDCollectionKey key,const std::string & trial_name,const std::string & group_name,VariationID id)182 void AssociateGoogleVariationIDForce(IDCollectionKey key,
183 const std::string& trial_name,
184 const std::string& group_name,
185 VariationID id) {
186 GroupMapAccessor::GetInstance()->AssociateID(
187 key, MakeActiveGroupId(trial_name, group_name), id, true);
188 }
189
GetGoogleVariationID(IDCollectionKey key,const std::string & trial_name,const std::string & group_name)190 VariationID GetGoogleVariationID(IDCollectionKey key,
191 const std::string& trial_name,
192 const std::string& group_name) {
193 return GroupMapAccessor::GetInstance()->GetID(
194 key, MakeActiveGroupId(trial_name, group_name));
195 }
196
AssociateVariationParams(const std::string & trial_name,const std::string & group_name,const std::map<std::string,std::string> & params)197 bool AssociateVariationParams(
198 const std::string& trial_name,
199 const std::string& group_name,
200 const std::map<std::string, std::string>& params) {
201 return VariationsParamAssociator::GetInstance()->AssociateVariationParams(
202 trial_name, group_name, params);
203 }
204
GetVariationParams(const std::string & trial_name,std::map<std::string,std::string> * params)205 bool GetVariationParams(const std::string& trial_name,
206 std::map<std::string, std::string>* params) {
207 return VariationsParamAssociator::GetInstance()->GetVariationParams(
208 trial_name, params);
209 }
210
GetVariationParamValue(const std::string & trial_name,const std::string & param_name)211 std::string GetVariationParamValue(const std::string& trial_name,
212 const std::string& param_name) {
213 std::map<std::string, std::string> params;
214 if (GetVariationParams(trial_name, ¶ms)) {
215 std::map<std::string, std::string>::iterator it = params.find(param_name);
216 if (it != params.end())
217 return it->second;
218 }
219 return std::string();
220 }
221
222 // Functions below are exposed for testing explicitly behind this namespace.
223 // They simply wrap existing functions in this file.
224 namespace testing {
225
ClearAllVariationIDs()226 void ClearAllVariationIDs() {
227 GroupMapAccessor::GetInstance()->ClearAllMapsForTesting();
228 }
229
ClearAllVariationParams()230 void ClearAllVariationParams() {
231 VariationsParamAssociator::GetInstance()->ClearAllParamsForTesting();
232 }
233
234 } // namespace testing
235
236 } // namespace chrome_variations
237