1 // Copyright 2022 gRPC authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "src/core/lib/experiments/config.h"
16
17 #include <grpc/support/port_platform.h>
18 #include <string.h>
19
20 #include <algorithm>
21 #include <atomic>
22 #include <map>
23 #include <string>
24 #include <utility>
25 #include <vector>
26
27 #include "absl/functional/any_invocable.h"
28 #include "absl/log/check.h"
29 #include "absl/log/log.h"
30 #include "absl/strings/str_join.h"
31 #include "absl/strings/str_split.h"
32 #include "absl/strings/string_view.h"
33 #include "absl/strings/strip.h"
34 #include "src/core/config/config_vars.h"
35 #include "src/core/lib/experiments/experiments.h"
36 #include "src/core/util/crash.h" // IWYU pragma: keep
37 #include "src/core/util/no_destruct.h"
38
39 #ifndef GRPC_EXPERIMENTS_ARE_FINAL
40 namespace grpc_core {
41
42 namespace {
43 struct Experiments {
44 bool enabled[kNumExperiments];
45 };
46
47 struct ForcedExperiment {
48 bool forced = false;
49 bool value;
50 };
51
ForcedExperiments()52 ForcedExperiment* ForcedExperiments() {
53 static NoDestruct<ForcedExperiment> forced_experiments[kNumExperiments];
54 return &**forced_experiments;
55 }
56
Loaded()57 std::atomic<bool>* Loaded() {
58 static NoDestruct<std::atomic<bool>> loaded(false);
59 return &*loaded;
60 }
61
62 absl::AnyInvocable<bool(struct ExperimentMetadata)>* g_check_constraints_cb =
63 nullptr;
64
65 class TestExperiments {
66 public:
TestExperiments(const ExperimentMetadata * experiment_metadata,size_t num_experiments)67 TestExperiments(const ExperimentMetadata* experiment_metadata,
68 size_t num_experiments)
69 : enabled_(num_experiments) {
70 for (size_t i = 0; i < num_experiments; i++) {
71 if (g_check_constraints_cb != nullptr) {
72 enabled_[i] = (*g_check_constraints_cb)(experiment_metadata[i]);
73 } else {
74 enabled_[i] = experiment_metadata[i].default_value;
75 }
76 }
77 // For each comma-separated experiment in the global config:
78 for (auto experiment : absl::StrSplit(ConfigVars::Get().Experiments(), ',',
79 absl::SkipWhitespace())) {
80 // Enable unless prefixed with '-' (=> disable).
81 bool enable = !absl::ConsumePrefix(&experiment, "-");
82 // See if we can find the experiment in the list in this binary.
83 for (size_t i = 0; i < num_experiments; i++) {
84 if (experiment == experiment_metadata[i].name) {
85 enabled_[i] = enable;
86 break;
87 }
88 }
89 }
90 }
91
92 // Overloading [] operator to access elements in array style
operator [](int index) const93 bool operator[](int index) const { return enabled_[index]; }
94
95 private:
96 std::vector<bool> enabled_;
97 };
98
99 TestExperiments* g_test_experiments = nullptr;
100
LoadExperimentsFromConfigVariableInner()101 GPR_ATTRIBUTE_NOINLINE Experiments LoadExperimentsFromConfigVariableInner() {
102 // Set defaults from metadata.
103 Experiments experiments;
104 for (size_t i = 0; i < kNumExperiments; i++) {
105 if (!ForcedExperiments()[i].forced) {
106 if (g_check_constraints_cb != nullptr) {
107 experiments.enabled[i] =
108 (*g_check_constraints_cb)(g_experiment_metadata[i]);
109 } else {
110 experiments.enabled[i] = g_experiment_metadata[i].default_value;
111 }
112 } else {
113 experiments.enabled[i] = ForcedExperiments()[i].value;
114 }
115 }
116 // For each comma-separated experiment in the global config:
117 for (auto experiment : absl::StrSplit(ConfigVars::Get().Experiments(), ',',
118 absl::SkipWhitespace())) {
119 // Enable unless prefixed with '-' (=> disable).
120 bool enable = true;
121 if (experiment[0] == '-') {
122 enable = false;
123 experiment.remove_prefix(1);
124 }
125 // See if we can find the experiment in the list in this binary.
126 bool found = false;
127 for (size_t i = 0; i < kNumExperiments; i++) {
128 if (experiment == g_experiment_metadata[i].name) {
129 experiments.enabled[i] = enable;
130 found = true;
131 break;
132 }
133 }
134 // If not found log an error, but don't take any other action.
135 // Allows us an easy path to disabling experiments.
136 if (!found) {
137 LOG(ERROR) << "Unknown experiment: " << experiment;
138 }
139 }
140 for (size_t i = 0; i < kNumExperiments; i++) {
141 // If required experiments are not enabled, disable this one too.
142 for (size_t j = 0; j < g_experiment_metadata[i].num_required_experiments;
143 j++) {
144 // Require that we can check dependent requirements with a linear sweep
145 // (implies the experiments generator must DAG sort the experiments)
146 CHECK(g_experiment_metadata[i].required_experiments[j] < i);
147 if (!experiments
148 .enabled[g_experiment_metadata[i].required_experiments[j]]) {
149 experiments.enabled[i] = false;
150 }
151 }
152 }
153 return experiments;
154 }
155
LoadExperimentsFromConfigVariable()156 Experiments LoadExperimentsFromConfigVariable() {
157 Loaded()->store(true, std::memory_order_relaxed);
158 return LoadExperimentsFromConfigVariableInner();
159 }
160
ExperimentsSingleton()161 Experiments& ExperimentsSingleton() {
162 // One time initialization:
163 static NoDestruct<Experiments> experiments{
164 LoadExperimentsFromConfigVariable()};
165 return *experiments;
166 }
167 } // namespace
168
TestOnlyReloadExperimentsFromConfigVariables()169 void TestOnlyReloadExperimentsFromConfigVariables() {
170 ExperimentFlags::TestOnlyClear();
171 ExperimentsSingleton() = LoadExperimentsFromConfigVariable();
172 PrintExperimentsList();
173 }
174
LoadTestOnlyExperimentsFromMetadata(const ExperimentMetadata * experiment_metadata,size_t num_experiments)175 void LoadTestOnlyExperimentsFromMetadata(
176 const ExperimentMetadata* experiment_metadata, size_t num_experiments) {
177 g_test_experiments =
178 new TestExperiments(experiment_metadata, num_experiments);
179 }
180
181 std::atomic<uint64_t>
182 ExperimentFlags::experiment_flags_[kNumExperimentFlagsWords];
183
LoadFlagsAndCheck(size_t experiment_id)184 bool ExperimentFlags::LoadFlagsAndCheck(size_t experiment_id) {
185 static_assert(kNumExperiments < kNumExperimentFlagsWords * kFlagsPerWord,
186 "kNumExperiments must be less than "
187 "kNumExperimentFlagsWords*kFlagsPerWord; if this fails then "
188 "make kNumExperimentFlagsWords bigger.");
189 const auto& experiments = ExperimentsSingleton();
190 uint64_t building[kNumExperimentFlagsWords];
191 for (size_t i = 0; i < kNumExperimentFlagsWords; i++) {
192 building[i] = kLoadedFlag;
193 }
194 for (size_t i = 0; i < kNumExperiments; i++) {
195 if (!experiments.enabled[i]) continue;
196 auto bit = i % kFlagsPerWord;
197 auto word = i / kFlagsPerWord;
198 building[word] |= 1ull << bit;
199 }
200 for (size_t i = 0; i < kNumExperimentFlagsWords; i++) {
201 experiment_flags_[i].store(building[i], std::memory_order_relaxed);
202 }
203 return experiments.enabled[experiment_id];
204 }
205
TestOnlyClear()206 void ExperimentFlags::TestOnlyClear() {
207 for (size_t i = 0; i < kNumExperimentFlagsWords; i++) {
208 experiment_flags_[i].store(0, std::memory_order_relaxed);
209 }
210 }
211
IsExperimentEnabledInConfiguration(size_t experiment_id)212 bool IsExperimentEnabledInConfiguration(size_t experiment_id) {
213 return LoadExperimentsFromConfigVariableInner().enabled[experiment_id];
214 }
215
IsTestExperimentEnabled(size_t experiment_id)216 bool IsTestExperimentEnabled(size_t experiment_id) {
217 return (*g_test_experiments)[experiment_id];
218 }
219
220 #define GRPC_EXPERIMENT_LOG VLOG(2)
221
PrintExperimentsList()222 void PrintExperimentsList() {
223 std::map<std::string, std::string> experiment_status;
224 std::set<std::string> defaulted_on_experiments;
225 for (size_t i = 0; i < kNumExperiments; i++) {
226 const char* name = g_experiment_metadata[i].name;
227 const bool enabled = IsExperimentEnabled(i);
228 const bool default_enabled = g_experiment_metadata[i].default_value;
229 const bool forced = ForcedExperiments()[i].forced;
230 if (!default_enabled && !enabled) continue;
231 if (default_enabled && enabled) {
232 defaulted_on_experiments.insert(name);
233 continue;
234 }
235 if (enabled) {
236 if (g_check_constraints_cb != nullptr &&
237 (*g_check_constraints_cb)(g_experiment_metadata[i])) {
238 experiment_status[name] = "on:constraints";
239 continue;
240 }
241 if (forced && ForcedExperiments()[i].value) {
242 experiment_status[name] = "on:forced";
243 continue;
244 }
245 experiment_status[name] = "on";
246 } else {
247 if (forced && !ForcedExperiments()[i].value) {
248 experiment_status[name] = "off:forced";
249 continue;
250 }
251 experiment_status[name] = "off";
252 }
253 }
254 if (experiment_status.empty()) {
255 if (!defaulted_on_experiments.empty()) {
256 GRPC_EXPERIMENT_LOG << "gRPC experiments enabled: "
257 << absl::StrJoin(defaulted_on_experiments, ", ");
258 }
259 } else {
260 if (defaulted_on_experiments.empty()) {
261 GRPC_EXPERIMENT_LOG << "gRPC experiments: "
262 << absl::StrJoin(experiment_status, ", ",
263 absl::PairFormatter(":"));
264 } else {
265 GRPC_EXPERIMENT_LOG << "gRPC experiments: "
266 << absl::StrJoin(experiment_status, ", ",
267 absl::PairFormatter(":"))
268 << "; default-enabled: "
269 << absl::StrJoin(defaulted_on_experiments, ", ");
270 }
271 }
272 }
273
ForceEnableExperiment(absl::string_view experiment,bool enable)274 void ForceEnableExperiment(absl::string_view experiment, bool enable) {
275 CHECK(Loaded()->load(std::memory_order_relaxed) == false);
276 for (size_t i = 0; i < kNumExperiments; i++) {
277 if (g_experiment_metadata[i].name != experiment) continue;
278 if (ForcedExperiments()[i].forced) {
279 CHECK(ForcedExperiments()[i].value == enable);
280 } else {
281 ForcedExperiments()[i].forced = true;
282 ForcedExperiments()[i].value = enable;
283 }
284 return;
285 }
286 LOG(INFO) << "gRPC EXPERIMENT " << experiment << " not found to force "
287 << (enable ? "enable" : "disable");
288 }
289
RegisterExperimentConstraintsValidator(absl::AnyInvocable<bool (struct ExperimentMetadata)> check_constraints_cb)290 void RegisterExperimentConstraintsValidator(
291 absl::AnyInvocable<bool(struct ExperimentMetadata)> check_constraints_cb) {
292 g_check_constraints_cb =
293 new absl::AnyInvocable<bool(struct ExperimentMetadata)>(
294 std::move(check_constraints_cb));
295 }
296
297 } // namespace grpc_core
298 #else
299 namespace grpc_core {
PrintExperimentsList()300 void PrintExperimentsList() {}
ForceEnableExperiment(absl::string_view experiment_name,bool)301 void ForceEnableExperiment(absl::string_view experiment_name, bool) {
302 Crash(absl::StrCat("ForceEnableExperiment(\"", experiment_name,
303 "\") called in final build"));
304 }
305
RegisterExperimentConstraintsValidator(absl::AnyInvocable<bool (struct ExperimentMetadata)>)306 void RegisterExperimentConstraintsValidator(
307 absl::AnyInvocable<
308 bool(struct ExperimentMetadata)> /*check_constraints_cb*/) {}
309
310 } // namespace grpc_core
311 #endif
312