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