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