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