diff options
Diffstat (limited to 'chrome/browser/metrics/variations_service.cc')
-rw-r--r-- | chrome/browser/metrics/variations_service.cc | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/chrome/browser/metrics/variations_service.cc b/chrome/browser/metrics/variations_service.cc new file mode 100644 index 0000000..8b9866f --- /dev/null +++ b/chrome/browser/metrics/variations_service.cc @@ -0,0 +1,279 @@ +// Copyright (c) 2012 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 "chrome/browser/metrics/variations_service.h" + +#include "base/base64.h" +#include "base/build_time.h" +#include "base/version.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/field_trial.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/metrics/proto/trials_seed.pb.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/common/pref_names.h" +#include "content/public/common/url_fetcher.h" +#include "googleurl/src/gurl.h" +#include "net/base/load_flags.h" +#include "net/base/network_change_notifier.h" +#include "net/url_request/url_request_status.h" + +namespace { + +// Default server of Variations seed info. +const char kDefaultVariationsServer[] = + "https://clients4.google.com/chrome-variations/seed"; +const int kMaxRetrySeedFetch = 5; + +// Maps chrome_variations::Study_Channel enum values to corresponding +// chrome::VersionInfo::Channel enum values. +chrome::VersionInfo::Channel ConvertStudyChannelToVersionChannel( + chrome_variations::Study_Channel study_channel) { + switch (study_channel) { + case chrome_variations::Study_Channel_CANARY: + return chrome::VersionInfo::CHANNEL_CANARY; + case chrome_variations::Study_Channel_DEV: + return chrome::VersionInfo::CHANNEL_DEV; + case chrome_variations::Study_Channel_BETA: + return chrome::VersionInfo::CHANNEL_BETA; + case chrome_variations::Study_Channel_STABLE: + return chrome::VersionInfo::CHANNEL_STABLE; + } + // All enum values of |study_channel| were handled above. + NOTREACHED(); + return chrome::VersionInfo::CHANNEL_UNKNOWN; +} + +} // namespace + +VariationsService::VariationsService() {} +VariationsService::~VariationsService() {} + +bool VariationsService::CreateTrialsFromSeed(PrefService* local_prefs) { + chrome_variations::TrialsSeed seed; + if (!LoadTrialsSeedFromPref(local_prefs, &seed)) + return false; + + for (int i = 0; i < seed.study_size(); ++i) + CreateTrialFromStudy(seed.study(i)); + + return true; +} + +void VariationsService::StartFetchingVariationsSeed() { + if (net::NetworkChangeNotifier::IsOffline()) + return; + + pending_seed_request_.reset(content::URLFetcher::Create( + GURL(kDefaultVariationsServer), net::URLFetcher::GET, this)); + pending_seed_request_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | + net::LOAD_DO_NOT_SAVE_COOKIES); + pending_seed_request_->SetRequestContext( + g_browser_process->system_request_context()); + pending_seed_request_->SetMaxRetries(kMaxRetrySeedFetch); + pending_seed_request_->Start(); +} + +void VariationsService::OnURLFetchComplete(const net::URLFetcher* source) { + DCHECK_EQ(pending_seed_request_.get(), source); + // When we're done handling the request, the fetcher will be deleted. + scoped_ptr<const content::URLFetcher> request( + pending_seed_request_.release()); + if (request->GetStatus().status() != net::URLRequestStatus::SUCCESS || + request->GetResponseCode() != 200) + return; + + std::string seed_data; + request->GetResponseAsString(&seed_data); + + StoreSeedData(seed_data, g_browser_process->local_state()); +} + +// static +void VariationsService::RegisterPrefs(PrefService* prefs) { + prefs->RegisterStringPref(prefs::kVariationsSeed, std::string()); +} + +void VariationsService::StoreSeedData(const std::string& seed_data, + PrefService* local_prefs) { + // Only store the seed data if it parses correctly. + chrome_variations::TrialsSeed seed; + if (!seed.ParseFromString(seed_data)) { + VLOG(1) << "Variations Seed data from server is not in valid proto format, " + << "rejecting the seed."; + return; + } + std::string base64_seed_data; + if (!base::Base64Encode(seed_data, &base64_seed_data)) { + VLOG(1) << "Variations Seed data from server fails Base64Encode, rejecting " + << "the seed."; + return; + } + local_prefs->SetString(prefs::kVariationsSeed, base64_seed_data); +} + +// static +bool VariationsService::ShouldAddStudy(const chrome_variations::Study& study) { + const chrome::VersionInfo current_version_info; + if (!current_version_info.is_valid()) + return false; + + if (!CheckStudyChannel(study, chrome::VersionInfo::GetChannel())) { + DVLOG(1) << "Filtered out study " << study.name() << " due to version."; + return false; + } + + if (!CheckStudyVersion(study, current_version_info.Version())) { + DVLOG(1) << "Filtered out study " << study.name() << " due to version."; + return false; + } + + // Use build time and not system time to match what is done in field_trial.cc. + if (!CheckStudyDate(study, base::GetBuildTime())) { + DVLOG(1) << "Filtered out study " << study.name() << " due to date."; + return false; + } + + DVLOG(1) << "Kept study " << study.name() << "."; + return true; +} + +// static +bool VariationsService::CheckStudyChannel( + const chrome_variations::Study& study, + chrome::VersionInfo::Channel channel) { + if (study.channel_size() == 0) { + // An empty channel list matches all channels. + return true; + } + + for (int i = 0; i < study.channel_size(); ++i) { + if (ConvertStudyChannelToVersionChannel(study.channel(i)) == channel) + return true; + } + return false; +} + +// static +bool VariationsService::CheckStudyVersion(const chrome_variations::Study& study, + const std::string& version_string) { + const Version current_version(version_string); + if (!current_version.IsValid()) { + DCHECK(false); + return false; + } + + if (study.has_min_version()) { + const Version min_version(study.min_version()); + if (!min_version.IsValid()) + return false; + if (current_version.CompareTo(min_version) < 0) + return false; + } + + if (study.has_max_version()) { + const Version max_version(study.max_version()); + if (!max_version.IsValid()) + return false; + if (current_version.CompareTo(max_version) > 0) + return false; + } + + return true; +} + +// static +bool VariationsService::CheckStudyDate(const chrome_variations::Study& study, + const base::Time& date_time) { + const base::Time epoch = base::Time::UnixEpoch(); + + if (study.has_start_date()) { + const base::Time start_date = + epoch + base::TimeDelta::FromSeconds(study.start_date()); + if (date_time < start_date) + return false; + } + + if (study.has_expiry_date()) { + const base::Time expiry_date = + epoch + base::TimeDelta::FromSeconds(study.expiry_date()); + if (date_time >= expiry_date) + return false; + } + + return true; +} + +bool VariationsService::LoadTrialsSeedFromPref( + PrefService* local_prefs, + chrome_variations::TrialsSeed* seed) { + std::string base64_seed_data = local_prefs->GetString(prefs::kVariationsSeed); + std::string seed_data; + + // If the decode process fails, assume the pref value is corrupt, and clear + // it. + if (!base::Base64Decode(base64_seed_data, &seed_data) || + !seed->ParseFromString(seed_data)) { + VLOG(1) << "Variations Seed data in local pref is corrupt, clearing the " + << "pref."; + local_prefs->ClearPref(prefs::kVariationsSeed); + return false; + } + return true; +} + +void VariationsService::CreateTrialFromStudy( + const chrome_variations::Study& study) { + if (!ShouldAddStudy(study)) + return; + + // At the moment, a missing default_experiment_name makes the study invalid. + if (!study.has_default_experiment_name()) { + DVLOG(1) << study.name() << " has no default experiment defined."; + return; + } + + const std::string& default_group_name = study.default_experiment_name(); + base::FieldTrial::Probability divisor = 0; + + bool found_default_group = false; + for (int i = 0; i < study.experiment_size(); ++i) { + divisor += study.experiment(i).probability_weight(); + if (study.experiment(i).name() == default_group_name) + found_default_group = true; + } + if (!found_default_group) { + DVLOG(1) << study.name() << " is missing default experiment in it's " + << "experiment list"; + // The default group was not found in the list of groups. This study is not + // valid. + return; + } + + const base::Time epoch = base::Time::UnixEpoch(); + const base::Time expiry_date = + epoch + base::TimeDelta::FromSeconds(study.expiry_date()); + base::Time::Exploded exploded_end_date; + expiry_date.UTCExplode(&exploded_end_date); + + scoped_refptr<base::FieldTrial> trial( + base::FieldTrialList::FactoryGetFieldTrial( + study.name(), divisor, default_group_name, exploded_end_date.year, + exploded_end_date.month, exploded_end_date.day_of_month, NULL)); + + if (study.has_consistency() && + study.consistency() == chrome_variations::Study_Consistency_PERMANENT) { + trial->UseOneTimeRandomization(); + } + + for (int i = 0; i < study.experiment_size(); ++i) { + if (study.experiment(i).name() != default_group_name) { + trial->AppendGroup(study.experiment(i).name(), + study.experiment(i).probability_weight()); + } + } + + // TODO(jwd): Add experiment_id association code. + trial->SetForced(); +} |