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) { |
30 // Lazily initialize the header, if not already done, before attempting to | 31 // Lazily initialize the header, if not already done, before attempting to |
31 // transmit it. | 32 // transmit it. |
32 InitVariationIDsCacheIfNeeded(); | 33 InitVariationIDsCacheIfNeeded(); |
33 | 34 |
34 std::string variation_ids_header_copy; | 35 std::string variation_ids_header_copy; |
35 { | 36 { |
36 base::AutoLock scoped_lock(lock_); | 37 base::AutoLock scoped_lock(lock_); |
37 variation_ids_header_copy = variation_ids_header_; | 38 variation_ids_header_copy = is_signed_in |
| 39 ? cached_variation_ids_header_signed_in_ |
| 40 : cached_variation_ids_header_; |
38 } | 41 } |
39 return variation_ids_header_copy; | 42 return variation_ids_header_copy; |
40 } | 43 } |
41 | 44 |
42 std::string VariationsHttpHeaderProvider::GetVariationsString() { | 45 std::string VariationsHttpHeaderProvider::GetVariationsString() { |
43 InitVariationIDsCacheIfNeeded(); | 46 InitVariationIDsCacheIfNeeded(); |
44 | 47 |
45 // Construct a space-separated string with leading and trailing spaces from | 48 // Construct a space-separated string with leading and trailing spaces from |
46 // the variations set. Note: The ids in it will be in sorted order per the | 49 // the variations set. Note: The ids in it will be in sorted order per the |
47 // std::set contract. | 50 // std::set contract. |
48 std::string ids_string = " "; | 51 std::string ids_string = " "; |
49 { | 52 { |
50 base::AutoLock scoped_lock(lock_); | 53 base::AutoLock scoped_lock(lock_); |
51 for (VariationID id : GetAllVariationIds()) { | 54 for (const VariationIDEntry& entry : GetAllVariationIds()) { |
52 ids_string.append(base::IntToString(id)); | 55 if (entry.second == GOOGLE_WEB_PROPERTIES) { |
53 ids_string.push_back(' '); | 56 ids_string.append(base::IntToString(entry.first)); |
| 57 ids_string.push_back(' '); |
| 58 } |
54 } | 59 } |
55 } | 60 } |
56 return ids_string; | 61 return ids_string; |
57 } | 62 } |
58 | 63 |
59 bool VariationsHttpHeaderProvider::ForceVariationIds( | 64 bool VariationsHttpHeaderProvider::ForceVariationIds( |
60 const std::string& command_line_variation_ids, | 65 const std::string& command_line_variation_ids, |
61 std::vector<std::string>* variation_ids) { | 66 std::vector<std::string>* variation_ids) { |
62 if (!command_line_variation_ids.empty()) { | 67 if (!command_line_variation_ids.empty()) { |
63 // Combine |variation_ids| with |command_line_variation_ids|. | 68 // Combine |variation_ids| with |command_line_variation_ids|. |
64 std::vector<std::string> variation_ids_flags = | 69 std::vector<std::string> variation_ids_flags = |
65 base::SplitString(command_line_variation_ids, ",", | 70 base::SplitString(command_line_variation_ids, ",", |
66 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); | 71 base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); |
67 variation_ids->insert(variation_ids->end(), variation_ids_flags.begin(), | 72 variation_ids->insert(variation_ids->end(), variation_ids_flags.begin(), |
68 variation_ids_flags.end()); | 73 variation_ids_flags.end()); |
69 } | 74 } |
70 | 75 |
71 if (!variation_ids->empty()) { | 76 if (!variation_ids->empty()) { |
72 // Create default variation ids which will always be included in the | 77 // Create default variation ids which will always be included in the |
73 // X-Client-Data request header. | 78 // X-Client-Data request header. |
74 return SetDefaultVariationIds(*variation_ids); | 79 return SetDefaultVariationIds(*variation_ids); |
75 } | 80 } |
76 return true; | 81 return true; |
77 } | 82 } |
78 | 83 |
79 | |
80 bool VariationsHttpHeaderProvider::SetDefaultVariationIds( | 84 bool VariationsHttpHeaderProvider::SetDefaultVariationIds( |
81 const std::vector<std::string>& variation_ids) { | 85 const std::vector<std::string>& variation_ids) { |
82 default_variation_ids_set_.clear(); | 86 default_variation_ids_set_.clear(); |
83 default_trigger_id_set_.clear(); | |
84 for (const std::string& entry : variation_ids) { | 87 for (const std::string& entry : variation_ids) { |
85 if (entry.empty()) { | 88 if (entry.empty()) { |
86 default_variation_ids_set_.clear(); | 89 default_variation_ids_set_.clear(); |
87 default_trigger_id_set_.clear(); | |
88 return false; | 90 return false; |
89 } | 91 } |
90 bool trigger_id = | 92 bool trigger_id = |
91 base::StartsWith(entry, "t", base::CompareCase::SENSITIVE); | 93 base::StartsWith(entry, "t", base::CompareCase::SENSITIVE); |
92 // Remove the "t" prefix if it's there. | 94 // Remove the "t" prefix if it's there. |
93 std::string trimmed_entry = trigger_id ? entry.substr(1) : entry; | 95 std::string trimmed_entry = trigger_id ? entry.substr(1) : entry; |
94 | 96 |
95 int variation_id = 0; | 97 int variation_id = 0; |
96 if (!base::StringToInt(trimmed_entry, &variation_id)) { | 98 if (!base::StringToInt(trimmed_entry, &variation_id)) { |
97 default_variation_ids_set_.clear(); | 99 default_variation_ids_set_.clear(); |
98 default_trigger_id_set_.clear(); | |
99 return false; | 100 return false; |
100 } | 101 } |
101 if (trigger_id) | 102 default_variation_ids_set_.insert(VariationIDEntry( |
102 default_trigger_id_set_.insert(variation_id); | 103 variation_id, |
103 else | 104 trigger_id ? GOOGLE_WEB_PROPERTIES_TRIGGER : GOOGLE_WEB_PROPERTIES)); |
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 | |
133 base::AutoLock scoped_lock(lock_); | 126 base::AutoLock scoped_lock(lock_); |
134 if (new_id != EMPTY_ID) | 127 const size_t old_size = variation_ids_set_.size(); |
135 variation_ids_set_.insert(new_id); | 128 CacheVariationsId(trial_name, group_name, GOOGLE_WEB_PROPERTIES); |
136 if (new_trigger_id != EMPTY_ID) | 129 CacheVariationsId(trial_name, group_name, GOOGLE_WEB_PROPERTIES_SIGNED_IN); |
137 variation_trigger_ids_set_.insert(new_trigger_id); | 130 CacheVariationsId(trial_name, group_name, GOOGLE_WEB_PROPERTIES_TRIGGER); |
138 | 131 if (variation_ids_set_.size() != old_size) |
139 UpdateVariationIDsHeaderValue(); | 132 UpdateVariationIDsHeaderValue(); |
140 } | 133 } |
141 | 134 |
142 void VariationsHttpHeaderProvider::OnSyntheticTrialsChanged( | 135 void VariationsHttpHeaderProvider::OnSyntheticTrialsChanged( |
143 const std::vector<SyntheticTrialGroup>& groups) { | 136 const std::vector<SyntheticTrialGroup>& groups) { |
144 base::AutoLock scoped_lock(lock_); | 137 base::AutoLock scoped_lock(lock_); |
145 | 138 |
146 synthetic_variation_ids_set_.clear(); | 139 synthetic_variation_ids_set_.clear(); |
147 for (const SyntheticTrialGroup& group : groups) { | 140 for (const SyntheticTrialGroup& group : groups) { |
148 const VariationID id = | 141 VariationID id = |
149 GetGoogleVariationIDFromHashes(GOOGLE_WEB_PROPERTIES, group.id); | 142 GetGoogleVariationIDFromHashes(GOOGLE_WEB_PROPERTIES, group.id); |
150 if (id != EMPTY_ID) | 143 if (id != EMPTY_ID) { |
151 synthetic_variation_ids_set_.insert(id); | 144 synthetic_variation_ids_set_.insert( |
| 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 } |
152 } | 153 } |
153 UpdateVariationIDsHeaderValue(); | 154 UpdateVariationIDsHeaderValue(); |
154 } | 155 } |
155 | 156 |
156 void VariationsHttpHeaderProvider::InitVariationIDsCacheIfNeeded() { | 157 void VariationsHttpHeaderProvider::InitVariationIDsCacheIfNeeded() { |
157 base::AutoLock scoped_lock(lock_); | 158 base::AutoLock scoped_lock(lock_); |
158 if (variation_ids_cache_initialized_) | 159 if (variation_ids_cache_initialized_) |
159 return; | 160 return; |
160 | 161 |
161 // Register for additional cache updates. This is done first to avoid a race | 162 // Register for additional cache updates. This is done first to avoid a race |
162 // that could cause registered FieldTrials to be missed. | 163 // that could cause registered FieldTrials to be missed. |
163 DCHECK(base::ThreadTaskRunnerHandle::IsSet()); | 164 DCHECK(base::ThreadTaskRunnerHandle::IsSet()); |
164 base::FieldTrialList::AddObserver(this); | 165 base::FieldTrialList::AddObserver(this); |
165 | 166 |
166 base::TimeTicks before_time = base::TimeTicks::Now(); | 167 base::TimeTicks before_time = base::TimeTicks::Now(); |
167 | 168 |
168 base::FieldTrial::ActiveGroups initial_groups; | 169 base::FieldTrial::ActiveGroups initial_groups; |
169 base::FieldTrialList::GetActiveFieldTrialGroups(&initial_groups); | 170 base::FieldTrialList::GetActiveFieldTrialGroups(&initial_groups); |
170 | 171 |
171 for (const auto& entry : initial_groups) { | 172 for (const auto& entry : initial_groups) { |
172 const VariationID id = | 173 CacheVariationsId(entry.trial_name, entry.group_name, |
173 GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, entry.trial_name, | 174 GOOGLE_WEB_PROPERTIES); |
174 entry.group_name); | 175 CacheVariationsId(entry.trial_name, entry.group_name, |
175 if (id != EMPTY_ID) | 176 GOOGLE_WEB_PROPERTIES_SIGNED_IN); |
176 variation_ids_set_.insert(id); | 177 CacheVariationsId(entry.trial_name, entry.group_name, |
177 | 178 GOOGLE_WEB_PROPERTIES_TRIGGER); |
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); | |
184 } | 179 } |
185 UpdateVariationIDsHeaderValue(); | 180 UpdateVariationIDsHeaderValue(); |
186 | 181 |
187 UMA_HISTOGRAM_CUSTOM_COUNTS( | 182 UMA_HISTOGRAM_CUSTOM_COUNTS( |
188 "Variations.HeaderConstructionTime", | 183 "Variations.HeaderConstructionTime", |
189 (base::TimeTicks::Now() - before_time).InMicroseconds(), 1, | 184 (base::TimeTicks::Now() - before_time).InMicroseconds(), 1, |
190 base::TimeDelta::FromSeconds(1).InMicroseconds(), 50); | 185 base::TimeDelta::FromSeconds(1).InMicroseconds(), 50); |
191 | 186 |
192 variation_ids_cache_initialized_ = true; | 187 variation_ids_cache_initialized_ = true; |
193 } | 188 } |
194 | 189 |
| 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 |
195 void VariationsHttpHeaderProvider::UpdateVariationIDsHeaderValue() { | 199 void VariationsHttpHeaderProvider::UpdateVariationIDsHeaderValue() { |
196 lock_.AssertAcquired(); | 200 lock_.AssertAcquired(); |
197 | 201 |
198 // The header value is a serialized protobuffer of Variation IDs which is | 202 // The header value is a serialized protobuffer of Variation IDs which is |
199 // base64 encoded before transmitting as a string. | 203 // base64 encoded before transmitting as a string. |
200 variation_ids_header_.clear(); | 204 cached_variation_ids_header_.clear(); |
| 205 cached_variation_ids_header_signed_in_.clear(); |
201 | 206 |
202 if (variation_ids_set_.empty() && default_variation_ids_set_.empty() && | 207 // If successful, swap the header value with the new one. |
203 variation_trigger_ids_set_.empty() && default_trigger_id_set_.empty() && | 208 // Note that the list of IDs and the header could be temporarily out of sync |
204 synthetic_variation_ids_set_.empty()) { | 209 // if IDs are added as the header is recreated. The receiving servers are OK |
205 return; | 210 // with such discrepancies. |
| 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 } |
206 } | 238 } |
207 | 239 |
| 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 |
208 // This is the bottleneck for the creation of the header, so validate the size | 246 // This is the bottleneck for the creation of the header, so validate the size |
209 // here. Force a hard maximum on the ID count in case the Variations server | 247 // here. Force a hard maximum on the ID count in case the Variations server |
210 // returns too many IDs and DOSs receiving servers with large requests. | 248 // 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(); | |
213 DCHECK_LE(total_id_count, 10U); | 249 DCHECK_LE(total_id_count, 10U); |
214 UMA_HISTOGRAM_COUNTS_100("Variations.Headers.ExperimentCount", | 250 UMA_HISTOGRAM_COUNTS_100("Variations.Headers.ExperimentCount", |
215 total_id_count); | 251 total_id_count); |
216 if (total_id_count > 20) | 252 if (total_id_count > 20) |
217 return; | 253 return std::string(); |
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); | |
229 | 254 |
230 std::string serialized; | 255 std::string serialized; |
231 proto.SerializeToString(&serialized); | 256 proto.SerializeToString(&serialized); |
232 | 257 |
233 std::string hashed; | 258 std::string hashed; |
234 base::Base64Encode(serialized, &hashed); | 259 base::Base64Encode(serialized, &hashed); |
235 // If successful, swap the header value with the new one. | 260 return hashed; |
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; | |
240 } | 261 } |
241 | 262 |
242 std::set<VariationID> VariationsHttpHeaderProvider::GetAllVariationIds() { | 263 std::set<VariationsHttpHeaderProvider::VariationIDEntry> |
| 264 VariationsHttpHeaderProvider::GetAllVariationIds() { |
243 lock_.AssertAcquired(); | 265 lock_.AssertAcquired(); |
244 | 266 |
245 std::set<VariationID> all_variation_ids_set = default_variation_ids_set_; | 267 std::set<VariationIDEntry> all_variation_ids_set = default_variation_ids_set_; |
246 for (VariationID id : variation_ids_set_) | 268 for (const VariationIDEntry& entry : variation_ids_set_) { |
247 all_variation_ids_set.insert(id); | 269 all_variation_ids_set.insert(entry); |
248 for (VariationID id : synthetic_variation_ids_set_) | 270 } |
249 all_variation_ids_set.insert(id); | 271 for (const VariationIDEntry& entry : synthetic_variation_ids_set_) { |
| 272 all_variation_ids_set.insert(entry); |
| 273 } |
250 return all_variation_ids_set; | 274 return all_variation_ids_set; |
251 } | 275 } |
252 | 276 |
253 } // namespace variations | 277 } // namespace variations |
OLD | NEW |