| 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 |