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