diff options
Diffstat (limited to 'components/variations/variations_http_header_provider.cc')
-rw-r--r-- | components/variations/variations_http_header_provider.cc | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/components/variations/variations_http_header_provider.cc b/components/variations/variations_http_header_provider.cc new file mode 100644 index 0000000..316fe52 --- /dev/null +++ b/components/variations/variations_http_header_provider.cc @@ -0,0 +1,206 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/variations/variations_http_header_provider.h" + +#include <set> +#include <string> +#include <vector> + +#include "base/base64.h" +#include "base/memory/singleton.h" +#include "base/metrics/histogram_macros.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "components/variations/proto/client_variations.pb.h" + +namespace variations { + +// static +VariationsHttpHeaderProvider* VariationsHttpHeaderProvider::GetInstance() { + return base::Singleton<VariationsHttpHeaderProvider>::get(); +} + +std::string VariationsHttpHeaderProvider::GetClientDataHeader() { + // Lazily initialize the header, if not already done, before attempting to + // transmit it. + InitVariationIDsCacheIfNeeded(); + + std::string variation_ids_header_copy; + { + base::AutoLock scoped_lock(lock_); + variation_ids_header_copy = variation_ids_header_; + } + return variation_ids_header_copy; +} + +bool VariationsHttpHeaderProvider::SetDefaultVariationIds( + const std::string& variation_ids) { + default_variation_ids_set_.clear(); + default_trigger_id_set_.clear(); + for (const base::StringPiece& entry : base::SplitStringPiece( + variation_ids, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) { + if (entry.empty()) { + default_variation_ids_set_.clear(); + default_trigger_id_set_.clear(); + return false; + } + bool trigger_id = + base::StartsWith(entry, "t", base::CompareCase::SENSITIVE); + // Remove the "t" prefix if it's there. + base::StringPiece trimmed_entry = trigger_id ? entry.substr(1) : entry; + + int variation_id = 0; + if (!base::StringToInt(trimmed_entry, &variation_id)) { + default_variation_ids_set_.clear(); + default_trigger_id_set_.clear(); + return false; + } + if (trigger_id) + default_trigger_id_set_.insert(variation_id); + else + default_variation_ids_set_.insert(variation_id); + } + return true; +} + +void VariationsHttpHeaderProvider::ResetForTesting() { + base::AutoLock scoped_lock(lock_); + + // Stop observing field trials so that it can be restarted when this is + // re-inited. Note: This is a no-op if this is not currently observing. + base::FieldTrialList::RemoveObserver(this); + variation_ids_cache_initialized_ = false; +} + +VariationsHttpHeaderProvider::VariationsHttpHeaderProvider() + : variation_ids_cache_initialized_(false) {} + +VariationsHttpHeaderProvider::~VariationsHttpHeaderProvider() {} + +void VariationsHttpHeaderProvider::OnFieldTrialGroupFinalized( + const std::string& trial_name, + const std::string& group_name) { + VariationID new_id = + GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, trial_name, group_name); + VariationID new_trigger_id = GetGoogleVariationID( + GOOGLE_WEB_PROPERTIES_TRIGGER, trial_name, group_name); + if (new_id == EMPTY_ID && new_trigger_id == EMPTY_ID) + return; + + base::AutoLock scoped_lock(lock_); + if (new_id != EMPTY_ID) + variation_ids_set_.insert(new_id); + if (new_trigger_id != EMPTY_ID) + variation_trigger_ids_set_.insert(new_trigger_id); + + UpdateVariationIDsHeaderValue(); +} + +void VariationsHttpHeaderProvider::OnSyntheticTrialsChanged( + const std::vector<SyntheticTrialGroup>& groups) { + base::AutoLock scoped_lock(lock_); + + synthetic_variation_ids_set_.clear(); + for (const SyntheticTrialGroup& group : groups) { + const VariationID id = + GetGoogleVariationIDFromHashes(GOOGLE_WEB_PROPERTIES, group.id); + if (id != EMPTY_ID) + synthetic_variation_ids_set_.insert(id); + } + UpdateVariationIDsHeaderValue(); +} + +void VariationsHttpHeaderProvider::InitVariationIDsCacheIfNeeded() { + base::AutoLock scoped_lock(lock_); + if (variation_ids_cache_initialized_) + return; + + // Register for additional cache updates. This is done first to avoid a race + // that could cause registered FieldTrials to be missed. + DCHECK(base::MessageLoop::current()); + base::FieldTrialList::AddObserver(this); + + base::TimeTicks before_time = base::TimeTicks::Now(); + + base::FieldTrial::ActiveGroups initial_groups; + base::FieldTrialList::GetActiveFieldTrialGroups(&initial_groups); + for (base::FieldTrial::ActiveGroups::const_iterator it = + initial_groups.begin(); + it != initial_groups.end(); ++it) { + const VariationID id = GetGoogleVariationID(GOOGLE_WEB_PROPERTIES, + it->trial_name, it->group_name); + if (id != EMPTY_ID) + variation_ids_set_.insert(id); + + const VariationID trigger_id = GetGoogleVariationID( + GOOGLE_WEB_PROPERTIES_TRIGGER, it->trial_name, it->group_name); + if (trigger_id != EMPTY_ID) + variation_trigger_ids_set_.insert(trigger_id); + } + UpdateVariationIDsHeaderValue(); + + UMA_HISTOGRAM_CUSTOM_COUNTS( + "Variations.HeaderConstructionTime", + (base::TimeTicks::Now() - before_time).InMicroseconds(), 0, + base::TimeDelta::FromSeconds(1).InMicroseconds(), 50); + + variation_ids_cache_initialized_ = true; +} + +void VariationsHttpHeaderProvider::UpdateVariationIDsHeaderValue() { + lock_.AssertAcquired(); + + // The header value is a serialized protobuffer of Variation IDs which is + // base64 encoded before transmitting as a string. + variation_ids_header_.clear(); + + if (variation_ids_set_.empty() && default_variation_ids_set_.empty() && + variation_trigger_ids_set_.empty() && default_trigger_id_set_.empty() && + synthetic_variation_ids_set_.empty()) { + return; + } + + // This is the bottleneck for the creation of the header, so validate the size + // here. Force a hard maximum on the ID count in case the Variations server + // returns too many IDs and DOSs receiving servers with large requests. + const size_t total_id_count = + variation_ids_set_.size() + variation_trigger_ids_set_.size(); + DCHECK_LE(total_id_count, 10U); + UMA_HISTOGRAM_COUNTS_100("Variations.Headers.ExperimentCount", + total_id_count); + if (total_id_count > 20) + return; + + // Merge the two sets of experiment ids. + std::set<VariationID> all_variation_ids_set = default_variation_ids_set_; + for (VariationID id : variation_ids_set_) + all_variation_ids_set.insert(id); + for (VariationID id : synthetic_variation_ids_set_) + all_variation_ids_set.insert(id); + + std::set<VariationID> all_trigger_ids_set = default_trigger_id_set_; + for (VariationID id : variation_trigger_ids_set_) + all_trigger_ids_set.insert(id); + + ClientVariations proto; + for (VariationID id : all_variation_ids_set) + proto.add_variation_id(id); + for (VariationID id : all_trigger_ids_set) + proto.add_trigger_variation_id(id); + + std::string serialized; + proto.SerializeToString(&serialized); + + std::string hashed; + base::Base64Encode(serialized, &hashed); + // If successful, swap the header value with the new one. + // Note that the list of IDs and the header could be temporarily out of sync + // if IDs are added as the header is recreated. The receiving servers are OK + // with such discrepancies. + variation_ids_header_ = hashed; +} + +} // namespace variations |