xref: /aosp_15_r20/external/grpc-grpc/src/core/lib/experiments/config.cc (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
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