• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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