• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2
3# Copyright 2022 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"""
17Generate experiment related code artifacts.
18
19Invoke as: tools/codegen/core/gen_experiments.py
20Experiment definitions are in src/core/lib/experiments/experiments.yaml
21"""
22
23from __future__ import print_function
24
25import argparse
26import os
27import sys
28
29import experiments_compiler as exp
30import yaml
31
32REPO_ROOT = os.path.normpath(
33    os.path.join(os.path.dirname(__file__), "../../..")
34)
35print(REPO_ROOT)
36os.chdir(REPO_ROOT)
37
38DEFAULTS = {
39    "broken": "false",
40    False: "false",
41    True: "true",
42    "debug": "kDefaultForDebugOnly",
43}
44
45PLATFORMS_DEFINE = {
46    "windows": "GPR_WINDOWS",
47    "ios": "GRPC_CFSTREAM",
48    "posix": "",
49}
50
51FINAL_RETURN = {
52    "broken": "return false;",
53    False: "return false;",
54    True: "return true;",
55    "debug": "\n#ifdef NDEBUG\nreturn false;\n#else\nreturn true;\n#endif\n",
56}
57
58FINAL_DEFINE = {
59    "broken": None,
60    False: None,
61    True: "#define %s",
62    "debug": "#ifndef NDEBUG\n#define %s\n#endif",
63}
64
65BZL_LIST_FOR_DEFAULTS = {
66    "broken": None,
67    False: "off",
68    True: "on",
69    "debug": "dbg",
70}
71
72
73def ParseCommandLineArguments(args):
74    """Wrapper for argparse command line arguments handling.
75
76    Args:
77    args: List of command line arguments.
78
79    Returns:
80    Command line arguments namespace built by argparse.ArgumentParser().
81    """
82    # formatter_class=argparse.ArgumentDefaultsHelpFormatter is not used here
83    # intentionally, We want more formatting than this class can provide.
84    flag_parser = argparse.ArgumentParser()
85    flag_parser.add_argument(
86        "--check",
87        action="store_false",
88        help="If specified, disables checking experiment expiry dates",
89    )
90    return flag_parser.parse_args(args)
91
92
93args = ParseCommandLineArguments(sys.argv[1:])
94
95
96def _InjectGithubPath(path):
97    base, ext = os.path.splitext(path)
98    return base + ".github" + ext
99
100
101def _GenerateExperimentFiles(args, mode):
102    if mode == "test":
103        _EXPERIMENTS_DEFS = (
104            "test/core/experiments/fixtures/test_experiments.yaml"
105        )
106        _EXPERIMENTS_ROLLOUTS = (
107            "test/core/experiments/fixtures/test_experiments_rollout.yaml"
108        )
109        _EXPERIMENTS_HDR_FILE = "test/core/experiments/fixtures/experiments.h"
110        _EXPERIMENTS_SRC_FILE = "test/core/experiments/fixtures/experiments.cc"
111        _EXPERIMENTS_BZL_FILE = "bazel/test_experiments.bzl"
112    else:
113        _EXPERIMENTS_DEFS = "src/core/lib/experiments/experiments.yaml"
114        _EXPERIMENTS_ROLLOUTS = "src/core/lib/experiments/rollouts.yaml"
115        _EXPERIMENTS_HDR_FILE = "src/core/lib/experiments/experiments.h"
116        _EXPERIMENTS_SRC_FILE = "src/core/lib/experiments/experiments.cc"
117        _EXPERIMENTS_BZL_FILE = "bazel/experiments.bzl"
118        if "/google3/" in REPO_ROOT:
119            _EXPERIMENTS_ROLLOUTS = _InjectGithubPath(_EXPERIMENTS_ROLLOUTS)
120            _EXPERIMENTS_HDR_FILE = _InjectGithubPath(_EXPERIMENTS_HDR_FILE)
121            _EXPERIMENTS_SRC_FILE = _InjectGithubPath(_EXPERIMENTS_SRC_FILE)
122            _EXPERIMENTS_BZL_FILE = _InjectGithubPath(_EXPERIMENTS_BZL_FILE)
123
124    with open(_EXPERIMENTS_DEFS) as f:
125        attrs = yaml.safe_load(f.read())
126
127    if not exp.AreExperimentsOrdered(attrs):
128        print("Experiments are not ordered")
129        sys.exit(1)
130
131    with open(_EXPERIMENTS_ROLLOUTS) as f:
132        rollouts = yaml.safe_load(f.read())
133
134    if not exp.AreExperimentsOrdered(rollouts):
135        print("Rollouts are not ordered")
136        sys.exit(1)
137
138    compiler = exp.ExperimentsCompiler(
139        DEFAULTS,
140        FINAL_RETURN,
141        FINAL_DEFINE,
142        PLATFORMS_DEFINE,
143        BZL_LIST_FOR_DEFAULTS,
144    )
145
146    experiment_annotation = "gRPC Experiments: "
147    for attr in attrs:
148        exp_definition = exp.ExperimentDefinition(attr)
149        if not exp_definition.IsValid(args.check):
150            sys.exit(1)
151        experiment_annotation += exp_definition.name + ":0,"
152        if not compiler.AddExperimentDefinition(exp_definition):
153            print("Experiment = %s ERROR adding" % exp_definition.name)
154            sys.exit(1)
155
156    if len(experiment_annotation) > 2000:
157        print("comma-delimited string of experiments is too long")
158        sys.exit(1)
159
160    for rollout_attr in rollouts:
161        if not compiler.AddRolloutSpecification(rollout_attr):
162            print("ERROR adding rollout spec")
163            sys.exit(1)
164
165    print(f"Mode = {mode} Generating experiments headers")
166    compiler.GenerateExperimentsHdr(_EXPERIMENTS_HDR_FILE, mode)
167
168    print(f"Mode = {mode} Generating experiments srcs")
169    compiler.GenerateExperimentsSrc(
170        _EXPERIMENTS_SRC_FILE, _EXPERIMENTS_HDR_FILE, mode
171    )
172
173    print("Generating experiments.bzl")
174    compiler.GenExperimentsBzl(mode, _EXPERIMENTS_BZL_FILE)
175    if mode == "test":
176        print("Generating experiments tests")
177        compiler.GenTest(
178            os.path.join(REPO_ROOT, "test/core/experiments/experiments_test.cc")
179        )
180
181
182_GenerateExperimentFiles(args, "production")
183_GenerateExperimentFiles(args, "test")
184