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 <grpc/support/port_platform.h>
16
17 #include "src/core/lib/experiments/config.h"
18
19 #include <string.h>
20
21 #include <algorithm>
22 #include <atomic>
23 #include <map>
24 #include <string>
25 #include <utility>
26
27 #include "absl/functional/any_invocable.h"
28 #include "absl/strings/str_join.h"
29 #include "absl/strings/str_split.h"
30 #include "absl/strings/string_view.h"
31 #include "absl/strings/strip.h"
32
33 #include <grpc/support/log.h>
34
35 #include "src/core/lib/config/config_vars.h"
36 #include "src/core/lib/experiments/experiments.h"
37 #include "src/core/lib/gprpp/crash.h" // IWYU pragma: keep
38 #include "src/core/lib/gprpp/no_destruct.h"
39
40 #ifndef GRPC_EXPERIMENTS_ARE_FINAL
41 namespace grpc_core {
42
43 namespace {
44 struct Experiments {
45 bool enabled[kNumExperiments];
46 };
47
48 struct ForcedExperiment {
49 bool forced = false;
50 bool value;
51 };
52
ForcedExperiments()53 ForcedExperiment* ForcedExperiments() {
54 static NoDestruct<ForcedExperiment> forced_experiments[kNumExperiments];
55 return &**forced_experiments;
56 }
57
Loaded()58 std::atomic<bool>* Loaded() {
59 static NoDestruct<std::atomic<bool>> loaded(false);
60 return &*loaded;
61 }
62
63 absl::AnyInvocable<bool(struct ExperimentMetadata)>* g_check_constraints_cb =
64 nullptr;
65
66 class TestExperiments {
67 public:
TestExperiments(const ExperimentMetadata * experiment_metadata,size_t num_experiments)68 TestExperiments(const ExperimentMetadata* experiment_metadata,
69 size_t num_experiments) {
70 enabled_ = new bool[num_experiments];
71 for (size_t i = 0; i < num_experiments; i++) {
72 if (g_check_constraints_cb != nullptr) {
73 enabled_[i] = (*g_check_constraints_cb)(experiment_metadata[i]);
74 } else {
75 enabled_[i] = experiment_metadata[i].default_value;
76 }
77 }
78 // For each comma-separated experiment in the global config:
79 for (auto experiment : absl::StrSplit(ConfigVars::Get().Experiments(), ',',
80 absl::SkipWhitespace())) {
81 // Enable unless prefixed with '-' (=> disable).
82 bool enable = !absl::ConsumePrefix(&experiment, "-");
83 // See if we can find the experiment in the list in this binary.
84 for (size_t i = 0; i < num_experiments; i++) {
85 if (experiment == experiment_metadata[i].name) {
86 enabled_[i] = enable;
87 break;
88 }
89 }
90 }
91 }
92
93 // Overloading [] operator to access elements in array style
operator [](int index)94 bool operator[](int index) { return enabled_[index]; }
95
~TestExperiments()96 ~TestExperiments() { delete enabled_; }
97
98 private:
99 bool* enabled_;
100 };
101
102 TestExperiments* g_test_experiments = nullptr;
103
LoadExperimentsFromConfigVariableInner()104 GPR_ATTRIBUTE_NOINLINE Experiments LoadExperimentsFromConfigVariableInner() {
105 // Set defaults from metadata.
106 Experiments experiments;
107 for (size_t i = 0; i < kNumExperiments; i++) {
108 if (!ForcedExperiments()[i].forced) {
109 if (g_check_constraints_cb != nullptr) {
110 experiments.enabled[i] =
111 (*g_check_constraints_cb)(g_experiment_metadata[i]);
112 } else {
113 experiments.enabled[i] = g_experiment_metadata[i].default_value;
114 }
115 } else {
116 experiments.enabled[i] = ForcedExperiments()[i].value;
117 }
118 }
119 // For each comma-separated experiment in the global config:
120 for (auto experiment : absl::StrSplit(ConfigVars::Get().Experiments(), ',',
121 absl::SkipWhitespace())) {
122 // Enable unless prefixed with '-' (=> disable).
123 bool enable = true;
124 if (experiment[0] == '-') {
125 enable = false;
126 experiment.remove_prefix(1);
127 }
128 // See if we can find the experiment in the list in this binary.
129 bool found = false;
130 for (size_t i = 0; i < kNumExperiments; i++) {
131 if (experiment == g_experiment_metadata[i].name) {
132 experiments.enabled[i] = enable;
133 found = true;
134 break;
135 }
136 }
137 // If not found log an error, but don't take any other action.
138 // Allows us an easy path to disabling experiments.
139 if (!found) {
140 gpr_log(GPR_ERROR, "Unknown experiment: %s",
141 std::string(experiment).c_str());
142 }
143 }
144 for (size_t i = 0; i < kNumExperiments; i++) {
145 // If required experiments are not enabled, disable this one too.
146 for (size_t j = 0; j < g_experiment_metadata[i].num_required_experiments;
147 j++) {
148 // Require that we can check dependent requirements with a linear sweep
149 // (implies the experiments generator must DAG sort the experiments)
150 GPR_ASSERT(g_experiment_metadata[i].required_experiments[j] < i);
151 if (!experiments
152 .enabled[g_experiment_metadata[i].required_experiments[j]]) {
153 experiments.enabled[i] = false;
154 }
155 }
156 }
157 return experiments;
158 }
159
LoadExperimentsFromConfigVariable()160 Experiments LoadExperimentsFromConfigVariable() {
161 Loaded()->store(true, std::memory_order_relaxed);
162 return LoadExperimentsFromConfigVariableInner();
163 }
164
ExperimentsSingleton()165 Experiments& ExperimentsSingleton() {
166 // One time initialization:
167 static NoDestruct<Experiments> experiments{
168 LoadExperimentsFromConfigVariable()};
169 return *experiments;
170 }
171 } // namespace
172
TestOnlyReloadExperimentsFromConfigVariables()173 void TestOnlyReloadExperimentsFromConfigVariables() {
174 ExperimentsSingleton() = LoadExperimentsFromConfigVariable();
175 PrintExperimentsList();
176 }
177
LoadTestOnlyExperimentsFromMetadata(const ExperimentMetadata * experiment_metadata,size_t num_experiments)178 void LoadTestOnlyExperimentsFromMetadata(
179 const ExperimentMetadata* experiment_metadata, size_t num_experiments) {
180 g_test_experiments =
181 new TestExperiments(experiment_metadata, num_experiments);
182 }
183
IsExperimentEnabled(size_t experiment_id)184 bool IsExperimentEnabled(size_t experiment_id) {
185 return ExperimentsSingleton().enabled[experiment_id];
186 }
187
IsExperimentEnabledInConfiguration(size_t experiment_id)188 bool IsExperimentEnabledInConfiguration(size_t experiment_id) {
189 return LoadExperimentsFromConfigVariableInner().enabled[experiment_id];
190 }
191
IsTestExperimentEnabled(size_t experiment_id)192 bool IsTestExperimentEnabled(size_t experiment_id) {
193 return (*g_test_experiments)[experiment_id];
194 }
195
PrintExperimentsList()196 void PrintExperimentsList() {
197 std::map<std::string, std::string> experiment_status;
198 std::set<std::string> defaulted_on_experiments;
199 for (size_t i = 0; i < kNumExperiments; i++) {
200 const char* name = g_experiment_metadata[i].name;
201 const bool enabled = IsExperimentEnabled(i);
202 const bool default_enabled = g_experiment_metadata[i].default_value;
203 const bool forced = ForcedExperiments()[i].forced;
204 if (!default_enabled && !enabled) continue;
205 if (default_enabled && enabled) {
206 defaulted_on_experiments.insert(name);
207 continue;
208 }
209 if (enabled) {
210 if (g_check_constraints_cb != nullptr &&
211 (*g_check_constraints_cb)(g_experiment_metadata[i])) {
212 experiment_status[name] = "on:constraints";
213 continue;
214 }
215 if (forced && ForcedExperiments()[i].value) {
216 experiment_status[name] = "on:forced";
217 continue;
218 }
219 experiment_status[name] = "on";
220 } else {
221 if (forced && !ForcedExperiments()[i].value) {
222 experiment_status[name] = "off:forced";
223 continue;
224 }
225 experiment_status[name] = "off";
226 }
227 }
228 if (experiment_status.empty()) {
229 if (!defaulted_on_experiments.empty()) {
230 gpr_log(GPR_INFO, "gRPC experiments enabled: %s",
231 absl::StrJoin(defaulted_on_experiments, ", ").c_str());
232 }
233 } else {
234 if (defaulted_on_experiments.empty()) {
235 gpr_log(GPR_INFO, "gRPC experiments: %s",
236 absl::StrJoin(experiment_status, ", ", absl::PairFormatter(":"))
237 .c_str());
238 } else {
239 gpr_log(GPR_INFO, "gRPC experiments: %s; default-enabled: %s",
240 absl::StrJoin(experiment_status, ", ", absl::PairFormatter(":"))
241 .c_str(),
242 absl::StrJoin(defaulted_on_experiments, ", ").c_str());
243 }
244 }
245 }
246
ForceEnableExperiment(absl::string_view experiment,bool enable)247 void ForceEnableExperiment(absl::string_view experiment, bool enable) {
248 GPR_ASSERT(Loaded()->load(std::memory_order_relaxed) == false);
249 for (size_t i = 0; i < kNumExperiments; i++) {
250 if (g_experiment_metadata[i].name != experiment) continue;
251 if (ForcedExperiments()[i].forced) {
252 GPR_ASSERT(ForcedExperiments()[i].value == enable);
253 } else {
254 ForcedExperiments()[i].forced = true;
255 ForcedExperiments()[i].value = enable;
256 }
257 return;
258 }
259 gpr_log(GPR_INFO, "gRPC EXPERIMENT %s not found to force %s",
260 std::string(experiment).c_str(), enable ? "enable" : "disable");
261 }
262
RegisterExperimentConstraintsValidator(absl::AnyInvocable<bool (struct ExperimentMetadata)> check_constraints_cb)263 void RegisterExperimentConstraintsValidator(
264 absl::AnyInvocable<bool(struct ExperimentMetadata)> check_constraints_cb) {
265 g_check_constraints_cb =
266 new absl::AnyInvocable<bool(struct ExperimentMetadata)>(
267 std::move(check_constraints_cb));
268 }
269
270 } // namespace grpc_core
271 #else
272 namespace grpc_core {
PrintExperimentsList()273 void PrintExperimentsList() {}
ForceEnableExperiment(absl::string_view experiment_name,bool)274 void ForceEnableExperiment(absl::string_view experiment_name, bool) {
275 Crash(absl::StrCat("ForceEnableExperiment(\"", experiment_name,
276 "\") called in final build"));
277 }
278
RegisterExperimentConstraintsValidator(absl::AnyInvocable<bool (struct ExperimentMetadata)>)279 void RegisterExperimentConstraintsValidator(
280 absl::AnyInvocable<
281 bool(struct ExperimentMetadata)> /*check_constraints_cb*/) {}
282
283 } // namespace grpc_core
284 #endif
285