OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 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 "components/variations/variations_http_header_provider.h" | 5 #include "components/variations/variations_http_header_provider.h" |
6 | 6 |
7 #include <stddef.h> | 7 #include <stddef.h> |
8 | 8 |
9 #include <set> | 9 #include <set> |
10 #include <string> | 10 #include <string> |
11 #include <vector> | 11 #include <vector> |
12 | 12 |
13 #include "base/base64.h" | 13 #include "base/base64.h" |
14 #include "base/memory/singleton.h" | 14 #include "base/memory/singleton.h" |
15 #include "base/metrics/histogram_macros.h" | 15 #include "base/metrics/histogram_macros.h" |
16 #include "base/strings/string_number_conversions.h" | 16 #include "base/strings/string_number_conversions.h" |
17 #include "base/strings/string_split.h" | 17 #include "base/strings/string_split.h" |
18 #include "base/strings/string_util.h" | 18 #include "base/strings/string_util.h" |
19 #include "base/threading/thread_task_runner_handle.h" | 19 #include "base/threading/thread_task_runner_handle.h" |
20 #include "components/variations/proto/client_variations.pb.h" | 20 #include "components/variations/proto/client_variations.pb.h" |
21 | 21 |
22 namespace variations { | 22 namespace variations { |
23 | 23 |
24 // static | 24 // static |
25 VariationsHttpHeaderProvider* VariationsHttpHeaderProvider::GetInstance() { | 25 VariationsHttpHeaderProvider* VariationsHttpHeaderProvider::GetInstance() { |
26 return base::Singleton<VariationsHttpHeaderProvider>::get(); | 26 return base::Singleton<VariationsHttpHeaderProvider>::get(); |
27 } | 27 } |
28 | 28 |
29 std::string VariationsHttpHeaderProvider::GetClientDataHeader( | 29 std::string VariationsHttpHeaderProvider::GetClientDataHeader() { |
30 bool is_signed_in) { | |
31 // Lazily initialize the header, if not already done, before attempting to | 30 // Lazily initialize the header, if not already done, before attempting to |
32 // transmit it. | 31 // transmit it. |
33 InitVariationIDsCacheIfNeeded(); | 32 InitVariationIDsCacheIfNeeded(); |
34 | 33 |
35 std::string variation_ids_header_copy; | 34 std::string variation_ids_header_copy; |
36 { | 35 { |
37 base::AutoLock scoped_lock(lock_); | 36 base::AutoLock scoped_lock(lock_); |
38 variation_ids_header_copy = is_signed_in | 37 variation_ids_header_copy = variation_ids_header_; |
39 ? cached_variation_ids_header_signed_in_ | |
40 : cached_variation_ids_header_; | |
41 } | 38 } |
42 return variation_ids_header_copy; | 39 return variation_ids_header_copy; |
43 } | 40 } |
44 | 41 |
45 std::string VariationsHttpHeaderProvider::GetVariationsString() { | 42 std::string VariationsHttpHeaderProvider::GetVariationsString() { |
46 InitVariationIDsCacheIfNeeded(); | 43 InitVariationIDsCacheIfNeeded(); |
47 | 44 |
48 // Construct a space-separated string with leading and trailing spaces from | 45 // Construct a space-separated string with leading and trailing spaces from |
49 // the variations set. Note: The ids in it will be in sorted order per the | 46 // the variations set. Note: The ids in it will be in sorted order per the |
50 // std::set contract. | 47 // std::set contract. |
51 std::string ids_string = " "; | 48 std::string ids_string = " "; |
52 { | 49 { |
53 base::AutoLock scoped_lock(lock_); | 50 base::AutoLock scoped_lock(lock_); |
54 for (const VariationIDEntry& entry : GetAllVariationIds()) { | 51 for (VariationID id : GetAllVariationIds()) { |
55 if (entry.second == GOOGLE_WEB_PROPERTIES) { | 52 ids_string.append(base::IntToString(id)); |
56 ids_string.append(base::IntToString(entry.first)); | 53 ids_string.push_back(' '); |
57 ids_string.push_back(' '); | |
58 } | |
59 } | 54 } |
60 } | 55 } |
61 return ids_string; | 56 return ids_string; |
62 } | 57 } |
63 | 58 |
64 bool VariationsHttpHeaderProvider::ForceVariationIds( | 59 bool VariationsHttpHeaderProvider::ForceVariationIds( |
65 const std::string& command_line_variation_ids, | 60 const std::string& command_line_variation_ids, |
66 std::vector<std::string>* variation_ids) { | 61 std::vector<std::string>* variation_ids) { |
67 if (!command_line_variation_ids.empty()) { | 62 if (!command_line_variation_ids.empty()) { |
68 // Combine |variation_ids| with |command_line_variation_ids|. | 63 // Combine |variation_ids| with |command_line_variation_ids|. |
69 std::vector<std::string> variation_ids_flags = | 64 std::vector<std::string> variation_ids_flags = |
70 base::SplitString(command_line_variation_ids, ",", | 65 base::SplitString(command_line_variation_ids, ",", |
71 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); | 66 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
72 variation_ids->insert(variation_ids->end(), variation_ids_flags.begin(), | 67 variation_ids->insert(variation_ids->end(), variation_ids_flags.begin(), |
73 variation_ids_flags.end()); | 68 variation_ids_flags.end()); |
74 } | 69 } |
75 | 70 |
76 if (!variation_ids->empty()) { | 71 if (!variation_ids->empty()) { |
77 // Create default variation ids which will always be included in the | 72 // Create default variation ids which will always be included in the |
78 // X-Client-Data request header. | 73 // X-Client-Data request header. |
79 return SetDefaultVariationIds(*variation_ids); | 74 return SetDefaultVariationIds(*variation_ids); |
80 } | 75 } |
81 return true; | 76 return true; |
82 } | 77 } |
83 | 78 |
| 79 |
84 bool VariationsHttpHeaderProvider::SetDefaultVariationIds( | 80 bool VariationsHttpHeaderProvider::SetDefaultVariationIds( |
85 const std::vector<std::string>& variation_ids) { | 81 const std::vector<std::string>& variation_ids) { |
86 default_variation_ids_set_.clear(); | 82 default_variation_ids_set_.clear(); |
| 83 default_trigger_id_set_.clear(); |
87 for (const std::string& entry : variation_ids) { | 84 for (const std::string& entry : variation_ids) { |
88 if (entry.empty()) { | 85 if (entry.empty()) { |
89 default_variation_ids_set_.clear(); | 86 default_variation_ids_set_.clear(); |
| 87 default_trigger_id_set_.clear(); |
90 return false; | 88 return false; |
91 } | 89 } |
92 bool trigger_id = | 90 bool trigger_id = |
93 base::StartsWith(entry, "t", base::CompareCase::SENSITIVE); | 91 base::StartsWith(entry, "t", base::CompareCase::SENSITIVE); |
94 // Remove the "t" prefix if it's there. | 92 // Remove the "t" prefix if it's there. |
95 std::string trimmed_entry = trigger_id ? entry.substr(1) : entry; | 93 std::string trimmed_entry = trigger_id ? entry.substr(1) : entry; |
96 | 94 |
97 int variation_id = 0; | 95 int variation_id = 0; |
98 if (!base::StringToInt(trimmed_entry, &variation_id)) { | 96 if (!base::StringToInt(trimmed_entry, &variation_id)) { |
99 default_variation_ids_set_.clear(); | 97 default_variation_ids_set_.clear(); |
| 98 default_trigger_id_set_.clear(); |
100 return false; | 99 return false; |
101 } | 100 } |
102 default_variation_ids_set_.insert(VariationIDEntry( | 101 if (trigger_id) |
103 variation_id, | 102 default_trigger_id_set_.insert(variation_id); |
104 trigger_id ? GOOGLE_WEB_PROPERTIES_TRIGGER : GOOGLE_WEB_PROPERTIES)); | 103 else |
| 104 default_variation_ids_set_.insert(variation_id); |
105 } | 105 } |
106 return true; | 106 return true; |
107 } | 107 } |
108 | 108 |
109 void VariationsHttpHeaderProvider::ResetForTesting() { | 109 void VariationsHttpHeaderProvider::ResetForTesting() { |
110 base::AutoLock scoped_lock(lock_); | 110 base::AutoLock scoped_lock(lock_); |
111 | 111 |
112 // Stop observing field trials so that it can be restarted when this is | 112 // Stop observing field trials so that it can be restarted when this is |
113 // re-inited. Note: This is a no-op if this is not currently observing. | 113 // re-inited. Note: This is a no-op if this is not currently observing. |
114 base::FieldTrialList::RemoveObserver(this); | 114 base::FieldTrialList::RemoveObserver(this); |
115 variation_ids_cache_initialized_ = false; | 115 variation_ids_cache_initialized_ = false; |
116 } | 116 } |
117 | 117 |
118 VariationsHttpHeaderProvider::VariationsHttpHeaderProvider() | 118 VariationsHttpHeaderProvider::VariationsHttpHeaderProvider() |
119 : variation_ids_cache_initialized_(false) {} | 119 : variation_ids_cache_initialized_(false) {} |
120 | 120 |
121 VariationsHttpHeaderProvider::~VariationsHttpHeaderProvider() {} | 121 VariationsHttpHeaderProvider::~VariationsHttpHeaderProvider() {} |
122 | 122 |
123 void VariationsHttpHeaderProvider::OnFieldTrialGroupFinalized( | 123 void VariationsHttpHeaderProvider::OnFieldTrialGroupFinalized( |
124 const std::string& trial_name, | 124 const std::string& trial_name, |
125 const std::string& group_name) { | 125 const std::string& group_name) { |
| 126 VariationID new_id = |
| 127 GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, trial_name, group_name); |
| 128 VariationID new_trigger_id = GetGoogleVariationID( |
| 129 GOOGLE_WEB_PROPERTIES_TRIGGER, trial_name, group_name); |
| 130 if (new_id == EMPTY_ID && new_trigger_id == EMPTY_ID) |
| 131 return; |
| 132 |
126 base::AutoLock scoped_lock(lock_); | 133 base::AutoLock scoped_lock(lock_); |
127 const size_t old_size = variation_ids_set_.size(); | 134 if (new_id != EMPTY_ID) |
128 CacheVariationsId(trial_name, group_name, GOOGLE_WEB_PROPERTIES); | 135 variation_ids_set_.insert(new_id); |
129 CacheVariationsId(trial_name, group_name, GOOGLE_WEB_PROPERTIES_SIGNED_IN); | 136 if (new_trigger_id != EMPTY_ID) |
130 CacheVariationsId(trial_name, group_name, GOOGLE_WEB_PROPERTIES_TRIGGER); | 137 variation_trigger_ids_set_.insert(new_trigger_id); |
131 if (variation_ids_set_.size() != old_size) | 138 |
132 UpdateVariationIDsHeaderValue(); | 139 UpdateVariationIDsHeaderValue(); |
133 } | 140 } |
134 | 141 |
135 void VariationsHttpHeaderProvider::OnSyntheticTrialsChanged( | 142 void VariationsHttpHeaderProvider::OnSyntheticTrialsChanged( |
136 const std::vector<SyntheticTrialGroup>& groups) { | 143 const std::vector<SyntheticTrialGroup>& groups) { |
137 base::AutoLock scoped_lock(lock_); | 144 base::AutoLock scoped_lock(lock_); |
138 | 145 |
139 synthetic_variation_ids_set_.clear(); | 146 synthetic_variation_ids_set_.clear(); |
140 for (const SyntheticTrialGroup& group : groups) { | 147 for (const SyntheticTrialGroup& group : groups) { |
141 VariationID id = | 148 const VariationID id = |
142 GetGoogleVariationIDFromHashes(GOOGLE_WEB_PROPERTIES, group.id); | 149 GetGoogleVariationIDFromHashes(GOOGLE_WEB_PROPERTIES, group.id); |
143 if (id != EMPTY_ID) { | 150 if (id != EMPTY_ID) |
144 synthetic_variation_ids_set_.insert( | 151 synthetic_variation_ids_set_.insert(id); |
145 VariationIDEntry(id, GOOGLE_WEB_PROPERTIES)); | |
146 } | |
147 id = GetGoogleVariationIDFromHashes(GOOGLE_WEB_PROPERTIES_SIGNED_IN, | |
148 group.id); | |
149 if (id != EMPTY_ID) { | |
150 synthetic_variation_ids_set_.insert( | |
151 VariationIDEntry(id, GOOGLE_WEB_PROPERTIES_SIGNED_IN)); | |
152 } | |
153 } | 152 } |
154 UpdateVariationIDsHeaderValue(); | 153 UpdateVariationIDsHeaderValue(); |
155 } | 154 } |
156 | 155 |
157 void VariationsHttpHeaderProvider::InitVariationIDsCacheIfNeeded() { | 156 void VariationsHttpHeaderProvider::InitVariationIDsCacheIfNeeded() { |
158 base::AutoLock scoped_lock(lock_); | 157 base::AutoLock scoped_lock(lock_); |
159 if (variation_ids_cache_initialized_) | 158 if (variation_ids_cache_initialized_) |
160 return; | 159 return; |
161 | 160 |
162 // Register for additional cache updates. This is done first to avoid a race | 161 // Register for additional cache updates. This is done first to avoid a race |
163 // that could cause registered FieldTrials to be missed. | 162 // that could cause registered FieldTrials to be missed. |
164 DCHECK(base::ThreadTaskRunnerHandle::IsSet()); | 163 DCHECK(base::ThreadTaskRunnerHandle::IsSet()); |
165 base::FieldTrialList::AddObserver(this); | 164 base::FieldTrialList::AddObserver(this); |
166 | 165 |
167 base::TimeTicks before_time = base::TimeTicks::Now(); | 166 base::TimeTicks before_time = base::TimeTicks::Now(); |
168 | 167 |
169 base::FieldTrial::ActiveGroups initial_groups; | 168 base::FieldTrial::ActiveGroups initial_groups; |
170 base::FieldTrialList::GetActiveFieldTrialGroups(&initial_groups); | 169 base::FieldTrialList::GetActiveFieldTrialGroups(&initial_groups); |
171 | 170 |
172 for (const auto& entry : initial_groups) { | 171 for (const auto& entry : initial_groups) { |
173 CacheVariationsId(entry.trial_name, entry.group_name, | 172 const VariationID id = |
174 GOOGLE_WEB_PROPERTIES); | 173 GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, entry.trial_name, |
175 CacheVariationsId(entry.trial_name, entry.group_name, | 174 entry.group_name); |
176 GOOGLE_WEB_PROPERTIES_SIGNED_IN); | 175 if (id != EMPTY_ID) |
177 CacheVariationsId(entry.trial_name, entry.group_name, | 176 variation_ids_set_.insert(id); |
178 GOOGLE_WEB_PROPERTIES_TRIGGER); | 177 |
| 178 const VariationID trigger_id = |
| 179 GetGoogleVariationID(GOOGLE_WEB_PROPERTIES_TRIGGER, entry.trial_name, |
| 180 entry.group_name); |
| 181 |
| 182 if (trigger_id != EMPTY_ID) |
| 183 variation_trigger_ids_set_.insert(trigger_id); |
179 } | 184 } |
180 UpdateVariationIDsHeaderValue(); | 185 UpdateVariationIDsHeaderValue(); |
181 | 186 |
182 UMA_HISTOGRAM_CUSTOM_COUNTS( | 187 UMA_HISTOGRAM_CUSTOM_COUNTS( |
183 "Variations.HeaderConstructionTime", | 188 "Variations.HeaderConstructionTime", |
184 (base::TimeTicks::Now() - before_time).InMicroseconds(), 1, | 189 (base::TimeTicks::Now() - before_time).InMicroseconds(), 1, |
185 base::TimeDelta::FromSeconds(1).InMicroseconds(), 50); | 190 base::TimeDelta::FromSeconds(1).InMicroseconds(), 50); |
186 | 191 |
187 variation_ids_cache_initialized_ = true; | 192 variation_ids_cache_initialized_ = true; |
188 } | 193 } |
189 | 194 |
190 void VariationsHttpHeaderProvider::CacheVariationsId( | |
191 const std::string& trial_name, | |
192 const std::string& group_name, | |
193 IDCollectionKey key) { | |
194 const VariationID id = GetGoogleVariationID(key, trial_name, group_name); | |
195 if (id != EMPTY_ID) | |
196 variation_ids_set_.insert(VariationIDEntry(id, key)); | |
197 } | |
198 | |
199 void VariationsHttpHeaderProvider::UpdateVariationIDsHeaderValue() { | 195 void VariationsHttpHeaderProvider::UpdateVariationIDsHeaderValue() { |
200 lock_.AssertAcquired(); | 196 lock_.AssertAcquired(); |
201 | 197 |
202 // The header value is a serialized protobuffer of Variation IDs which is | 198 // The header value is a serialized protobuffer of Variation IDs which is |
203 // base64 encoded before transmitting as a string. | 199 // base64 encoded before transmitting as a string. |
204 cached_variation_ids_header_.clear(); | 200 variation_ids_header_.clear(); |
205 cached_variation_ids_header_signed_in_.clear(); | |
206 | 201 |
207 // If successful, swap the header value with the new one. | 202 if (variation_ids_set_.empty() && default_variation_ids_set_.empty() && |
208 // Note that the list of IDs and the header could be temporarily out of sync | 203 variation_trigger_ids_set_.empty() && default_trigger_id_set_.empty() && |
209 // if IDs are added as the header is recreated. The receiving servers are OK | 204 synthetic_variation_ids_set_.empty()) { |
210 // with such discrepancies. | 205 return; |
211 cached_variation_ids_header_ = GenerateBase64EncodedProto(false); | |
212 cached_variation_ids_header_signed_in_ = GenerateBase64EncodedProto(true); | |
213 } | |
214 | |
215 std::string VariationsHttpHeaderProvider::GenerateBase64EncodedProto( | |
216 bool is_signed_in) { | |
217 std::set<VariationIDEntry> all_variation_ids_set = GetAllVariationIds(); | |
218 | |
219 ClientVariations proto; | |
220 for (const VariationIDEntry& entry : all_variation_ids_set) { | |
221 switch (entry.second) { | |
222 case GOOGLE_WEB_PROPERTIES_SIGNED_IN: | |
223 if (is_signed_in) | |
224 proto.add_variation_id(entry.first); | |
225 break; | |
226 case GOOGLE_WEB_PROPERTIES: | |
227 proto.add_variation_id(entry.first); | |
228 break; | |
229 case GOOGLE_WEB_PROPERTIES_TRIGGER: | |
230 proto.add_trigger_variation_id(entry.first); | |
231 break; | |
232 case CHROME_SYNC_SERVICE: | |
233 case ID_COLLECTION_COUNT: | |
234 // These cases included to get full enum coverage for switch, so that | |
235 // new enums introduce compiler warnings. Nothing to do for these. | |
236 break; | |
237 } | |
238 } | 206 } |
239 | 207 |
240 const size_t total_id_count = | |
241 proto.variation_id_size() + proto.trigger_variation_id_size(); | |
242 | |
243 if (total_id_count == 0) | |
244 return std::string(); | |
245 | |
246 // This is the bottleneck for the creation of the header, so validate the size | 208 // This is the bottleneck for the creation of the header, so validate the size |
247 // here. Force a hard maximum on the ID count in case the Variations server | 209 // here. Force a hard maximum on the ID count in case the Variations server |
248 // returns too many IDs and DOSs receiving servers with large requests. | 210 // returns too many IDs and DOSs receiving servers with large requests. |
| 211 const size_t total_id_count = |
| 212 variation_ids_set_.size() + variation_trigger_ids_set_.size(); |
249 DCHECK_LE(total_id_count, 10U); | 213 DCHECK_LE(total_id_count, 10U); |
250 UMA_HISTOGRAM_COUNTS_100("Variations.Headers.ExperimentCount", | 214 UMA_HISTOGRAM_COUNTS_100("Variations.Headers.ExperimentCount", |
251 total_id_count); | 215 total_id_count); |
252 if (total_id_count > 20) | 216 if (total_id_count > 20) |
253 return std::string(); | 217 return; |
| 218 |
| 219 std::set<VariationID> all_variation_ids_set = GetAllVariationIds(); |
| 220 std::set<VariationID> all_trigger_ids_set = default_trigger_id_set_; |
| 221 for (VariationID id : variation_trigger_ids_set_) |
| 222 all_trigger_ids_set.insert(id); |
| 223 |
| 224 ClientVariations proto; |
| 225 for (VariationID id : all_variation_ids_set) |
| 226 proto.add_variation_id(id); |
| 227 for (VariationID id : all_trigger_ids_set) |
| 228 proto.add_trigger_variation_id(id); |
254 | 229 |
255 std::string serialized; | 230 std::string serialized; |
256 proto.SerializeToString(&serialized); | 231 proto.SerializeToString(&serialized); |
257 | 232 |
258 std::string hashed; | 233 std::string hashed; |
259 base::Base64Encode(serialized, &hashed); | 234 base::Base64Encode(serialized, &hashed); |
260 return hashed; | 235 // If successful, swap the header value with the new one. |
| 236 // Note that the list of IDs and the header could be temporarily out of sync |
| 237 // if IDs are added as the header is recreated. The receiving servers are OK |
| 238 // with such discrepancies. |
| 239 variation_ids_header_ = hashed; |
261 } | 240 } |
262 | 241 |
263 std::set<VariationsHttpHeaderProvider::VariationIDEntry> | 242 std::set<VariationID> VariationsHttpHeaderProvider::GetAllVariationIds() { |
264 VariationsHttpHeaderProvider::GetAllVariationIds() { | |
265 lock_.AssertAcquired(); | 243 lock_.AssertAcquired(); |
266 | 244 |
267 std::set<VariationIDEntry> all_variation_ids_set = default_variation_ids_set_; | 245 std::set<VariationID> all_variation_ids_set = default_variation_ids_set_; |
268 for (const VariationIDEntry& entry : variation_ids_set_) { | 246 for (VariationID id : variation_ids_set_) |
269 all_variation_ids_set.insert(entry); | 247 all_variation_ids_set.insert(id); |
270 } | 248 for (VariationID id : synthetic_variation_ids_set_) |
271 for (const VariationIDEntry& entry : synthetic_variation_ids_set_) { | 249 all_variation_ids_set.insert(id); |
272 all_variation_ids_set.insert(entry); | |
273 } | |
274 return all_variation_ids_set; | 250 return all_variation_ids_set; |
275 } | 251 } |
276 | 252 |
277 } // namespace variations | 253 } // namespace variations |
OLD | NEW |