Change histogram.h includes to histogram_macros.h in metrics code.
[chromium-blink-merge.git] / components / variations / net / variations_http_header_provider.cc
blobfd7be82a312a6ad5e966eb66d122c4b312ad0809
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
3 // found in the LICENSE file.
5 #include "components/variations/net/variations_http_header_provider.h"
7 #include <set>
8 #include <string>
9 #include <vector>
11 #include "base/base64.h"
12 #include "base/memory/singleton.h"
13 #include "base/metrics/histogram_macros.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/strings/string_split.h"
16 #include "base/strings/string_util.h"
17 #include "components/google/core/browser/google_util.h"
18 #include "components/variations/proto/client_variations.pb.h"
19 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
20 #include "net/http/http_request_headers.h"
21 #include "url/gurl.h"
23 namespace variations {
25 namespace {
27 const char* kSuffixesToSetHeadersFor[] = {
28 ".android.com",
29 ".doubleclick.com",
30 ".doubleclick.net",
31 ".ggpht.com",
32 ".googleadservices.com",
33 ".googleapis.com",
34 ".googlesyndication.com",
35 ".googleusercontent.com",
36 ".googlevideo.com",
37 ".gstatic.com",
38 ".ytimg.com",
41 const char kChromeUMAEnabled[] = "X-Chrome-UMA-Enabled";
42 const char kClientData[] = "X-Client-Data";
44 } // namespace
46 VariationsHttpHeaderProvider* VariationsHttpHeaderProvider::GetInstance() {
47 return Singleton<VariationsHttpHeaderProvider>::get();
50 void VariationsHttpHeaderProvider::AppendHeaders(
51 const GURL& url,
52 bool incognito,
53 bool uma_enabled,
54 net::HttpRequestHeaders* headers) {
55 // Note the criteria for attaching client experiment headers:
56 // 1. We only transmit to Google owned domains which can evaluate experiments.
57 // 1a. These include hosts which have a standard postfix such as:
58 // *.doubleclick.net or *.googlesyndication.com or
59 // exactly www.googleadservices.com or
60 // international TLD domains *.google.<TLD> or *.youtube.<TLD>.
61 // 2. Only transmit for non-Incognito profiles.
62 // 3. For the X-Chrome-UMA-Enabled bit, only set it if UMA is in fact enabled
63 // for this install of Chrome.
64 // 4. For the X-Client-Data header, only include non-empty variation IDs.
65 if (incognito || !ShouldAppendHeaders(url))
66 return;
68 if (uma_enabled)
69 headers->SetHeaderIfMissing(kChromeUMAEnabled, "1");
71 // Lazily initialize the header, if not already done, before attempting to
72 // transmit it.
73 InitVariationIDsCacheIfNeeded();
75 std::string variation_ids_header_copy;
77 base::AutoLock scoped_lock(lock_);
78 variation_ids_header_copy = variation_ids_header_;
81 if (!variation_ids_header_copy.empty()) {
82 // Note that prior to M33 this header was named X-Chrome-Variations.
83 headers->SetHeaderIfMissing(kClientData, variation_ids_header_copy);
87 bool VariationsHttpHeaderProvider::SetDefaultVariationIds(
88 const std::string& variation_ids) {
89 default_variation_ids_set_.clear();
90 default_trigger_id_set_.clear();
91 std::vector<std::string> entries;
92 base::SplitString(variation_ids, ',', &entries);
93 for (std::vector<std::string>::const_iterator it = entries.begin();
94 it != entries.end(); ++it) {
95 if (it->empty()) {
96 default_variation_ids_set_.clear();
97 default_trigger_id_set_.clear();
98 return false;
100 bool trigger_id = base::StartsWithASCII(*it, "t", true);
101 // Remove the "t" prefix if it's there.
102 std::string entry = trigger_id ? it->substr(1) : *it;
104 int variation_id = 0;
105 if (!base::StringToInt(entry, &variation_id)) {
106 default_variation_ids_set_.clear();
107 default_trigger_id_set_.clear();
108 return false;
110 if (trigger_id)
111 default_trigger_id_set_.insert(variation_id);
112 else
113 default_variation_ids_set_.insert(variation_id);
115 return true;
118 std::set<std::string> VariationsHttpHeaderProvider::GetVariationHeaderNames()
119 const {
120 std::set<std::string> headers;
121 headers.insert(kChromeUMAEnabled);
122 headers.insert(kClientData);
123 return headers;
126 void VariationsHttpHeaderProvider::ResetForTesting() {
127 base::AutoLock scoped_lock(lock_);
129 // Stop observing field trials so that it can be restarted when this is
130 // re-inited. Note: This is a no-op if this is not currently observing.
131 base::FieldTrialList::RemoveObserver(this);
132 variation_ids_cache_initialized_ = false;
135 VariationsHttpHeaderProvider::VariationsHttpHeaderProvider()
136 : variation_ids_cache_initialized_(false) {
139 VariationsHttpHeaderProvider::~VariationsHttpHeaderProvider() {
142 void VariationsHttpHeaderProvider::OnFieldTrialGroupFinalized(
143 const std::string& trial_name,
144 const std::string& group_name) {
145 VariationID new_id =
146 GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, trial_name, group_name);
147 VariationID new_trigger_id = GetGoogleVariationID(
148 GOOGLE_WEB_PROPERTIES_TRIGGER, trial_name, group_name);
149 if (new_id == EMPTY_ID && new_trigger_id == EMPTY_ID)
150 return;
152 base::AutoLock scoped_lock(lock_);
153 if (new_id != EMPTY_ID)
154 variation_ids_set_.insert(new_id);
155 if (new_trigger_id != EMPTY_ID)
156 variation_trigger_ids_set_.insert(new_trigger_id);
158 UpdateVariationIDsHeaderValue();
161 void VariationsHttpHeaderProvider::OnSyntheticTrialsChanged(
162 const std::vector<metrics::SyntheticTrialGroup>& groups) {
163 base::AutoLock scoped_lock(lock_);
165 synthetic_variation_ids_set_.clear();
166 for (const metrics::SyntheticTrialGroup& group : groups) {
167 const VariationID id =
168 GetGoogleVariationIDFromHashes(GOOGLE_WEB_PROPERTIES, group.id);
169 if (id != EMPTY_ID)
170 synthetic_variation_ids_set_.insert(id);
172 UpdateVariationIDsHeaderValue();
175 void VariationsHttpHeaderProvider::InitVariationIDsCacheIfNeeded() {
176 base::AutoLock scoped_lock(lock_);
177 if (variation_ids_cache_initialized_)
178 return;
180 // Register for additional cache updates. This is done first to avoid a race
181 // that could cause registered FieldTrials to be missed.
182 DCHECK(base::MessageLoop::current());
183 base::FieldTrialList::AddObserver(this);
185 base::TimeTicks before_time = base::TimeTicks::Now();
187 base::FieldTrial::ActiveGroups initial_groups;
188 base::FieldTrialList::GetActiveFieldTrialGroups(&initial_groups);
189 for (base::FieldTrial::ActiveGroups::const_iterator it =
190 initial_groups.begin();
191 it != initial_groups.end(); ++it) {
192 const VariationID id =
193 GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, it->trial_name,
194 it->group_name);
195 if (id != EMPTY_ID)
196 variation_ids_set_.insert(id);
198 const VariationID trigger_id =
199 GetGoogleVariationID(GOOGLE_WEB_PROPERTIES_TRIGGER, it->trial_name,
200 it->group_name);
201 if (trigger_id != EMPTY_ID)
202 variation_trigger_ids_set_.insert(trigger_id);
204 UpdateVariationIDsHeaderValue();
206 UMA_HISTOGRAM_CUSTOM_COUNTS(
207 "Variations.HeaderConstructionTime",
208 (base::TimeTicks::Now() - before_time).InMicroseconds(),
210 base::TimeDelta::FromSeconds(1).InMicroseconds(),
211 50);
213 variation_ids_cache_initialized_ = true;
216 void VariationsHttpHeaderProvider::UpdateVariationIDsHeaderValue() {
217 lock_.AssertAcquired();
219 // The header value is a serialized protobuffer of Variation IDs which is
220 // base64 encoded before transmitting as a string.
221 variation_ids_header_.clear();
223 if (variation_ids_set_.empty() && default_variation_ids_set_.empty() &&
224 variation_trigger_ids_set_.empty() && default_trigger_id_set_.empty() &&
225 synthetic_variation_ids_set_.empty()) {
226 return;
229 // This is the bottleneck for the creation of the header, so validate the size
230 // here. Force a hard maximum on the ID count in case the Variations server
231 // returns too many IDs and DOSs receiving servers with large requests.
232 const size_t total_id_count =
233 variation_ids_set_.size() + variation_trigger_ids_set_.size();
234 DCHECK_LE(total_id_count, 10U);
235 UMA_HISTOGRAM_COUNTS_100("Variations.Headers.ExperimentCount",
236 total_id_count);
237 if (total_id_count > 20)
238 return;
240 // Merge the two sets of experiment ids.
241 std::set<VariationID> all_variation_ids_set = default_variation_ids_set_;
242 for (VariationID id : variation_ids_set_)
243 all_variation_ids_set.insert(id);
244 for (VariationID id : synthetic_variation_ids_set_)
245 all_variation_ids_set.insert(id);
247 std::set<VariationID> all_trigger_ids_set = default_trigger_id_set_;
248 for (VariationID id : variation_trigger_ids_set_)
249 all_trigger_ids_set.insert(id);
251 ClientVariations proto;
252 for (VariationID id : all_variation_ids_set)
253 proto.add_variation_id(id);
254 for (VariationID id : all_trigger_ids_set)
255 proto.add_trigger_variation_id(id);
257 std::string serialized;
258 proto.SerializeToString(&serialized);
260 std::string hashed;
261 base::Base64Encode(serialized, &hashed);
262 // If successful, swap the header value with the new one.
263 // Note that the list of IDs and the header could be temporarily out of sync
264 // if IDs are added as the header is recreated. The receiving servers are OK
265 // with such discrepancies.
266 variation_ids_header_ = hashed;
269 // static
270 bool VariationsHttpHeaderProvider::ShouldAppendHeaders(const GURL& url) {
271 if (google_util::IsGoogleDomainUrl(url, google_util::ALLOW_SUBDOMAIN,
272 google_util::ALLOW_NON_STANDARD_PORTS)) {
273 return true;
276 if (!url.is_valid() || !url.SchemeIsHTTPOrHTTPS())
277 return false;
279 // Some domains don't have international TLD extensions, so testing for them
280 // is very straight forward.
281 const std::string host = url.host();
282 for (size_t i = 0; i < arraysize(kSuffixesToSetHeadersFor); ++i) {
283 if (base::EndsWith(host, kSuffixesToSetHeadersFor[i], false))
284 return true;
287 return google_util::IsYoutubeDomainUrl(url, google_util::ALLOW_SUBDOMAIN,
288 google_util::ALLOW_NON_STANDARD_PORTS);
291 } // namespace variations