Chromium Code Reviews| 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 int32_t id; | |
|
Alexei Svitkine (slow)
2017/04/21 19:48:01
Nit: Move this into the loop right before it's use
slan
2017/04/21 21:43:27
Done.
| |
| 32 auto ids = base::MakeUnique<std::unordered_set<int32_t>>(); | |
|
Alexei Svitkine (slow)
2017/04/21 19:48:01
I don't think you need to allocate this on the hea
slan
2017/04/21 21:43:27
Right you are, this was a relic of an older patch.
| |
| 33 for (size_t i = 0; i < list.GetSize(); ++i) { | |
| 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, we simple need it to | |
| 69 // be unique among features. | |
|
Alexei Svitkine (slow)
2017/04/21 19:48:01
Can this sentence be expanded to mention what's be
slan
2017/04/21 21:43:27
Sure, let me know if it's clearer now.
| |
| 70 // - The probability is hard-coded to 100% so that the FeatureList always | |
| 71 // respects the value from DCS. | |
| 72 // - The default group is unused; it will be the same for every feature. | |
| 73 // - Expiration year, month, and day use a special value such that the | |
| 74 // feature will never expire. | |
| 75 // - SESSION_RANDOMIZED is used to prevent the need for an | |
| 76 // entropy_provider. However, this value doesn't matter. | |
| 77 // - We don't care about the group_id. | |
| 78 // | |
| 79 const std::string& field_trial_name = it.key(); | |
|
Alexei Svitkine (slow)
2017/04/21 19:48:01
Nit: I'd actually name this feature_name and also
slan
2017/04/21 21:43:28
I like it. Done.
| |
| 80 auto* field_trial = base::FieldTrialList::FactoryGetFieldTrial( | |
| 81 field_trial_name, k100PercentProbability, kDefaultDCSFeaturesGroup, | |
| 82 base::FieldTrialList::kNoExpirationYear, 1 /* month */, 1 /* day */, | |
| 83 base::FieldTrial::SESSION_RANDOMIZED, nullptr); | |
| 84 | |
| 85 bool enabled; | |
| 86 if (it.value().GetAsBoolean(&enabled)) { | |
| 87 // A boolean entry simply either enables or disables a feature. | |
| 88 feature_list->RegisterFieldTrialOverride( | |
| 89 it.key(), | |
| 90 enabled ? base::FeatureList::OVERRIDE_ENABLE_FEATURE | |
| 91 : base::FeatureList::OVERRIDE_DISABLE_FEATURE, | |
| 92 field_trial); | |
| 93 continue; | |
| 94 } | |
| 95 | |
| 96 const base::DictionaryValue* params_dict; | |
| 97 if (it.value().GetAsDictionary(¶ms_dict)) { | |
| 98 // A dictionary entry implies that the feature is enabled. | |
| 99 feature_list->RegisterFieldTrialOverride( | |
| 100 it.key(), base::FeatureList::OVERRIDE_ENABLE_FEATURE, field_trial); | |
| 101 | |
| 102 // If the feature has not been overriden from the command line, set its | |
| 103 // parameters accordingly. | |
| 104 if (!feature_list->IsFeatureOverriddenFromCommandLine( | |
| 105 it.key(), base::FeatureList::OVERRIDE_DISABLE_FEATURE)) { | |
| 106 // Build a map of the FieldTrial parameters and associate it to the | |
| 107 // FieldTrial. | |
| 108 base::FieldTrialParamAssociator::FieldTrialParams params; | |
| 109 for (Iterator p(*params_dict); !p.IsAtEnd(); p.Advance()) { | |
| 110 std::string val; | |
| 111 if (p.value().GetAsString(&val)) { | |
| 112 params[p.key()] = val; | |
| 113 } else { | |
| 114 LOG(ERROR) << "Entry in params dict for \"" << it.key() << "\"" | |
| 115 << " feature is not a string. Skipping."; | |
| 116 } | |
| 117 } | |
| 118 | |
| 119 // Register the params, so that they can be queried by client code. | |
| 120 bool success = base::AssociateFieldTrialParams( | |
| 121 field_trial_name, kDefaultDCSFeaturesGroup, params); | |
| 122 DCHECK(success); | |
| 123 } | |
| 124 continue; | |
| 125 } | |
| 126 | |
| 127 // Other base::Value types are not supported. | |
| 128 LOG(ERROR) << "A DCS feature mapped to an unsupported value. key: " | |
| 129 << it.key() << " type: " << it.value().type(); | |
| 130 } | |
| 131 | |
| 132 base::FeatureList::SetInstance(std::move(feature_list)); | |
| 133 } | |
| 134 | |
| 135 std::unique_ptr<base::DictionaryValue> GetOverriddenFeaturesForStorage( | |
|
Alexei Svitkine (slow)
2017/04/21 19:48:01
Nit: base::DictionaryValue is already copiable, so
| |
| 136 const base::DictionaryValue& features) { | |
| 137 auto persistent_dict = base::MakeUnique<base::DictionaryValue>(); | |
| 138 | |
| 139 // |features| maps feature names to either a boolean or a dict of params. | |
| 140 for (Iterator it(features); !it.IsAtEnd(); it.Advance()) { | |
| 141 bool enabled; | |
| 142 if (it.value().GetAsBoolean(&enabled)) { | |
| 143 persistent_dict->SetBoolean(it.key(), enabled); | |
| 144 continue; | |
| 145 } | |
| 146 | |
| 147 const base::DictionaryValue* params_dict; | |
| 148 if (it.value().GetAsDictionary(¶ms_dict)) { | |
| 149 auto params = base::MakeUnique<base::DictionaryValue>(); | |
| 150 | |
| 151 bool bval; | |
| 152 int ival; | |
| 153 double dval; | |
| 154 std::string sval; | |
| 155 for (Iterator p(*params_dict); !p.IsAtEnd(); p.Advance()) { | |
| 156 const auto& param_key = p.key(); | |
| 157 const auto& param_val = p.value(); | |
| 158 if (param_val.GetAsBoolean(&bval)) { | |
| 159 params->SetString(param_key, bval ? "true" : "false"); | |
| 160 } else if (param_val.GetAsInteger(&ival)) { | |
| 161 params->SetString(param_key, base::IntToString(ival)); | |
| 162 } else if (param_val.GetAsDouble(&dval)) { | |
| 163 params->SetString(param_key, base::DoubleToString(dval)); | |
| 164 } else if (param_val.GetAsString(&sval)) { | |
| 165 params->SetString(param_key, sval); | |
| 166 } else { | |
| 167 LOG(ERROR) << "Entry in params dict for \"" << it.key() << "\"" | |
| 168 << " is not of a supported type (key: " << p.key() | |
| 169 << ", type: " << param_val.type(); | |
| 170 } | |
| 171 } | |
| 172 persistent_dict->Set(it.key(), std::move(params)); | |
| 173 continue; | |
| 174 } | |
| 175 | |
| 176 // Other base::Value types are not supported. | |
| 177 LOG(ERROR) << "A DCS feature mapped to an unsupported value. key: " | |
| 178 << it.key() << " type: " << it.value().type(); | |
| 179 } | |
| 180 | |
| 181 return persistent_dict; | |
| 182 } | |
| 183 | |
| 184 const std::unordered_set<int32_t>& GetDCSExperimentIds() { | |
| 185 DCHECK(g_experiment_ids_initialized); | |
| 186 return g_experiment_ids.Get(); | |
| 187 } | |
| 188 | |
| 189 void ResetCastFeaturesForTest() { | |
|
Alexei Svitkine (slow)
2017/04/21 19:48:01
Nit: I think convention is ForTesting rather than
slan
2017/04/21 21:43:27
Done.
| |
| 190 g_experiment_ids_initialized = false; | |
| 191 base::FeatureList::ClearInstanceForTesting(); | |
| 192 } | |
| 193 | |
| 194 } // namespace chromecast | |
| OLD | NEW |