Commit aab6bf24 authored by slan's avatar slan Committed by Commit bot

Reland "[Chromecast] Use base::FeatureList to control features."

This feature was reverted due to the new browsertest being flaky on
internal Cast infrastructure: crrev.com/2838813003

=== Original Commit Message ===
In Chromium, Finch-enabled features are controlled through base::FeatureList,
a class which abstracts the experiment framework and developer overrides
from client code. Though Chromecast's experiment framework is fundamentally
different (in that it is server-driven) Cast builds can still make use of
this class. Introduce some utilities to help.

At boot-up, the pref store will be queried for experiment configs, which
were cached to disk on the most recent config fetch from the last boot
cycle. If a developer overrides these features from the command line,
that value takes precedence. These features will be used to initialize
base::FeatureList, which can then be statically queried from any client
code that depends on //base.

This patch does not actually introduce or convert any existing features
to use this framework.

BUG=714291
BUG= internal b/35424335

Review-Url: https://codereview.chromium.org/2836263003
Cr-Commit-Position: refs/heads/master@{#467998}
parent c0079495
...@@ -38,6 +38,8 @@ source_set("base") { ...@@ -38,6 +38,8 @@ source_set("base") {
"bind_to_task_runner.h", "bind_to_task_runner.h",
"cast_constants.cc", "cast_constants.cc",
"cast_constants.h", "cast_constants.h",
"cast_features.cc",
"cast_features.h",
"cast_paths.cc", "cast_paths.cc",
"cast_paths.h", "cast_paths.h",
"cast_resource.cc", "cast_resource.cc",
...@@ -126,6 +128,7 @@ test("cast_base_unittests") { ...@@ -126,6 +128,7 @@ test("cast_base_unittests") {
sources = [ sources = [
"alarm_manager_unittest.cc", "alarm_manager_unittest.cc",
"bind_to_task_runner_unittest.cc", "bind_to_task_runner_unittest.cc",
"cast_features_unittest.cc",
"device_capabilities_impl_unittest.cc", "device_capabilities_impl_unittest.cc",
"error_codes_unittest.cc", "error_codes_unittest.cc",
"path_utils_unittest.cc", "path_utils_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 "chromecast/base/cast_features.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/lazy_instance.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/field_trial_param_associator.h"
#include "base/metrics/field_trial_params.h"
#include "base/strings/string_number_conversions.h"
#include "base/values.h"
namespace chromecast {
namespace {
// A constant used to always activate a FieldTrial.
const base::FieldTrial::Probability k100PercentProbability = 100;
// The name of the default group to use for Cast DCS features.
const char kDefaultDCSFeaturesGroup[] = "default_dcs_features_group";
base::LazyInstance<std::unordered_set<int32_t>>::Leaky g_experiment_ids =
LAZY_INSTANCE_INITIALIZER;
bool g_experiment_ids_initialized = false;
void SetExperimentIds(const base::ListValue& list) {
DCHECK(!g_experiment_ids_initialized);
std::unordered_set<int32_t> ids;
for (size_t i = 0; i < list.GetSize(); ++i) {
int32_t id;
if (list.GetInteger(i, &id)) {
ids.insert(id);
} else {
LOG(ERROR) << "Non-integer value found in experiment id list!";
}
}
g_experiment_ids.Get().swap(ids);
g_experiment_ids_initialized = true;
}
} // namespace
// An iterator for a base::DictionaryValue. Use an alias for brevity in loops.
using Iterator = base::DictionaryValue::Iterator;
void InitializeFeatureList(const base::DictionaryValue& dcs_features,
const base::ListValue& dcs_experiment_ids,
const std::string& cmd_line_enable_features,
const std::string& cmd_line_disable_features) {
DCHECK(!base::FeatureList::GetInstance());
// Set the experiments.
SetExperimentIds(dcs_experiment_ids);
// Initialize the FeatureList from the command line.
auto feature_list = base::MakeUnique<base::FeatureList>();
feature_list->InitializeFromCommandLine(cmd_line_enable_features,
cmd_line_disable_features);
// Override defaults from the DCS config.
for (Iterator it(dcs_features); !it.IsAtEnd(); it.Advance()) {
// Each feature must have its own FieldTrial object. Since experiments are
// controlled server-side for Chromecast, and this class is designed with a
// client-side experimentation framework in mind, these parameters are
// carefully chosen:
// - The field trial name is unused for our purposes. However, we need to
// maintain a 1:1 mapping with Features in order to properly store and
// access parameters associated with each Feature. Therefore, use the
// Feature's name as the FieldTrial name to ensure uniqueness.
// - The probability is hard-coded to 100% so that the FeatureList always
// respects the value from DCS.
// - The default group is unused; it will be the same for every feature.
// - Expiration year, month, and day use a special value such that the
// feature will never expire.
// - SESSION_RANDOMIZED is used to prevent the need for an
// entropy_provider. However, this value doesn't matter.
// - We don't care about the group_id.
//
const std::string& feature_name = it.key();
auto* field_trial = base::FieldTrialList::FactoryGetFieldTrial(
feature_name, k100PercentProbability, kDefaultDCSFeaturesGroup,
base::FieldTrialList::kNoExpirationYear, 1 /* month */, 1 /* day */,
base::FieldTrial::SESSION_RANDOMIZED, nullptr);
bool enabled;
if (it.value().GetAsBoolean(&enabled)) {
// A boolean entry simply either enables or disables a feature.
feature_list->RegisterFieldTrialOverride(
feature_name,
enabled ? base::FeatureList::OVERRIDE_ENABLE_FEATURE
: base::FeatureList::OVERRIDE_DISABLE_FEATURE,
field_trial);
continue;
}
const base::DictionaryValue* params_dict;
if (it.value().GetAsDictionary(&params_dict)) {
// A dictionary entry implies that the feature is enabled.
feature_list->RegisterFieldTrialOverride(
feature_name, base::FeatureList::OVERRIDE_ENABLE_FEATURE,
field_trial);
// If the feature has not been overriden from the command line, set its
// parameters accordingly.
if (!feature_list->IsFeatureOverriddenFromCommandLine(
feature_name, base::FeatureList::OVERRIDE_DISABLE_FEATURE)) {
// Build a map of the FieldTrial parameters and associate it to the
// FieldTrial.
base::FieldTrialParamAssociator::FieldTrialParams params;
for (Iterator p(*params_dict); !p.IsAtEnd(); p.Advance()) {
std::string val;
if (p.value().GetAsString(&val)) {
params[p.key()] = val;
} else {
LOG(ERROR) << "Entry in params dict for \"" << feature_name << "\""
<< " feature is not a string. Skipping.";
}
}
// Register the params, so that they can be queried by client code.
bool success = base::AssociateFieldTrialParams(
feature_name, kDefaultDCSFeaturesGroup, params);
DCHECK(success);
}
continue;
}
// Other base::Value types are not supported.
LOG(ERROR) << "A DCS feature mapped to an unsupported value. key: "
<< feature_name << " type: " << it.value().type();
}
base::FeatureList::SetInstance(std::move(feature_list));
}
base::DictionaryValue GetOverriddenFeaturesForStorage(
const base::DictionaryValue& features) {
base::DictionaryValue persistent_dict;
// |features| maps feature names to either a boolean or a dict of params.
for (Iterator it(features); !it.IsAtEnd(); it.Advance()) {
bool enabled;
if (it.value().GetAsBoolean(&enabled)) {
persistent_dict.SetBoolean(it.key(), enabled);
continue;
}
const base::DictionaryValue* params_dict;
if (it.value().GetAsDictionary(&params_dict)) {
auto params = base::MakeUnique<base::DictionaryValue>();
bool bval;
int ival;
double dval;
std::string sval;
for (Iterator p(*params_dict); !p.IsAtEnd(); p.Advance()) {
const auto& param_key = p.key();
const auto& param_val = p.value();
if (param_val.GetAsBoolean(&bval)) {
params->SetString(param_key, bval ? "true" : "false");
} else if (param_val.GetAsInteger(&ival)) {
params->SetString(param_key, base::IntToString(ival));
} else if (param_val.GetAsDouble(&dval)) {
params->SetString(param_key, base::DoubleToString(dval));
} else if (param_val.GetAsString(&sval)) {
params->SetString(param_key, sval);
} else {
LOG(ERROR) << "Entry in params dict for \"" << it.key() << "\""
<< " is not of a supported type (key: " << p.key()
<< ", type: " << param_val.type();
}
}
persistent_dict.Set(it.key(), std::move(params));
continue;
}
// Other base::Value types are not supported.
LOG(ERROR) << "A DCS feature mapped to an unsupported value. key: "
<< it.key() << " type: " << it.value().type();
}
return persistent_dict;
}
const std::unordered_set<int32_t>& GetDCSExperimentIds() {
DCHECK(g_experiment_ids_initialized);
return g_experiment_ids.Get();
}
void ResetCastFeaturesForTesting() {
g_experiment_ids_initialized = false;
base::FeatureList::ClearInstanceForTesting();
}
} // namespace chromecast
// 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 CHROMECAST_BASE_CAST_FEATURES_H_
#define CHROMECAST_BASE_CAST_FEATURES_H_
#include <cstdint>
#include <memory>
#include <string>
#include <unordered_set>
#include "base/feature_list.h"
#include "base/macros.h"
namespace base {
class DictionaryValue;
class ListValue;
}
namespace chromecast {
// Initialize the global base::FeatureList instance, taking into account
// overrides from DCS and the command line. |dcs_features| and
// |dcs_experiment_ids| are read from the PrefService in the browser process.
// |cmd_line_enable_features| and |cmd_line_disable_features| should be passed
// to this function, unmodified from the command line.
//
// This function should be called before the browser's main loop. After this is
// called, the other functions in this file may be called on any thread.
void InitializeFeatureList(const base::DictionaryValue& dcs_features,
const base::ListValue& dcs_experiment_ids,
const std::string& cmd_line_enable_features,
const std::string& cmd_line_disable_features);
// Given a dictionary of features, create a copy that is ready to be persisted
// to disk. Encodes all values as strings, which is how the FieldTrial
// classes expect the param data.
base::DictionaryValue GetOverriddenFeaturesForStorage(
const base::DictionaryValue& features);
// Query the set of experiment ids set for this run. Intended only for metrics
// reporting. Must be called after InitializeFeatureList(). May be called on any
// thread.
const std::unordered_set<int32_t>& GetDCSExperimentIds();
// Reset static state to ensure clean unittests. For tests only.
void ResetCastFeaturesForTesting();
} // namespace chromecast
#endif // CHROMECAST_BASE_CAST_FEATURES_H_
\ No newline at end of file
This diff is collapsed.
...@@ -7,6 +7,9 @@ ...@@ -7,6 +7,9 @@
namespace chromecast { namespace chromecast {
namespace prefs { namespace prefs {
// List of experiments enabled by DCS. For metrics purposes only.
const char kActiveDCSExperiments[] = "experiments.ids";
// Boolean which specifies if remote debugging is enabled // Boolean which specifies if remote debugging is enabled
const char kEnableRemoteDebugging[] = "enable_remote_debugging"; const char kEnableRemoteDebugging[] = "enable_remote_debugging";
...@@ -14,6 +17,9 @@ const char kEnableRemoteDebugging[] = "enable_remote_debugging"; ...@@ -14,6 +17,9 @@ const char kEnableRemoteDebugging[] = "enable_remote_debugging";
// due to bug b/9487011. // due to bug b/9487011.
const char kMetricsIsNewClientID[] = "user_experience_metrics.is_new_client_id"; const char kMetricsIsNewClientID[] = "user_experience_metrics.is_new_client_id";
// Dictionary of remotely-enabled features from the latest DCS config fetch.
const char kLatestDCSFeatures[] = "experiments.features";
// Whether or not to report metrics and crashes. // Whether or not to report metrics and crashes.
const char kOptInStats[] = "opt-in.stats"; const char kOptInStats[] = "opt-in.stats";
......
...@@ -8,7 +8,9 @@ ...@@ -8,7 +8,9 @@
namespace chromecast { namespace chromecast {
namespace prefs { namespace prefs {
extern const char kActiveDCSExperiments[];
extern const char kEnableRemoteDebugging[]; extern const char kEnableRemoteDebugging[];
extern const char kLatestDCSFeatures[];
extern const char kMetricsIsNewClientID[]; extern const char kMetricsIsNewClientID[];
extern const char kOptInStats[]; extern const char kOptInStats[];
extern const char kStabilityChildProcessCrashCount[]; extern const char kStabilityChildProcessCrashCount[];
......
...@@ -246,6 +246,7 @@ source_set("browsertests") { ...@@ -246,6 +246,7 @@ source_set("browsertests") {
sources = [ sources = [
"cast_media_blocker_browsertest.cc", "cast_media_blocker_browsertest.cc",
"renderer_prelauncher_test.cc", "renderer_prelauncher_test.cc",
"test/cast_features_browsertest.cc",
"test/cast_navigation_browsertest.cc", "test/cast_navigation_browsertest.cc",
] ]
...@@ -254,8 +255,10 @@ source_set("browsertests") { ...@@ -254,8 +255,10 @@ source_set("browsertests") {
deps = [ deps = [
":test_support", ":test_support",
"//chromecast:chromecast_features", "//chromecast:chromecast_features",
"//chromecast/base",
"//chromecast/base:chromecast_switches", "//chromecast/base:chromecast_switches",
"//chromecast/base/metrics", "//chromecast/base/metrics",
"//components/prefs",
"//media/base:test_support", "//media/base:test_support",
] ]
} }
......
...@@ -22,11 +22,13 @@ ...@@ -22,11 +22,13 @@
#include "build/build_config.h" #include "build/build_config.h"
#include "cc/base/switches.h" #include "cc/base/switches.h"
#include "chromecast/base/cast_constants.h" #include "chromecast/base/cast_constants.h"
#include "chromecast/base/cast_features.h"
#include "chromecast/base/cast_paths.h" #include "chromecast/base/cast_paths.h"
#include "chromecast/base/cast_sys_info_util.h" #include "chromecast/base/cast_sys_info_util.h"
#include "chromecast/base/chromecast_switches.h" #include "chromecast/base/chromecast_switches.h"
#include "chromecast/base/metrics/cast_metrics_helper.h" #include "chromecast/base/metrics/cast_metrics_helper.h"
#include "chromecast/base/metrics/grouped_histogram.h" #include "chromecast/base/metrics/grouped_histogram.h"
#include "chromecast/base/pref_names.h"
#include "chromecast/base/version.h" #include "chromecast/base/version.h"
#include "chromecast/browser/cast_browser_context.h" #include "chromecast/browser/cast_browser_context.h"
#include "chromecast/browser/cast_browser_process.h" #include "chromecast/browser/cast_browser_process.h"
...@@ -279,6 +281,7 @@ CastBrowserMainParts::CastBrowserMainParts( ...@@ -279,6 +281,7 @@ CastBrowserMainParts::CastBrowserMainParts(
URLRequestContextFactory* url_request_context_factory) URLRequestContextFactory* url_request_context_factory)
: BrowserMainParts(), : BrowserMainParts(),
cast_browser_process_(new CastBrowserProcess()), cast_browser_process_(new CastBrowserProcess()),
field_trial_list_(nullptr),
parameters_(parameters), parameters_(parameters),
url_request_context_factory_(url_request_context_factory), url_request_context_factory_(url_request_context_factory),
net_log_(new CastNetLog()), net_log_(new CastNetLog()),
...@@ -414,6 +417,26 @@ int CastBrowserMainParts::PreCreateThreads() { ...@@ -414,6 +417,26 @@ int CastBrowserMainParts::PreCreateThreads() {
if (!base::CreateDirectory(home_dir)) if (!base::CreateDirectory(home_dir))
return 1; return 1;
scoped_refptr<PrefRegistrySimple> pref_registry(new PrefRegistrySimple());
metrics::RegisterPrefs(pref_registry.get());
PrefProxyConfigTrackerImpl::RegisterPrefs(pref_registry.get());
cast_browser_process_->SetPrefService(
PrefServiceHelper::CreatePrefService(pref_registry.get()));
// As soon as the PrefService is set, initialize the base::FeatureList, so
// objects initialized after this point can use features from
// base::FeatureList.
const auto* features_dict =
cast_browser_process_->pref_service()->GetDictionary(
prefs::kLatestDCSFeatures);
const auto* experiment_ids = cast_browser_process_->pref_service()->GetList(
prefs::kActiveDCSExperiments);
auto* command_line = base::CommandLine::ForCurrentProcess();
InitializeFeatureList(
*features_dict, *experiment_ids,
command_line->GetSwitchValueASCII(switches::kEnableFeatures),
command_line->GetSwitchValueASCII(switches::kDisableFeatures));
// Hook for internal code // Hook for internal code
cast_browser_process_->browser_client()->PreCreateThreads(); cast_browser_process_->browser_client()->PreCreateThreads();
...@@ -438,11 +461,6 @@ int CastBrowserMainParts::PreCreateThreads() { ...@@ -438,11 +461,6 @@ int CastBrowserMainParts::PreCreateThreads() {
} }
void CastBrowserMainParts::PreMainMessageLoopRun() { void CastBrowserMainParts::PreMainMessageLoopRun() {
scoped_refptr<PrefRegistrySimple> pref_registry(new PrefRegistrySimple());
metrics::RegisterPrefs(pref_registry.get());
PrefProxyConfigTrackerImpl::RegisterPrefs(pref_registry.get());
cast_browser_process_->SetPrefService(
PrefServiceHelper::CreatePrefService(pref_registry.get()));
#if !defined(OS_ANDROID) #if !defined(OS_ANDROID)
memory_pressure_monitor_.reset(new CastMemoryPressureMonitor()); memory_pressure_monitor_.reset(new CastMemoryPressureMonitor());
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/metrics/field_trial.h"
#include "build/buildflag.h" #include "build/buildflag.h"
#include "chromecast/chromecast_features.h" #include "chromecast/chromecast_features.h"
#include "content/public/browser/browser_main_parts.h" #include "content/public/browser/browser_main_parts.h"
...@@ -65,6 +66,7 @@ class CastBrowserMainParts : public content::BrowserMainParts { ...@@ -65,6 +66,7 @@ class CastBrowserMainParts : public content::BrowserMainParts {
private: private:
std::unique_ptr<CastBrowserProcess> cast_browser_process_; std::unique_ptr<CastBrowserProcess> cast_browser_process_;
base::FieldTrialList field_trial_list_;
const content::MainFunctionParams parameters_; // For running browser tests. const content::MainFunctionParams parameters_; // For running browser tests.
URLRequestContextFactory* const url_request_context_factory_; URLRequestContextFactory* const url_request_context_factory_;
std::unique_ptr<net::NetLog> net_log_; std::unique_ptr<net::NetLog> net_log_;
......
...@@ -53,6 +53,8 @@ std::unique_ptr<PrefService> PrefServiceHelper::CreatePrefService( ...@@ -53,6 +53,8 @@ std::unique_ptr<PrefService> PrefServiceHelper::CreatePrefService(
// opts out, nothing further will be sent (honoring the user's setting). // opts out, nothing further will be sent (honoring the user's setting).
// 2) Dogfood users (see dogfood agreement). // 2) Dogfood users (see dogfood agreement).
registry->RegisterBooleanPref(prefs::kOptInStats, true); registry->RegisterBooleanPref(prefs::kOptInStats, true);
registry->RegisterListPref(prefs::kActiveDCSExperiments);
registry->RegisterDictionaryPref(prefs::kLatestDCSFeatures);
RegisterPlatformPrefs(registry); RegisterPlatformPrefs(registry);
......
This diff is collapsed.
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