OLD | NEW |
| (Empty) |
1 // Copyright 2017 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "chromecast/base/cast_features.h" | |
6 | |
7 #include "base/command_line.h" | |
8 #include "base/feature_list.h" | |
9 #include "base/lazy_instance.h" | |
10 #include "base/metrics/field_trial.h" | |
11 #include "base/metrics/field_trial_param_associator.h" | |
12 #include "base/metrics/field_trial_params.h" | |
13 #include "base/strings/string_number_conversions.h" | |
14 #include "base/values.h" | |
15 | |
16 namespace chromecast { | |
17 namespace { | |
18 | |
19 // A constant used to always activate a FieldTrial. | |
20 const base::FieldTrial::Probability k100PercentProbability = 100; | |
21 | |
22 // The name of the default group to use for Cast DCS features. | |
23 const char kDefaultDCSFeaturesGroup[] = "default_dcs_features_group"; | |
24 | |
25 base::LazyInstance<std::unordered_set<int32_t>>::Leaky g_experiment_ids = | |
26 LAZY_INSTANCE_INITIALIZER; | |
27 bool g_experiment_ids_initialized = false; | |
28 | |
29 void SetExperimentIds(const base::ListValue& list) { | |
30 DCHECK(!g_experiment_ids_initialized); | |
31 std::unordered_set<int32_t> ids; | |
32 for (size_t i = 0; i < list.GetSize(); ++i) { | |
33 int32_t id; | |
34 if (list.GetInteger(i, &id)) { | |
35 ids.insert(id); | |
36 } else { | |
37 LOG(ERROR) << "Non-integer value found in experiment id list!"; | |
38 } | |
39 } | |
40 g_experiment_ids.Get().swap(ids); | |
41 g_experiment_ids_initialized = true; | |
42 } | |
43 } // namespace | |
44 | |
45 // An iterator for a base::DictionaryValue. Use an alias for brevity in loops. | |
46 using Iterator = base::DictionaryValue::Iterator; | |
47 | |
48 void InitializeFeatureList(const base::DictionaryValue& dcs_features, | |
49 const base::ListValue& dcs_experiment_ids, | |
50 const std::string& cmd_line_enable_features, | |
51 const std::string& cmd_line_disable_features) { | |
52 DCHECK(!base::FeatureList::GetInstance()); | |
53 | |
54 // Set the experiments. | |
55 SetExperimentIds(dcs_experiment_ids); | |
56 | |
57 // Initialize the FeatureList from the command line. | |
58 auto feature_list = base::MakeUnique<base::FeatureList>(); | |
59 feature_list->InitializeFromCommandLine(cmd_line_enable_features, | |
60 cmd_line_disable_features); | |
61 | |
62 // Override defaults from the DCS config. | |
63 for (Iterator it(dcs_features); !it.IsAtEnd(); it.Advance()) { | |
64 // Each feature must have its own FieldTrial object. Since experiments are | |
65 // controlled server-side for Chromecast, and this class is designed with a | |
66 // client-side experimentation framework in mind, these parameters are | |
67 // carefully chosen: | |
68 // - The field trial name is unused for our purposes. However, we need to | |
69 // maintain a 1:1 mapping with Features in order to properly store and | |
70 // access parameters associated with each Feature. Therefore, use the | |
71 // Feature's name as the FieldTrial name to ensure uniqueness. | |
72 // - The probability is hard-coded to 100% so that the FeatureList always | |
73 // respects the value from DCS. | |
74 // - The default group is unused; it will be the same for every feature. | |
75 // - Expiration year, month, and day use a special value such that the | |
76 // feature will never expire. | |
77 // - SESSION_RANDOMIZED is used to prevent the need for an | |
78 // entropy_provider. However, this value doesn't matter. | |
79 // - We don't care about the group_id. | |
80 // | |
81 const std::string& feature_name = it.key(); | |
82 auto* field_trial = base::FieldTrialList::FactoryGetFieldTrial( | |
83 feature_name, k100PercentProbability, kDefaultDCSFeaturesGroup, | |
84 base::FieldTrialList::kNoExpirationYear, 1 /* month */, 1 /* day */, | |
85 base::FieldTrial::SESSION_RANDOMIZED, nullptr); | |
86 | |
87 bool enabled; | |
88 if (it.value().GetAsBoolean(&enabled)) { | |
89 // A boolean entry simply either enables or disables a feature. | |
90 feature_list->RegisterFieldTrialOverride( | |
91 feature_name, | |
92 enabled ? base::FeatureList::OVERRIDE_ENABLE_FEATURE | |
93 : base::FeatureList::OVERRIDE_DISABLE_FEATURE, | |
94 field_trial); | |
95 continue; | |
96 } | |
97 | |
98 const base::DictionaryValue* params_dict; | |
99 if (it.value().GetAsDictionary(¶ms_dict)) { | |
100 // A dictionary entry implies that the feature is enabled. | |
101 feature_list->RegisterFieldTrialOverride( | |
102 feature_name, base::FeatureList::OVERRIDE_ENABLE_FEATURE, | |
103 field_trial); | |
104 | |
105 // If the feature has not been overriden from the command line, set its | |
106 // parameters accordingly. | |
107 if (!feature_list->IsFeatureOverriddenFromCommandLine( | |
108 feature_name, base::FeatureList::OVERRIDE_DISABLE_FEATURE)) { | |
109 // Build a map of the FieldTrial parameters and associate it to the | |
110 // FieldTrial. | |
111 base::FieldTrialParamAssociator::FieldTrialParams params; | |
112 for (Iterator p(*params_dict); !p.IsAtEnd(); p.Advance()) { | |
113 std::string val; | |
114 if (p.value().GetAsString(&val)) { | |
115 params[p.key()] = val; | |
116 } else { | |
117 LOG(ERROR) << "Entry in params dict for \"" << feature_name << "\"" | |
118 << " feature is not a string. Skipping."; | |
119 } | |
120 } | |
121 | |
122 // Register the params, so that they can be queried by client code. | |
123 bool success = base::AssociateFieldTrialParams( | |
124 feature_name, kDefaultDCSFeaturesGroup, params); | |
125 DCHECK(success); | |
126 } | |
127 continue; | |
128 } | |
129 | |
130 // Other base::Value types are not supported. | |
131 LOG(ERROR) << "A DCS feature mapped to an unsupported value. key: " | |
132 << feature_name << " type: " << it.value().type(); | |
133 } | |
134 | |
135 base::FeatureList::SetInstance(std::move(feature_list)); | |
136 } | |
137 | |
138 base::DictionaryValue GetOverriddenFeaturesForStorage( | |
139 const base::DictionaryValue& features) { | |
140 base::DictionaryValue persistent_dict; | |
141 | |
142 // |features| maps feature names to either a boolean or a dict of params. | |
143 for (Iterator it(features); !it.IsAtEnd(); it.Advance()) { | |
144 bool enabled; | |
145 if (it.value().GetAsBoolean(&enabled)) { | |
146 persistent_dict.SetBoolean(it.key(), enabled); | |
147 continue; | |
148 } | |
149 | |
150 const base::DictionaryValue* params_dict; | |
151 if (it.value().GetAsDictionary(¶ms_dict)) { | |
152 auto params = base::MakeUnique<base::DictionaryValue>(); | |
153 | |
154 bool bval; | |
155 int ival; | |
156 double dval; | |
157 std::string sval; | |
158 for (Iterator p(*params_dict); !p.IsAtEnd(); p.Advance()) { | |
159 const auto& param_key = p.key(); | |
160 const auto& param_val = p.value(); | |
161 if (param_val.GetAsBoolean(&bval)) { | |
162 params->SetString(param_key, bval ? "true" : "false"); | |
163 } else if (param_val.GetAsInteger(&ival)) { | |
164 params->SetString(param_key, base::IntToString(ival)); | |
165 } else if (param_val.GetAsDouble(&dval)) { | |
166 params->SetString(param_key, base::DoubleToString(dval)); | |
167 } else if (param_val.GetAsString(&sval)) { | |
168 params->SetString(param_key, sval); | |
169 } else { | |
170 LOG(ERROR) << "Entry in params dict for \"" << it.key() << "\"" | |
171 << " is not of a supported type (key: " << p.key() | |
172 << ", type: " << param_val.type(); | |
173 } | |
174 } | |
175 persistent_dict.Set(it.key(), std::move(params)); | |
176 continue; | |
177 } | |
178 | |
179 // Other base::Value types are not supported. | |
180 LOG(ERROR) << "A DCS feature mapped to an unsupported value. key: " | |
181 << it.key() << " type: " << it.value().type(); | |
182 } | |
183 | |
184 return persistent_dict; | |
185 } | |
186 | |
187 const std::unordered_set<int32_t>& GetDCSExperimentIds() { | |
188 DCHECK(g_experiment_ids_initialized); | |
189 return g_experiment_ids.Get(); | |
190 } | |
191 | |
192 void ResetCastFeaturesForTesting() { | |
193 g_experiment_ids_initialized = false; | |
194 base::FeatureList::ClearInstanceForTesting(); | |
195 } | |
196 | |
197 } // namespace chromecast | |
OLD | NEW |