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