Commit f3aaa1c4 authored by Alexei Svitkine (slow)'s avatar Alexei Svitkine (slow) Committed by Commit Bot

Revert "Add the VariationsFieldTrialCreator class"

This reverts commit 34b11e87.

Reason for revert: Causing crashes: crbug.com/742707

Original change's description:
> Add the VariationsFieldTrialCreator class
> 
> This is part 3 of 6 for FieldTrial refactoring for WebView
> 
> Part 1: https://chromium-review.googlesource.com/c/562098/
> Part 2: https://chromium-review.googlesource.com/c/561920/
> Part 3: https://chromium-review.googlesource.com/c/561922/
> Part 4: https://chromium-review.googlesource.com/c/561980/
> Part 5: https://chromium-review.googlesource.com/c/562417/
> Part 6: https://chromium-review.googlesource.com/c/562021/
> 
> This CL creates the VariationsFieldTrialCreator class. This class
> contains the code previously in VariationsService that
> is needed by both Chrome and WebView, mainly the portions
> needed to call CreateTrialsFromSeed.  The VariationsService
> class now contains an instance of this new class.
> 
> BUG=678288
> 
> Change-Id: Ic4340d5a0396cca56892a3b541e4cf6115bb8afb
> Reviewed-on: https://chromium-review.googlesource.com/561922
> Commit-Queue: Kyle Milka <kmilka@google.com>
> Reviewed-by: Alexei Svitkine (slow) <asvitkine@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#486590}

TBR=asvitkine@chromium.org,timav@chromium.org,paulmiller@chromium.org,kmilka@google.com

