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/lib/config/config_vars.yaml') as f: 32 attrs = yaml.safe_load(f.read(), Loader=yaml.FullLoader) 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 'experiment' in attr['name'] and attr['name'] != 'experiments': 43 print('use experiment system for experiments') 44 error = True 45 if 'description' not in attr: 46 print("no description for %s" % attr['name']) 47 error = True 48 if 'default' not in attr: 49 print("no default for %s" % attr['name']) 50 error = True 51 52if error: 53 sys.exit(1) 54 55 56def c_str(s, encoding='ascii'): 57 if s is None: 58 return '""' 59 if isinstance(s, str): 60 s = s.encode(encoding) 61 result = '' 62 for c in s: 63 c = chr(c) if isinstance(c, int) else c 64 if not (32 <= ord(c) < 127) or c in ('\\', '"'): 65 result += '\\%03o' % ord(c) 66 else: 67 result += c 68 return '"' + result + '"' 69 70 71def snake_to_pascal(s): 72 return ''.join(x.capitalize() for x in s.split('_')) 73 74 75# utility: print a big comment block into a set of files 76def put_banner(files, banner): 77 for f in files: 78 for line in banner: 79 print('// %s' % line, file=f) 80 print(file=f) 81 82 83def put_copyright(file): 84 # copy-paste copyright notice from this file 85 with open(sys.argv[0]) as my_source: 86 copyright = [] 87 for line in my_source: 88 if line[0] != '#': 89 break 90 for line in my_source: 91 if line[0] == '#': 92 copyright.append(line) 93 break 94 for line in my_source: 95 if line[0] != '#': 96 break 97 copyright.append(line) 98 put_banner([file], [line[2:].rstrip() for line in copyright]) 99 100 101RETURN_TYPE = { 102 "int": "int32_t", 103 "string": "absl::string_view", 104 "comma_separated_string": "absl::string_view", 105 "bool": "bool", 106} 107 108MEMBER_TYPE = { 109 "int": "int32_t", 110 "string": "std::string", 111 "comma_separated_string": "std::string", 112 "bool": "bool", 113} 114 115FLAG_TYPE = { 116 "int": "absl::optional<int32_t>", 117 "string": "absl::optional<std::string>", 118 "comma_separated_string": "std::vector<std::string>", 119 "bool": "absl::optional<bool>", 120} 121 122PROTO_TYPE = { 123 "int": "int32", 124 "string": "string", 125 "comma_separated_string": "string", 126 "bool": "bool", 127} 128 129SORT_ORDER_FOR_PACKING = { 130 "int": 0, 131 "bool": 1, 132 "string": 2, 133 "comma_separated_string": 3 134} 135 136 137def bool_default_value(x, name): 138 if x == True: 139 return "true" 140 if x == False: 141 return "false" 142 if isinstance(x, str) and x.startswith("$"): 143 return x[1:] 144 return x 145 146 147def int_default_value(x, name): 148 if isinstance(x, str) and x.startswith("$"): 149 return x[1:] 150 return x 151 152 153def string_default_value(x, name): 154 if x is None: 155 return "\"\"" 156 if x.startswith("$"): 157 return x[1:] 158 return c_str(x) 159 160 161DEFAULT_VALUE = { 162 "int": int_default_value, 163 "bool": bool_default_value, 164 "string": string_default_value, 165 "comma_separated_string": string_default_value, 166} 167 168TO_STRING = { 169 "int": "$", 170 "bool": "$?\"true\":\"false\"", 171 "string": "\"\\\"\", absl::CEscape($), \"\\\"\"", 172 "comma_separated_string": "\"\\\"\", absl::CEscape($), \"\\\"\"", 173} 174 175attrs_in_packing_order = sorted(attrs, 176 key=lambda a: SORT_ORDER_FOR_PACKING[a['type']]) 177 178with open('test/core/util/fuzz_config_vars.proto', 'w') as P: 179 put_copyright(P) 180 181 put_banner([P], [ 182 "", "Automatically generated by tools/codegen/core/gen_config_vars.py", 183 "" 184 ]) 185 186 print("syntax = \"proto3\";", file=P) 187 print(file=P) 188 print("package grpc.testing;", file=P) 189 print(file=P) 190 print("message FuzzConfigVars {", file=P) 191 for attr in attrs_in_packing_order: 192 if attr.get("fuzz", False) == False: 193 continue 194 print(" optional %s %s = %d;" % 195 (PROTO_TYPE[attr['type']], attr['name'], 196 binascii.crc32(attr['name'].encode('ascii')) & 0x1FFFFFFF), 197 file=P) 198 print("};", file=P) 199 200with open('test/core/util/fuzz_config_vars.h', 'w') as H: 201 put_copyright(H) 202 203 put_banner([H], [ 204 "", "Automatically generated by tools/codegen/core/gen_config_vars.py", 205 "" 206 ]) 207 208 print("#ifndef GRPC_TEST_CORE_UTIL_FUZZ_CONFIG_VARS_H", file=H) 209 print("#define GRPC_TEST_CORE_UTIL_FUZZ_CONFIG_VARS_H", file=H) 210 print(file=H) 211 print("#include <grpc/support/port_platform.h>", file=H) 212 print(file=H) 213 print("#include \"test/core/util/fuzz_config_vars.pb.h\"", file=H) 214 print("#include \"src/core/lib/config/config_vars.h\"", file=H) 215 print(file=H) 216 print("namespace grpc_core {", file=H) 217 print(file=H) 218 print( 219 "ConfigVars::Overrides OverridesFromFuzzConfigVars(const grpc::testing::FuzzConfigVars& vars);", 220 file=H) 221 print( 222 "void ApplyFuzzConfigVars(const grpc::testing::FuzzConfigVars& vars);", 223 file=H) 224 print(file=H) 225 print("} // namespace grpc_core", file=H) 226 print(file=H) 227 print("#endif // GRPC_TEST_CORE_UTIL_FUZZ_CONFIG_VARS_H", file=H) 228 229with open('test/core/util/fuzz_config_vars.cc', 'w') as C: 230 put_copyright(C) 231 232 put_banner([C], [ 233 "", "Automatically generated by tools/codegen/core/gen_config_vars.py", 234 "" 235 ]) 236 237 print("#include \"test/core/util/fuzz_config_vars.h\"", file=C) 238 print("#include \"test/core/util/fuzz_config_vars_helpers.h\"", file=C) 239 print(file=C) 240 print("namespace grpc_core {", file=C) 241 print(file=C) 242 print( 243 "ConfigVars::Overrides OverridesFromFuzzConfigVars(const grpc::testing::FuzzConfigVars& vars) {", 244 file=C) 245 print(" ConfigVars::Overrides overrides;", file=C) 246 for attr in attrs_in_packing_order: 247 fuzz = attr.get("fuzz", False) 248 if not fuzz: 249 continue 250 print(" if (vars.has_%s()) {" % attr['name'], file=C) 251 if isinstance(fuzz, str): 252 print(" overrides.%s = %s(vars.%s());" % 253 (attr['name'], fuzz, attr['name']), 254 file=C) 255 else: 256 print(" overrides.%s = vars.%s();" % 257 (attr['name'], attr['name']), 258 file=C) 259 print(" }", file=C) 260 print(" return overrides;", file=C) 261 print("}", file=C) 262 print( 263 "void ApplyFuzzConfigVars(const grpc::testing::FuzzConfigVars& vars) {", 264 file=C) 265 print(" ConfigVars::SetOverrides(OverridesFromFuzzConfigVars(vars));", 266 file=C) 267 print("}", file=C) 268 print(file=C) 269 print("} // namespace grpc_core", file=C) 270 271with open('src/core/lib/config/config_vars.h', 'w') as H: 272 put_copyright(H) 273 274 put_banner([H], [ 275 "", "Automatically generated by tools/codegen/core/gen_config_vars.py", 276 "" 277 ]) 278 279 print("#ifndef GRPC_SRC_CORE_LIB_CONFIG_CONFIG_VARS_H", file=H) 280 print("#define GRPC_SRC_CORE_LIB_CONFIG_CONFIG_VARS_H", file=H) 281 print(file=H) 282 print("#include <grpc/support/port_platform.h>", file=H) 283 print(file=H) 284 print("#include <string>", file=H) 285 print("#include <atomic>", file=H) 286 print("#include <stdint.h>", file=H) 287 print("#include \"absl/strings/string_view.h\"", file=H) 288 print("#include \"absl/types/optional.h\"", file=H) 289 print(file=H) 290 print("namespace grpc_core {", file=H) 291 print(file=H) 292 print("class ConfigVars {", file=H) 293 print(" public:", file=H) 294 print(" struct Overrides {", file=H) 295 for attr in attrs_in_packing_order: 296 print(" absl::optional<%s> %s;" % 297 (MEMBER_TYPE[attr['type']], attr['name']), 298 file=H) 299 print(" };", file=H) 300 print(" ConfigVars(const ConfigVars&) = delete;", file=H) 301 print(" ConfigVars& operator=(const ConfigVars&) = delete;", file=H) 302 print(" // Get the core configuration; if it does not exist, create it.", 303 file=H) 304 print(" static const ConfigVars& Get() {", file=H) 305 print(" auto* p = config_vars_.load(std::memory_order_acquire);", file=H) 306 print(" if (p != nullptr) return *p;", file=H) 307 print(" return Load();", file=H) 308 print(" }", file=H) 309 print(" static void SetOverrides(const Overrides& overrides);", file=H) 310 print(" // Drop the config vars. Users must ensure no other threads are", 311 file=H) 312 print(" // accessing the configuration.", file=H) 313 print(" static void Reset();", file=H) 314 print(" std::string ToString() const;", file=H) 315 for attr in attrs: 316 for line in attr['description'].splitlines(): 317 print(" // %s" % line, file=H) 318 if attr.get('force-load-on-access', False): 319 print(" %s %s() const;" % 320 (MEMBER_TYPE[attr['type']], snake_to_pascal(attr['name'])), 321 file=H) 322 else: 323 print(" %s %s() const { return %s_; }" % 324 (RETURN_TYPE[attr['type']], snake_to_pascal( 325 attr['name']), attr['name']), 326 file=H) 327 print(" private:", file=H) 328 print(" explicit ConfigVars(const Overrides& overrides);", file=H) 329 print(" static const ConfigVars& Load();", file=H) 330 print(" static std::atomic<ConfigVars*> config_vars_;", file=H) 331 for attr in attrs_in_packing_order: 332 if attr.get('force-load-on-access', False): 333 continue 334 print(" %s %s_;" % (MEMBER_TYPE[attr['type']], attr['name']), file=H) 335 for attr in attrs_in_packing_order: 336 if attr.get('force-load-on-access', False) == False: 337 continue 338 print(" absl::optional<%s> override_%s_;" % 339 (MEMBER_TYPE[attr['type']], attr['name']), 340 file=H) 341 print("};", file=H) 342 print(file=H) 343 print("} // namespace grpc_core", file=H) 344 print(file=H) 345 print("#endif // GRPC_SRC_CORE_LIB_CONFIG_CONFIG_VARS_H", file=H) 346 347with open('src/core/lib/config/config_vars.cc', 'w') as C: 348 put_copyright(C) 349 350 put_banner([C], [ 351 "", "Automatically generated by tools/codegen/core/gen_config_vars.py", 352 "" 353 ]) 354 355 print("#include <grpc/support/port_platform.h>", file=C) 356 print("#include \"src/core/lib/config/config_vars.h\"", file=C) 357 print("#include \"src/core/lib/config/load_config.h\"", file=C) 358 print("#include \"absl/strings/escaping.h\"", file=C) 359 print("#include \"absl/flags/flag.h\"", file=C) 360 print(file=C) 361 362 for attr in attrs: 363 if 'prelude' in attr: 364 print(attr['prelude'], file=C) 365 366 for attr in attrs: 367 print("ABSL_FLAG(%s, %s, {}, %s);" % 368 (FLAG_TYPE[attr["type"]], 'grpc_' + attr['name'], 369 c_str(attr['description'])), 370 file=C) 371 print(file=C) 372 print("namespace grpc_core {", file=C) 373 print(file=C) 374 print("ConfigVars::ConfigVars(const Overrides& overrides) :", file=C) 375 initializers = [ 376 "%s_(LoadConfig(FLAGS_grpc_%s, \"GRPC_%s\", overrides.%s, %s))" % 377 (attr['name'], attr['name'], attr['name'].upper(), attr['name'], 378 DEFAULT_VALUE[attr['type']](attr['default'], attr['name'])) 379 for attr in attrs_in_packing_order 380 if attr.get('force-load-on-access', False) == False 381 ] 382 initializers += [ 383 "override_%s_(overrides.%s)" % (attr['name'], attr['name']) 384 for attr in attrs_in_packing_order 385 if attr.get('force-load-on-access', False) 386 ] 387 print(",".join(initializers), file=C) 388 print("{}", file=C) 389 print(file=C) 390 for attr in attrs: 391 if attr.get('force-load-on-access', False): 392 print( 393 "%s ConfigVars::%s() const { return LoadConfig(FLAGS_grpc_%s, \"GRPC_%s\", override_%s_, %s); }" 394 % (MEMBER_TYPE[attr['type']], snake_to_pascal(attr['name']), 395 attr['name'], attr['name'].upper(), attr['name'], 396 DEFAULT_VALUE[attr['type']](attr['default'], attr['name'])), 397 file=C) 398 print(file=C) 399 print("std::string ConfigVars::ToString() const {", file=C) 400 print(" return absl::StrCat(", file=C) 401 for i, attr in enumerate(attrs): 402 if i: 403 print(",", file=C) 404 print(c_str(", " + attr['name'] + ": "), file=C) 405 else: 406 print(c_str(attr['name'] + ": "), file=C) 407 print(",", 408 TO_STRING[attr['type']].replace( 409 "$", 410 snake_to_pascal(attr['name']) + "()"), 411 file=C) 412 print(");}", file=C) 413 print(file=C) 414 print("}", file=C) 415