xref: /aosp_15_r20/external/webrtc/system_wrappers/source/field_trial.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1 // Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
2 //
3 // Use of this source code is governed by a BSD-style license
4 // that can be found in the LICENSE file in the root of the source
5 // tree. An additional intellectual property rights grant can be found
6 // in the file PATENTS.  All contributing project authors may
7 // be found in the AUTHORS file in the root of the source tree.
8 //
9 
10 #include "system_wrappers/include/field_trial.h"
11 
12 #include <stddef.h>
13 
14 #include <map>
15 #include <string>
16 #include <utility>
17 
18 #include "absl/algorithm/container.h"
19 #include "absl/strings/string_view.h"
20 #include "experiments/registered_field_trials.h"
21 #include "rtc_base/checks.h"
22 #include "rtc_base/containers/flat_set.h"
23 #include "rtc_base/logging.h"
24 #include "rtc_base/string_encode.h"
25 
26 // Simple field trial implementation, which allows client to
27 // specify desired flags in InitFieldTrialsFromString.
28 namespace webrtc {
29 namespace field_trial {
30 
31 static const char* trials_init_string = NULL;
32 
33 namespace {
34 
35 constexpr char kPersistentStringSeparator = '/';
36 
TestKeys()37 flat_set<std::string>& TestKeys() {
38   static auto* test_keys = new flat_set<std::string>();
39   return *test_keys;
40 }
41 
42 // Validates the given field trial string.
43 //  E.g.:
44 //    "WebRTC-experimentFoo/Enabled/WebRTC-experimentBar/Enabled100kbps/"
45 //    Assigns the process to group "Enabled" on WebRTCExperimentFoo trial
46 //    and to group "Enabled100kbps" on WebRTCExperimentBar.
47 //
48 //  E.g. invalid config:
49 //    "WebRTC-experiment1/Enabled"  (note missing / separator at the end).
FieldTrialsStringIsValidInternal(const absl::string_view trials)50 bool FieldTrialsStringIsValidInternal(const absl::string_view trials) {
51   if (trials.empty())
52     return true;
53 
54   size_t next_item = 0;
55   std::map<absl::string_view, absl::string_view> field_trials;
56   while (next_item < trials.length()) {
57     size_t name_end = trials.find(kPersistentStringSeparator, next_item);
58     if (name_end == trials.npos || next_item == name_end)
59       return false;
60     size_t group_name_end =
61         trials.find(kPersistentStringSeparator, name_end + 1);
62     if (group_name_end == trials.npos || name_end + 1 == group_name_end)
63       return false;
64     absl::string_view name = trials.substr(next_item, name_end - next_item);
65     absl::string_view group_name =
66         trials.substr(name_end + 1, group_name_end - name_end - 1);
67 
68     next_item = group_name_end + 1;
69 
70     // Fail if duplicate with different group name.
71     if (field_trials.find(name) != field_trials.end() &&
72         field_trials.find(name)->second != group_name) {
73       return false;
74     }
75 
76     field_trials[name] = group_name;
77   }
78 
79   return true;
80 }
81 
82 }  // namespace
83 
FieldTrialsStringIsValid(absl::string_view trials_string)84 bool FieldTrialsStringIsValid(absl::string_view trials_string) {
85   return FieldTrialsStringIsValidInternal(trials_string);
86 }
87 
InsertOrReplaceFieldTrialStringsInMap(std::map<std::string,std::string> * fieldtrial_map,const absl::string_view trials_string)88 void InsertOrReplaceFieldTrialStringsInMap(
89     std::map<std::string, std::string>* fieldtrial_map,
90     const absl::string_view trials_string) {
91   if (FieldTrialsStringIsValidInternal(trials_string)) {
92     std::vector<absl::string_view> tokens = rtc::split(trials_string, '/');
93     // Skip last token which is empty due to trailing '/'.
94     for (size_t idx = 0; idx < tokens.size() - 1; idx += 2) {
95       (*fieldtrial_map)[std::string(tokens[idx])] =
96           std::string(tokens[idx + 1]);
97     }
98   } else {
99     RTC_DCHECK_NOTREACHED() << "Invalid field trials string:" << trials_string;
100   }
101 }
102 
MergeFieldTrialsStrings(absl::string_view first,absl::string_view second)103 std::string MergeFieldTrialsStrings(absl::string_view first,
104                                     absl::string_view second) {
105   std::map<std::string, std::string> fieldtrial_map;
106   InsertOrReplaceFieldTrialStringsInMap(&fieldtrial_map, first);
107   InsertOrReplaceFieldTrialStringsInMap(&fieldtrial_map, second);
108 
109   // Merge into fieldtrial string.
110   std::string merged = "";
111   for (auto const& fieldtrial : fieldtrial_map) {
112     merged += fieldtrial.first + '/' + fieldtrial.second + '/';
113   }
114   return merged;
115 }
116 
117 #ifndef WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT
FindFullName(absl::string_view name)118 std::string FindFullName(absl::string_view name) {
119 #if WEBRTC_STRICT_FIELD_TRIALS
120   RTC_DCHECK(absl::c_linear_search(kRegisteredFieldTrials, name) ||
121              TestKeys().contains(name))
122       << name << " is not registered.";
123 #endif
124 
125   if (trials_init_string == NULL)
126     return std::string();
127 
128   absl::string_view trials_string(trials_init_string);
129   if (trials_string.empty())
130     return std::string();
131 
132   size_t next_item = 0;
133   while (next_item < trials_string.length()) {
134     // Find next name/value pair in field trial configuration string.
135     size_t field_name_end =
136         trials_string.find(kPersistentStringSeparator, next_item);
137     if (field_name_end == trials_string.npos || field_name_end == next_item)
138       break;
139     size_t field_value_end =
140         trials_string.find(kPersistentStringSeparator, field_name_end + 1);
141     if (field_value_end == trials_string.npos ||
142         field_value_end == field_name_end + 1)
143       break;
144     absl::string_view field_name =
145         trials_string.substr(next_item, field_name_end - next_item);
146     absl::string_view field_value = trials_string.substr(
147         field_name_end + 1, field_value_end - field_name_end - 1);
148     next_item = field_value_end + 1;
149 
150     if (name == field_name)
151       return std::string(field_value);
152   }
153   return std::string();
154 }
155 #endif  // WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT
156 
157 // Optionally initialize field trial from a string.
InitFieldTrialsFromString(const char * trials_string)158 void InitFieldTrialsFromString(const char* trials_string) {
159   RTC_LOG(LS_INFO) << "Setting field trial string:" << trials_string;
160   if (trials_string) {
161     RTC_DCHECK(FieldTrialsStringIsValidInternal(trials_string))
162         << "Invalid field trials string:" << trials_string;
163   };
164   trials_init_string = trials_string;
165 }
166 
GetFieldTrialString()167 const char* GetFieldTrialString() {
168   return trials_init_string;
169 }
170 
FieldTrialsAllowedInScopeForTesting(flat_set<std::string> keys)171 FieldTrialsAllowedInScopeForTesting::FieldTrialsAllowedInScopeForTesting(
172     flat_set<std::string> keys) {
173   TestKeys() = std::move(keys);
174 }
175 
~FieldTrialsAllowedInScopeForTesting()176 FieldTrialsAllowedInScopeForTesting::~FieldTrialsAllowedInScopeForTesting() {
177   TestKeys().clear();
178 }
179 
180 }  // namespace field_trial
181 }  // namespace webrtc
182