Change-Id: Idf0f12b5bd6d1bb7edf41cbdc6f767f8790de6e9
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: 678288
Reviewed-on: https://chromium-review.googlesource.com/571920Reviewed-by: default avatarAlexei Svitkine (slow) <asvitkine@chromium.org>
Commit-Queue: Alexei Svitkine (slow) <asvitkine@chromium.org>
Cr-Commit-Position: refs/heads/master@{#486755}
parent 1a6a1f02
...@@ -6,8 +6,6 @@ static_library("service") { ...@@ -6,8 +6,6 @@ static_library("service") {
sources = [ sources = [
"ui_string_overrider.cc", "ui_string_overrider.cc",
"ui_string_overrider.h", "ui_string_overrider.h",
"variations_field_trial_creator.cc",
"variations_field_trial_creator.h",
"variations_service.cc", "variations_service.cc",
"variations_service.h", "variations_service.h",
"variations_service_client.h", "variations_service_client.h",
...@@ -32,7 +30,6 @@ static_library("service") { ...@@ -32,7 +30,6 @@ static_library("service") {
source_set("unit_tests") { source_set("unit_tests") {
testonly = true testonly = true
sources = [ sources = [
"field_trial_creator_unittest.cc",
"ui_string_overrider_unittest.cc", "ui_string_overrider_unittest.cc",
"variations_service_unittest.cc", "variations_service_unittest.cc",
] ]
......
// Copyright 2017 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/service/variations_field_trial_creator.h"
#include <stddef.h>
#include "base/feature_list.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/version.h"
#include "components/metrics/clean_exit_beacon.h"
#include "components/metrics/client_info.h"
#include "components/metrics/metrics_state_manager.h"
#include "components/metrics/test_enabled_state_provider.h"
#include "components/prefs/testing_pref_service.h"
#include "components/variations/pref_names.h"
#include "components/variations/service/variations_service.h"
#include "components/web_resource/resource_request_allowed_notifier_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace variations {
namespace {
// A stub for the metrics state manager.
void StubStoreClientInfo(const metrics::ClientInfo& /* client_info */) {}
// A stub for the metrics state manager.
std::unique_ptr<metrics::ClientInfo> StubLoadClientInfo() {
return std::unique_ptr<metrics::ClientInfo>();
}
class TestVariationsServiceClient : public VariationsServiceClient {
public:
TestVariationsServiceClient() {}
~TestVariationsServiceClient() override {}
// VariationsServiceClient:
std::string GetApplicationLocale() override { return std::string(); }
base::Callback<base::Version(void)> GetVersionForSimulationCallback()
override {
return base::Callback<base::Version(void)>();
}
net::URLRequestContextGetter* GetURLRequestContext() override {
return nullptr;
}
network_time::NetworkTimeTracker* GetNetworkTimeTracker() override {
return nullptr;
}
version_info::Channel GetChannel() override {
return version_info::Channel::UNKNOWN;
}
bool OverridesRestrictParameter(std::string* parameter) override {
if (restrict_parameter_.empty())
return false;
*parameter = restrict_parameter_;
return true;
}
void set_restrict_parameter(const std::string& value) {
restrict_parameter_ = value;
}
private:
std::string restrict_parameter_;
DISALLOW_COPY_AND_ASSIGN(TestVariationsServiceClient);
};
class TestVariationsFieldTrialCreator : public VariationsFieldTrialCreator {
public:
TestVariationsFieldTrialCreator(
std::unique_ptr<web_resource::TestRequestAllowedNotifier> test_notifier,
PrefService* local_state,
TestVariationsServiceClient* client)
: VariationsFieldTrialCreator(local_state, client, UIStringOverrider()),
client_(client) {
SetCreateTrialsFromSeedCalledForTesting(true);
}
~TestVariationsFieldTrialCreator() {
delete client_;
client_ = 0;
}
bool StoreSeed(const std::string& seed_data,
const std::string& seed_signature,
const std::string& country_code,
const base::Time& date_fetched,
bool is_delta_compressed,
bool is_gzip_compressed) {
seed_stored_ = true;
stored_seed_data_ = seed_data;
stored_country_ = country_code;
delta_compressed_seed_ = is_delta_compressed;
gzip_compressed_seed_ = is_gzip_compressed;
return true;
}
private:
bool LoadSeed(VariationsSeed* seed) override {
if (!seed_stored_)
return false;
return seed->ParseFromString(stored_seed_data_);
}
bool seed_stored_;
std::string stored_seed_data_;
std::string stored_country_;
bool delta_compressed_seed_;
bool gzip_compressed_seed_;
TestVariationsServiceClient* client_;
DISALLOW_COPY_AND_ASSIGN(TestVariationsFieldTrialCreator);
};
// Constants used to create the test seed.
const char kTestSeedStudyName[] = "test";
const char kTestSeedExperimentName[] = "abc";
const int kTestSeedExperimentProbability = 100;
const char kTestSeedSerialNumber[] = "123";
// Populates |seed| with simple test data. The resulting seed will contain one
// study called "test", which contains one experiment called "abc" with
// probability weight 100. |seed|'s study field will be cleared before adding
// the new study.
VariationsSeed CreateTestSeed() {
VariationsSeed seed;
Study* study = seed.add_study();
study->set_name(kTestSeedStudyName);
study->set_default_experiment_name(kTestSeedExperimentName);
Study_Experiment* experiment = study->add_experiment();
experiment->set_name(kTestSeedExperimentName);
experiment->set_probability_weight(kTestSeedExperimentProbability);
seed.set_serial_number(kTestSeedSerialNumber);
return seed;
}
// Serializes |seed| to protobuf binary format.
std::string SerializeSeed(const VariationsSeed& seed) {
std::string serialized_seed;
seed.SerializeToString(&serialized_seed);
return serialized_seed;
}
} // namespace
class FieldTrialCreatorTest : public ::testing::Test {
protected:
FieldTrialCreatorTest()
: enabled_state_provider_(
new metrics::TestEnabledStateProvider(false, false)) {
VariationsService::RegisterPrefs(prefs_.registry());
metrics::CleanExitBeacon::RegisterPrefs(prefs_.registry());
metrics::MetricsStateManager::RegisterPrefs(prefs_.registry());
}
metrics::MetricsStateManager* GetMetricsStateManager() {
// Lazy-initialize the metrics_state_manager so that it correctly reads the
// stability state from prefs after tests have a chance to initialize it.
if (!metrics_state_manager_) {
metrics_state_manager_ = metrics::MetricsStateManager::Create(
&prefs_, enabled_state_provider_.get(), base::string16(),
base::Bind(&StubStoreClientInfo), base::Bind(&StubLoadClientInfo));
}
return metrics_state_manager_.get();
}
protected:
TestingPrefServiceSimple prefs_;
private:
base::MessageLoop message_loop_;
std::unique_ptr<metrics::TestEnabledStateProvider> enabled_state_provider_;
std::unique_ptr<metrics::MetricsStateManager> metrics_state_manager_;
DISALLOW_COPY_AND_ASSIGN(FieldTrialCreatorTest);
};
TEST_F(FieldTrialCreatorTest, CreateTrialsFromSeed) {
// Create a local base::FieldTrialList, to hold the field trials created in
// this test.
base::FieldTrialList field_trial_list(nullptr);
// Create a variations service.
TestVariationsFieldTrialCreator field_trial_creator(
base::MakeUnique<web_resource::TestRequestAllowedNotifier>(&prefs_),
&prefs_, new TestVariationsServiceClient());
field_trial_creator.SetCreateTrialsFromSeedCalledForTesting(false);
// Store a seed.
field_trial_creator.StoreSeed(SerializeSeed(CreateTestSeed()), std::string(),
std::string(), base::Time::Now(), false, false);
prefs_.SetInt64(prefs::kVariationsLastFetchTime,
base::Time::Now().ToInternalValue());
// Check that field trials are created from the seed. Since the test study has
// only 1 experiment with 100% probability weight, we must be part of it.
EXPECT_TRUE(field_trial_creator.CreateTrialsFromSeed(
std::unique_ptr<const base::FieldTrial::EntropyProvider>(nullptr),
base::FeatureList::GetInstance()));
EXPECT_EQ(kTestSeedExperimentName,
base::FieldTrialList::FindFullName(kTestSeedStudyName));
}
TEST_F(FieldTrialCreatorTest, CreateTrialsFromSeedNoLastFetchTime) {
// Create a local base::FieldTrialList, to hold the field trials created in
// this test.
base::FieldTrialList field_trial_list(nullptr);
// Create a variations service
TestVariationsFieldTrialCreator field_trial_creator(
base::MakeUnique<web_resource::TestRequestAllowedNotifier>(&prefs_),
&prefs_, new TestVariationsServiceClient());
field_trial_creator.SetCreateTrialsFromSeedCalledForTesting(false);
// Store a seed. To simulate a first run, |prefs::kVariationsLastFetchTime|
// is left empty.
field_trial_creator.StoreSeed(SerializeSeed(CreateTestSeed()), std::string(),
std::string(), base::Time::Now(), false, false);
EXPECT_EQ(0, prefs_.GetInt64(prefs::kVariationsLastFetchTime));
// Check that field trials are created from the seed. Since the test study has
// only 1 experiment with 100% probability weight, we must be part of it.
EXPECT_TRUE(field_trial_creator.CreateTrialsFromSeed(
std::unique_ptr<const base::FieldTrial::EntropyProvider>(nullptr),
base::FeatureList::GetInstance()));
EXPECT_EQ(base::FieldTrialList::FindFullName(kTestSeedStudyName),
kTestSeedExperimentName);
}
TEST_F(FieldTrialCreatorTest, CreateTrialsFromOutdatedSeed) {
// Create a local base::FieldTrialList, to hold the field trials created in
// this test.
base::FieldTrialList field_trial_list(nullptr);
// Create a variations service.
TestVariationsFieldTrialCreator field_trial_creator(
base::MakeUnique<web_resource::TestRequestAllowedNotifier>(&prefs_),
&prefs_, new TestVariationsServiceClient());
field_trial_creator.SetCreateTrialsFromSeedCalledForTesting(false);
// Store a seed, with a fetch time 31 days in the past.
const base::Time seed_date =
base::Time::Now() - base::TimeDelta::FromDays(31);
field_trial_creator.StoreSeed(SerializeSeed(CreateTestSeed()), std::string(),
std::string(), seed_date, false, false);
prefs_.SetInt64(prefs::kVariationsLastFetchTime, seed_date.ToInternalValue());
// Check that field trials are not created from the seed.
EXPECT_FALSE(field_trial_creator.CreateTrialsFromSeed(
std::unique_ptr<const base::FieldTrial::EntropyProvider>(nullptr),
base::FeatureList::GetInstance()));
EXPECT_TRUE(base::FieldTrialList::FindFullName(kTestSeedStudyName).empty());
}
} // namespace variations
// 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 "components/variations/service/variations_field_trial_creator.h"
#include <stddef.h>
#include <stdint.h>
#include <utility>
#include <vector>
#include "base/build_time.h"
#include "base/command_line.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/sparse_histogram.h"
#include "base/sys_info.h"
#include "base/version.h"
#include "build/build_config.h"
#include "components/prefs/pref_service.h"
#include "components/variations/pref_names.h"
#include "components/variations/proto/variations_seed.pb.h"
#include "components/variations/service/variations_service_client.h"
#include "components/variations/variations_seed_processor.h"
#include "components/variations/variations_switches.h"
#include "ui/base/device_form_factor.h"
namespace variations {
// Maximum age permitted for a variations seed, in days.
const int kMaxVariationsSeedAgeDays = 30;
enum VariationsSeedExpiry {
VARIATIONS_SEED_EXPIRY_NOT_EXPIRED,
VARIATIONS_SEED_EXPIRY_FETCH_TIME_MISSING,
VARIATIONS_SEED_EXPIRY_EXPIRED,
VARIATIONS_SEED_EXPIRY_ENUM_SIZE,
};
// Gets current form factor and converts it from enum DeviceFormFactor to enum
// Study_FormFactor.
Study::FormFactor GetCurrentFormFactor() {
switch (ui::GetDeviceFormFactor()) {
case ui::DEVICE_FORM_FACTOR_PHONE:
return Study::PHONE;
case ui::DEVICE_FORM_FACTOR_TABLET:
return Study::TABLET;
case ui::DEVICE_FORM_FACTOR_DESKTOP:
return Study::DESKTOP;
}
NOTREACHED();
return Study::DESKTOP;
}
// Gets the hardware class and returns it as a string. This returns an empty
// string if the client is not ChromeOS.
std::string GetHardwareClass() {
#if defined(OS_CHROMEOS)
return base::SysInfo::GetLsbReleaseBoard();
#endif // OS_CHROMEOS
return std::string();
}
// Returns the date that should be used by the VariationsSeedProcessor to do
// expiry and start date checks.
base::Time GetReferenceDateForExpiryChecks(PrefService* local_state) {
const int64_t date_value = local_state->GetInt64(prefs::kVariationsSeedDate);
const base::Time seed_date = base::Time::FromInternalValue(date_value);
const base::Time build_time = base::GetBuildTime();
// Use the build time for date checks if either the seed date is invalid or
// the build time is newer than the seed date.
base::Time reference_date = seed_date;
if (seed_date.is_null() || seed_date < build_time)
reference_date = build_time;
return reference_date;
}
// Wrapper around channel checking, used to enable channel mocking for
// testing. If the current browser channel is not UNKNOWN, this will return
// that channel value. Otherwise, if the fake channel flag is provided, this
// will return the fake channel. Failing that, this will return the UNKNOWN
// channel.
Study::Channel GetChannelForVariations(version_info::Channel product_channel) {
switch (product_channel) {
case version_info::Channel::CANARY:
return Study::CANARY;
case version_info::Channel::DEV:
return Study::DEV;
case version_info::Channel::BETA:
return Study::BETA;
case version_info::Channel::STABLE:
return Study::STABLE;
case version_info::Channel::UNKNOWN:
break;
}
const std::string forced_channel =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kFakeVariationsChannel);
if (forced_channel == "stable")
return Study::STABLE;
if (forced_channel == "beta")
return Study::BETA;
if (forced_channel == "dev")
return Study::DEV;
if (forced_channel == "canary")
return Study::CANARY;
DVLOG(1) << "Invalid channel provided: " << forced_channel;
return Study::UNKNOWN;
}
// Records UMA histogram with the result of the variations seed expiry check.
void RecordCreateTrialsSeedExpiry(VariationsSeedExpiry expiry_check_result) {
UMA_HISTOGRAM_ENUMERATION("Variations.CreateTrials.SeedExpiry",
expiry_check_result,
VARIATIONS_SEED_EXPIRY_ENUM_SIZE);
}
VariationsFieldTrialCreator::VariationsFieldTrialCreator(
PrefService* local_state,
VariationsServiceClient* client,
const UIStringOverrider& ui_string_overrider)
: client_(client),
ui_string_overrider_(ui_string_overrider),
local_state_(local_state),
seed_store_(local_state),
create_trials_from_seed_called_(false) {}
VariationsFieldTrialCreator::~VariationsFieldTrialCreator() {}
std::string VariationsFieldTrialCreator::GetLatestCountry() const {
const std::string override_country =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kVariationsOverrideCountry);
return !override_country.empty()
? override_country
: local_state_->GetString(prefs::kVariationsCountry);
}
bool VariationsFieldTrialCreator::CreateTrialsFromSeed(
std::unique_ptr<const base::FieldTrial::EntropyProvider>
low_entropy_provider,
base::FeatureList* feature_list) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!create_trials_from_seed_called_);
create_trials_from_seed_called_ = true;
VariationsSeed seed;
if (!LoadSeed(&seed))
return false;
const int64_t last_fetch_time_internal =
local_state_->GetInt64(prefs::kVariationsLastFetchTime);
const base::Time last_fetch_time =
base::Time::FromInternalValue(last_fetch_time_internal);
if (last_fetch_time.is_null()) {
// If the last fetch time is missing and we have a seed, then this must be
// the first run of Chrome. Store the current time as the last fetch time.
RecordLastFetchTime();
RecordCreateTrialsSeedExpiry(VARIATIONS_SEED_EXPIRY_FETCH_TIME_MISSING);
} else {
// Reject the seed if it is more than 30 days old.
const base::TimeDelta seed_age = base::Time::Now() - last_fetch_time;
if (seed_age.InDays() > kMaxVariationsSeedAgeDays) {
RecordCreateTrialsSeedExpiry(VARIATIONS_SEED_EXPIRY_EXPIRED);
return false;
}
RecordCreateTrialsSeedExpiry(VARIATIONS_SEED_EXPIRY_NOT_EXPIRED);
}
const base::Version current_version(version_info::GetVersionNumber());
if (!current_version.IsValid())
return false;
std::unique_ptr<ClientFilterableState> client_state =
GetClientFilterableStateForVersion(current_version);
// Note that passing |&ui_string_overrider_| via base::Unretained below is
// safe because the callback is executed synchronously. It is not possible
// to pass UIStringOverrider itself to VariationSeedProcessor as variations
// components should not depends on //ui/base.
VariationsSeedProcessor().CreateTrialsFromSeed(
seed, *client_state,
base::Bind(&UIStringOverrider::OverrideUIString,
base::Unretained(&ui_string_overrider_)),
low_entropy_provider.get(), feature_list);
const base::Time now = base::Time::Now();
// Log the "freshness" of the seed that was just used. The freshness is the
// time between the last successful seed download and now.
if (!last_fetch_time.is_null()) {
const base::TimeDelta delta = now - last_fetch_time;
// Log the value in number of minutes.
UMA_HISTOGRAM_CUSTOM_COUNTS("Variations.SeedFreshness", delta.InMinutes(),
1, base::TimeDelta::FromDays(30).InMinutes(),
50);
}
return true;
}
void VariationsFieldTrialCreator::SetCreateTrialsFromSeedCalledForTesting(
bool called) {
create_trials_from_seed_called_ = called;
}
std::unique_ptr<ClientFilterableState>
VariationsFieldTrialCreator::GetClientFilterableStateForVersion(
const base::Version& version) {
std::unique_ptr<ClientFilterableState> state =
base::MakeUnique<ClientFilterableState>();
state->locale = client_->GetApplicationLocale();
state->reference_date = GetReferenceDateForExpiryChecks(local_state_);
state->version = version;
state->channel = GetChannelForVariations(client_->GetChannel());
state->form_factor = GetCurrentFormFactor();
state->platform = ClientFilterableState::GetCurrentPlatform();
state->hardware_class = GetHardwareClass();
#if defined(OS_ANDROID)
// This is set on Android only currently, because the IsLowEndDevice() API
// on other platforms has no intrinsic meaning outside of a field trial that
// controls its value. Since this is before server-side field trials are
// evaluated, that field trial would not be able to apply for this case.
state->is_low_end_device = base::SysInfo::IsLowEndDevice();
#endif
state->session_consistency_country = GetLatestCountry();
state->permanent_consistency_country = LoadPermanentConsistencyCountry(
version, state->session_consistency_country);
return state;
}
std::string VariationsFieldTrialCreator::LoadPermanentConsistencyCountry(
const base::Version& version,
const std::string& latest_country) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(version.IsValid());
const std::string override_country =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kVariationsOverrideCountry);
if (!override_country.empty())
return override_country;
const base::ListValue* list_value =
local_state_->GetList(prefs::kVariationsPermanentConsistencyCountry);
std::string stored_version_string;
std::string stored_country;
// Determine if the saved pref value is present and valid.
const bool is_pref_empty = list_value->empty();
const bool is_pref_valid = list_value->GetSize() == 2 &&
list_value->GetString(0, &stored_version_string) &&
list_value->GetString(1, &stored_country) &&
base::Version(stored_version_string).IsValid();
// Determine if the version from the saved pref matches |version|.
const bool does_version_match =
is_pref_valid && version == base::Version(stored_version_string);
// Determine if the country in the saved pref matches the country in
// |latest_country|.
const bool does_country_match = is_pref_valid && !latest_country.empty() &&
stored_country == latest_country;
// Record a histogram for how the saved pref value compares to the current
// version and the country code in the variations seed.
LoadPermanentConsistencyCountryResult result;
if (is_pref_empty) {
result = !latest_country.empty() ? LOAD_COUNTRY_NO_PREF_HAS_SEED
: LOAD_COUNTRY_NO_PREF_NO_SEED;
} else if (!is_pref_valid) {
result = !latest_country.empty() ? LOAD_COUNTRY_INVALID_PREF_HAS_SEED
: LOAD_COUNTRY_INVALID_PREF_NO_SEED;
} else if (latest_country.empty()) {
result = does_version_match ? LOAD_COUNTRY_HAS_PREF_NO_SEED_VERSION_EQ
: LOAD_COUNTRY_HAS_PREF_NO_SEED_VERSION_NEQ;
} else if (does_version_match) {
result = does_country_match ? LOAD_COUNTRY_HAS_BOTH_VERSION_EQ_COUNTRY_EQ
: LOAD_COUNTRY_HAS_BOTH_VERSION_EQ_COUNTRY_NEQ;
} else {
result = does_country_match ? LOAD_COUNTRY_HAS_BOTH_VERSION_NEQ_COUNTRY_EQ
: LOAD_COUNTRY_HAS_BOTH_VERSION_NEQ_COUNTRY_NEQ;
}
UMA_HISTOGRAM_ENUMERATION("Variations.LoadPermanentConsistencyCountryResult",
result, LOAD_COUNTRY_MAX);
// Use the stored country if one is available and was fetched since the last
// time Chrome was updated.
if (does_version_match)
return stored_country;
if (latest_country.empty()) {
if (!is_pref_valid)
local_state_->ClearPref(prefs::kVariationsPermanentConsistencyCountry);
// If we've never received a country code from the server, use an empty
// country so that it won't pass any filters that specifically include
// countries, but so that it will pass any filters that specifically exclude
// countries.
return std::string();
}
// Otherwise, update the pref with the current Chrome version and country.
StorePermanentCountry(version, latest_country);
return latest_country;
}
void VariationsFieldTrialCreator::StorePermanentCountry(
const base::Version& version,
const std::string& country) {
base::ListValue new_list_value;
new_list_value.AppendString(version.GetString());
new_list_value.AppendString(country);
local_state_->Set(prefs::kVariationsPermanentConsistencyCountry,
new_list_value);
}
void VariationsFieldTrialCreator::RecordLastFetchTime() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
local_state_->SetInt64(prefs::kVariationsLastFetchTime,
base::Time::Now().ToInternalValue());
}
bool VariationsFieldTrialCreator::LoadSeed(VariationsSeed* seed) {
return seed_store_.LoadSeed(seed);
}
} // namespace variations
// Copyright 2017 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.
#ifndef COMPONENTS_VARIATIONS_FIELD_TRIAL_CREATOR_H_
#define COMPONENTS_VARIATIONS_FIELD_TRIAL_CREATOR_H_
#include <string>
#include "base/compiler_specific.h"
#include "base/macros.h"
#include "base/metrics/field_trial.h"
#include "components/variations/client_filterable_state.h"
#include "components/variations/service/ui_string_overrider.h"
#include "components/variations/variations_seed_store.h"
namespace variations {
class VariationsServiceClient;
}
namespace variations {
// Used to setup field trials based on stored variations seed data.
class VariationsFieldTrialCreator {
public:
// Caller is responsible for ensuring that objects passed to the constructor
// stay valid for the lifetime of this object.
VariationsFieldTrialCreator(PrefService* local_state,
VariationsServiceClient* client,
const UIStringOverrider& ui_string_overrider);
~VariationsFieldTrialCreator();
// Returns what variations will consider to be the latest country. Returns
// empty if it is not available.
std::string GetLatestCountry() const;
// Creates field trials based on the variations seed loaded from local state.
// If there is a problem loading the seed data, all trials specified by the
// seed may not be created. Some field trials are configured to override or
// associate with (for reporting) specific features. These associations are
// registered with |feature_list|.
bool CreateTrialsFromSeed(
std::unique_ptr<const base::FieldTrial::EntropyProvider>
low_entropy_provider,
base::FeatureList* feature_list);
VariationsSeedStore& seed_store() { return seed_store_; }
const VariationsSeedStore& seed_store() const { return seed_store_; }
bool create_trials_from_seed_called() const {
return create_trials_from_seed_called_;
}
// Exposed for testing.
void SetCreateTrialsFromSeedCalledForTesting(bool called);
// Returns all of the client state used for filtering studies.
// As a side-effect, may update the stored permanent consistency country.
std::unique_ptr<ClientFilterableState> GetClientFilterableStateForVersion(
const base::Version& version);
// Loads the country code to use for filtering permanent consistency studies,
// updating the stored country code if the stored value was for a different
// Chrome version. The country used for permanent consistency studies is kept
// consistent between Chrome upgrades in order to avoid annoying the user due
// to experiment churn while traveling.
std::string LoadPermanentConsistencyCountry(
const base::Version& version,
const std::string& latest_country);
// Sets the stored permanent country pref for this client.
void StorePermanentCountry(const base::Version& version,
const std::string& country);
// Records the time of the most recent successful fetch.
void RecordLastFetchTime();
// Loads the seed from the variations store into |seed|. If successfull,
// |seed| will contain the loaded data and true is returned. Set as virtual
// so that it can be overridden by tests.
virtual bool LoadSeed(VariationsSeed* seed);
private:
// Set of different possible values to report for the
// Variations.LoadPermanentConsistencyCountryResult histogram. This enum must
// be kept consistent with its counterpart in histograms.xml.
enum LoadPermanentConsistencyCountryResult {
LOAD_COUNTRY_NO_PREF_NO_SEED = 0,
LOAD_COUNTRY_NO_PREF_HAS_SEED,
LOAD_COUNTRY_INVALID_PREF_NO_SEED,
LOAD_COUNTRY_INVALID_PREF_HAS_SEED,
LOAD_COUNTRY_HAS_PREF_NO_SEED_VERSION_EQ,
LOAD_COUNTRY_HAS_PREF_NO_SEED_VERSION_NEQ,
LOAD_COUNTRY_HAS_BOTH_VERSION_EQ_COUNTRY_EQ,
LOAD_COUNTRY_HAS_BOTH_VERSION_EQ_COUNTRY_NEQ,
LOAD_COUNTRY_HAS_BOTH_VERSION_NEQ_COUNTRY_EQ,
LOAD_COUNTRY_HAS_BOTH_VERSION_NEQ_COUNTRY_NEQ,
LOAD_COUNTRY_MAX,
};
VariationsServiceClient* client_;
UIStringOverrider ui_string_overrider_;
// The pref service used to store persist the variations seed.
PrefService* local_state_;
VariationsSeedStore seed_store_;
// Tracks whether |CreateTrialsFromSeed| has been called, to ensure that
// it gets called prior to |StartRepeatedVariationsSeedFetch|.
bool create_trials_from_seed_called_;
SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(VariationsFieldTrialCreator);
};
} // namespace variations
#endif // COMPONENTS_VARIATIONS_FIELD_TRIAL_CREATOR_H_
...@@ -32,7 +32,6 @@ ...@@ -32,7 +32,6 @@
#include "components/prefs/pref_service.h" #include "components/prefs/pref_service.h"
#include "components/variations/pref_names.h" #include "components/variations/pref_names.h"
#include "components/variations/proto/variations_seed.pb.h" #include "components/variations/proto/variations_seed.pb.h"
#include "components/variations/service/variations_field_trial_creator.h"
#include "components/variations/variations_seed_processor.h" #include "components/variations/variations_seed_processor.h"
#include "components/variations/variations_seed_simulator.h" #include "components/variations/variations_seed_simulator.h"
#include "components/variations/variations_switches.h" #include "components/variations/variations_switches.h"
...@@ -57,10 +56,46 @@ namespace { ...@@ -57,10 +56,46 @@ namespace {
// For the HTTP date headers, the resolution of the server time is 1 second. // For the HTTP date headers, the resolution of the server time is 1 second.
const int64_t kServerTimeResolutionMs = 1000; const int64_t kServerTimeResolutionMs = 1000;
// Maximum age permitted for a variations seed, in days.
const int kMaxVariationsSeedAgeDays = 30;
// Whether the VariationsService should always be created, even in Chromium // Whether the VariationsService should always be created, even in Chromium
// builds. // builds.
bool g_enabled_for_testing = false; bool g_enabled_for_testing = false;
// Wrapper around channel checking, used to enable channel mocking for
// testing. If the current browser channel is not UNKNOWN, this will return
// that channel value. Otherwise, if the fake channel flag is provided, this
// will return the fake channel. Failing that, this will return the UNKNOWN
// channel.
Study::Channel GetChannelForVariations(version_info::Channel product_channel) {
switch (product_channel) {
case version_info::Channel::CANARY:
return Study::CANARY;
case version_info::Channel::DEV:
return Study::DEV;
case version_info::Channel::BETA:
return Study::BETA;
case version_info::Channel::STABLE:
return Study::STABLE;
case version_info::Channel::UNKNOWN:
break;
}
const std::string forced_channel =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kFakeVariationsChannel);
if (forced_channel == "stable")
return Study::STABLE;
if (forced_channel == "beta")
return Study::BETA;
if (forced_channel == "dev")
return Study::DEV;
if (forced_channel == "canary")
return Study::CANARY;
DVLOG(1) << "Invalid channel provided: " << forced_channel;
return Study::UNKNOWN;
}
// Returns a string that will be used for the value of the 'osname' URL param // Returns a string that will be used for the value of the 'osname' URL param
// to the variations server. // to the variations server.
std::string GetPlatformString() { std::string GetPlatformString() {
...@@ -116,6 +151,13 @@ enum VariationsSeedExpiry { ...@@ -116,6 +151,13 @@ enum VariationsSeedExpiry {
VARIATIONS_SEED_EXPIRY_ENUM_SIZE, VARIATIONS_SEED_EXPIRY_ENUM_SIZE,
}; };
// Records UMA histogram with the result of the variations seed expiry check.
void RecordCreateTrialsSeedExpiry(VariationsSeedExpiry expiry_check_result) {
UMA_HISTOGRAM_ENUMERATION("Variations.CreateTrials.SeedExpiry",
expiry_check_result,
VARIATIONS_SEED_EXPIRY_ENUM_SIZE);
}
// Converts ResourceRequestAllowedNotifier::State to the corresponding // Converts ResourceRequestAllowedNotifier::State to the corresponding
// ResourceRequestsAllowedState value. // ResourceRequestsAllowedState value.
ResourceRequestsAllowedState ResourceRequestStateToHistogramValue( ResourceRequestsAllowedState ResourceRequestStateToHistogramValue(
...@@ -135,6 +177,45 @@ ResourceRequestsAllowedState ResourceRequestStateToHistogramValue( ...@@ -135,6 +177,45 @@ ResourceRequestsAllowedState ResourceRequestStateToHistogramValue(
return RESOURCE_REQUESTS_NOT_ALLOWED; return RESOURCE_REQUESTS_NOT_ALLOWED;
} }
// Gets current form factor and converts it from enum DeviceFormFactor to enum
// Study_FormFactor.
Study::FormFactor GetCurrentFormFactor() {
switch (ui::GetDeviceFormFactor()) {
case ui::DEVICE_FORM_FACTOR_PHONE:
return Study::PHONE;
case ui::DEVICE_FORM_FACTOR_TABLET:
return Study::TABLET;
case ui::DEVICE_FORM_FACTOR_DESKTOP:
return Study::DESKTOP;
}
NOTREACHED();
return Study::DESKTOP;
}
// Gets the hardware class and returns it as a string. This returns an empty
// string if the client is not ChromeOS.
std::string GetHardwareClass() {
#if defined(OS_CHROMEOS)
return base::SysInfo::GetLsbReleaseBoard();
#endif // OS_CHROMEOS
return std::string();
}
// Returns the date that should be used by the VariationsSeedProcessor to do
// expiry and start date checks.
base::Time GetReferenceDateForExpiryChecks(PrefService* local_state) {
const int64_t date_value = local_state->GetInt64(prefs::kVariationsSeedDate);
const base::Time seed_date = base::Time::FromInternalValue(date_value);
const base::Time build_time = base::GetBuildTime();
// Use the build time for date checks if either the seed date is invalid or
// the build time is newer than the seed date.
base::Time reference_date = seed_date;
if (seed_date.is_null() || seed_date < build_time)
reference_date = build_time;
return reference_date;
}
// Returns the header value for |name| from |headers| or an empty string if not // Returns the header value for |name| from |headers| or an empty string if not
// set. // set.
std::string GetHeaderValue(const net::HttpResponseHeaders* headers, std::string GetHeaderValue(const net::HttpResponseHeaders* headers,
...@@ -202,14 +283,16 @@ VariationsService::VariationsService( ...@@ -202,14 +283,16 @@ VariationsService::VariationsService(
metrics::MetricsStateManager* state_manager, metrics::MetricsStateManager* state_manager,
const UIStringOverrider& ui_string_overrider) const UIStringOverrider& ui_string_overrider)
: client_(std::move(client)), : client_(std::move(client)),
ui_string_overrider_(ui_string_overrider),
local_state_(local_state), local_state_(local_state),
state_manager_(state_manager), state_manager_(state_manager),
policy_pref_service_(local_state), policy_pref_service_(local_state),
seed_store_(local_state),
create_trials_from_seed_called_(false),
initial_request_completed_(false), initial_request_completed_(false),
disable_deltas_for_next_request_(false), disable_deltas_for_next_request_(false),
resource_request_allowed_notifier_(std::move(notifier)), resource_request_allowed_notifier_(std::move(notifier)),
request_count_(0), request_count_(0),
field_trial_creator_(local_state, client.get(), ui_string_overrider),
weak_ptr_factory_(this) { weak_ptr_factory_(this) {
DCHECK(client_.get()); DCHECK(client_.get());
DCHECK(resource_request_allowed_notifier_.get()); DCHECK(resource_request_allowed_notifier_.get());
...@@ -220,14 +303,77 @@ VariationsService::VariationsService( ...@@ -220,14 +303,77 @@ VariationsService::VariationsService(
VariationsService::~VariationsService() { VariationsService::~VariationsService() {
} }
bool VariationsService::CreateTrialsFromSeed(base::FeatureList* feature_list) {
DCHECK(thread_checker_.CalledOnValidThread());
CHECK(!create_trials_from_seed_called_);
create_trials_from_seed_called_ = true;
VariationsSeed seed;
if (!LoadSeed(&seed))
return false;
const int64_t last_fetch_time_internal =
local_state_->GetInt64(prefs::kVariationsLastFetchTime);
const base::Time last_fetch_time =
base::Time::FromInternalValue(last_fetch_time_internal);
if (last_fetch_time.is_null()) {
// If the last fetch time is missing and we have a seed, then this must be
// the first run of Chrome. Store the current time as the last fetch time.
RecordLastFetchTime();
RecordCreateTrialsSeedExpiry(VARIATIONS_SEED_EXPIRY_FETCH_TIME_MISSING);
} else {
// Reject the seed if it is more than 30 days old.
const base::TimeDelta seed_age = base::Time::Now() - last_fetch_time;
if (seed_age.InDays() > kMaxVariationsSeedAgeDays) {
RecordCreateTrialsSeedExpiry(VARIATIONS_SEED_EXPIRY_EXPIRED);
return false;
}
RecordCreateTrialsSeedExpiry(VARIATIONS_SEED_EXPIRY_NOT_EXPIRED);
}
const base::Version current_version(version_info::GetVersionNumber());
if (!current_version.IsValid())
return false;
std::unique_ptr<ClientFilterableState> client_state =
GetClientFilterableStateForVersion(current_version);
UMA_HISTOGRAM_SPARSE_SLOWLY("Variations.UserChannel", client_state->channel);
std::unique_ptr<const base::FieldTrial::EntropyProvider> low_entropy_provider(
CreateLowEntropyProvider());
// Note that passing |&ui_string_overrider_| via base::Unretained below is
// safe because the callback is executed synchronously. It is not possible
// to pass UIStringOverrider itself to VariationSeedProcessor as variations
// components should not depends on //ui/base.
VariationsSeedProcessor().CreateTrialsFromSeed(
seed, *client_state,
base::Bind(&UIStringOverrider::OverrideUIString,
base::Unretained(&ui_string_overrider_)),
low_entropy_provider.get(), feature_list);
const base::Time now = base::Time::Now();
// Log the "freshness" of the seed that was just used. The freshness is the
// time between the last successful seed download and now.
if (!last_fetch_time.is_null()) {
const base::TimeDelta delta = now - last_fetch_time;
// Log the value in number of minutes.
UMA_HISTOGRAM_CUSTOM_COUNTS("Variations.SeedFreshness", delta.InMinutes(),
1, base::TimeDelta::FromDays(30).InMinutes(), 50);
}
return true;
}
void VariationsService::PerformPreMainMessageLoopStartup() { void VariationsService::PerformPreMainMessageLoopStartup() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(thread_checker_.CalledOnValidThread());
StartRepeatedVariationsSeedFetch(); StartRepeatedVariationsSeedFetch();
} }
void VariationsService::StartRepeatedVariationsSeedFetch() { void VariationsService::StartRepeatedVariationsSeedFetch() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(thread_checker_.CalledOnValidThread());
// Initialize the Variations server URL. // Initialize the Variations server URL.
variations_server_url_ = variations_server_url_ =
...@@ -235,7 +381,7 @@ void VariationsService::StartRepeatedVariationsSeedFetch() { ...@@ -235,7 +381,7 @@ void VariationsService::StartRepeatedVariationsSeedFetch() {
// Check that |CreateTrialsFromSeed| was called, which is necessary to // Check that |CreateTrialsFromSeed| was called, which is necessary to
// retrieve the serial number that will be sent to the server. // retrieve the serial number that will be sent to the server.
DCHECK(field_trial_creator_.create_trials_from_seed_called()); DCHECK(create_trials_from_seed_called_);
DCHECK(!request_scheduler_.get()); DCHECK(!request_scheduler_.get());
request_scheduler_.reset(VariationsRequestScheduler::Create( request_scheduler_.reset(VariationsRequestScheduler::Create(
...@@ -247,25 +393,18 @@ void VariationsService::StartRepeatedVariationsSeedFetch() { ...@@ -247,25 +393,18 @@ void VariationsService::StartRepeatedVariationsSeedFetch() {
request_scheduler_->Start(); request_scheduler_->Start();
} }
std::string VariationsService::LoadPermanentConsistencyCountry(
const base::Version& version,
const std::string& latest_country) {
return field_trial_creator_.LoadPermanentConsistencyCountry(version,
latest_country);
}
void VariationsService::AddObserver(Observer* observer) { void VariationsService::AddObserver(Observer* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(thread_checker_.CalledOnValidThread());
observer_list_.AddObserver(observer); observer_list_.AddObserver(observer);
} }
void VariationsService::RemoveObserver(Observer* observer) { void VariationsService::RemoveObserver(Observer* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(thread_checker_.CalledOnValidThread());
observer_list_.RemoveObserver(observer); observer_list_.RemoveObserver(observer);
} }
void VariationsService::OnAppEnterForeground() { void VariationsService::OnAppEnterForeground() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(thread_checker_.CalledOnValidThread());
// On mobile platforms, initialize the fetch scheduler when we receive the // On mobile platforms, initialize the fetch scheduler when we receive the
// first app foreground notification. // first app foreground notification.
...@@ -275,7 +414,7 @@ void VariationsService::OnAppEnterForeground() { ...@@ -275,7 +414,7 @@ void VariationsService::OnAppEnterForeground() {
} }
void VariationsService::SetRestrictMode(const std::string& restrict_mode) { void VariationsService::SetRestrictMode(const std::string& restrict_mode) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(thread_checker_.CalledOnValidThread());
// This should be called before the server URL has been computed. // This should be called before the server URL has been computed.
DCHECK(variations_server_url_.is_empty()); DCHECK(variations_server_url_.is_empty());
...@@ -283,8 +422,8 @@ void VariationsService::SetRestrictMode(const std::string& restrict_mode) { ...@@ -283,8 +422,8 @@ void VariationsService::SetRestrictMode(const std::string& restrict_mode) {
} }
void VariationsService::SetCreateTrialsFromSeedCalledForTesting(bool called) { void VariationsService::SetCreateTrialsFromSeedCalledForTesting(bool called) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(thread_checker_.CalledOnValidThread());
field_trial_creator_.SetCreateTrialsFromSeedCalledForTesting(called); create_trials_from_seed_called_ = called;
} }
GURL VariationsService::GetVariationsServerURL( GURL VariationsService::GetVariationsServerURL(
...@@ -374,7 +513,7 @@ void VariationsService::EnableForTesting() { ...@@ -374,7 +513,7 @@ void VariationsService::EnableForTesting() {
} }
void VariationsService::DoActualFetch() { void VariationsService::DoActualFetch() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(thread_checker_.CalledOnValidThread());
// Normally, there shouldn't be a |pending_request_| when this fires. However // Normally, there shouldn't be a |pending_request_| when this fires. However
// it's not impossible - for example if Chrome was paused (e.g. in a debugger // it's not impossible - for example if Chrome was paused (e.g. in a debugger
...@@ -413,15 +552,14 @@ void VariationsService::DoActualFetch() { ...@@ -413,15 +552,14 @@ void VariationsService::DoActualFetch() {
net::LOAD_DO_NOT_SAVE_COOKIES); net::LOAD_DO_NOT_SAVE_COOKIES);
pending_seed_request_->SetRequestContext(client_->GetURLRequestContext()); pending_seed_request_->SetRequestContext(client_->GetURLRequestContext());
bool enable_deltas = false; bool enable_deltas = false;
if (!field_trial_creator_.seed_store().variations_serial_number().empty() && if (!seed_store_.variations_serial_number().empty() &&
!disable_deltas_for_next_request_) { !disable_deltas_for_next_request_) {
// Tell the server that delta-compressed seeds are supported. // Tell the server that delta-compressed seeds are supported.
enable_deltas = true; enable_deltas = true;
// Get the seed only if its serial number doesn't match what we have. // Get the seed only if its serial number doesn't match what we have.
pending_seed_request_->AddExtraRequestHeader( pending_seed_request_->AddExtraRequestHeader(
"If-None-Match:" + "If-None-Match:" + seed_store_.variations_serial_number());
field_trial_creator_.seed_store().variations_serial_number());
} }
// Tell the server that delta-compressed and gzipped seeds are supported. // Tell the server that delta-compressed and gzipped seeds are supported.
const char* supported_im = enable_deltas ? "A-IM:x-bm,gzip" : "A-IM:gzip"; const char* supported_im = enable_deltas ? "A-IM:x-bm,gzip" : "A-IM:gzip";
...@@ -449,15 +587,15 @@ bool VariationsService::StoreSeed(const std::string& seed_data, ...@@ -449,15 +587,15 @@ bool VariationsService::StoreSeed(const std::string& seed_data,
const base::Time& date_fetched, const base::Time& date_fetched,
bool is_delta_compressed, bool is_delta_compressed,
bool is_gzip_compressed) { bool is_gzip_compressed) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(thread_checker_.CalledOnValidThread());
std::unique_ptr<VariationsSeed> seed(new VariationsSeed); std::unique_ptr<VariationsSeed> seed(new VariationsSeed);
if (!field_trial_creator_.seed_store().StoreSeedData( if (!seed_store_.StoreSeedData(seed_data, seed_signature, country_code,
seed_data, seed_signature, country_code, date_fetched, date_fetched, is_delta_compressed,
is_delta_compressed, is_gzip_compressed, seed.get())) { is_gzip_compressed, seed.get())) {
return false; return false;
} }
field_trial_creator_.RecordLastFetchTime(); RecordLastFetchTime();
base::PostTaskWithTraitsAndReplyWithResult( base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND}, FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
...@@ -472,8 +610,37 @@ VariationsService::CreateLowEntropyProvider() { ...@@ -472,8 +610,37 @@ VariationsService::CreateLowEntropyProvider() {
return state_manager_->CreateLowEntropyProvider(); return state_manager_->CreateLowEntropyProvider();
} }
bool VariationsService::LoadSeed(VariationsSeed* seed) {
return seed_store_.LoadSeed(seed);
}
std::unique_ptr<ClientFilterableState>
VariationsService::GetClientFilterableStateForVersion(
const base::Version& version) {
std::unique_ptr<ClientFilterableState> state =
base::MakeUnique<ClientFilterableState>();
state->locale = client_->GetApplicationLocale();
state->reference_date = GetReferenceDateForExpiryChecks(local_state_);
state->version = version;
state->channel = GetChannelForVariations(client_->GetChannel());
state->form_factor = GetCurrentFormFactor();
state->platform = ClientFilterableState::GetCurrentPlatform();
state->hardware_class = GetHardwareClass();
#if defined(OS_ANDROID)
// This is set on Android only currently, because the IsLowEndDevice() API
// on other platforms has no intrinsic meaning outside of a field trial that
// controls its value. Since this is before server-side field trials are
// evaluated, that field trial would not be able to apply for this case.
state->is_low_end_device = base::SysInfo::IsLowEndDevice();
#endif
state->session_consistency_country = GetLatestCountry();
state->permanent_consistency_country = LoadPermanentConsistencyCountry(
version, state->session_consistency_country);
return state;
}
void VariationsService::FetchVariationsSeed() { void VariationsService::FetchVariationsSeed() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(thread_checker_.CalledOnValidThread());
const web_resource::ResourceRequestAllowedNotifier::State state = const web_resource::ResourceRequestAllowedNotifier::State state =
resource_request_allowed_notifier_->GetResourceRequestsAllowedState(); resource_request_allowed_notifier_->GetResourceRequestsAllowedState();
...@@ -488,7 +655,7 @@ void VariationsService::FetchVariationsSeed() { ...@@ -488,7 +655,7 @@ void VariationsService::FetchVariationsSeed() {
void VariationsService::NotifyObservers( void VariationsService::NotifyObservers(
const VariationsSeedSimulator::Result& result) { const VariationsSeedSimulator::Result& result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(thread_checker_.CalledOnValidThread());
if (result.kill_critical_group_change_count > 0) { if (result.kill_critical_group_change_count > 0) {
for (auto& observer : observer_list_) for (auto& observer : observer_list_)
...@@ -500,7 +667,7 @@ void VariationsService::NotifyObservers( ...@@ -500,7 +667,7 @@ void VariationsService::NotifyObservers(
} }
void VariationsService::OnURLFetchComplete(const net::URLFetcher* source) { void VariationsService::OnURLFetchComplete(const net::URLFetcher* source) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(thread_checker_.CalledOnValidThread());
DCHECK_EQ(pending_seed_request_.get(), source); DCHECK_EQ(pending_seed_request_.get(), source);
const bool is_first_request = !initial_request_completed_; const bool is_first_request = !initial_request_completed_;
...@@ -547,11 +714,10 @@ void VariationsService::OnURLFetchComplete(const net::URLFetcher* source) { ...@@ -547,11 +714,10 @@ void VariationsService::OnURLFetchComplete(const net::URLFetcher* source) {
DVLOG(1) << "Variations server request returned non-HTTP_OK response code: " DVLOG(1) << "Variations server request returned non-HTTP_OK response code: "
<< response_code; << response_code;
if (response_code == net::HTTP_NOT_MODIFIED) { if (response_code == net::HTTP_NOT_MODIFIED) {
field_trial_creator_.RecordLastFetchTime(); RecordLastFetchTime();
// Update the seed date value in local state (used for expiry check on // Update the seed date value in local state (used for expiry check on
// next start up), since 304 is a successful response. // next start up), since 304 is a successful response.
field_trial_creator_.seed_store().UpdateSeedDateAndLogDayChange( seed_store_.UpdateSeedDateAndLogDayChange(response_date);
response_date);
} }
return; return;
} }
...@@ -567,7 +733,7 @@ void VariationsService::OnURLFetchComplete(const net::URLFetcher* source) { ...@@ -567,7 +733,7 @@ void VariationsService::OnURLFetchComplete(const net::URLFetcher* source) {
&is_gzip_compressed)) { &is_gzip_compressed)) {
// The header does not specify supported instance manipulations, unable to // The header does not specify supported instance manipulations, unable to
// process data. Details of errors were logged by GetInstanceManipulations. // process data. Details of errors were logged by GetInstanceManipulations.
field_trial_creator_.seed_store().ReportUnsupportedSeedFormatError(); seed_store_.ReportUnsupportedSeedFormatError();
return; return;
} }
...@@ -583,7 +749,7 @@ void VariationsService::OnURLFetchComplete(const net::URLFetcher* source) { ...@@ -583,7 +749,7 @@ void VariationsService::OnURLFetchComplete(const net::URLFetcher* source) {
} }
void VariationsService::OnResourceRequestsAllowed() { void VariationsService::OnResourceRequestsAllowed() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(thread_checker_.CalledOnValidThread());
// Note that this only attempts to fetch the seed at most once per period // Note that this only attempts to fetch the seed at most once per period
// (kSeedFetchPeriodHours). This works because // (kSeedFetchPeriodHours). This works because
...@@ -603,7 +769,7 @@ void VariationsService::OnResourceRequestsAllowed() { ...@@ -603,7 +769,7 @@ void VariationsService::OnResourceRequestsAllowed() {
void VariationsService::PerformSimulationWithVersion( void VariationsService::PerformSimulationWithVersion(
std::unique_ptr<VariationsSeed> seed, std::unique_ptr<VariationsSeed> seed,
const base::Version& version) { const base::Version& version) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(thread_checker_.CalledOnValidThread());
if (!version.IsValid()) if (!version.IsValid())
return; return;
...@@ -617,7 +783,7 @@ void VariationsService::PerformSimulationWithVersion( ...@@ -617,7 +783,7 @@ void VariationsService::PerformSimulationWithVersion(
VariationsSeedSimulator seed_simulator(*default_provider, *low_provider); VariationsSeedSimulator seed_simulator(*default_provider, *low_provider);
std::unique_ptr<ClientFilterableState> client_state = std::unique_ptr<ClientFilterableState> client_state =
field_trial_creator_.GetClientFilterableStateForVersion(version); GetClientFilterableStateForVersion(version);
const VariationsSeedSimulator::Result result = const VariationsSeedSimulator::Result result =
seed_simulator.SimulateSeedStudies(*seed, *client_state); seed_simulator.SimulateSeedStudies(*seed, *client_state);
...@@ -633,18 +799,100 @@ void VariationsService::PerformSimulationWithVersion( ...@@ -633,18 +799,100 @@ void VariationsService::PerformSimulationWithVersion(
NotifyObservers(result); NotifyObservers(result);
} }
void VariationsService::RecordLastFetchTime() {
DCHECK(thread_checker_.CalledOnValidThread());
local_state_->SetInt64(prefs::kVariationsLastFetchTime,
base::Time::Now().ToInternalValue());
}
std::string VariationsService::GetInvalidVariationsSeedSignature() const { std::string VariationsService::GetInvalidVariationsSeedSignature() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(thread_checker_.CalledOnValidThread());
return field_trial_creator_.seed_store().GetInvalidSignature(); return seed_store_.GetInvalidSignature();
} }
std::string VariationsService::GetLatestCountry() const { std::string VariationsService::LoadPermanentConsistencyCountry(
return field_trial_creator_.GetLatestCountry(); const base::Version& version,
const std::string& latest_country) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(version.IsValid());
const std::string override_country =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kVariationsOverrideCountry);
if (!override_country.empty())
return override_country;
const base::ListValue* list_value =
local_state_->GetList(prefs::kVariationsPermanentConsistencyCountry);
std::string stored_version_string;
std::string stored_country;
// Determine if the saved pref value is present and valid.
const bool is_pref_empty = list_value->empty();
const bool is_pref_valid = list_value->GetSize() == 2 &&
list_value->GetString(0, &stored_version_string) &&
list_value->GetString(1, &stored_country) &&
base::Version(stored_version_string).IsValid();
// Determine if the version from the saved pref matches |version|.
const bool does_version_match =
is_pref_valid && version == base::Version(stored_version_string);
// Determine if the country in the saved pref matches the country in
// |latest_country|.
const bool does_country_match = is_pref_valid && !latest_country.empty() &&
stored_country == latest_country;
// Record a histogram for how the saved pref value compares to the current
// version and the country code in the variations seed.
LoadPermanentConsistencyCountryResult result;
if (is_pref_empty) {
result = !latest_country.empty() ? LOAD_COUNTRY_NO_PREF_HAS_SEED
: LOAD_COUNTRY_NO_PREF_NO_SEED;
} else if (!is_pref_valid) {
result = !latest_country.empty() ? LOAD_COUNTRY_INVALID_PREF_HAS_SEED
: LOAD_COUNTRY_INVALID_PREF_NO_SEED;
} else if (latest_country.empty()) {
result = does_version_match ? LOAD_COUNTRY_HAS_PREF_NO_SEED_VERSION_EQ
: LOAD_COUNTRY_HAS_PREF_NO_SEED_VERSION_NEQ;
} else if (does_version_match) {
result = does_country_match ? LOAD_COUNTRY_HAS_BOTH_VERSION_EQ_COUNTRY_EQ
: LOAD_COUNTRY_HAS_BOTH_VERSION_EQ_COUNTRY_NEQ;
} else {
result = does_country_match ? LOAD_COUNTRY_HAS_BOTH_VERSION_NEQ_COUNTRY_EQ
: LOAD_COUNTRY_HAS_BOTH_VERSION_NEQ_COUNTRY_NEQ;
}
UMA_HISTOGRAM_ENUMERATION("Variations.LoadPermanentConsistencyCountryResult",
result, LOAD_COUNTRY_MAX);
// Use the stored country if one is available and was fetched since the last
// time Chrome was updated.
if (does_version_match)
return stored_country;
if (latest_country.empty()) {
if (!is_pref_valid)
local_state_->ClearPref(prefs::kVariationsPermanentConsistencyCountry);
// If we've never received a country code from the server, use an empty
// country so that it won't pass any filters that specifically include
// countries, but so that it will pass any filters that specifically exclude
// countries.
return std::string();
}
// Otherwise, update the pref with the current Chrome version and country.
StorePermanentCountry(version, latest_country);
return latest_country;
} }
bool VariationsService::CreateTrialsFromSeed(base::FeatureList* feature_list) { void VariationsService::StorePermanentCountry(const base::Version& version,
return field_trial_creator_.CreateTrialsFromSeed(CreateLowEntropyProvider(), const std::string& country) {
feature_list); base::ListValue new_list_value;
new_list_value.AppendString(version.GetString());
new_list_value.AppendString(country);
local_state_->Set(prefs::kVariationsPermanentConsistencyCountry,
new_list_value);
} }
std::string VariationsService::GetStoredPermanentCountry() { std::string VariationsService::GetStoredPermanentCountry() {
...@@ -661,7 +909,7 @@ std::string VariationsService::GetStoredPermanentCountry() { ...@@ -661,7 +909,7 @@ std::string VariationsService::GetStoredPermanentCountry() {
bool VariationsService::OverrideStoredPermanentCountry( bool VariationsService::OverrideStoredPermanentCountry(
const std::string& country_override) { const std::string& country_override) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(thread_checker_.CalledOnValidThread());
if (country_override.empty()) if (country_override.empty())
return false; return false;
...@@ -677,8 +925,17 @@ bool VariationsService::OverrideStoredPermanentCountry( ...@@ -677,8 +925,17 @@ bool VariationsService::OverrideStoredPermanentCountry(
return false; return false;
base::Version version(version_info::GetVersionNumber()); base::Version version(version_info::GetVersionNumber());
field_trial_creator_.StorePermanentCountry(version, country_override); StorePermanentCountry(version, country_override);
return true; return true;
} }
std::string VariationsService::GetLatestCountry() const {
const std::string override_country =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kVariationsOverrideCountry);
return !override_country.empty()
? override_country
: local_state_->GetString(prefs::kVariationsCountry);
}
} // namespace variations } // namespace variations
...@@ -14,10 +14,10 @@ ...@@ -14,10 +14,10 @@
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/metrics/field_trial.h" #include "base/metrics/field_trial.h"
#include "base/observer_list.h" #include "base/observer_list.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "components/variations/client_filterable_state.h" #include "components/variations/client_filterable_state.h"
#include "components/variations/service/ui_string_overrider.h" #include "components/variations/service/ui_string_overrider.h"
#include "components/variations/service/variations_field_trial_creator.h"
#include "components/variations/service/variations_service_client.h" #include "components/variations/service/variations_service_client.h"
#include "components/variations/variations_request_scheduler.h" #include "components/variations/variations_request_scheduler.h"
#include "components/variations/variations_seed_simulator.h" #include "components/variations/variations_seed_simulator.h"
...@@ -230,6 +230,20 @@ class VariationsService ...@@ -230,6 +230,20 @@ class VariationsService
LOAD_COUNTRY_MAX, LOAD_COUNTRY_MAX,
}; };
// Loads the seed from the variations store into |seed|. If successfull,
// |seed| will contain the loaded data and true is returned. Set as virtual
// so that it can be overridden by tests.
virtual bool LoadSeed(VariationsSeed* seed);
// Returns all of the client state used for filtering studies.
// As a side-effect, may update the stored permanent consistency country.
std::unique_ptr<ClientFilterableState> GetClientFilterableStateForVersion(
const base::Version& version);
// Sets the stored permanent country pref for this client.
void StorePermanentCountry(const base::Version& version,
const std::string& country);
// Checks if prerequisites for fetching the Variations seed are met, and if // Checks if prerequisites for fetching the Variations seed are met, and if
// so, performs the actual fetch using |DoActualFetch|. // so, performs the actual fetch using |DoActualFetch|.
void FetchVariationsSeed(); void FetchVariationsSeed();
...@@ -250,6 +264,9 @@ class VariationsService ...@@ -250,6 +264,9 @@ class VariationsService
std::unique_ptr<variations::VariationsSeed> seed, std::unique_ptr<variations::VariationsSeed> seed,
const base::Version& version); const base::Version& version);
// Record the time of the most recent successful fetch.
void RecordLastFetchTime();
// Loads the country code to use for filtering permanent consistency studies, // Loads the country code to use for filtering permanent consistency studies,
// updating the stored country code if the stored value was for a different // updating the stored country code if the stored value was for a different
// Chrome version. The country used for permanent consistency studies is kept // Chrome version. The country used for permanent consistency studies is kept
...@@ -260,6 +277,7 @@ class VariationsService ...@@ -260,6 +277,7 @@ class VariationsService
const std::string& latest_country); const std::string& latest_country);
std::unique_ptr<VariationsServiceClient> client_; std::unique_ptr<VariationsServiceClient> client_;
UIStringOverrider ui_string_overrider_;
// The pref service used to store persist the variations seed. // The pref service used to store persist the variations seed.
PrefService* local_state_; PrefService* local_state_;
...@@ -272,6 +290,8 @@ class VariationsService ...@@ -272,6 +290,8 @@ class VariationsService
// either be Local State or Profile prefs. // either be Local State or Profile prefs.
PrefService* policy_pref_service_; PrefService* policy_pref_service_;
VariationsSeedStore seed_store_;
// Contains the scheduler instance that handles timing for requests to the // Contains the scheduler instance that handles timing for requests to the
// server. Initially NULL and instantiated when the initial fetch is // server. Initially NULL and instantiated when the initial fetch is
// requested. // requested.
...@@ -289,6 +309,10 @@ class VariationsService ...@@ -289,6 +309,10 @@ class VariationsService
// The URL to use for querying the variations server. // The URL to use for querying the variations server.
GURL variations_server_url_; GURL variations_server_url_;
// Tracks whether |CreateTrialsFromSeed| has been called, to ensure that
// it gets called prior to |StartRepeatedVariationsSeedFetch|.
bool create_trials_from_seed_called_;
// Tracks whether the initial request to the variations server had completed. // Tracks whether the initial request to the variations server had completed.
bool initial_request_completed_; bool initial_request_completed_;
...@@ -312,10 +336,7 @@ class VariationsService ...@@ -312,10 +336,7 @@ class VariationsService
// List of observers of the VariationsService. // List of observers of the VariationsService.
base::ObserverList<Observer> observer_list_; base::ObserverList<Observer> observer_list_;
// Member responsible for creating trials from a variations seed. base::ThreadChecker thread_checker_;
VariationsFieldTrialCreator field_trial_creator_;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<VariationsService> weak_ptr_factory_; base::WeakPtrFactory<VariationsService> weak_ptr_factory_;
......
...@@ -149,6 +149,11 @@ class TestVariationsService : public VariationsService { ...@@ -149,6 +149,11 @@ class TestVariationsService : public VariationsService {
} }
private: private:
bool LoadSeed(VariationsSeed* seed) override {
if (!seed_stored_)
return false;
return seed->ParseFromString(stored_seed_data_);
}
bool intercepts_fetch_; bool intercepts_fetch_;
bool fetch_attempted_; bool fetch_attempted_;
...@@ -293,6 +298,77 @@ class VariationsServiceTest : public ::testing::Test { ...@@ -293,6 +298,77 @@ class VariationsServiceTest : public ::testing::Test {
DISALLOW_COPY_AND_ASSIGN(VariationsServiceTest); DISALLOW_COPY_AND_ASSIGN(VariationsServiceTest);
}; };
TEST_F(VariationsServiceTest, CreateTrialsFromSeed) {
// Create a local base::FieldTrialList, to hold the field trials created in
// this test.
base::FieldTrialList field_trial_list(nullptr);
// Create a variations service.
TestVariationsService service(
base::MakeUnique<web_resource::TestRequestAllowedNotifier>(&prefs_),
&prefs_, GetMetricsStateManager());
service.SetCreateTrialsFromSeedCalledForTesting(false);
// Store a seed.
service.StoreSeed(SerializeSeed(CreateTestSeed()), std::string(),
std::string(), base::Time::Now(), false, false);
prefs_.SetInt64(prefs::kVariationsLastFetchTime,
base::Time::Now().ToInternalValue());
// Check that field trials are created from the seed. Since the test study has
// only 1 experiment with 100% probability weight, we must be part of it.
EXPECT_TRUE(service.CreateTrialsFromSeed(base::FeatureList::GetInstance()));
EXPECT_EQ(kTestSeedExperimentName,
base::FieldTrialList::FindFullName(kTestSeedStudyName));
}
TEST_F(VariationsServiceTest, CreateTrialsFromSeedNoLastFetchTime) {
// Create a local base::FieldTrialList, to hold the field trials created in
// this test.
base::FieldTrialList field_trial_list(nullptr);
// Create a variations service
TestVariationsService service(
base::MakeUnique<web_resource::TestRequestAllowedNotifier>(&prefs_),
&prefs_, GetMetricsStateManager());
service.SetCreateTrialsFromSeedCalledForTesting(false);
// Store a seed. To simulate a first run, |prefs::kVariationsLastFetchTime|
// is left empty.
service.StoreSeed(SerializeSeed(CreateTestSeed()), std::string(),
std::string(), base::Time::Now(), false, false);
EXPECT_EQ(0, prefs_.GetInt64(prefs::kVariationsLastFetchTime));
// Check that field trials are created from the seed. Since the test study has
// only 1 experiment with 100% probability weight, we must be part of it.
EXPECT_TRUE(service.CreateTrialsFromSeed(base::FeatureList::GetInstance()));
EXPECT_EQ(base::FieldTrialList::FindFullName(kTestSeedStudyName),
kTestSeedExperimentName);
}
TEST_F(VariationsServiceTest, CreateTrialsFromOutdatedSeed) {
// Create a local base::FieldTrialList, to hold the field trials created in
// this test.
base::FieldTrialList field_trial_list(nullptr);
// Create a variations service.
TestVariationsService service(
base::MakeUnique<web_resource::TestRequestAllowedNotifier>(&prefs_),
&prefs_, GetMetricsStateManager());
service.SetCreateTrialsFromSeedCalledForTesting(false);
// Store a seed, with a fetch time 31 days in the past.
const base::Time seed_date =
base::Time::Now() - base::TimeDelta::FromDays(31);
service.StoreSeed(SerializeSeed(CreateTestSeed()), std::string(),
std::string(), seed_date, false, false);
prefs_.SetInt64(prefs::kVariationsLastFetchTime, seed_date.ToInternalValue());
// Check that field trials are not created from the seed.
EXPECT_FALSE(service.CreateTrialsFromSeed(base::FeatureList::GetInstance()));
EXPECT_TRUE(base::FieldTrialList::FindFullName(kTestSeedStudyName).empty());
}
TEST_F(VariationsServiceTest, GetVariationsServerURL) { TEST_F(VariationsServiceTest, GetVariationsServerURL) {
const std::string default_variations_url = const std::string default_variations_url =
VariationsService::GetDefaultVariationsServerURLForTesting(); VariationsService::GetDefaultVariationsServerURLForTesting();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment