• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2
3# Copyright 2023 gRPC authors.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17from __future__ import print_function
18
19import binascii
20import collections
21import ctypes
22import datetime
23import json
24import math
25import os
26import re
27import sys
28
29import yaml
30
31with open("src/core/config/config_vars.yaml") as f:
32    attrs = yaml.safe_load(f.read())
33
34error = False
35today = datetime.date.today()
36two_quarters_from_now = today + datetime.timedelta(days=180)
37for attr in attrs:
38    if "name" not in attr:
39        print("config has no name: %r" % attr)
40        error = True
41        continue
42    if (
43        "experiment" in attr["name"] and "experimental" not in attr["name"]
44    ) and attr["name"] != "experiments":
45        print("use experiment system for experiments")
46        error = True
47    if "description" not in attr:
48        print("no description for %s" % attr["name"])
49        error = True
50    if "default" not in attr:
51        print("no default for %s" % attr["name"])
52        error = True
53
54if error:
55    sys.exit(1)
56
57
58def c_str(s, encoding="ascii"):
59    if s is None:
60        return '""'
61    if isinstance(s, str):
62        s = s.encode(encoding)
63    result = ""
64    for c in s:
65        c = chr(c) if isinstance(c, int) else c
66        if not (32 <= ord(c) < 127) or c in ("\\", '"'):
67            result += "\\%03o" % ord(c)
68        else:
69            result += c
70    return '"' + result + '"'
71
72
73def snake_to_pascal(s):
74    return "".join(x.capitalize() for x in s.split("_"))
75
76
77# utility: print a big comment block into a set of files
78def put_banner(files, banner):
79    for f in files:
80        for line in banner:
81            print("// %s" % line, file=f)
82        print(file=f)
83
84
85def put_copyright(file):
86    # copy-paste copyright notice from this file
87    with open(sys.argv[0]) as my_source:
88        copyright = []
89        for line in my_source:
90            if line[0] != "#":
91                break
92        for line in my_source:
93            if line[0] == "#":
94                copyright.append(line)
95                break
96        for line in my_source:
97            if line[0] != "#":
98                break
99            copyright.append(line)
100        put_banner([file], [line[2:].rstrip() for line in copyright])
101
102
103RETURN_TYPE = {
104    "int": "int32_t",
105    "string": "absl::string_view",
106    "comma_separated_string": "absl::string_view",
107    "bool": "bool",
108}
109
110MEMBER_TYPE = {
111    "int": "int32_t",
112    "string": "std::string",
113    "comma_separated_string": "std::string",
114    "bool": "bool",
115}
116
117FLAG_TYPE = {
118    "int": "absl::optional<int32_t>",
119    "string": "absl::optional<std::string>",
120    "comma_separated_string": "std::vector<std::string>",
121    "bool": "absl::optional<bool>",
122}
123
124PROTO_TYPE = {
125    "int": "int32",
126    "string": "string",
127    "comma_separated_string": "string",
128    "bool": "bool",
129}
130
131SORT_ORDER_FOR_PACKING = {
132    "int": 0,
133    "bool": 1,
134    "string": 2,
135    "comma_separated_string": 3,
136}
137
138
139def bool_default_value(x, name):
140    if x == True:
141        return "true"
142    if x == False:
143        return "false"
144    if isinstance(x, str) and x.startswith("$"):
145        return x[1:]
146    return x
147
148
149def int_default_value(x, name):
150    if isinstance(x, str) and x.startswith("$"):
151        return x[1:]
152    return x
153
154
155def string_default_value(x, name):
156    if x is None:
157        return '""'
158    if x.startswith("$"):
159        return x[1:]
160    return c_str(x)
161
162
163DEFAULT_VALUE = {
164    "int": int_default_value,
165    "bool": bool_default_value,
166    "string": string_default_value,
167    "comma_separated_string": string_default_value,
168}
169
170TO_STRING = {
171    "int": "$",
172    "bool": '$?"true":"false"',
173    "string": '"\\"", absl::CEscape($), "\\""',
174    "comma_separated_string": '"\\"", absl::CEscape($), "\\""',
175}
176
177attrs_in_packing_order = sorted(
178    attrs, key=lambda a: SORT_ORDER_FOR_PACKING[a["type"]]
179)
180
181with open("test/core/test_util/fuzz_config_vars.proto", "w") as P:
182    put_copyright(P)
183
184    put_banner(
185        [P],
186        [
187            "",
188            "Automatically generated by tools/codegen/core/gen_config_vars.py",
189            "",
190        ],
191    )
192
193    print('syntax = "proto3";', file=P)
194    print(file=P)
195    print("package grpc.testing;", file=P)
196    print(file=P)
197    print("message FuzzConfigVars {", file=P)
198    for attr in attrs_in_packing_order:
199        if attr.get("fuzz", False) == False:
200            continue
201        print(
202            "  optional %s %s = %d;"
203            % (
204                attr.get("fuzz_type", PROTO_TYPE[attr["type"]]),
205                attr["name"],
206                binascii.crc32(attr["name"].encode("ascii")) & 0x1FFFFFFF,
207            ),
208            file=P,
209        )
210    print("};", file=P)
211
212with open("test/core/test_util/fuzz_config_vars.h", "w") as H:
213    put_copyright(H)
214
215    put_banner(
216        [H],
217        [
218            "",
219            "Automatically generated by tools/codegen/core/gen_config_vars.py",
220            "",
221        ],
222    )
223
224    print("#ifndef GRPC_TEST_CORE_TEST_UTIL_FUZZ_CONFIG_VARS_H", file=H)
225    print("#define GRPC_TEST_CORE_TEST_UTIL_FUZZ_CONFIG_VARS_H", file=H)
226    print(file=H)
227    print("#include <grpc/support/port_platform.h>", file=H)
228    print(file=H)
229    print('#include "test/core/test_util/fuzz_config_vars.pb.h"', file=H)
230    print('#include "src/core/config/config_vars.h"', file=H)
231    print(file=H)
232    print("namespace grpc_core {", file=H)
233    print(file=H)
234    print(
235        (
236            "ConfigVars::Overrides OverridesFromFuzzConfigVars(const"
237            " grpc::testing::FuzzConfigVars& vars);"
238        ),
239        file=H,
240    )
241    print(
242        "void ApplyFuzzConfigVars(const grpc::testing::FuzzConfigVars& vars);",
243        file=H,
244    )
245    print(file=H)
246    print("}  // namespace grpc_core", file=H)
247    print(file=H)
248    print("#endif  // GRPC_TEST_CORE_TEST_UTIL_FUZZ_CONFIG_VARS_H", file=H)
249
250with open("test/core/test_util/fuzz_config_vars.cc", "w") as C:
251    put_copyright(C)
252
253    put_banner(
254        [C],
255        [
256            "",
257            "Automatically generated by tools/codegen/core/gen_config_vars.py",
258            "",
259        ],
260    )
261
262    print('#include "test/core/test_util/fuzz_config_vars.h"', file=C)
263    print('#include "test/core/test_util/fuzz_config_vars_helpers.h"', file=C)
264    print(file=C)
265    print("namespace grpc_core {", file=C)
266    print(file=C)
267    print(
268        (
269            "ConfigVars::Overrides OverridesFromFuzzConfigVars(const"
270            " grpc::testing::FuzzConfigVars& vars) {"
271        ),
272        file=C,
273    )
274    print("  ConfigVars::Overrides overrides;", file=C)
275    for attr in attrs_in_packing_order:
276        fuzz = attr.get("fuzz", False)
277        if not fuzz:
278            continue
279        print("  if (vars.has_%s()) {" % attr["name"], file=C)
280        if isinstance(fuzz, str):
281            print(
282                "    overrides.%s = %s(vars.%s());"
283                % (attr["name"], fuzz, attr["name"]),
284                file=C,
285            )
286        else:
287            print(
288                "    overrides.%s = vars.%s();" % (attr["name"], attr["name"]),
289                file=C,
290            )
291        print("  }", file=C)
292    print("  return overrides;", file=C)
293    print("}", file=C)
294    print(
295        "void ApplyFuzzConfigVars(const grpc::testing::FuzzConfigVars& vars) {",
296        file=C,
297    )
298    print(
299        "  ConfigVars::SetOverrides(OverridesFromFuzzConfigVars(vars));", file=C
300    )
301    print("}", file=C)
302    print(file=C)
303    print("}  // namespace grpc_core", file=C)
304
305with open("src/core/config/config_vars.h", "w") as H:
306    put_copyright(H)
307
308    put_banner(
309        [H],
310        [
311            "",
312            "Automatically generated by tools/codegen/core/gen_config_vars.py",
313            "",
314        ],
315    )
316
317    print("#ifndef GRPC_SRC_CORE_LIB_CONFIG_CONFIG_VARS_H", file=H)
318    print("#define GRPC_SRC_CORE_LIB_CONFIG_CONFIG_VARS_H", file=H)
319    print(file=H)
320    print("#include <grpc/support/port_platform.h>", file=H)
321    print(file=H)
322    print("#include <string>", file=H)
323    print("#include <atomic>", file=H)
324    print("#include <stdint.h>", file=H)
325    print('#include "absl/strings/string_view.h"', file=H)
326    print('#include "absl/types/optional.h"', file=H)
327    print(file=H)
328    print("namespace grpc_core {", file=H)
329    print(file=H)
330    print("class GPR_DLL ConfigVars {", file=H)
331    print(" public:", file=H)
332    print("  struct Overrides {", file=H)
333    for attr in attrs_in_packing_order:
334        print(
335            "    absl::optional<%s> %s;"
336            % (MEMBER_TYPE[attr["type"]], attr["name"]),
337            file=H,
338        )
339    print("  };", file=H)
340    print("  ConfigVars(const ConfigVars&) = delete;", file=H)
341    print("  ConfigVars& operator=(const ConfigVars&) = delete;", file=H)
342    print(
343        "  // Get the core configuration; if it does not exist, create it.",
344        file=H,
345    )
346    print("  static const ConfigVars& Get() {", file=H)
347    print("    auto* p = config_vars_.load(std::memory_order_acquire);", file=H)
348    print("    if (p != nullptr) return *p;", file=H)
349    print("    return Load();", file=H)
350    print("  }", file=H)
351    print("  static void SetOverrides(const Overrides& overrides);", file=H)
352    print(
353        "  // Drop the config vars. Users must ensure no other threads are",
354        file=H,
355    )
356    print("  // accessing the configuration.", file=H)
357    print("  static void Reset();", file=H)
358    print("  std::string ToString() const;", file=H)
359    for attr in attrs:
360        for line in attr["description"].splitlines():
361            print("  // %s" % line, file=H)
362        if attr.get("force-load-on-access", False):
363            print(
364                "  %s %s() const;"
365                % (MEMBER_TYPE[attr["type"]], snake_to_pascal(attr["name"])),
366                file=H,
367            )
368        else:
369            print(
370                "  %s %s() const { return %s_; }"
371                % (
372                    RETURN_TYPE[attr["type"]],
373                    snake_to_pascal(attr["name"]),
374                    attr["name"],
375                ),
376                file=H,
377            )
378    print(" private:", file=H)
379    print("  explicit ConfigVars(const Overrides& overrides);", file=H)
380    print("  static const ConfigVars& Load();", file=H)
381    print("  static std::atomic<ConfigVars*> config_vars_;", file=H)
382    for attr in attrs_in_packing_order:
383        if attr.get("force-load-on-access", False):
384            continue
385        print("  %s %s_;" % (MEMBER_TYPE[attr["type"]], attr["name"]), file=H)
386    for attr in attrs_in_packing_order:
387        if attr.get("force-load-on-access", False) == False:
388            continue
389        print(
390            "  absl::optional<%s> override_%s_;"
391            % (MEMBER_TYPE[attr["type"]], attr["name"]),
392            file=H,
393        )
394    print("};", file=H)
395    print(file=H)
396    print("}  // namespace grpc_core", file=H)
397    print(file=H)
398    print("#endif  // GRPC_SRC_CORE_LIB_CONFIG_CONFIG_VARS_H", file=H)
399
400with open("src/core/config/config_vars.cc", "w") as C:
401    put_copyright(C)
402
403    put_banner(
404        [C],
405        [
406            "",
407            "Automatically generated by tools/codegen/core/gen_config_vars.py",
408            "",
409        ],
410    )
411
412    print("#include <grpc/support/port_platform.h>", file=C)
413    print('#include "src/core/config/config_vars.h"', file=C)
414    print('#include "src/core/config/load_config.h"', file=C)
415    print('#include "absl/strings/escaping.h"', file=C)
416    print('#include "absl/flags/flag.h"', file=C)
417    print(file=C)
418
419    for attr in attrs:
420        if "prelude" in attr:
421            print(attr["prelude"], file=C)
422
423    for attr in attrs:
424        print(
425            "ABSL_FLAG(%s, %s, {}, %s);"
426            % (
427                FLAG_TYPE[attr["type"]],
428                "grpc_" + attr["name"],
429                c_str(attr["description"]),
430            ),
431            file=C,
432        )
433    print(file=C)
434    print("namespace grpc_core {", file=C)
435    print(file=C)
436    print("ConfigVars::ConfigVars(const Overrides& overrides) :", file=C)
437    initializers = [
438        '%s_(LoadConfig(FLAGS_grpc_%s, "GRPC_%s", overrides.%s, %s))'
439        % (
440            attr["name"],
441            attr["name"],
442            attr["name"].upper(),
443            attr["name"],
444            DEFAULT_VALUE[attr["type"]](attr["default"], attr["name"]),
445        )
446        for attr in attrs_in_packing_order
447        if attr.get("force-load-on-access", False) == False
448    ]
449    initializers += [
450        "override_%s_(overrides.%s)" % (attr["name"], attr["name"])
451        for attr in attrs_in_packing_order
452        if attr.get("force-load-on-access", False)
453    ]
454    print(",".join(initializers), file=C)
455    print("{}", file=C)
456    print(file=C)
457    for attr in attrs:
458        if attr.get("force-load-on-access", False):
459            print(
460                "%s ConfigVars::%s() const { return LoadConfig(FLAGS_grpc_%s,"
461                ' "GRPC_%s", override_%s_, %s); }'
462                % (
463                    MEMBER_TYPE[attr["type"]],
464                    snake_to_pascal(attr["name"]),
465                    attr["name"],
466                    attr["name"].upper(),
467                    attr["name"],
468                    DEFAULT_VALUE[attr["type"]](attr["default"], attr["name"]),
469                ),
470                file=C,
471            )
472            print(file=C)
473    print("std::string ConfigVars::ToString() const {", file=C)
474    print("  return absl::StrCat(", file=C)
475    for i, attr in enumerate(attrs):
476        if i:
477            print(",", file=C)
478            print(c_str(", " + attr["name"] + ": "), file=C)
479        else:
480            print(c_str(attr["name"] + ": "), file=C)
481        print(
482            ",",
483            TO_STRING[attr["type"]].replace(
484                "$", snake_to_pascal(attr["name"]) + "()"
485            ),
486            file=C,
487        )
488    print(");}", file=C)
489    print(file=C)
490    print("}", file=C)
491