• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 The Chromium Authors
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 "base/metrics/field_trial_params.h"
6 
7 #include <optional>
8 #include <set>
9 #include <string_view>
10 #include <utility>
11 #include <vector>
12 
13 #include "base/debug/crash_logging.h"
14 #include "base/debug/dump_without_crashing.h"
15 #include "base/feature_list.h"
16 #include "base/features.h"
17 #include "base/logging.h"
18 #include "base/metrics/field_trial.h"
19 #include "base/metrics/field_trial_param_associator.h"
20 #include "base/metrics/histogram_functions.h"
21 #include "base/metrics/metrics_hashes.h"
22 #include "base/strings/escape.h"
23 #include "base/strings/string_number_conversions.h"
24 #include "base/strings/string_split.h"
25 #include "base/strings/stringprintf.h"
26 #include "base/time/time_delta_from_string.h"
27 
28 namespace base {
29 
30 namespace internal {
31 
IsFeatureParamWithCacheEnabled()32 bool IsFeatureParamWithCacheEnabled() {
33   return FeatureList::IsEnabled(features::kFeatureParamWithCache);
34 }
35 
36 }  // namespace internal
37 
LogInvalidValue(const Feature & feature,const char * type,const std::string & param_name,const std::string & value_as_string,const std::string & default_value_as_string)38 void LogInvalidValue(const Feature& feature,
39                      const char* type,
40                      const std::string& param_name,
41                      const std::string& value_as_string,
42                      const std::string& default_value_as_string) {
43   UmaHistogramSparse("Variations.FieldTriamParamsLogInvalidValue",
44                      static_cast<int>(base::HashFieldTrialName(
45                          FeatureList::GetFieldTrial(feature)->trial_name())));
46   // To anyone noticing these crash dumps in the wild, these parameters come
47   // from server-side experiment configuration. If you're seeing an increase it
48   // is likely due to a bad experiment rollout rather than changes in the client
49   // code.
50   SCOPED_CRASH_KEY_STRING32("FieldTrialParams", "feature_name", feature.name);
51   SCOPED_CRASH_KEY_STRING32("FieldTrialParams", "param_name", param_name);
52   SCOPED_CRASH_KEY_STRING32("FieldTrialParams", "value", value_as_string);
53   SCOPED_CRASH_KEY_STRING32("FieldTrialParams", "default",
54                             default_value_as_string);
55   LOG(ERROR) << "Failed to parse field trial param " << param_name
56              << " with string value " << value_as_string << " under feature "
57              << feature.name << " into " << type
58              << ". Falling back to default value of "
59              << default_value_as_string;
60   base::debug::DumpWithoutCrashing();
61 }
62 
UnescapeValue(const std::string & value)63 std::string UnescapeValue(const std::string& value) {
64   return UnescapeURLComponent(
65       value, UnescapeRule::PATH_SEPARATORS |
66                  UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS);
67 }
68 
AssociateFieldTrialParams(const std::string & trial_name,const std::string & group_name,const FieldTrialParams & params)69 bool AssociateFieldTrialParams(const std::string& trial_name,
70                                const std::string& group_name,
71                                const FieldTrialParams& params) {
72   return FieldTrialParamAssociator::GetInstance()->AssociateFieldTrialParams(
73       trial_name, group_name, params);
74 }
75 
AssociateFieldTrialParamsFromString(const std::string & params_string,FieldTrialParamsDecodeStringFunc decode_data_func)76 bool AssociateFieldTrialParamsFromString(
77     const std::string& params_string,
78     FieldTrialParamsDecodeStringFunc decode_data_func) {
79   // Format: Trial1.Group1:k1/v1/k2/v2,Trial2.Group2:k1/v1/k2/v2
80   std::set<std::pair<std::string, std::string>> trial_groups;
81   for (std::string_view experiment_group :
82        SplitStringPiece(params_string, ",", TRIM_WHITESPACE, SPLIT_WANT_ALL)) {
83     std::vector<std::string_view> experiment = SplitStringPiece(
84         experiment_group, ":", TRIM_WHITESPACE, SPLIT_WANT_ALL);
85     if (experiment.size() != 2) {
86       DLOG(ERROR) << "Experiment and params should be separated by ':'";
87       return false;
88     }
89 
90     std::vector<std::string> group_parts =
91         SplitString(experiment[0], ".", TRIM_WHITESPACE, SPLIT_WANT_ALL);
92     if (group_parts.size() != 2) {
93       DLOG(ERROR) << "Trial and group name should be separated by '.'";
94       return false;
95     }
96 
97     std::vector<std::string> key_values =
98         SplitString(experiment[1], "/", TRIM_WHITESPACE, SPLIT_WANT_ALL);
99     if (key_values.size() % 2 != 0) {
100       DLOG(ERROR) << "Param name and param value should be separated by '/'";
101       return false;
102     }
103     std::string trial = decode_data_func(group_parts[0]);
104     std::string group = decode_data_func(group_parts[1]);
105     auto trial_group = std::make_pair(trial, group);
106     if (trial_groups.find(trial_group) != trial_groups.end()) {
107       DLOG(ERROR) << StringPrintf(
108           "A (trial, group) pair listed more than once. (%s, %s)",
109           trial.c_str(), group.c_str());
110       return false;
111     }
112     trial_groups.insert(trial_group);
113     std::map<std::string, std::string> params;
114     for (size_t i = 0; i < key_values.size(); i += 2) {
115       std::string key = decode_data_func(key_values[i]);
116       std::string value = decode_data_func(key_values[i + 1]);
117       params[key] = value;
118     }
119     bool result = AssociateFieldTrialParams(trial, group, params);
120     if (!result) {
121       DLOG(ERROR) << "Failed to associate field trial params for group \""
122                   << group << "\" in trial \"" << trial << "\"";
123       return false;
124     }
125   }
126   return true;
127 }
128 
GetFieldTrialParams(const std::string & trial_name,FieldTrialParams * params)129 bool GetFieldTrialParams(const std::string& trial_name,
130                          FieldTrialParams* params) {
131   FieldTrial* trial = FieldTrialList::Find(trial_name);
132   return FieldTrialParamAssociator::GetInstance()->GetFieldTrialParams(trial,
133                                                                        params);
134 }
135 
GetFieldTrialParamsByFeature(const Feature & feature,FieldTrialParams * params)136 bool GetFieldTrialParamsByFeature(const Feature& feature,
137                                   FieldTrialParams* params) {
138   if (!FeatureList::IsEnabled(feature)) {
139     return false;
140   }
141 
142   FieldTrial* trial = FeatureList::GetFieldTrial(feature);
143   return FieldTrialParamAssociator::GetInstance()->GetFieldTrialParams(trial,
144                                                                        params);
145 }
146 
GetFieldTrialParamValue(const std::string & trial_name,const std::string & param_name)147 std::string GetFieldTrialParamValue(const std::string& trial_name,
148                                     const std::string& param_name) {
149   FieldTrialParams params;
150   if (GetFieldTrialParams(trial_name, &params)) {
151     auto it = params.find(param_name);
152     if (it != params.end()) {
153       return it->second;
154     }
155   }
156   return std::string();
157 }
158 
GetFieldTrialParamValueByFeature(const Feature & feature,const std::string & param_name)159 std::string GetFieldTrialParamValueByFeature(const Feature& feature,
160                                              const std::string& param_name) {
161   FieldTrialParams params;
162   if (GetFieldTrialParamsByFeature(feature, &params)) {
163     auto it = params.find(param_name);
164     if (it != params.end()) {
165       return it->second;
166     }
167   }
168   return std::string();
169 }
170 
GetFieldTrialParamByFeatureAsString(const Feature & feature,const std::string & param_name,const std::string & default_value)171 std::string GetFieldTrialParamByFeatureAsString(
172     const Feature& feature,
173     const std::string& param_name,
174     const std::string& default_value) {
175   FieldTrialParams params;
176   if (!GetFieldTrialParamsByFeature(feature, &params)) {
177     return default_value;
178   }
179   auto it = params.find(param_name);
180   if (it == params.end()) {
181     return default_value;
182   }
183   return it->second;
184 }
185 
GetFieldTrialParamByFeatureAsInt(const Feature & feature,const std::string & param_name,int default_value)186 int GetFieldTrialParamByFeatureAsInt(const Feature& feature,
187                                      const std::string& param_name,
188                                      int default_value) {
189   std::string value_as_string =
190       GetFieldTrialParamValueByFeature(feature, param_name);
191   int value_as_int = 0;
192   if (!StringToInt(value_as_string, &value_as_int)) {
193     if (!value_as_string.empty()) {
194       LogInvalidValue(feature, "an int", param_name, value_as_string,
195                       base::NumberToString(default_value));
196     }
197     value_as_int = default_value;
198   }
199   return value_as_int;
200 }
201 
GetFieldTrialParamByFeatureAsDouble(const Feature & feature,const std::string & param_name,double default_value)202 double GetFieldTrialParamByFeatureAsDouble(const Feature& feature,
203                                            const std::string& param_name,
204                                            double default_value) {
205   std::string value_as_string =
206       GetFieldTrialParamValueByFeature(feature, param_name);
207   double value_as_double = 0;
208   if (!StringToDouble(value_as_string, &value_as_double)) {
209     if (!value_as_string.empty()) {
210       LogInvalidValue(feature, "a double", param_name, value_as_string,
211                       base::NumberToString(default_value));
212     }
213     value_as_double = default_value;
214   }
215   return value_as_double;
216 }
217 
GetFieldTrialParamByFeatureAsBool(const Feature & feature,const std::string & param_name,bool default_value)218 bool GetFieldTrialParamByFeatureAsBool(const Feature& feature,
219                                        const std::string& param_name,
220                                        bool default_value) {
221   std::string value_as_string =
222       GetFieldTrialParamValueByFeature(feature, param_name);
223   if (value_as_string == "true") {
224     return true;
225   }
226   if (value_as_string == "false") {
227     return false;
228   }
229 
230   if (!value_as_string.empty()) {
231     LogInvalidValue(feature, "a bool", param_name, value_as_string,
232                     default_value ? "true" : "false");
233   }
234   return default_value;
235 }
236 
GetFieldTrialParamByFeatureAsTimeDelta(const Feature & feature,const std::string & param_name,base::TimeDelta default_value)237 base::TimeDelta GetFieldTrialParamByFeatureAsTimeDelta(
238     const Feature& feature,
239     const std::string& param_name,
240     base::TimeDelta default_value) {
241   std::string value_as_string =
242       GetFieldTrialParamValueByFeature(feature, param_name);
243 
244   if (value_as_string.empty()) {
245     return default_value;
246   }
247 
248   std::optional<base::TimeDelta> ret = TimeDeltaFromString(value_as_string);
249   if (!ret.has_value()) {
250     LogInvalidValue(feature, "a base::TimeDelta", param_name, value_as_string,
251                     base::NumberToString(default_value.InSecondsF()) + " s");
252     return default_value;
253   }
254 
255   return ret.value();
256 }
257 
258 template <>
GetWithoutCache() const259 bool FeatureParam<bool>::GetWithoutCache() const {
260   return GetFieldTrialParamByFeatureAsBool(*feature, name, default_value);
261 }
262 
263 template <>
GetWithoutCache() const264 int FeatureParam<int>::GetWithoutCache() const {
265   return GetFieldTrialParamByFeatureAsInt(*feature, name, default_value);
266 }
267 
268 template <>
GetWithoutCache() const269 size_t FeatureParam<size_t>::GetWithoutCache() const {
270   return checked_cast<size_t>(GetFieldTrialParamByFeatureAsInt(
271       *feature, name, checked_cast<int>(default_value)));
272 }
273 
274 template <>
GetWithoutCache() const275 double FeatureParam<double>::GetWithoutCache() const {
276   return GetFieldTrialParamByFeatureAsDouble(*feature, name, default_value);
277 }
278 
279 template <>
GetWithoutCache() const280 std::string FeatureParam<std::string>::GetWithoutCache() const {
281   return GetFieldTrialParamByFeatureAsString(*feature, name, default_value);
282 }
283 
284 template <>
GetWithoutCache() const285 base::TimeDelta FeatureParam<base::TimeDelta>::GetWithoutCache() const {
286   return GetFieldTrialParamByFeatureAsTimeDelta(*feature, name, default_value);
287 }
288 
LogInvalidEnumValue(const Feature & feature,const std::string & param_name,const std::string & value_as_string,int default_value_as_int)289 void LogInvalidEnumValue(const Feature& feature,
290                          const std::string& param_name,
291                          const std::string& value_as_string,
292                          int default_value_as_int) {
293   LogInvalidValue(feature, "an enum", param_name, value_as_string,
294                   base::NumberToString(default_value_as_int));
295 }
296 
297 }  // namespace base
298