The C based gRPC (C++, Python, Ruby, Objective-C, PHP, C#) https://grpc.io/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

264 lines
8.6 KiB

#!/usr/bin/env python3
# Copyright 2022 gRPC authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Generate experiment related code artifacts.
Invoke as: tools/codegen/core/gen_experiments.py
Experiment definitions are in src/core/lib/experiments/experiments.yaml
"""
from __future__ import print_function
import collections
import ctypes
import datetime
import json
import math
import os
import re
import sys
import yaml
# TODO(ctiller): if we ever add another argument switch this to argparse
check_dates = True
if sys.argv[1:] == ["--check"]:
check_dates = False # for formatting checks we don't verify expiry dates
with open('src/core/lib/experiments/experiments.yaml') as f:
attrs = yaml.load(f.read(), Loader=yaml.FullLoader)
DEFAULTS = {
'broken': 'false',
False: 'false',
True: 'true',
'debug': 'kDefaultForDebugOnly',
'release': 'kDefaultForReleaseOnly',
}
BZL_LIST_FOR_DEFAULTS = {
'broken': None,
False: 'off',
True: 'on',
'debug': 'dbg',
'release': 'opt',
}
error = False
today = datetime.date.today()
two_quarters_from_now = today + datetime.timedelta(days=180)
for attr in attrs:
if 'name' not in attr:
print("experiment with no name: %r" % attr)
error = True
continue # can't run other diagnostics because we don't know a name
if 'description' not in attr:
print("no description for experiment %s" % attr['name'])
error = True
if 'default' not in attr:
print("no default for experiment %s" % attr['name'])
error = True
if attr['default'] not in DEFAULTS:
print("invalid default for experiment %s: %r" %
(attr['name'], attr['default']))
error = True
if 'owner' not in attr:
print("no owner for experiment %s" % attr['name'])
error = True
if 'expiry' not in attr:
print("no expiry for experiment %s" % attr['name'])
error = True
expiry = datetime.datetime.strptime(attr['expiry'], '%Y/%m/%d').date()
if check_dates:
if expiry < today:
print("experiment %s expired on %s" %
(attr['name'], attr['expiry']))
error = True
if expiry > two_quarters_from_now:
print("experiment %s expires far in the future on %s" %
(attr['name'], attr['expiry']))
print("expiry should be no more than two quarters from now")
error = True
if error:
sys.exit(1)
def c_str(s, encoding='ascii'):
if isinstance(s, str):
s = s.encode(encoding)
result = ''
for c in s:
c = chr(c) if isinstance(c, int) else c
if not (32 <= ord(c) < 127) or c in ('\\', '"'):
result += '\\%03o' % ord(c)
else:
result += c
return '"' + result + '"'
def snake_to_pascal(s):
return ''.join(x.capitalize() for x in s.split('_'))
# utility: print a big comment block into a set of files
def put_banner(files, banner, prefix):
for f in files:
for line in banner:
if not line:
print(prefix, file=f)
else:
print('%s %s' % (prefix, line), file=f)
print(file=f)
def put_copyright(file, prefix):
# copy-paste copyright notice from this file
with open(sys.argv[0]) as my_source:
copyright = []
for line in my_source:
if line[0] != '#':
break
for line in my_source:
if line[0] == '#':
copyright.append(line)
break
for line in my_source:
if line[0] != '#':
break
copyright.append(line)
put_banner([file], [line[2:].rstrip() for line in copyright], prefix)
EXPERIMENT_METADATA = """struct ExperimentMetadata {
const char* name;
const char* description;
bool default_value;
};"""
with open('src/core/lib/experiments/experiments.h', 'w') as H:
put_copyright(H, "//")
put_banner(
[H],
["Automatically generated by tools/codegen/core/gen_experiments.py"],
"//")
print("#ifndef GRPC_CORE_LIB_EXPERIMENTS_EXPERIMENTS_H", file=H)
print("#define GRPC_CORE_LIB_EXPERIMENTS_EXPERIMENTS_H", file=H)
print(file=H)
print("#include <grpc/support/port_platform.h>", file=H)
print(file=H)
print("#include <stddef.h>", file=H)
print("#include \"src/core/lib/experiments/config.h\"", file=H)
print(file=H)
print("namespace grpc_core {", file=H)
print(file=H)
for i, attr in enumerate(attrs):
print("inline bool Is%sEnabled() { return IsExperimentEnabled(%d); }" %
(snake_to_pascal(attr['name']), i),
file=H)
print(file=H)
print(EXPERIMENT_METADATA, file=H)
print(file=H)
print("constexpr const size_t kNumExperiments = %d;" % len(attrs), file=H)
print(
"extern const ExperimentMetadata g_experiment_metadata[kNumExperiments];",
file=H)
print(file=H)
print("} // namespace grpc_core", file=H)
print(file=H)
print("#endif // GRPC_CORE_LIB_EXPERIMENTS_EXPERIMENTS_H", file=H)
with open('src/core/lib/experiments/experiments.cc', 'w') as C:
put_copyright(C, "//")
put_banner(
[C],
["Automatically generated by tools/codegen/core/gen_experiments.py"],
"//")
print("#include <grpc/support/port_platform.h>", file=C)
print("#include \"src/core/lib/experiments/experiments.h\"", file=C)
print(file=C)
print("namespace {", file=C)
for attr in attrs:
print("const char* const description_%s = %s;" %
(attr['name'], c_str(attr['description'])),
file=C)
have_defaults = set(DEFAULTS[attr['default']] for attr in attrs)
if 'kDefaultForDebugOnly' in have_defaults or 'kDefaultForReleaseOnly' in have_defaults:
print("#ifdef NDEBUG", file=C)
if 'kDefaultForDebugOnly' in have_defaults:
print("const bool kDefaultForDebugOnly = false;", file=C)
if 'kDefaultForReleaseOnly' in have_defaults:
print("const bool kDefaultForReleaseOnly = true;", file=C)
print("#else", file=C)
if 'kDefaultForDebugOnly' in have_defaults:
print("const bool kDefaultForDebugOnly = true;", file=C)
if 'kDefaultForReleaseOnly' in have_defaults:
print("const bool kDefaultForReleaseOnly = false;", file=C)
print("#endif", file=C)
print("}", file=C)
print(file=C)
print("namespace grpc_core {", file=C)
print(file=C)
print("const ExperimentMetadata g_experiment_metadata[] = {", file=C)
for attr in attrs:
print(" {%s, description_%s, %s}," %
(c_str(attr['name']), attr['name'], DEFAULTS[attr['default']]),
file=C)
print("};", file=C)
print(file=C)
print("} // namespace grpc_core", file=C)
bzl_to_tags_to_experiments = dict((key, collections.defaultdict(list))
for key in BZL_LIST_FOR_DEFAULTS.keys()
if key is not None)
for attr in attrs:
for tag in attr['test_tags']:
bzl_to_tags_to_experiments[attr['default']][tag].append(attr['name'])
with open('bazel/experiments.bzl', 'w') as B:
put_copyright(B, "#")
put_banner(
[B],
["Automatically generated by tools/codegen/core/gen_experiments.py"],
"#")
print(
"\"\"\"Dictionary of tags to experiments so we know when to test different experiments.\"\"\"",
file=B)
bzl_to_tags_to_experiments = sorted(
(BZL_LIST_FOR_DEFAULTS[default], tags_to_experiments)
for default, tags_to_experiments in bzl_to_tags_to_experiments.items()
if BZL_LIST_FOR_DEFAULTS[default] is not None)
print(file=B)
print("EXPERIMENTS = {", file=B)
for key, tags_to_experiments in bzl_to_tags_to_experiments:
print(" \"%s\": {" % key, file=B)
for tag, experiments in sorted(tags_to_experiments.items()):
print(" \"%s\": [" % tag, file=B)
for experiment in sorted(experiments):
print(" \"%s\"," % experiment, file=B)
print(" ],", file=B)
print(" },", file=B)
print("}", file=B)